利用 Direct3D 绘制几何体—2.顶点缓冲区

为了使 GPU 可以访问顶点数组,就需要把它们放置在称为缓冲区 (buffer的 GPU 资源(ID3D12Resource) 里。我们把存储顶点的缓冲区叫作顶点缓冲区 (vertex buffer)。缓冲区的结构比纹理更为简单:既非多维资源,也不支持 mipmap、过滤器以及多重采样等技术。当需要向 GPU 提供如顶点这类数据元素所构成的数组时,我们便会使用缓冲区。

就像在创建深度/模板缓冲区中所做的那样,我们先通过填写 D3D12_RESOURCE_DESC 结构体来描述缓冲区资源,接着再调用 ID3D12Device::CreateCommittedResource 方法去创建ID3D12Resource 对象。至于 D3D12_RESOURCE_DESC 结构体中所有成员的介绍,可参考4.3.8节。Direct3D 12 提供了一个 C++ 包装类 CD3DX12_RESOURCE_DESC,它派生自D3D12_RESOURCE_DESC 结构体,并附有多种便于使用的构造函数以及方法。特别是它提供的下列方法,一种简化缓冲区描述过程的 D3D12_RESOURCE_DESC 的构造函数:

static inline CD3DX12_RESOURCE_DESC Buffer( 
    UINT64 width,
    D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE,
    UINT64 alignment = 0 ){
  return CD3DX12_RESOURCE_DESC( D3D12_RESOURCE_DIMENSION_BUFFER, 
    alignment, width, 1, 1, 1, 
    DXGI_FORMAT_UNKNOWN, 1, 0, 
    D3D12_TEXTURE_LAYOUT_ROW_MAJOR, flags );
}

对于缓冲区而言,函数代码中的 width 即表示缓冲区中所占的字节数。例如,若缓冲区存储了 64 个 float 类型的数据,那么 width 的值即为 64*sizeof(float)。

除此之外,CD3DX12_RESOURCE_DESC 类还提供了以下构建 D3D12_RESOURCE_DESC 结构体的简便方法,用于描述纹理资源及其可供查询的相关信息:

1.CD3DX12_RESOURCE_DESC::Tex1D

2.CD3DX12_RESOURCE_DESC::Tex2D

3.CD3DX12_RESOURCE_DESC::Tex3D

我们在第4章中曾提到深度/模板缓冲区,它是一种以 ID3D12Resource 对象表示的 2D 纹理。在Direct3D 12中,所有的资源均用 ID3D12Resource 接口表示。相比之下,Direct3D 11 则采用如ID3D11Buffer 与 ID3D11Texture2D 等多种不同的接口来表示各种不同的资源。而且,在 Direct3D 12 中,资源的类型由 D3D12_RESOURCE_DESC::D3D12_RESOURCE_DIMENSION 字段来加以区分。
例如:
缓冲区用 D3D12_RESOURCE_DIMENSION_BUFFER 类型表示。
2D 纹理用 D3D12_RESOURCE_DIMENSION_TEXTURE2D 类型表示。

对于静态几何体 (static geometry,即每一帧都不会发生改变的几何体,如游戏中的树木、建筑物、地形和角色动画) 而言,我们会将其顶点缓冲区置于默认堆 (D3D12_HEAP_TYPE_DEFAULT) 中来优化性能。在这种情况下,顶点缓冲区初始化完毕之后,只有 GPU 需要从其中读取数据来绘制几何体。

如果 CPU 不能向默认堆中的顶点缓冲区写入数据,那么如何初始化此顶点缓冲区:

除了创建顶点缓冲区资源本身之外,还需用 D3D12_HEAP_TYPE_UPLOAD 这种堆类型来创建中介类型的上传缓冲区 (upload buffer) 资源。在4.3.8节里,我们就是通过把资源提交至上传堆,才得以将数据从 CPU 复制到 GPU 显存中。在创建了上传缓冲区之后,我们就可以将顶点数据从系统内存复制到上传缓冲区,而后再把顶点数据从上传缓冲区复制到真正的顶点缓冲区中。

3D12 的 ID3D12Heap 分为以下几种类型:

D3D12_HEAP_TYPE_DEFAULT,同 Vulkan 的 DEVICE_LOCAL 类似,这是一种 GPU 访问最快的 Heap 类型。但不允许 CPU 通过 Mapping 直接访问。

D3D12_HEAP_TYPE_UPLOAD ,这是一种可以由 CPU 访问的系统内存,可以通过 Mapping 访问数据。一般用于上传数据到在 D3D12_HEAP_TYPE_DEFAULT 堆上创建的资源。

D3D12_HEAP_TYPE_READBACK,这是一种可以由 GPU 写入数据,CPU 回读数据的系统内存,此堆类型最适合 GPU 一次写入,CPU 可读的数据。

由于我们需要利用作为中介的上传缓冲区来初始化默认缓冲区(即用堆类型D3D12_HEAP_TYPE_DEFAULT 创建的缓冲区)中的数据,因此,我们就在 d3dUtil.h/.cpp 文件中构建了下列工具函数。

D3D12_SUBRESORSE_DATA 结构体的定义:

typedef struct D3D12_SUBRESOURCE_DATA
{
  const void *pData;
  LONG_PTR RowPitch;
  LONG_PTR SlicePitch;
} D3D12_SUBRESOURCE_DATA;

1. pData:指向某个系统内存块的指针,其中有初始化缓冲区所用的数据。如果欲初始化的缓冲区能够存储 n 个顶点数据,则该系统内存块必定可容纳至少 n 个顶点数据,以此来初始化整个缓冲区。

2. RowPitch 和 SlicePitch:对于缓冲区而言,此参数为欲复制数据的字节数。

下面的代码演示了此类将如何创建存有立方体8个顶点的默认缓冲区,并为其中的每个顶点都分别赋予了不同的颜色。

Vertex vertices[] =
{
  { XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4(Colors::White) },
  { XMFLOAT3(-1.0f, +1.0f, -1.0f), XMFLOAT4(Colors::Black) },
  { XMFLOAT3(+1.0f, +1.0f, -1.0f), XMFLOAT4(Colors::Red) },
  { XMFLOAT3(+1.0f, -1.0f, -1.0f), XMFLOAT4(Colors::Green) },
  { XMFLOAT3(-1.0f, -1.0f, +1.0f), XMFLOAT4(Colors::Blue) },
  { XMFLOAT3(-1.0f, +1.0f, +1.0f), XMFLOAT4(Colors::Yellow) },
  { XMFLOAT3(+1.0f, +1.0f, +1.0f), XMFLOAT4(Colors::Cyan) },
  { XMFLOAT3(+1.0f, -1.0f, +1.0f), XMFLOAT4(Colors::Magenta) }
};
const UINT64 vbByteSize = 8 * sizeof(Vertex);

ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
ComPtr<ID3D12Resource> VertexBufferUploader = nullptr;
VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
  mCommandList.Get(), vertices, vbByteSize, VertexBufferUploader);

