d3d渲染管线全流程详解


0. 渲染管线概述

Direct3D的渲染管线由一系列阶段组成. 整个渲染管线需要读取顶点数据,并经过一系列的操作输出最终画面.
渲染流程主要分为几个大的阶段:输入装配阶段,顶点处理阶段,光栅操作阶段,像素着色阶段

1. 输入装配阶段

这是一个不可编程阶段 , 也就是说它的功能和实现都是固定的.
输入装配器从顶点缓冲区读取顶点数据 , 再从索引缓冲区读取索引数据 . 最后根据索引数据将各个顶点组装成一个个图元 , 这些图元将被送往下一个阶段 (顶点处理阶段)

图元的类型可以是三角形 , 四边形 , 线段 以及 点

把数据送往输入装配阶段有如下步骤

(1). 创建输入布局对象

D3D11_INPUT_ELEMENT_DESC inputLayout[2] = {
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 }
};

根据顶点数据的结构来设置输入布局 . 例如: 上面的输入布局表示每一个顶点有一组位置数据和一组颜色数据

(2). 在渲染管线上创建并绑定输入布局

HR(m_pd3dDevice->CreateInputLayout(VertexPosColor::inputLayout,       ARRAYSIZE(VertexPosColor::inputLayout),
        blob->GetBufferPointer(), blob->GetBufferSize(), m_pVertexLayout.GetAddressOf()));

blob即顶点着色器
此时我们获得了一个顶点布局m_pVertexLayout (后面要用)

(3). 设置顶点数据

VertexPosColor vertices[] =
    {
        { XMFLOAT3(0.0f, 0.5f, 0.9f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
        { XMFLOAT3(0.5f, -0.5f, 0.9f), XMFLOAT4(0.0f, 1.0f, 1.0f, 1.0f) },
        { XMFLOAT3(0.0f, -0.5f, 0.9f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },
        { XMFLOAT3(0.0f, 0.5f, 0.1f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },
        { XMFLOAT3(0.5f, 0.5f, 0.1f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
        { XMFLOAT3(0.5f, -0.5f, 0.1f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) }
    };

这里展示了六个顶点

(4). 设置顶点缓冲区描述

D3D11_BUFFER_DESC vbd;
ZeroMemory(&vbd, sizeof(vbd));     
vbd.Usage = D3D11_USAGE_IMMUTABLE;
vbd.ByteWidth = sizeof vertices;
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;

(5). 新建顶点缓冲区

D3D11_SUBRESOURCE_DATA InitData;
ZeroMemory(&InitData, sizeof(InitData));
InitData.pSysMem = vertices;
HR(m_pd3dDevice->CreateBuffer(&vbd, &InitData, m_pVertexBuffer.GetAddressOf()));

(6). 将顶点缓冲区绑定到输入装配器(IA)

// 输入装配阶段的顶点缓冲区设置
    UINT stride = sizeof(VertexPosColor);	// 跨越字节数
    UINT offset = 0;						// 起始偏移量

    m_pd3dImmediateContext->IASetVertexBuffers(0, 1, m_pVertexBuffer.GetAddressOf(), &stride, &offset);

(7). 设定图元类型

// 设置图元类型
    m_pd3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

TRIANGLELIST即为三角形

(8). 设定输入布局

//设定输入布局
m_pd3dImmediateContext->IASetInputLayout(m_pVertexLayout.Get());

将第2步中得到的输入布局绑定到输入装配器(IA)

(9). 将着色器绑定到渲染管线

// 将着色器绑定到渲染管线
    m_pd3dImmediateContext->VSSetShader(m_pVertexShader.Get(), nullptr, 0);
    m_pd3dImmediateContext->PSSetShader(m_pPixelShader.Get(), nullptr, 0);

2.顶点处理阶段:

2.1 顶点着色器

顶点着色器 (Vertex Shader , VS ) 是d3d渲染管线上第一个可编程的着色器

输入装配阶段组装完图元后 , 这些图元的顶点都要经过顶点着色器的处理

顶点着色器每次读取一个顶点 , 并输出一个顶点. 在这个过程中 , 可以对顶点进行位移, 缩放 (坐标变换) , 光照, 为纹理和材质替换贴图. (顶点着色)

即使不需要对顶点做任何处理, 也必须实现顶点着色器.

float4 VS(float4 inPos : POSITION) : SV_POSITION
{
    return inPos;
}

最简单的顶点着色器, 它不做任何操作, 只将顶点数据直接送往下一个阶段.

2.1.1 坐标变换

顶点原始的坐标数据一般是在模型空间内的局部坐标 ,所以首先要乘以模型矩阵把局部坐标变换为世界坐标,
世界坐标再乘以摄像机的观察矩阵得到观察坐标
观察坐标再乘以投影矩阵变换到裁剪坐标
裁剪坐标经过透视除法得到标准设备空间(NDC)坐标
NDC坐标通过视口变换得到窗口坐标才是最终显示在屏幕上的坐标.

2.1.2 顶点着色(待填坑)

2.2 外壳着色器

外壳着色器是一个可编程着色器, 它分为两种: 常量外壳着色器和控制点外壳着色器.

同时, 外壳着色器也是曲面细分三个阶段中的第一个阶段. 如果使用曲面细分技术, 必须实现所有的三个阶段

作用:

  1. LOD(细节级别): 可以根据顶点与摄像机的远近来决定细分的程度. 距离摄像机较远的几何体将生成更少的顶点来提高性能
  2. 物理模拟与动画特效: 对顶点进行物理模拟非常消耗性能, 因此我们使用尽可能少的顶点进行物理模拟, 然后在外壳着色器中补充顶点以增加细节
  3. 节约内存: 我们不需要在内存中存放大量顶点数据, 可以根据需要进行细化

2.3 镶嵌器(待填坑)

2.4 域着色器

镶嵌器阶段会输出新建的所有顶点与三角形, 在此阶段创建的所有顶点都会逐一调用域着色器进行后续处理. 因此域着色器相当于顶点着色器.

域着色器的输入是常量外壳着色器输出的曲面细分因子, 控制点外壳着色器输出的每个面片控制点. 镶嵌化处理后的顶点位置. (注意这并不是镶嵌顶点的实际位置,而是这些点在面片域内的参数坐标)

2.5 几何着色器

如果不启用曲面细分环节. 几何着色器在渲染管线上的位置是顶点着色器像素着色器之间.
几何着色器的输入是完整的图元. 输出是齐次裁剪空间中由一系列顶点所定义的多个图元.

几何着色器的特点是可以创建或销毁几何图形. 比如将一个图元扩展为一个或更多其他类型的图元, 例如将一个点扩展成一个四边形(也就是两个三角形).

这使得几何着色器很适合实现公告板技术.

2.6 流输出阶段(待填坑)

3. 光栅化阶段(待填坑)

4. 像素着色阶段(待填坑)

参考资料:

  1. D3D管线以及着色器工作原理-画一个三角形
  2. 细说图形学渲染管线
  3. DirectX12 3D游戏开发实战》第十二章 :几何着色器 Geometry Shader
  4. # Direct3D11学习:(六)渲染管线

Author: morphotherain
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source morphotherain !
  TOC