Compartilhar via


Portar os buffers de vértice e os dados

APIs importantes

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 vez de uma faixa ou outro layout de triângulo mais eficiente). Todos os vértices em ambas as representações também têm índices associados e valores de cor. Grande parte do código Direct3D neste tópico refere-se a variáveis e objetos definidos no projeto 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ê observará que o cubo no código 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 do seu modelo e alterar os índices de cada malha, percorrendo os triângulos de acordo com a mudança no sistema de coordenadas.

Supondo que tenhamos movido com sucesso a malha do cubo do sistema de coordenadas destro do OpenGL ES 2.0 para o canhoto do Direct3D, 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 de sombreador. Normalmente, você fornece uma cadeia de caracteres que contém o nome do atributo usado no GLSL do sombreador para o objeto do programa de sombreador e obtém um local de memória de volta que você pode fornecer ao sombreador. Neste exemplo, um objeto de 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 são implícitos; você tem um buffer de uso geral GL_ELEMENT_ARRAY_BUFFER e fornece o stride e offset para que o sombreador de vértice possa interpretar os dados após o envio. Informar o sombreador, antes de renderizar, sobre quais atributos mapeiam 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 de 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 vetores de 2 coordenadas 3: 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, elementos dos quais representamos no código como XMFLOAT3(X.Xf, X.Xf, X.Xf). Você deve usar tipos da biblioteca DirectXMath sempre que estiver tratando de 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 de vetor 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 grava 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értices.

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 de malha de 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 uma struct que contenha ambos, os dados de posição e cor, em um único buffer.) Você associa cada buffer e insere os 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 ao sombreador são representados como estruturas de D3D11_SUBRESOURCE_DATA. Para associar o local desse buffer ao objeto de sombreador, você precisa criar uma estrutura CD3D11_BUFFER_DESC para cada buffer com ID3DDevice::CreateBuffere, em seguida, definir o buffer do contexto do dispositivo Direct3D chamando um método definido 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) a partir do início do buffer.

Observe que atribuimos 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 procure vértices individuais. Embora elas não sejam necessárias, as usamos neste renderizador de exemplo. Assim como acontece com buffers de vértice no OpenGL ES 2.0, um buffer de índice é criado e associado como um buffer de finalidade geral e os índices de vértice criados anteriormente são copiados nele.

Quando estiver pronto para desenhar, você associará o vértice e o buffer de índice novamente e chamará glDrawElements.

OpenGL ES 2.0: envie a ordem dos índices para o comando 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 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. Faça 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: criar 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::DrawIndexed (ou ID3D11DeviceContext::Draw para vértices não indexados), da seguinte maneira. (Para obter mais detalhes, avance para Desenhar 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

Portar o GLSL

Observações

Ao estruturar seu Direct3D, separe o código que chama métodos no ID3D11Device em um método chamado sempre que os recursos do dispositivo precisarem ser recriados. (No modelo de projeto direct3D, esse código está nos métodos CreateDeviceResource do objeto renderizador. O código que atualiza o contexto do dispositivo (ID3D11DeviceContext), por outro lado, é colocado no método Render, pois é aí que você realmente constrói os estágios do sombreador e associa os dados.