顶点着色器阶段通过执行转换、皮肤化和照明等操作来处理顶点。 顶点着色器始终在单个输入顶点上运行并生成单个输出顶点。 呈现管道的此阶段必须始终处于活动状态。
1. 核心功能与特性
顶点着色器 (Vertex Shader, VS) 是渲染管线中首个可编程阶段,其核心职责包括:
- 坐标变换:将顶点从模型空间转换到齐次裁剪空间(MVP矩阵运算)。
- 顶点级光照:计算逐顶点光照(如漫反射、镜面反射)。
- 皮肤化 (Skining):支持骨骼动画的顶点混合。
- 数据传递:为后续阶段(如几何着色器/像素着色器)准备插值属性(法线、UV等)。
关键约束:
- 单输入单输出:每次处理一个顶点,输出一个顶点。
- 必须激活:即使仅传递数据,VS 也必须存在(不可跳过)。
2. 驱动函数与实现
Direct3D 运行时通过以下 DDI 函数管理顶点着色器:
函数 | 职责 |
---|---|
CalcPrivateShaderSize |
计算着色器私有数据所需内存大小(如常量缓冲区指针表)。 |
CreateVertexShader |
创建VS对象,编译字节码并初始化驱动私有状态。 |
DestroyShader |
释放着色器相关资源。 |
VsSetConstantBuffers |
绑定常量缓冲区(如变换矩阵、光照参数)。 |
VsSetSamplers |
设置纹理采样器(VS中较少使用,但支持纹理采样)。 |
VsSetShader |
激活指定的VS程序。 |
VsSetShaderResources |
绑定着色器资源视图(如纹理缓冲区)。 |
3. 数据流与交互
(1) 输入数据来源
- 顶点缓冲区 (IA阶段提供):位置、法线、UV等属性。
- 常量缓冲区 (VsSetConstantBuffers):矩阵、光照参数等全局数据。
- 纹理资源 (VsSetShaderResources):如高度图变形(需配合 VsSetSamplers)。
(2) 典型HLSL示例
// 输入结构(匹配IA阶段的布局)
struct VS_INPUT {
float3 Pos : POSITION;
float3 Normal : NORMAL;
float2 TexCoord : TEXCOORD0;
};
// 输出结构(传递到后续阶段)
struct VS_OUTPUT {
float4 Pos : SV_POSITION;
float3 Normal : NORMAL;
float2 TexCoord : TEXCOORD0;
};
// 顶点着色器主体
VS_OUTPUT VS_Main(VS_INPUT input) {
VS_OUTPUT output;
output.Pos = mul(float4(input.Pos, 1.0), gWorldViewProj);
output.Normal = mul(input.Normal, (float3x3)gWorldInvTranspose);
output.TexCoord = input.TexCoord;
return output;
}
4. 驱动实现详解
(1) CreateVertexShader 实现示例
HRESULT APIENTRY CreateVertexShader(
D3D10DDI_HDEVICE hDevice,
const UINT* pShaderBytecode, // 编译后的字节码
D3D10DDI_HSHADER hShader, // 运行时句柄
D3D10DDI_HRTSHADER hRTShader // 驱动私有数据
) {
MyDeviceContext* pCtx = (MyDeviceContext*)hDevice.pDrvPrivate;
MyShaderData* pData = new MyShaderData;
// 1. 解析字节码(提取常量缓冲区布局等)
ParseShaderBytecode(pShaderBytecode, &pData->cbLayout);
// 2. 生成GPU指令(如NVIDIA PTX/AMD GCN)
pData->pMicrocode = CompileToMicrocode(pShaderBytecode);
// 3. 关联句柄
pCtx->shaderTable[hShader] = pData;
return S_OK;
}
(2) VsSetConstantBuffers 实现示例
void APIENTRY VsSetConstantBuffers(
D3D10DDI_HDEVICE hDevice,
UINT StartSlot,
UINT NumBuffers,
const D3D10DDI_HRESOURCE* phBuffers
) {
MyDeviceContext* pCtx = (MyDeviceContext*)hDevice.pDrvPrivate;
for (UINT i = 0; i < NumBuffers; ++i) {
pCtx->vsConstantBuffers[StartSlot + i] = phBuffers[i];
// 标记常量缓冲区为脏(需更新GPU缓存)
pCtx->dirtyFlags |= VS_CONSTANT_BUFFER_DIRTY;
}
}
5. 性能优化与调试
(1) 优化建议
- 最小化常量缓冲区更新:批量更新或使用动态缓冲区。
- 避免VS分支:复杂逻辑可移至几何/像素着色器。
- 利用实例化:通过 SV_InstanceID 减少重复计算。
(2) 常见问题排查
问题现象 | 可能原因 | 解决方案 |
---|---|---|
顶点位置错误 | MVP矩阵未正确绑定 | 检查 VsSetConstantBuffers 调用 |
法线/UV传递失败 | 输入布局不匹配 | 验证 CreateElementLayout 声明 |
着色器未执行 | VsSetShader 未调用或句柄无效 |
调试驱动状态机 |
6. 高级应用场景
(1) 顶点纹理采样 (Vertex Texture Fetch)
通过 VsSetShaderResources 绑定高度图,实现动态地形变形:
Texture2D gHeightMap;
SamplerState gSampler;
float height = gHeightMap.SampleLevel(gSampler, input.TexCoord, 0).r;
output.Pos.y += height * 10.0; // 位移顶点
(2) GPU皮肤化 (Skinned Mesh)
// 常量缓冲区存储骨骼矩阵
float4x4 gBoneMatrices[100];
// 顶点着色器中混合骨骼权重
float4 skinnedPos = 0;
for (int i = 0; i < 4; i++) {
skinnedPos += input.Weights[i] * mul(float4(input.Pos, 1), gBoneMatrices[input.BoneIndices[i]]);
}
output.Pos = mul(skinnedPos, gViewProj);
总结
顶点着色器是Direct3D 10管线的几何处理核心,其关键设计要点包括:
- 强制激活:必须存在且输出有效顶点。
- 灵活编程:支持矩阵变换、光照、纹理采样等。
- 驱动协作:通过DDI函数管理编译、资源和状态。
开发者需关注:
- 输入布局匹配:确保IA与VS声明一致。
- 常量缓冲区优化:减少GPU带宽开销。
- 硬件特性利用:如动态分支代价、实例化支持。