代码中的 Vertex 类型(即顶点的坐标及其颜色)的定义如下。

struct Vertex
{
  XMFLOAT3 Pos;
  XMFLOAT4 Color;
};

为了将顶点缓冲区绑定到渲染流水线上,我们需要给这种资源创建一个顶点缓冲区视图 (vertex buffer view)。与 RTV (渲染目标视图) 不同的是,我们无须为顶点缓冲区视图创建描述符堆。而且,顶点缓冲区视图是由 D3D12_VERTEX_BUFFER_VIEW 结构体来表示。

typedef struct D3D12_VERTEX_BUFFER_VIEW
{
  D3D12_GPU_VIRTUAL_ADDRESS BufferLocation;
  UINT SizeInBytes;
  UINT StrideInBytes;
}  D3D12_VERTEX_BUFFER_VIEW;

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

2. SizeInBytes:待创建视图的顶点缓冲区大小(用字节表示)。

3. StrideInBytes:每个顶点元素所占用的字节数。

在顶点缓冲区及其对应视图创建完成后,便可以将它与渲染流水线上的一个输入槽 (input slot) 相绑定。这样一来,我们就能向流水线中的输入装配器阶段传递顶点数据了。此操作可以通过下列方法来实现。

void ID3D12GraphicsCommandList::IASetVertexBuffers(
  UINT StartSlot,
  UINT NumView,
  const D3D12_VERTEX_BUFFER_VIEW *pViews);

