Direct X 12 – Drawing

在上一章中,我们主要聚焦在渲染管线的理论知识和数学知识。在本章节中,我们将着重介绍DX12中用来组成渲染管线的各个接口以及函数,定义顶点与像素着色器,并将图元/图形提交至渲染管线进行绘制。通过这一章的学习,我们将能够使用DX12绘制一个3D的立方体。

目标:

1. 掌握DX12中用来定义,存储并绘制图形数据的接口以及函数。

2. 学会书写最基础的顶点着色器与像素着色器。

3. 掌握如果通过pipeline state objects来设置渲染管线。

4. 理解如何创建constant buffer并将其绑定至渲染管线,并熟悉root signature。

6.1 顶点与输入布局(layout)

在上一章,5.5.1小节中我们提到,在DX12中顶点不单单只包含其位置信息,我们还能将额外的数据附加在顶点属性中。为了创建一个自定义的顶点格式,我们首先需要创建一个结构体,其包含了我们所需要的顶点数据。例如,下面的代码就展示了两种顶点格式;其中一个只包含了坐标位置和颜色,另一个则包含了坐标位置,法线向量和两组2D贴图坐标。

struct Vertex1
{
    XMFLOAT3 Pos;
    XMFLOAT4 Color;
};

struct Vertex2
{
    XMFLOAT3 Pos;
    XMFLOAT3 Normal;
    XMFLOAT2 Tex0;
    XMFLOAT2 Tex1;
};

当我们定义了顶点的结构体,我们需要创建一个顶点结构体的description(类似于descriptor),这样DX12才知道该结构体中多少成员,如何处理这些成员。DX12使用结构体D3D12_INPUT_LAYOUT_DESC来表示这个description:

typedef
struct D3D12_INPUT_LAYOUT_DESC
    {
        const D3D12_INPUT_ELEMENT_DESC *pInputElementDescs;
        UINT NumElements;
    }   D3D12_INPUT_LAYOUT_DESC;

我们可以看到该结构体很简单,只包含两个成员,分别是一个指向D3D12_INPUT_ELEMENT_DESC数组的指针,和该数组的元素个数。而D3D12_INPUT_ELEMENT_DESC数组中的每个元素与顶点结构体中的每个成员相对应,也就是该成员的description。所以,如果顶点结构体有两个成员变量,那么D3D12_INPUT_ELEMENT_DESC数组就有两个元素。结构体D3D12_INPUT_ELEMENT_DESC的定义如下:

typedef
struct D3D12_INPUT_ELEMENT_DESC
    {
        LPCSTR SemanticName;
        UINT SemanticIndex;
        DXGI_FORMAT Format;
        UINT InputSlot;
        UINT AlignedByteOffset;
        D3D12_INPUT_CLASSIFICATION InputSlotClass;
        UINT InstanceDataStepRate;
    }   D3D12_INPUT_ELEMENT_DESC;

1. SemanticName:与顶点结构体中的成员相关的字符串,我们对于命名规则没有要求。SemanticName用来将顶点结构体中的成员映射到顶点着色器中的输入签名中;如下图所示:

// cpp
struct Vertex
{
    XMFLOAT3 Pos;
    XMFLOAT3 Normal;
    XMFLOAT2 Tex0;
    XMFLOAT2 Tex1;
};

D3D12_INPUT_ELEMENT_DESC vetexDesc[] = 
{
    {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
        D3D12_INPUT_PER_VERTEX_DATA, 0},
    {"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12,
        D3D12_INPUT_PER_VERTEX_DATA, 0},
    {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24,
        D3D12_INPUT_PER_VERTEX_DATA, 0},
    {"TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 0, 32,
        D3D12_INPUT_PER_VERTEX_DATA, 0}
};

// HLSL
VertexOut VS(float3 iPos : POSITION,
             float3 iNormal : NORMAL,
             float2 iTex0 : TEXCOORD0,
             float2 iTex1 : TEXCOORD1)

由上图我们可以看到,顶点结构体中的每个成员都与D3D12_INPUT_ELEMENT_DESC数组中的元素相对应。而且semantic name和index将顶点结构体中的元素映射至与其相对应的顶点着色器参数。

2. SemanticIndex:semantic name的索引。从上图中我们可以看到,顶点结构体拥有两个贴图坐标成员(Tex0和Tex1);所以只要声明了索引,我们就能避免创建一个新的semantic name。如果一个semantic没有索引,那么着色器默认其索引为0;例如,上面代码中,POSITION就等价于POSITION0。

3. Format:枚举DXGI_FORMAT的成员,表示相对于DX12,顶点结构中成员的格式;以下是一些常用的格式:

DXGI_FORMAT_R32_FLOAT           // 32位浮点数
DXGI_FORMAT_R32G32_FLOAT        // 2D 32位浮点数向量
DXGI_FORMAT_R32G32B32_FLOAT     // 3D 32位浮点数向量
DXGI_FORMAT_R32G32B32A32_FLOAT  // 4D 32位浮点数向量

DXGI_FORMAT_R8_UINT             // 8位无符号整数
DXGI_FORMAT_R16G16_SINT         // 2D 16位整数向量
DXGI_FORMAT_R32G32B32_UINT      // 3D 32位无符号整数向量
DXGI_FORMAT_R8G8B8A8_SINT       // 4D 8位整数向量
DXGI_FORMAT_R8G8B8A8_UINT       // 4D 8位无符号整数向量

4. InputSlot:表示该顶点结构体成员来自于哪一个输入槽。DX12总共支持16个输入槽(0-15)用来放置顶点数据。现在我们只会使用输入槽0(也就是说所有的定点结构体成员来自于同一个输入槽);在书后练习2中,作者将让你使用多个输入槽。

5. AlignedByteOffset:基于该顶点结构体成员所在的输入槽,从顶点结构体的起点到该成员所需要平移的内存空间,以字节作为单位。以上述代码为例,第一个成员Pos的平移量为0,因为它是结构体的第一个成员;第二个成员Normal需要平移12个字节,因为Pos是个32位的3D向量其占了12个字节的空间,而且其也来自输入槽1;第三个成员Tex0需要平移24个字节,原因和Pos和Normal总共占了24个字节,且这两个成员都来自输入槽1;第四个成员Tex1需要平移32个字节,因为Pos,Normal和Tex0总共占了32个字节。

6. InputSlotClass:本章节中我们使用D3D12_INPUT_PER_VERTEX_DATA;在之后的章节讨论instancing技术时我们将使用其他值。

7. InstanceDataStepRate:本章节中我们使用0;理由同上。

对于之前两个顶点结构体,Vertex1和Vertex2;他们相对应的输入布局描述(input layout description)如下:

// Vertex1
D3D12_INPUT_ELEMENT_DESC desc1[] =
{
    {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
        D3D12_INPUT_PER_VERTEX_DATA, 0},
    {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12,
        D3D12_INPUT_PER_VERTEX_DATA, 0}
};

// Vertex2
D3D12_INPUT_ELEMENT_DESC desc2[] = 
{
    {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
        D3D12_INPUT_PER_VERTEX_DATA, 0},
    {"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12,
        D3D12_INPUT_PER_VERTEX_DATA, 0},
    {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24,
        D3D12_INPUT_PER_VERTEX_DATA, 0},
    {"TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 0, 32,
        D3D12_INPUT_PER_VERTEX_DATA, 0}
};

留下评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据