Skip to content

绘制

Note

写在前面:

对于后续提到的数据,尽管c++支持在类体中直接初始化,但直接在类体初始化会导致阅读和管理困难,且DX12项目中的初始化往往依赖D3D12提供的接口,在极端情况下可能存在相关接口还没完成初始化的时序问题

因此,在通常实践中,应该将初始化语法写在具体的方法内,并统一调用。譬如,下文提到的顶点与输入布局描述就可以在BuildPSO方法中初始化。

Note

对于任何代码中创建的任何数据和对象,最初都只存在于CPU中,为了使GPU能够访问和处理这些数据,需要手动将数据送到GPU中,即在初始化渲染器时大段的代码都是在执行”创建数据-创建上传缓冲区和资源缓冲区-拷贝数据到上传缓冲区-从上传缓冲区拷贝到资源缓冲区“这一过程。

约定以下命名:

上传缓冲区/上传堆(upload Buffer):CPU无法将资源直接送进GPU用于计算的区域,只能将它暂存在RAM或GPU划分出来用于和CPU通讯的区域内。称这些区域为上传缓冲区/上传堆,是CPU可读写GPU可读但速度较慢的区域。

资源缓冲区/默认堆(Default Buffer):GPU内部存储资源和计算的区域,按照对应的资源进行进一步的命名划分,如顶点缓冲区、视图缓冲区,其本质是连续内存的二进制数据,需要根据描述符来确定具体的读取方式。

其中,常量缓冲区是例外,由于每帧都有大量的变化,通常直接写在上传缓冲区内。

顶点与输入布局描述

1. 定义顶点结构体,声明顶点中存储的具体数据。譬如,一个包含位置和颜色信息的顶点以如下方式进行定义:

顶点结构体和顶点类型绑定,有多少顶点类型就需要创建多少顶点结构体。

1
2
3
4
5
    struct Vertex
    {
        XMFLOAT3 Pos; 
        XMFLOAT4 Color; 
    }; 

2. 为每个顶点结构体创建D3D12_INPUT_ELEMENT_DESC数组,用于描述顶点结构体中每个元素。

\(1.\)中的Vertex为例,创建D3D12_INPUT_ELEMENT_DESC的语句如下:

D3D12_INPUT_ELEMENT_DESC elementDescs[] = {
    {
        "POSITION",                     // 语义名:必须与 HLSL 中的冒号后名称一致
        0,                              // 语义索引:如果有两个坐标,这里写 0 和 1
        DXGI_FORMAT_R32G32B32_FLOAT,    // 格式:对应 XMFLOAT3 (3个32位浮点数)
        0,                              // 输入槽:通常设为 0
        0,                              // 偏移量:Pos 是结构体第一个成员,偏移量为 0
        D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, // 类别:按顶点数据处理
        0                               // 实例步进:非实例化渲染设为 0
    },
    {
        "COLOR",                        // 语义名:对应 HLSL 中的 COLOR
        0,                              // 语义索引
        DXGI_FORMAT_R32G32B32A32_FLOAT, // 格式:对应 XMFLOAT4 (4个32位浮点数)
        0,                              // 输入槽:同一个 Buffer,槽位相同
        12,                             // 偏移量:跳过前面的 Pos (3*4=12字节)
        D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,
        0
    }
};

D3D12_INPUT_ELEMENT_DESC包含\(7\)个元素,每个元素有固定含义,描述如下:

D3D12_INPUT_ELEMENT_DESC结构体定义及含义
1
2
3
4
5
6
7
8
9
           typedef struct D3D12_INPUT_ELEMENT_DESC {
               LPCSTR                     SemanticName;         // 1. 语义名
               UINT                       SemanticIndex;        // 2. 语义索引
               DXGI_FORMAT                Format;               // 3. 数据格式
               UINT                       InputSlot;            // 4. 输入槽
               UINT                       AlignedByteOffset;    // 5. 对齐字节偏移
               D3D12_INPUT_CLASSIFICATION InputSlotClass;       // 6. 输入分类
               UINT                       InstanceDataStepRate; // 7. 实例步进速率
           } D3D12_INPUT_ELEMENT_DESC;
  • SemanticName:语义名,一个和元素相关联的特定字符串,表明该元素的用途。

    语义名需要和顶点着色器输入签名(参数列表)保持一致,因此通常会使用规范化命名,常用语义名包括"POSITION""COLOR""NORMAL""TEXCOORD"等。

  • SemanticIndex:语义索引,如果一个顶点结构体中存在多个相同语义的的元素,则需要通过语义索引区分它们。

    这样做能在区分相同语义的元素的同时不引进新的语义名,默认从0开始。

  • Format:数据格式,即顶点结构体中对应的元素的数据格式,但这里不写在顶点结构体中声明元素时的类型名,应该写DXGI_FORMAT枚举
  • InputSlot:输入槽索引,指定传递元素所以所用的输入槽(顶点缓冲区),值范围为0-15,只有一个时默认设为0。
  • AlignedByteOffset:对齐字节偏移,值为顶点结构体中这个元素前所有元素占用的字节总和。

    如果是首个元素的话,值为0。

  • InputSlotClass:输入分类,表明数据按顶点还是实例读取,有以下两个选项:

    D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA:每个顶点读取一次数据。最常用的选项,画普通物体时使用。

    D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA:用于实例化渲染。

  • InstanceDataStepRate:实例步进速率,默认设为0,开启实例化渲染时设为1。

3. 声明D3D12_INPUT_LAYOUT_DESC对象,作为D3D12_INPUT_ELEMENT_DESC的清单,并将D3D12_INPUT_ELEMENT_DESC数组绑定到该对象上。

\(1.\)\(2.\)声明的顶点结构体为例,该步骤的语句如下:

1
2
3
D3D12_INPUT_LAYOUT_DESC inputLayoutDesc;     //声明对象
inputLayoutDesc.pInputElementDescs = elementDescs.data();      // 指向描述顶点结构体的D3D12_INPUT_ELEMENT_DESC数组
inputLayoutDesc.NumElements = _countof(elementDescs);   // 告诉 GPU 顶点结构体中有两个元素

其中,data()方法是标准STL容器提供的方法,返回指向容器首个元素的原始指针

Note

D3D12_INPUT_LAYOUT_DESC对象是一个过程对象,仅用于初始化渲染管线,因此不必将其声明为类的成员。

顶点缓冲区

1. 使用上文中的顶点类型创建顶点数组。

vertices
Vertex vertieces[]={
        {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)}
    };

2. 根据顶点数组的大小计算缓冲区大小,创建上传堆和默认堆