Я пытаюсь создавать мозаичную 2D-графику в Direct3D 11. Плиточная 2D-графика — это, по сути, просто множество текстурированных четырехугольников. Кажется естественным использовать для этого создание экземпляров вместо рисования каждого квадрата по отдельности. Рисование одного квадрата работает, но если я попытаюсь использовать создание экземпляров для рисования двух квадратов, я получу пустой экран. Если я все правильно понимаю, идея состоит в том, что вы создаете макет, который имеет макет буфера вершин в слоте 0 и макет буфера экземпляра в слоте 1. Вы создаете буфер вершин с данными вершин и буфер экземпляра с данными экземпляра. . Вы помещаете их в массив. Это массив указателей (это правда?). Индекс в этом массиве соответствует номеру слота в макете. Вы получаете данные вершин и данные индекса в вершинном шейдере и делаете с ними свои дела. А затем нарисуйте его с помощью DrawIndexedInstanced. Как обычно в DirectX, понять это концептуально — это легко, а реализовать это на самом деле — сложнее.
Вот код (я знаю, что стиль именования не совсем последовательный, часть скопирована из руководства).
Определение макета:
D3D11_INPUT_ELEMENT_DESC inputElementDesc[] =
{
{ "POS", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEX", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{"INSTANCEPOS", 0, DXGI_FORMAT_R32G32_FLOAT, 1, 0, D3D11_INPUT_PER_INSTANCE_DATA, 1},
{"INSTANCETEX", 0, DXGI_FORMAT_R32G32_FLOAT, 1, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA,1}
};
HRESULT hResult = m_device->CreateInputLayout(inputElementDesc, ARRAYSIZE(inputElementDesc), vs_bytecode->GetBufferPointer(), vs_bytecode->GetBufferSize(), &m_layout);
assert(SUCCEEDED(hResult));
vs_bytecode->Release();```
Binding of the layout:
m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_context->IASetInputLayout(m_layout.Get()); // m_layout is a ComPtr
Vertex buffer header file:
#pragma once
#include "bindable.h"
#include "base_types.h"
#include <wrl.h>
#include <DirectXMath.h>
#include <array>
struct Vertex
{
DirectX::XMFLOAT2 pos;
DirectX::XMFLOAT2 tex_coords;
};
struct Instance_data
{
DirectX::XMFLOAT2 instance_pos;
DirectX::XMFLOAT2 instance_tex;
};
{
public:
class Vertex_buffer : public Bindable
Vertex_buffer(ID3D11DeviceContext1* context, ID3D11Device1* device, Rect vertex_pos, Rect texture_pos);
void bind() noexcept override;
int add_instance(Vec2 pos, Vec2 texture_coords);
private:
inline constexpr static size_t num_instances{ 2 };
std::array<Instance_data, num_instances> m_instance_data;
std::array<ID3D11Buffer*, 2> m_buffers;
std::array<UINT, 2> m_strides{ sizeof(Vertex), sizeof(Instance_data) };
std::array<UINT, 2> m_offsets{ 0u, 0u };
size_t next_index{ 0 };
};
Vertex buffer c++ file
#include "vertex_buffer.h"
#include <array>
#include <vector>
Vertex_buffer::Vertex_buffer(ID3D11DeviceContext1* context, ID3D11Device1* device, Rect vertex_pos, Rect texture_pos) : Bindable{ context, device }
{
std::array<Vertex, 4> vertices;
vertices[0]=(Vertex{ DirectX::XMFLOAT2{ vertex_pos.x, vertex_pos.y }, DirectX::XMFLOAT2{ texture_pos.x, texture_pos.y } });
vertices[1]=(Vertex{ DirectX::XMFLOAT2 {vertex_pos.x + vertex_pos.width, vertex_pos.y - vertex_pos.height}, DirectX::XMFLOAT2{ texture_pos.x + texture_pos.width, texture_pos.y + texture_pos.height } });
vertices[2] = (Vertex{ DirectX::XMFLOAT2 { vertex_pos.x, vertex_pos.y - vertex_pos.height }, DirectX::XMFLOAT2{ texture_pos.x, texture_pos.y + texture_pos.height } });
vertices[3] = (Vertex{ DirectX::XMFLOAT2 {vertex_pos.x + vertex_pos.width, vertex_pos.y}, DirectX::XMFLOAT2{texture_pos.x + texture_pos.width, texture_pos.y} });
UINT numVerts = vertices.size();
D3D11_BUFFER_DESC vertexBufferDesc = {};
vertexBufferDesc.ByteWidth = sizeof(vertices);//m_stride * numVerts;//sizeof(vertexData);
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
D3D11_SUBRESOURCE_DATA vertexSubresourceData = { vertices.data() };
//HRESULT hResult = device->CreateBuffer(&vertexBufferDesc, &vertexSubresourceData, &m_vertex_buffer);
HRESULT hResult = device->CreateBuffer(&vertexBufferDesc, &vertexSubresourceData, &m_buffers[0]);
assert(SUCCEEDED(hResult));
D3D11_BUFFER_DESC instance_buffer_desc{};
instance_buffer_desc.Usage = D3D11_USAGE_DEFAULT;
instance_buffer_desc.ByteWidth = sizeof(Instance_data) * num_instances;
instance_buffer_desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
instance_buffer_desc.CPUAccessFlags = 0;
instance_buffer_desc.MiscFlags = 0;
D3D11_SUBRESOURCE_DATA subres_data{};
subres_data.pSysMem = m_instance_data.data();
HRESULT hr = device->CreateBuffer(&instance_buffer_desc, &subres_data, &m_buffers[1]);
assert(SUCCEEDED(hr));
}
void Vertex_buffer::bind() noexcept
{
m_context->IASetVertexBuffers(0u, 2u, m_buffers.data(), m_strides.data(), m_offsets.data());
}
int Vertex_buffer::add_instance(Vec2 pos, Vec2 texture_coords)
{
m_instance_data[next_index] = Instance_data{ {pos.x, pos.y}, {texture_coords.x, texture_coords.y} };
return next_index++;
}
shader file
struct VS_Input {
float2 pos : POS;
float2 uv : TEX;
float2 instancepos : INSTANCEPOS;
float2 instancetex : INSTANCETEX;
};
struct VS_Output {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD;
};
Texture2D mytexture : register(t0);
SamplerState mysampler : register(s0);
VS_Output vs_main(VS_Input input)
{
VS_Output output;
output.pos = float4(input.instancepos, 0.0f, 1.0f);
output.uv = input.instancetex;
return output;
}
float4 ps_main(VS_Output input) : SV_Target
{
return mytexture.Sample(mysampler, input.uv);
}
In the main function (there is of course a lot of code that creates the window and does stuff that I know that works)
Vertex_buffer vertex_buffer {d3d11DeviceContext, d3d11Device, vertex_pos,texture_pos}; // вычисляется vertex_pos в диапазоне -1..1 и text_pos в диапазоне 0..1
// выполняем данные экземпляра
constexpr unsigned int num_instances{ 2u };
struct Instance_data // yes, this is defined in 2 places, that should be improved
{
DirectX::XMFLOAT2 instance_pos;
DirectX::XMFLOAT2 instance_tex;
};
std::array<Instance_data, num_instances> instance_data;
Rect v1 = calc_vertex_pos(3.0f, 32.0f, 32.0f, screenwidth, screenheight, 4, 3); // these functions that calculate vertex and texture coordinates work correctly
Rect v2 = calc_vertex_pos(3.0f, 32.0f, 32.0f, screenwidth, screenheight, 3, 2);
Rect t1 = calc_texture_pos(32.0f, 32.0f, 10, 3, 8);
Rect t2 = calc_texture_pos(32.0f, 32.0f, 10, 3, 15);
auto inst0 = vertex_buffer.add_instance({ v1.x, v1.y }, { t1.x, t1.y });
auto inst1 = vertex_buffer.add_instance({ v2.x, v2.y }, { t1.x, t2.y });
in the main loop:
// bind all the things
// ...
layout.bind();
vertex_buffer.bind();
index_buffer.bind();
d3d11DeviceContext->DrawIndexedInstanced(index_buffer.count(), 2u, 0u, 0u, 0u);
d3d11SwapChain->Present(1, 0);
The result is a blank screen. It all compiles and runs, no assertions are hit. I suspect that for some reason the data doesn't get into the shader correctly but I am not aware of any way to debug that.
A few related questions:
1- I assume that binding stuff includes sending the associated data to the graphics hardware. Is it efficient to bind everything every frame (I assume not). Would it work if I only bind things if there are mutations?
2- The instancebuffer description takes the size of the buffer in bytes. Of course it is just a C array underneath. That means that the size is essentially fixed. But the number of objects on the screen can vary: enemies, treasure, bullets and the like can spawn and get destroyed. How do you do that with instancing? Instance only the fixed objects like walls and floors? Or recreate the instance buffer when the number of instances changes? Is recreating the buffer expensive or cheap?
3- Are these semantics names in the shader case-insensitive? I sometimes see SV_POSITION and sometimes SV_Position. Would sv_position also work?
Fiddled around with it quite a bit. Expected two textured quads, got a blank screen.





Никаких реакций вообще. Кажется, я единственный человек в Интернете, который пытается это сделать ;-). У меня пока это не работает, но у меня есть пара наблюдений, которые могут быть полезны другим, кто попытается сделать что-то подобное.
#define verts_per_instance 4
#define num_entities 2
struct Instance_data
{
float2 position;
float2 texture_coords;
};
cbuffer cb
{
Instance_data instance[verts_per_instance * num_entities];
};
struct VS_in
{
uint instance_id : SV_InstanceID;
uint vertex_id : SV_VertexID;
};
struct VS_out
{
float4 pos : SV_Position;
float4 tex : TEXCOORD;
};
VS_out main(VS_in input)
{
VS_out out;
out.pos = float4(instance[instance_id * verts_per_instance + vertex_id].position.x, instance[instance_id * verts_per_instance + vertex_id].position.y, 0.0f, 1.0f);
// essentially the same for the texture coords;
return out;
}
Еще не пробовал, но думаю, что у этого способа больше шансов сработать.
Хорошо, пришла идея и все заработало! С буфером экземпляра.
Хитрость в том, что все экземпляры имеют доступ к данным буфера вершин. Таким образом, каждая вершина знает относительное положение вершины в буфере вершин. В буфере экземпляра вы сохраняете перевод всего объекта и добавляете его ко всем координатам вершин.
Итак, рецепт такой: для буфера вершин определите правильную форму, но в верхнем левом углу окна (-1, 1). для каждого экземпляра вычислите вектор перемещения (сколько вам нужно добавить к координатам x и y, чтобы попасть в новую позицию). в шейдере сделайте это дополнение. Что касается координат текстуры, то это примерно то же самое: координаты верхней левой текстуры помещаются в буфер вершин (здесь, чтобы не было единообразия, координаты изменяются от (0,0) до (1,1)). в данные экземпляра поместите вектор перевода. в шейдере добавьте вектор перевода к координатам в буфере вершин.
Теперь шейдер будет выглядеть так:
struct VS_Input
{
float2 pos : POS;
float2 uv : TEX;
float2 instancepos : INSTANCEPOS;
float2 instancetex : INSTANCETEX;
uint instance_id : SV_InstanceID;
};
struct VS_Output
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD;
};
VS_Output vs_main(VS_Input input)
{
VS_Output output;
output.pos = float4(input.pos.x + input.instancepos.x, input.pos.y - input.instancepos.y, 0.0f, 1.0f);
output.uv = float2(input.uv.x + input.instancetex.x, input.uv.y + input.instancetex.y);
return output;
}
В отладчике вижу, что действительно проблема в том, что данные экземпляра не попадают в вершинный шейдер. В вершинном шейдере значения INSTANCEPOS и INSTANCETEX равны нулю.