Portar os buffers de vértice e os dados
APIs importantes
- ID3DDevice::CreateBuffer
- ID3DDeviceContext::IASetVertexBuffers
- ID3D11DeviceContext::IASetIndexBuffer
Nesta etapa, você definirá os buffers de vértice que conterão suas malhas e os buffers de índice que permitem que os sombreadores percorram os vértices em uma ordem especificada.
Neste ponto, vamos examinar o modelo codificado para a malha de cubo que estamos usando. Ambas as representações têm os vértices organizados como uma lista de triângulos (em oposição a uma faixa ou outro layout de triângulo mais eficiente). Todos os vértices em ambas as representações também têm índices e valores de cor associados. Grande parte do código do Direct3D neste tópico refere-se a variáveis e objetos definidos no projeto do Direct3D.
Aqui está o cubo para processamento pelo OpenGL ES 2.0. Na implementação de exemplo, cada vértice tem 7 valores flutuantes: 3 coordenadas de posição seguidas por 4 valores de cor RGBA.
#define CUBE_INDICES 36
#define CUBE_VERTICES 8
GLfloat cubeVertsAndColors[] =
{
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 1.0f
};
GLuint cubeIndices[] =
{
0, 1, 2, // -x
1, 3, 2,
4, 6, 5, // +x
6, 7, 5,
0, 5, 1, // -y
5, 6, 1,
2, 6, 3, // +y
6, 7, 3,
0, 4, 2, // +z
4, 6, 2,
1, 7, 3, // -z
5, 7, 1
};
E aqui está o mesmo cubo para processamento pelo Direct3D 11.
VertexPositionColor cubeVerticesAndColors[] =
// struct format is position, color
{
{XMFLOAT3(-0.5f, -0.5f, -0.5f), XMFLOAT3(0.0f, 0.0f, 0.0f)},
{XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT3(0.0f, 0.0f, 1.0f)},
{XMFLOAT3(-0.5f, 0.5f, -0.5f), XMFLOAT3(0.0f, 1.0f, 0.0f)},
{XMFLOAT3(-0.5f, 0.5f, 0.5f), XMFLOAT3(0.0f, 1.0f, 1.0f)},
{XMFLOAT3( 0.5f, -0.5f, -0.5f), XMFLOAT3(1.0f, 0.0f, 0.0f)},
{XMFLOAT3( 0.5f, -0.5f, 0.5f), XMFLOAT3(1.0f, 0.0f, 1.0f)},
{XMFLOAT3( 0.5f, 0.5f, -0.5f), XMFLOAT3(1.0f, 1.0f, 0.0f)},
{XMFLOAT3( 0.5f, 0.5f, 0.5f), XMFLOAT3(1.0f, 1.0f, 1.0f)},
};
unsigned short cubeIndices[] =
{
0, 2, 1, // -x
1, 2, 3,
4, 5, 6, // +x
5, 7, 6,
0, 1, 5, // -y
0, 5, 4,
2, 6, 7, // +y
2, 7, 3,
0, 4, 6, // -z
0, 6, 2,
1, 3, 7, // +z
1, 7, 5
};
Ao examinar esse código, você observa que o cubo no código do OpenGL ES 2.0 é representado em um sistema de coordenadas à direita, enquanto o cubo no código específico do Direct3D é representado em um sistema de coordenadas à esquerda. Ao importar seus próprios dados de malha, você deve inverter as coordenadas do eixo z para seu modelo e alterar os índices de cada malha de acordo para percorrer os triângulos de acordo com a alteração no sistema de coordenadas.
Supondo que tenhamos movido com êxito a malha do cubo do sistema de coordenadas OpenGL ES 2.0 destro para o Direct3D canhoto, vamos ver como carregar os dados do cubo para processamento em ambos os modelos.
Instruções
Etapa 1: Criar um layout de entrada
No OpenGL ES 2.0, seus dados de vértice são fornecidos como atributos que serão fornecidos e lidos pelos objetos do sombreador. Normalmente, você fornece uma cadeia de caracteres que contém o nome do atributo usado no GLSL do sombreador para o objeto de programa do sombreador e obtém um local de memória de volta que você pode fornecer ao sombreador. Neste exemplo, um objeto buffer de vértice contém uma lista de estruturas de vértice personalizadas, definidas e formatadas da seguinte maneira:
OpenGL ES 2.0: configure os atributos que contêm as informações por vértice.
typedef struct
{
GLfloat pos[3];
GLfloat rgba[4];
} Vertex;
No OpenGL ES 2.0, os layouts de entrada estão implícitos; Você pega uma GL_ELEMENT_ARRAY_BUFFER de uso geral e fornece o passo e o deslocamento de modo que o sombreador de vértice possa interpretar os dados depois de carregá-los. Você informa ao sombreador antes de renderizar quais atributos são mapeados para quais partes de cada bloco de dados de vértice com glVertexAttribPointer.
No Direct3D, você deve fornecer um layout de entrada para descrever a estrutura dos dados de vértice no buffer de vértice ao criar o buffer, em vez de antes de desenhar a geometria. Para fazer isso, você usa um layout de entrada que corresponde ao layout dos dados para nossos vértices individuais na memória. É muito importante especificar isso com precisão!
Aqui, você cria uma descrição de entrada como uma matriz de estruturas D3D11_INPUT_ELEMENT_DESC.
Direct3D: defina uma descrição de layout de entrada.
struct VertexPositionColor
{
DirectX::XMFLOAT3 pos;
DirectX::XMFLOAT3 color;
};
// ...
const D3D11_INPUT_ELEMENT_DESC vertexDesc[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
Esta descrição de entrada define um vértice como um par de 2 vetores de 3 coordenadas: um vetor 3D para armazenar a posição do vértice nas coordenadas do modelo e outro vetor 3D para armazenar o valor de cor RGB associado ao vértice. Nesse caso, você usa o formato de ponto flutuante de 3x32 bits, cujos elementos representamos no código como XMFLOAT3(X.Xf, X.Xf, X.Xf)
. Você deve usar tipos da biblioteca DirectXMath sempre que estiver manipulando dados que serão usados por um sombreador, pois isso garante o empacotamento e o alinhamento adequados desses dados. (Por exemplo, use XMFLOAT3 ou XMFLOAT4 para dados vetoriais e XMFLOAT4X4 para matrizes.)
Para obter uma lista de todos os tipos de formato possíveis, consulte DXGI_FORMAT.
Com o layout de entrada por vértice definido, você cria o objeto de layout. No código a seguir, você o escreve em m_inputLayout, uma variável do tipo ComPtr (que aponta para um objeto do tipo ID3D11InputLayout). fileData contém o objeto de sombreador de vértice compilado da etapa anterior, Portar os sombreadores.
Direct3D: crie o layout de entrada usado pelo buffer de vértice.
Microsoft::WRL::ComPtr<ID3D11InputLayout> m_inputLayout;
// ...
m_d3dDevice->CreateInputLayout(
vertexDesc,
ARRAYSIZE(vertexDesc),
fileData->Data,
fileShaderData->Length,
&m_inputLayout
);
Definimos o layout de entrada. Agora, vamos criar um buffer que usa esse layout e carregá-lo com os dados da malha do cubo.
Etapa 2: Criar e carregar os buffers de vértice
No OpenGL ES 2.0, você cria um par de buffers, um para os dados de posição e outro para os dados de cor. (Você também pode criar um struct que contenha ambos e um único buffer.) Você vincula cada buffer e grava dados de posição e cor neles. Posteriormente, durante a função de renderização, associe os buffers novamente e forneça ao sombreador o formato dos dados no buffer para que ele possa interpretá-los corretamente.
OpenGL ES 2.0: Associar os buffers de vértice
// upload the data for the vertex position buffer
glGenBuffers(1, &renderer->vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, renderer->vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(VERTEX) * CUBE_VERTICES, renderer->vertices, GL_STATIC_DRAW);
No Direct3D, os buffers acessíveis por sombreador são representados como estruturas D3D11_SUBRESOURCE_DATA. Para associar o local desse buffer ao objeto de sombreador, você precisa criar uma estrutura de CD3D11_BUFFER_DESC para cada buffer com ID3DDevice::CreateBuffer e, em seguida, definir o buffer do contexto do dispositivo Direct3D chamando um método set específico para o tipo de buffer, como ID3DDeviceContext::IASetVertexBuffers.
Ao definir o buffer, você deve definir o passo (o tamanho do elemento de dados para um vértice individual), bem como o deslocamento (onde a matriz de dados de vértice realmente começa) desde o início do buffer.
Observe que atribuímos o ponteiro à matriz vertexIndices ao campo pSysMem da estrutura D3D11_SUBRESOURCE_DATA . Se isso não estiver correto, sua malha estará corrompida ou vazia!
Direct3D: Criar e definir o buffer de vértice
D3D11_SUBRESOURCE_DATA vertexBufferData = {0};
vertexBufferData.pSysMem = cubeVertices;
vertexBufferData.SysMemPitch = 0;
vertexBufferData.SysMemSlicePitch = 0;
CD3D11_BUFFER_DESC vertexBufferDesc(sizeof(cubeVertices), D3D11_BIND_VERTEX_BUFFER);
m_d3dDevice->CreateBuffer(
&vertexBufferDesc,
&vertexBufferData,
&m_vertexBuffer);
// ...
UINT stride = sizeof(VertexPositionColor);
UINT offset = 0;
m_d3dContext->IASetVertexBuffers(
0,
1,
m_vertexBuffer.GetAddressOf(),
&stride,
&offset);
Etapa 3: Criar e carregar o buffer de índice
Os buffers de índice são uma maneira eficiente de permitir que o sombreador de vértice pesquise vértices individuais. Embora não sejam obrigatórios, nós os usamos neste renderizador de exemplo. Assim como acontece com os buffers de vértice no OpenGL ES 2.0, um buffer de índice é criado e vinculado como um buffer de uso geral e os índices de vértice criados anteriormente são copiados para ele.
Quando estiver pronto para desenhar, associe o vértice e o buffer de índice novamente e chame glDrawElements.
OpenGL ES 2.0: Envie a ordem de índice para a chamada de desenho.
GLuint indexBuffer;
// ...
glGenBuffers(1, &renderer->indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, renderer->indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
sizeof(GLuint) * CUBE_INDICES,
renderer->vertexIndices,
GL_STATIC_DRAW);
// ...
// Drawing function
// Bind the index buffer
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, renderer->indexBuffer);
glDrawElements (GL_TRIANGLES, renderer->numIndices, GL_UNSIGNED_INT, 0);
Com o Direct3D, é um processo um pouco muito semelhante, embora um pouco mais didático. Forneça o buffer de índice como um sub-recurso Direct3D para o ID3D11DeviceContext que você criou quando configurou o Direct3D. Você faz isso chamando ID3D11DeviceContext::IASetIndexBuffer com o sub-recurso configurado para a matriz de índice, da seguinte maneira. (Novamente, observe que você atribui o ponteiro à matriz cubeIndices ao campo pSysMem da estrutura D3D11_SUBRESOURCE_DATA .)
Direct3D: crie o buffer de índice.
m_indexCount = ARRAYSIZE(cubeIndices);
D3D11_SUBRESOURCE_DATA indexBufferData = {0};
indexBufferData.pSysMem = cubeIndices;
indexBufferData.SysMemPitch = 0;
indexBufferData.SysMemSlicePitch = 0;
CD3D11_BUFFER_DESC indexBufferDesc(sizeof(cubeIndices), D3D11_BIND_INDEX_BUFFER);
m_d3dDevice->CreateBuffer(
&indexBufferDesc,
&indexBufferData,
&m_indexBuffer);
// ...
m_d3dContext->IASetIndexBuffer(
m_indexBuffer.Get(),
DXGI_FORMAT_R16_UINT,
0);
Posteriormente, você desenhará os triângulos com uma chamada para ID3D11DeviceContext::D rawIndexed (ou ID3D11DeviceContext::D raw para vértices não indexados), da seguinte maneira. (Para mais detalhes, vá para Desenhe na tela.)
Direct3D: desenhe os vértices indexados.
m_d3dContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_d3dContext->IASetInputLayout(m_inputLayout.Get());
// ...
m_d3dContext->DrawIndexed(
m_indexCount,
0,
0);
Etapa anterior
Portar os objetos de sombreador
Próxima etapa
Comentários
Ao estruturar seu Direct3D, separe o código que chama métodos em ID3D11Device em um método que é chamado sempre que os recursos do dispositivo precisam ser recriados. (No modelo de projeto do Direct3D, esse código está no objeto renderizadorCreateDeviceResource . O código que atualiza o contexto do dispositivo (ID3D11DeviceContext), por outro lado, é colocado no método Render, pois é aqui que você realmente constrói os estágios do sombreador e associa os dados.
Tópicos relacionados
- Como portar um renderizador OpenGL ES 2.0 simples para o Direct3D 11
- Portar os objetos de sombreador
- Portar os buffers de vértice e os dados
- Portar o GLSL