顶点着色器中的坐标
顶点默认使用Normalized Device Coordinates。 当你把一个顶点传进顶点着色器后,最终输出一个 SV_POSITION,这个位置被投影到了 NDC 空间(归一化设备坐标),它具有如下特性:
| 轴向 | 范围 | 含义 | 
|---|---|---|
| X | -1 到 +1 | 左到右 | 
| Y | +1 到 -1 | 上到下(注意是倒的) | 
| Z | 0 到 +1 | 前到后(视锥内的深度) | 
🤓 D3D11 的特殊点:
- 左手坐标系:D3D 默认使用左手坐标系(x右,y上,z向屏幕内)
 - NDC 的 Z 范围是 0 到 1(OpenGL 是 -1 到 +1)
 
Primitives(图元)
- Point Lists
 - Line Lists
 - Line Strips
 - Triangle Lists (最常用)
 - Triangle Strips
 
渲染管线
输入装配(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?
- GPU 使用的是“齐次坐标(Homogeneous Coordinates)”
 - 裁剪、透视除法、屏幕映射都依赖它
 - 这就是你要执行 output.pos = mul(MVP, float4(input.pos, 1.0f)); 的原因
 
缓冲区寄存器标记
| 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
};
参考文档
- https://www.cnblogs.com/X-Jun/p/10262524.html