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 外壳着色器
外壳着色器是一个可编程着色器, 它分为两种: 常量外壳着色器和控制点外壳着色器.
同时, 外壳着色器也是曲面细分三个阶段中的第一个阶段. 如果使用曲面细分技术, 必须实现所有的三个阶段
作用:
- LOD(细节级别): 可以根据顶点与摄像机的远近来决定细分的程度. 距离摄像机较远的几何体将生成更少的顶点来提高性能
- 物理模拟与动画特效: 对顶点进行物理模拟非常消耗性能, 因此我们使用尽可能少的顶点进行物理模拟, 然后在外壳着色器中补充顶点以增加细节
- 节约内存: 我们不需要在内存中存放大量顶点数据, 可以根据需要进行细化
2.3 镶嵌器(待填坑)
2.4 域着色器
镶嵌器阶段会输出新建的所有顶点与三角形, 在此阶段创建的所有顶点都会逐一调用域着色器进行后续处理. 因此域着色器相当于顶点着色器.
域着色器的输入是常量外壳着色器输出的曲面细分因子, 控制点外壳着色器输出的每个面片控制点. 镶嵌化处理后的顶点位置. (注意这并不是镶嵌顶点的实际位置,而是这些点在面片域内的参数坐标)
2.5 几何着色器
如果不启用曲面细分环节. 几何着色器在渲染管线上的位置是顶点着色器与像素着色器之间.
几何着色器的输入是完整的图元. 输出是齐次裁剪空间中由一系列顶点所定义的多个图元.
几何着色器的特点是可以创建或销毁几何图形. 比如将一个图元扩展为一个或更多其他类型的图元, 例如将一个点扩展成一个四边形(也就是两个三角形).
这使得几何着色器很适合实现公告板技术.
2.6 流输出阶段(待填坑)
3. 光栅化阶段(待填坑)
4. 像素着色阶段(待填坑)
参考资料: