Condividi tramite


Convertire i vertex buffer e i dati

API importanti

In questo passaggio verranno definiti i vertex buffer che conterranno le mesh e i buffer di indice che consentono agli shader di attraversare i vertici in un ordine specificato.

A questo punto, esaminiamo il modello hardcoded per la mesh del cubo in uso. Entrambe le rappresentazioni hanno i vertici organizzati come un elenco di triangoli (anziché una striscia o un altro layout di triangolo più efficiente). Tutti i vertici in entrambe le rappresentazioni hanno anche indici e valori di colore associati. Gran parte del codice Direct3D in questo argomento si riferisce a variabili e oggetti definiti nel progetto Direct3D.

Ecco il cubo per l'elaborazione da OpenGL ES 2.0. Nell'implementazione di esempio ogni vertice è 7 valori float: 3 coordinate di posizione seguite da 4 valori di colore 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
};

Ed ecco lo stesso cubo per l'elaborazione da 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
};

Esaminando questo codice, si nota che il cubo nel codice OpenGL ES 2.0 è rappresentato in un sistema di coordinate destro, mentre il cubo nel codice specifico di Direct3D è rappresentato in un sistema di coordinate a sinistra. Quando si importano dati mesh personalizzati, è necessario invertire le coordinate dell'asse z per il modello e modificare gli indici per ogni mesh di conseguenza per attraversare i triangoli in base alla modifica nel sistema di coordinate.

Supponendo che la mesh del cubo sia stata spostata correttamente dal sistema di coordinate OpenGL ES 2.0 a sinistra direct3D, si vedrà come caricare i dati del cubo per l'elaborazione in entrambi i modelli.

Istruzioni

Passaggio 1: Creare un layout di input

In OpenGL ES 2.0 i dati dei vertici vengono forniti come attributi che verranno forniti e letti dagli oggetti shader. In genere si specifica una stringa contenente il nome dell'attributo usato nel glSL dello shader all'oggetto programma shader e si ottiene una posizione di memoria che è possibile fornire allo shader. In questo esempio, un oggetto vertex buffer contiene un elenco di strutture vertex personalizzate, definite e formattate come segue:

OpenGL ES 2.0: configurare gli attributi che contengono le informazioni per vertice.

typedef struct 
{
  GLfloat pos[3];        
  GLfloat rgba[4];
} Vertex;

In OpenGL ES 2.0 i layout di input sono impliciti; si accetta un GL_ELEMENT_ARRAY_BUFFER per utilizzo generico e si specifica lo stride e l'offset in modo che il vertex shader possa interpretare i dati dopo il caricamento. È necessario informare lo shader prima di eseguire il rendering degli attributi a cui vengono mappati parti di ogni blocco di dati dei vertici con glVertexAttribPointer.

In Direct3D è necessario fornire un layout di input per descrivere la struttura dei dati dei vertici nel buffer dei vertici quando si crea il buffer, anziché prima di disegnare la geometria. A tale scopo, si usa un layout di input che corrisponde al layout dei dati per i singoli vertici in memoria. È molto importante specificare questo in modo accurato!

In questo caso si crea una descrizione di input come matrice di strutture D3D11_INPUT_ELEMENT_DESC.

Direct3D: definire una descrizione del layout di input.

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 },
};

Questa descrizione di input definisce un vertice come coppia di 2 vettori a 3 coordinate: un vettore 3D per archiviare la posizione del vertice nelle coordinate del modello e un altro vettore 3D per archiviare il valore del colore RGB associato al vertice. In questo caso, si usa il formato a virgola mobile a 3x32 bit, elementi di cui si rappresenta nel codice come XMFLOAT3(X.Xf, X.Xf, X.Xf). È consigliabile usare i tipi della libreria DirectXMath ogni volta che si gestiscono i dati che verranno usati da uno shader, in quanto garantiscono la compressione e l'allineamento appropriati dei dati. (Ad esempio, usare XMFLOAT3 or XMFLOAT4 per i dati vettoriali e XMFLOAT4X4 per le matrici.)

Per un elenco di tutti i tipi di formato possibili, vedere DXGI_FORMAT.

Dopo aver definito il layout di input per vertice, si crea l'oggetto layout. Nel codice seguente viene scritto in m_inputLayout una variabile di tipo ComPtr che punta a un oggetto di tipo ID3D11InputLayout. fileData contiene l'oggetto vertex shader compilato del passaggio precedente, Convertire gli shader.

