D3D11 渲染管线

memetao 于 2023-08-26 发布

顶点着色器中的坐标

顶点默认使用Normalized Device Coordinates。 当你把一个顶点传进顶点着色器后,最终输出一个 SV_POSITION,这个位置被投影到了 NDC 空间(归一化设备坐标),它具有如下特性:

轴向 范围 含义
X -1 到 +1 左到右
Y +1 到 -1 上到下(注意是倒的
Z 0 到 +1 前到后(视锥内的深度)

🤓 D3D11 的特殊点:

Primitives(图元)

渲染管线

输入装配(IA) → 顶点着色器(VS) → 曲面细分阶段(HS/DS) → 几何着色器(GS)
        ↓
光栅化(Rasterizer)
        ↓
像素着色器(PS)
        ↓
输出合并(OM)
阶段 可编程? Shader 名称 是否必须
IA
VS Vertex Shader ✅ 必须
HS/DS Hull/Domain Shader 可选
GS Geometry Shader 可选
Rasterizer
PS Pixel Shader ✅ 必须
OM

纹理

DXGI FORMAT对应HLSL中的类型

| DXGI | HLSL | | —————————— | ———— | | DXGI_FORMAT_R32_FLOAT | float | | DXGI_FORMAT_R32G32_FLOAT | float2 | | DXGI_FORMAT_R32G32B32A32_FLOAT | float4 | | DXGI_FORMAT_R32_UINT | uint | | DXGI_FORMAT_R32G32_UINT | uint2 | | DXGI_FORMAT_R32G32B32A32_UINT | uint4 | | DXGI_FORMAT_R32_SINT | int | | DXGI_FORMAT_R32G32_SINT | int2 | | DXGI_FORMAT_R32G32B32A32_SINT | int4 | | DXGI_FORMAT_R16G16B16A16_FLOAT | float4 | | DXGI_FORMAT_R8G8B8A8_UNORM | unorm float4 | | DXGI_FORMAT_R8G8B8A8_SNORM | snorm float4 |

其中unorm float表示的是一个32位无符号的,规格化的浮点数,可以表示范围0到1 而与之对应的snorm float表示的是32位有符号的,规格化的浮点数,可以表示范围-1到1

USAGE

| D3D11_USAGE | CPU读 | CPU写 | GPU读 | GPU写 | | ——————— | —– | —– | —– | —– | | D3D11_USAGE_DEFAULT | | | √ | √ | | D3D11_USAGE_IMMUTABLE | √ | | | | | D3D11_USAGE_DYNAMIC | | √ | √ | | | D3D11_USAGE_STAGING | √ | √ | √ | √ |

BindFlag

| D3D11_BIND_FLAG | 描述 | | ————————— | ———————————————————– | | D3D11_BIND_SHADER_RESOURCE | 纹理可以作为着色器资源绑定到渲染管线 | | D3D11_BIND_STREAM_OUTPUT | 纹理可以作为流输出阶段的输出点 | | D3D11_BIND_RENDER_TARGET | 纹理可以作为渲染目标的输出点,并且指定它可以用于生成mipmaps | | D3D11_BIND_DEPTH_STENCIL | 纹理可以作为深度/模板缓冲区 | | D3D11_BIND_UNORDERED_ACCESS | 纹理可以绑定到无序访问视图作为输出 |

CPU ACESS

D3D11_CPU_ACCESS_FLAG 描述
D3D11_CPU_ACCESS_WRITE 允许通过映射方式从CPU写入,它不能作为管线的输出,且只能用于D3D11_USAGE_DYNAMIC和D3D11_USAGE_STAGING绑定的资源
D3D11_CPU_ACCESS_READ 允许通过映射方式给CPU读取,它不能作为管线的输入或输出,且只能用于D3D11_USAGE_STAGING绑定的资源

Vertex Shadler

D3D11_INPUT_ELEMENT_DESC input_desc[] =
{
    // 我们知道DXGI_FORMAT_R32G32B32_FLOAT是12字节的
    {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,  D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"COLOR",    0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
};
CreateInputLayout(VERTEX::input_desc, std::size(VERTEX::input_desc), ...);

当我们在C++里面写入上述代码的时候, 是告诉D3D11已”Position”和”Color”解析输入的Vertex数据。

于是, 我们将下面的数据传给D3D11:

VERTEX OurVertices[] = {
    // 每组数据3 * 4 + 3 * 4 = 24字节
    {DirectX::XMFLOAT3{0.0f, 0.5f, 0.0f}, DirectX::XMFLOAT3(1.0f, 0.0f, 0.0f)},
    {DirectX::XMFLOAT3{0.45f, -0.5, 0.0f}, DirectX::XMFLOAT3(0.0f, 1.0f, 0.0f)},
    {DirectX::XMFLOAT3{-0.45f, -0.5f, 0.0f}, DirectX::XMFLOAT3(0.0f, 0.0f, 1.0f)},
};
CreateBuffer(&vertex_buf_);
IASetVertexBuffers(vertex_buf_);

关键字语义

Semantic Description
POSITION A float4 value that stores position. It is used to denote the position of vertices, usually (but not necessarily) in 3D space.
COLOR A float4 value that stored color
SV_POSITION A float4 value that stores position. It is used to denote the position in normalized screen coordinates, not 3D coordinates.
SV_TARGET A float4 value telling the output-merger to draw the given color on the render target.
// 为什么是float4, 而不是float3?搞不懂
// 实测float3也可以work
struct VOut {
    float4 position : SV_POSITION;
    float4 color : COLOR;
};


VOut VShader(float4 position : POSITION, float4 color : COLOR)
{
    VOut output;
    output.position = position;
    output.color = color;
    return output;
}

// 使用的是normailized坐标, 返回值是给输出合并阶段的用的, 输出到render Target上
float4 PShader(float4 position : SV_POSITION,
    float4 color: COLOR): SV_TARGET
{
    return color;
}

为什么CPU中的float3也能匹配上VertexShader中的POSITION(float4)语义

🧠 真相是:

✅ 如果你 VS 中写 float4,但 InputLayout 提供的是 float3,编译器自动补 w=1.0f

这是 HLSL 里的一个智能规则:

如果你的 InputLayout 是 float3,而你的 shader 要求 float4,系统会自动补成 float4(x, y, z, 1.0f)

这个行为完全合法,且广泛使用 —— 因为通常你需要 w=1.0f 来做变换矩阵乘法。

✅ 反过来就不行!

如果你上传的是 float4,但 shader 输入写的是 float3,会报错或行为不确定。因为你传的数据比 shader 想要的还多,HLSL 不知道要不要忽略、剪掉,容易出错。

为什么VertexShader的输出必须 float4?

缓冲区寄存器标记

HLSL register 类型 意义
b# 常量缓冲区 (CB) Constant Buffer,最多绑定 14 个
t# 纹理 (Texture) Texture2D, Texture3D, 等
s# 采样器 (Sampler) SamplerState
u# 无序访问 (UAV) 用于计算着色器、RW结构等

HLSL 结构对齐的问题

下面是常量缓冲区的对齐规则, 但是顶点缓冲区的对齐规则又是咋样的, 为什么大家都是写float4?

see: https://www.cnblogs.com/X-Jun/p/9376474.html

类型 是否需要对齐 常见用途
ConstantBuffer(常量缓冲区) ✅ 必须对齐 向 Shader 传递参数(register(b#))
StructuredBuffer ✅ 建议对齐 Shader 中访问结构化数据
RawBuffer(字节地址缓冲) ❌ 不强制 ByteAddressBuffer 类型按字节访问
VertexBuffer ✅ 通常对齐 顶点输入结构体
IndexBuffer ❌ 不要求 用于绘制顶点顺序(16或32bit)
RWStructuredBuffer(可写) ✅ 建议对齐 Shader 中写入结构体数据

常见对齐策略:

变量类型 占用空间(HLSL) 补齐建议(C++)
float, int, uint 4 字节 补 12 字节 → 成 16 字节对齐
float2 8 字节 补 8 字节
float3 12 字节 补 4 字节
float4 16 字节 不用补
matrix(4x4) 64 字节 已自动对齐

示例:

cbuffer Example : register(b0)
{
    float a;     // offset 0
    float3 b;    // offset 16(float + float3 共享 vec4)
    float4 c;    // offset 32
};

参考文档