利用 Direct3D 绘制几何体—3.索引和索引缓冲区

与顶点相似,为了使 GPU 可以访问索引数组,就需要将它们放置于 GPU 的缓冲区资源(ID3D12Resource) 内。我们称存储索引的缓冲区为索引缓冲区 (index buffer)。由于本书所采用的d3dUtil::CreateDefaultBuffer 函数是通过 void* 类型作为参数引入泛型数据,这就意味着我们也可以用此函数来创建索引缓冲区(或任意类型的默认缓冲区)。

为了使索引缓冲区与渲染流水线绑定,我们需要给索引缓冲区资源创建一个索引缓冲区视图 (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;

1. BufferLocation:待创建视图的索引缓冲区资源虚拟地址。我们可以通过调用ID3D12Resource::GetGPUVirtualAddress 方法来获取此地址

2. SizeInBytes:待创建视图的索引缓冲区大小(以字节表示)。

3. Format:索引的格式必须为表示 16 位索引的 DXGI_FORMAT_R16_UINT 类型,或表示 32 位索引的 DXGI_FORMAT_R32_UINT 类型。16 位的索引可以减少内存和带宽的占用,但如果索引值范围超过了 16 位数据的表达范围,则也只能采用 32 位索引了。

与顶点缓冲区相似(也包括其他的 Direct3D 资源在内),在使用之前,我们需要先将它们绑定到渲染流水线上。通过 ID3D12GraphicsCommandList::IASetIndexBuffer 方法即可将索引缓冲区绑定到输入装配器阶段。下面的代码演示了怎样创建一个索引缓冲区来定义构成立方体的三角形,以及为该索引缓冲区创建视图并将它绑定到渲染流水线:

std::uint16_t indices[] = {
  // 立方体前表面
  0, 1, 2,
  0, 2, 3,
  // 立方体后表面
  4, 6, 5,
  4, 7, 6,
  // 立方体左表面
  4, 5, 1,
  4, 1, 0,
  // 立方体右表面
  3, 2, 6,
  3, 6, 7,
  // 立方体上表面
  1, 5, 6,
  1, 6, 2,
  // 立方体下表面
  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 方法代替 DrawInstanced 方法进行绘制。

void ID3D12GraphicsCommandList::DrawIndexedInstanced(
  UINT IndexCountPerInstance,
  UINT InstanceCount,
  UINT StartIndexLocation,
  INT BaseVertexLocation,
  UINT StartInstanceLocation);

1. IndexCountPerInstance:每个实例将要绘制的索引数量

2. InstanceCount:用于实现实例化。目前只绘制一个实例,因而将此值设置为 1

3. StartIndexLocation:指向索引缓冲区中的某个元素,将其标记为欲读取的起始索引

4. BaseVertexLocation:在本次绘制调用读取顶点之前,要为每个索引都加上此整数值

5. StartInstanceLocation:用于实现实例化,暂将其设置为 0

为了理解这些参数,让我们来思考这样一个情景:假设有 3 个欲绘制的物体,一个球体、一个立方体以及一个圆柱体。首先,每个物体都有自己的顶点缓冲区以及索引缓冲区。而每个局部索引缓冲区中的索引,又都引用的是各自的局部顶点缓冲区。现在,我们把这 3 个物体的顶点索引分别连接全局顶点缓冲区全局索引缓冲区,如下图所示。(在顶点缓冲区和索引缓冲区合并的过程中,调用 API 时可能会产生一些开销,但这基本上不会成为程序的瓶颈。出于性能的原因,当应用中有一些可以轻易合并的零散顶点缓冲区和索引缓冲区时,便值得照这样试一试)待合并完成后,索引缓冲区里的元素就不能正确地引用对应的顶点数据了,这是因为它们存储的索引值是相对于物体各自的局部顶点缓冲区而言,而非全局的顶点缓冲区。所以,还需要对索引值进行重新计算,使之可以正确地引用全局顶点缓冲区中的顶点数据。假设原始的立方体索引是根据立方体顶点编号来计算的:

        0,1,...,numBoxVertices - 1

但是在顶点缓冲区与索引缓冲区一一合并之后,该立方体的索引编号将依次变为:

        firstBoxVertexPos,firstBoxVertexPos + 1,...,firstBoxVertexPos + numBoxVertices - 1

将几个顶点缓冲区合并为一个大的顶点缓冲区,再把对应的若干索引缓冲区合并为一个大的索引缓冲区

因此,为了拿到合并后的索引,我们需要为每个立方体的原索引加上 firstBoxVertexPos(缓冲区合并后立方体第一个顶点的索引值)。类似地,我们也需要给每个圆柱体的原索引加上firstCylVertexPos(缓冲区合并后圆柱体第一个顶点的索引值)。注意,球体的索引是不需要改变的(因为球体第一个顶点的位置始终为 0,在合并后的全局索引缓冲区中也未曾改变)。我们将每个物体的第一个顶点相对于全局顶点缓冲区的位置叫作它的基准顶点地址 (base vertex location)。通常来讲,一个物体的新索引是通过原始索引加上它的基准顶点地址来获取的。事实上,我们不必亲自计算新的索引值:通过向 DrawIndexedInstanced 函数的第4个参数传递基准顶点地址,即可让 Direct3D 去执行相关计算工作。

接下来,我们再通过下列 3 次绘制调用来依次绘制球体、立方体和圆柱体:

mCmdList->DrawIndexedInstanced(
  numSphereIndices, 1, 0, 0, 0);
mCmdList->DrawIndexedInstanced(
  numBoxIndices, 1, firstBoxIndex, firstBoxVertexPos, 0);
mCmdList->DrawIndexedInstanced(
  numCylIndices, 1, firstCylIndex, firstCylVertexPos, 0);

  • 11
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值