为了让GPU获得顶点索引的数组,这些数据需要放到GPU资源(ID3D12Resource)缓冲中,将存储索引信息的buffer称作索引缓冲Index buffer。

可以使用d3dUtil::CreateDefaultBuffer函数来创建索引缓冲。其实这个函数可以创建各种类型的默认缓冲。

当然,为了把索引信息绑定到渲染管线,也需要给索引缓冲创建view,index buffer view用D3D12_INDEX_BUFFER_VIEW结构体来表示:

typedef struct D3D12_INDEX_BUFFER_VIEW {

D3D12_GPU_VIRTUAL_ADDRESS BufferLocation;

UINT SizeInBytes;

DXGI_FORMAT Format;

} D3D12_INDEX_BUFFER_VIEW;

来自 <https://docs.microsoft.com/zh-cn/windows/win32/api/d3d12/ns-d3d12-d3d12_index_buffer_view>

参数:

  1. 索引缓冲的GPU虚拟地址,使用ID3D12Resource::GetGPUVirtualAddress函数来设置
  2. 索引缓冲的大小,字节单位
  3. 这里索引格式只有两个可选,DXGI_FORMAT_R16_UINT或者DXGI_FORMAT_R32_UINT

 
 

在使用索引资源之间还需要绑定到渲染管线,使用ID3D12CommandList::SetIndexBuffer函数将索引资源绑定到输入装配阶段。下面举个例子,创建了一个cube的索引缓冲并为其创建了view然后绑定到管线:

std::uint16_t indices[] = {

// front face

0, 1, 2, 0, 2, 3,

// back face

4, 6, 5, 4, 7, 6,

// left face

4, 5, 1, 4, 1, 0,

// right face

3, 2, 6,3, 6, 7,

// top face

1, 5, 6, 1, 6, 2,

// bottom face

4, 0, 3, 4, 3, 7

};

const UINT ibByteSize = 36 * sizeof(std::uint16_t);

ComPtr<ID3D12Resource> IndexBufferGPU = nullptr;

ComPtr<ID3D12Resource> IndexBufferUploader = nullptr;

IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(), mCommandList.Get(), indices), ibByteSize, IndexBufferUploader);

D3D12_INDEX_BUFFER_VIEW ibv;

ibv.BufferLocation = IndexBufferGPU->GetGPUVirtualAddress(); ibv.Format = DXGI_FORMAT_R16_UINT;

ibv.SizeInBytes = ibByteSize;

mCommandList->IASetIndexBuffer(&ibv);

最后在使用索引时,必须使用ID3D12GraphicsCommandList::DrawIndexedInstanced函数:

void DrawIndexedInstanced(

UINT IndexCountPerInstance,

UINT InstanceCount,

UINT StartIndexLocation,

INT BaseVertexLocation,

UINT StartInstanceLocation

);

参数:

  1. 要绘制的索引数量
  2. 要绘制的实例数。现在设置为1,高级特性需要的参数
  3. GPU从索引缓冲区中读取的第一个索引的位置。
  4. 在从顶点缓冲区读取顶点之前添加到每个索引的值。
  5. 在从顶点缓冲区读取每个实例数据之前添加到每个索引的值。现在设置为0

 
 

现在假设有下面的场景:

假设要绘制三个物体,一个球,一个正方体,一个圆柱体。首先每个物体都有自己的顶点缓冲和索引缓冲,三个物体之间的数据时相互独立的,没有关系的,自己的索引针对于自己的顶点。那么现在假设可以将三个物体的顶点信息进行关联,放在一个更大的单独的缓冲区中,但是如果三个物体的顶点数据被关联起来了,那么原有的索引,可能就失效了,就不能正确绘制了,这很容易理解。


将每个物体单独的顶点缓冲和索引缓冲叫做local,而联系之后的称作global。

可以看出至少球体的索引还是正确的,但是box和圆柱就需要更新索引缓冲,需要将firstBoxVertexPos加到所有的box的索引上。将一个物体的第一个顶点在global vertex buffer中的位置称作base vertex location。当然,DX可以帮助更新索引,将base vertex location作为DrawIndexedInstanced的第四个参数。然后可以按照下面的样子调用三个物体的绘制。

mCmdList->DrawIndexedInstanced(numSphereIndices, 1, 0, 0, 0);

mCmdList->DrawIndexedInstanced(numBoxIndices, 1, firstBoxIndex, firstBoxVertexPos, 0);

mCmdList->DrawIndexedInstanced(numCylIndices, 1, firstCylIndex, firstCylVertexPos, 0);