Direct3D: creare il layout di input usato dal buffer dei vertici.

Microsoft::WRL::ComPtr<ID3D11InputLayout>      m_inputLayout;

// ...

m_d3dDevice->CreateInputLayout(
  vertexDesc,
  ARRAYSIZE(vertexDesc),
  fileData->Data,
  fileShaderData->Length,
  &m_inputLayout
);

È stato definito il layout di input. Creare ora un buffer che usa questo layout e caricarlo con i dati della mesh del cubo.

Passaggio 2: Creare e caricare i vertex buffer

In OpenGL ES 2.0 viene creata una coppia di buffer, una per i dati di posizione e una per i dati colore. È anche possibile creare uno struct contenente sia che un singolo buffer. Ogni buffer viene associato e si scrivono dati di posizione e colore. Successivamente, durante la funzione di rendering, associare nuovamente i buffer e fornire allo shader il formato dei dati nel buffer in modo che possa interpretarlo correttamente.

OpenGL ES 2.0: associare i vertex buffer

// 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);   

In Direct3D i buffer accessibili da shader sono rappresentati come strutture D3D11_SUBRESOURCE_DATA. Per associare la posizione di questo buffer all'oggetto shader, è necessario creare una struttura CD3D11_BUFFER_DESC per ogni buffer con ID3DDevice::CreateBuffer e quindi impostare il buffer del contesto di dispositivo Direct3D chiamando un metodo set specifico per il tipo di buffer, ad esempio ID3DDeviceContext::IASetVertexBuffers.

Quando si imposta il buffer, è necessario impostare lo stride (le dimensioni dell'elemento dati per un singolo vertice), nonché l'offset (in cui viene effettivamente avviata la matrice di dati dei vertici) dall'inizio del buffer.

Si noti che il puntatore viene assegnato alla matrice vertexIndices al campo pSysMem della struttura D3D11_SUBRESOURCE_DATA . Se non è corretto, la mesh sarà danneggiata o vuota.

Direct3D: creare e impostare il buffer dei vertici

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);

Passaggio 3: Creare e caricare il buffer dell'indice

I buffer di indice sono un modo efficiente per consentire al vertex shader di cercare singoli vertici. Anche se non sono necessari, vengono usati in questo renderer di esempio. Come per i vertex buffer in OpenGL ES 2.0, un buffer di indice viene creato e associato come buffer per utilizzo generico e gli indici dei vertici creati in precedenza vengono copiati in esso.

Quando si è pronti a disegnare, si associano di nuovo il vertice e il buffer di indice e si chiama glDrawElements.

OpenGL ES 2.0: inviare l'ordine di indice alla chiamata di disegno.

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);

Con Direct3D, è un processo molto simile, anche se un po ' più didattico. Fornire il buffer di indice come sottorisorsa Direct3D all'ID3D11DeviceContext creato durante la configurazione di Direct3D. A tale scopo, chiamare ID3D11DeviceContext::IASetIndexBuffer con la sottorisorsa configurata per la matrice di indici, come indicato di seguito. Anche in questo caso, si noti che si assegna il puntatore alla matrice cubeIndices al campo pSysMem della struttura D3D11_SUBRESOURCE_DATA .

Direct3D: creare il buffer di indice.

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);

Successivamente, disegnare i triangoli con una chiamata a ID3D11DeviceContext::DrawIndexed (o ID3D11DeviceContext::Draw per i vertici non indicizzati), come indicato di seguito. (Per altri dettagli, passare a Disegnare sullo schermo.

Direct3D: disegnare i vertici indicizzati.

m_d3dContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_d3dContext->IASetInputLayout(m_inputLayout.Get());

// ...

m_d3dContext->DrawIndexed(
  m_indexCount,
  0,
  0);

Passaggio precedente

Convertire gli oggetti shader

Passaggio successivo

Convertire il codice GLSL

Osservazioni:

Quando si struttura l'oggetto Direct3D, separare il codice che chiama i metodi in ID3D11Device in un metodo chiamato ogni volta che è necessario ricreare le risorse del dispositivo. Nel modello di progetto Direct3D questo codice si trova nell'oggetto rendererMetodi CreateDeviceResource . Il codice che aggiorna il contesto di dispositivo (ID3D11DeviceContext), d'altra parte, viene inserito nel metodo Render, poiché è qui che si costruiscono effettivamente le fasi dello shader e si associano i dati.