移植顶点缓冲区和数据

重要的应用程序接口(API)

在此步骤中,你将定义将包含网格的顶点缓冲区和索引缓冲区,这些缓冲区允许着色器按指定顺序遍历顶点。

在此时刻,让我们检查一下我们正在使用的立方体网格的硬编码模型。 这两种表示形式的顶点都组织为了三角形列表,而不是条带或其他更高效的三角形布局。 这两种表示形式中的所有顶点都具有关联的索引和颜色值。 本主题中的 Direct3D 代码大部分引用 Direct3D 项目中定义的变量和对象。

这是用于 OpenGL ES 2.0 处理的立方体。 在示例实现中,每个顶点为 7 个浮点值:3 个位置坐标,后跟 4 个 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
};

下面是用于 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
};

查看此代码时,你会注意到 OpenGL ES 2.0 代码中的多维数据集在右侧坐标系中表示,而 Direct3D 特定代码中的多维数据集在左侧坐标系中表示。 导入自己的网格数据时,必须反转模型的 z 轴坐标,并相应地更改每个网格的索引,以便根据坐标系中的更改遍历三角形。

假设我们已成功地将多维数据集网格从右手 OpenGL ES 2.0 坐标系移动到左侧 Direct3D 坐标系,让我们了解如何加载多维数据集数据以在这两个模型中进行处理。

说明书

步骤 1:创建输入布局

在 OpenGL ES 2.0 中,顶点数据作为属性提供给着色器对象,由着色器对象读取。 通常,您会向着色器程序对象提供一个包含着色器 GLSL 中使用的属性名称的字符串,然后获取一个可以提供给着色器的内存位置。 在此示例中,顶点缓冲区对象包含自定义顶点结构的列表,定义并格式化如下:

OpenGL ES 2.0:配置包含每个顶点的相关信息的属性。

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

在 OpenGL ES 2.0 中,输入布局是隐式的,你使用通用的 GL_ELEMENT_ARRAY_BUFFER,并提供适当的步幅和偏移量,以便顶点着色器能在上传数据后解释这些数据。 在渲染之前,您需要用 glVertexAttribPointer通知着色器哪些属性映射到每个顶点数据块的部分。

在 Direct3D 中,在创建缓冲区时,必须提供输入布局来描述顶点缓冲区中顶点数据的结构,而不是在绘制几何图形之前。 为此,请使用与内存中各个顶点的数据布局相对应的输入布局。 准确指定这一点非常重要!

在这里,您将输入描述创建为一个由 D3D11_INPUT_ELEMENT_DESC 结构体组成的数组。

Direct3D:定义输入布局说明。

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

此输入说明将顶点定义为 2 个 3 坐标向量对:一个三维向量用于将顶点的位置存储在模型坐标中,另一个三维向量用于存储与顶点关联的 RGB 颜色值。 在本例中,使用 3x32 位浮点格式,其中元素在代码中表示为 XMFLOAT3(X.Xf, X.Xf, X.Xf)。 每当处理着色器将使用的数据时,应使用 DirectXMath 库中的类型,因为它可确保该数据的正确打包和对齐方式。 (例如,对矢量数据使用 XMFLOAT3XMFLOAT4,对矩阵使用 XMFLOAT4X4

有关所有可能的格式类型的列表,请参阅 DXGI_FORMAT

在定义了每顶点输入布局之后,就可以创建布局对象。 在以下代码中,你将它写入 m_inputLayout,这是一个类型为 ComPtr 的变量(它指向类型为 ID3D11InputLayout的对象)。 fileData 包含上一步编译的顶点着色器对象,移植着色器

Direct3D:创建顶点缓冲区使用的输入布局。

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

// ...

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

我们已定义输入布局。 现在,我们来创建一个使用此布局的缓冲区,并用立方体网格数据加载它。

步骤 2:创建并加载顶点缓冲区(s)

在 OpenGL ES 2.0 中,创建一对缓冲区,一个用于位置数据,一个用于颜色数据。 (还可以创建包含两者以及单个缓冲区的结构。将每个缓冲区绑定到其中并写入位置和颜色数据。 稍后,在呈现函数期间,再次绑定缓冲区,并为着色器提供缓冲区中的数据格式,以便它可以正确解释它。

OpenGL ES 2.0:绑定顶点缓冲区

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

在 Direct3D 中,着色器可访问的缓冲区表示为 D3D11_SUBRESOURCE_DATA 结构。 若要将此缓冲区的位置绑定到着色器对象,需要为每个具有 ID3DDevice::CreateBuffer的缓冲区创建CD3D11_BUFFER_DESC结构,然后通过调用特定于缓冲区类型的 set 方法(如 ID3DDeviceContext::IASetVertexBuffers)来设置 Direct3D 设备上下文的缓冲区。

设置缓冲区时,必须从缓冲区的开头设置步幅(单个顶点的数据元素的大小)以及偏移量(顶点数据数组实际开始的位置)。

请注意,我们将指向 顶点索引 数组的指针分配给 D3D11_SUBRESOURCE_DATA 结构的 pSysMem 字段。 如果不正确,网格将损坏或为空!

Direct3D:创建并设置顶点缓冲区

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

步骤 3:创建并加载索引缓冲区

索引缓冲区是允许顶点着色器查找各个顶点的有效方法。 虽然它们不是必需的,但我们在本示例呈现器中使用它们。 与 OpenGL ES 2.0 中的顶点缓冲区一样,索引缓冲区将创建并绑定为常规用途缓冲区,并将前面创建的顶点索引复制到其中。

当你准备好进行绘制时,请再次绑定顶点缓冲区和索引缓冲区,并调用glDrawElements

OpenGL ES 2.0:将索引顺序发送到绘图调用中。

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

使用 Direct3D 时,它的过程非常相似,不过更具指导性。 将索引缓冲区作为 Direct3D 子资源提供给配置 Direct3D 时创建的 ID3D11DeviceContext。 为此,请使用为索引数组配置的子资源调用 ID3D11DeviceContext::IASetIndexBuffer,如下所示。 (同样,请注意,将指向 cubeIndices 数组的指针分配给 D3D11_SUBRESOURCE_DATA 结构的 pSysMem 字段。)

Direct3D:创建索引缓冲区。

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

稍后,您将通过调用 ID3D11DeviceContext::DrawIndexed(或 ID3D11DeviceContext::Draw 用于未索引的顶点)来绘制三角形,如下所示。 (有关更多详细信息,请跳到 绘图到屏幕。)

Direct3D:绘制索引顶点。

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

// ...

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

上一步

移植着色器对象

后续步骤

移植 GLSL

注解

构建 Direct3D 时,请将调用 ID3D11Device 上的方法的代码 分隔为每当需要重新创建设备资源时调用的方法。 (在 Direct3D 项目模板中,此代码位于呈现器对象的 CreateDeviceResource 方法中。 另一方面,更新设备上下文(ID3D11DeviceContext)的代码放置在 Render 方法中,因为这是实际构造着色器阶段并绑定数据的位置。