1. StartSlot:在绑定多个顶点缓冲区时,所用的起始输入槽(若仅有一个顶点缓冲区,则将其绑定至此槽)。输入槽共有16个,索引为0~15。

2. NumViews:将要与输入槽绑定的顶点缓冲区数量(即视图数组 pViews 中视图的数量)。如果起始输入槽 StartSlot 的索引值为 k,且我们要绑定 n 个顶点缓冲区,那么这些缓冲区将依次与输入槽 I_{k}I_{k+1},...,I_{k+n-1} 相绑定。

3. pViews:指向顶点缓冲区视图数组中第一个元素的指针。

该函数的调用示例:

D3D12_VERTEX_BUFFER_VIEW vbv;
vbv.BufferLocation = VertexBufferGPU->GetGPUVirtualAddress();
vbv.StrideInBytes = sizeof(Vertex);
vbv.SizeInBytes = 8 * sizeof(Vertex);

D3D12_VERTEX_BUFFER_VIEW vertexBuffers[1] = { vbv };
mCommandList->IASetVertexBuffers(0, 1, vertexBuffers);

由于 IASetVertexBuffers 方法会将顶点缓冲区数组中的元素设置到不同的输入槽上去,所以使它看起来似乎有些复杂。但是,在我们的示例中实际只会使用一个输入槽。

我们若不对顶点缓冲区进行任何修改,它就将一直被绑定于所在的输入槽上。所以,如果使用多个顶点缓冲区,那么就可以按以下流程来构建代码:

ID3D12Resource* mVB1; // 存储Vertex1类型的顶点
ID3D12Resource* mVB2; // 存储Vertex2类型的顶点

D3D12_VERTEX_BUFFER_VIEW mVBView1; // mVB1的视图
D3D12_VERTEX_BUFFER_VIEW mVBView2; // mVB2的视图
/*……创建顶点缓冲区及其视图……*/

mCommandList->IASetVertexBuffers(0, 1, &mVBView1);/* ……使用顶点缓冲区1来绘制物体…… */

mCommandList->IASetVertexBuffers(0, 1, &mVBView2);/* ……使用顶点缓冲区2来绘制物体…… */

将顶点缓冲区设置到输入槽上并不会对其执行实际的绘制操作,而是仅为顶点数据送至渲染流水线做好准备而已。这最后一步才是通过 ID3D12GraphicsCommandList::DrawInstanced 方法真正地绘制顶点:

void ID3D12GraphicsCommandList::DrawInstanced (
  UINT VertexCountPerInstance,
  UINT InstanceCount,
  UINT StartVertexLocation,
  UINT StartInstanceLocation);

1. VertexCountPerInstance:每个实例要绘制的顶点数量。

2. InstanceCount:启用实例化 (instancing) 高级技术。就目前来说,我们只绘制一个实例,因而将此参数设置为 1。

3. StartVertexLocation:指定顶点缓冲区内第一个被绘制顶点的索引(该索引值以 0 为基准)。

4. StartInstanceLocation:实例化 (instancing) 相关,暂时只需将其设置为 0。

VertexCountPerInstance 和 StartVertexLocation 两个参数定义了顶点缓冲区中将要被绘制的一组连续顶点,如图所示:

StartVertexLocation 参数指定了顶点缓冲区中第一个被绘制顶点的索引(此索引从 0 开始计),VertexCountPerInstance 指定了欲绘制顶点的个数

既然 DrawInstanced 方法没有指定顶点被定义为何种图元,那么,它们应该被绘制为点、线列表还是三角形列表呢?回顾 5.5.2 节可知,图元拓扑状态实由ID3D12GraphicsCommandList::IASetPrimitiveTopology 方法来设置。下面给出一个相关的调用示例:

cmdList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值