DirectX延迟着色中位置数据异常问题排查求助
DirectX延迟着色异常光照与GBuffer位置错误问题修复
问题现象
- 画面左上角持续出现异常光线
- GBuffer位置数据仅包含物体区域的屏幕UV坐标,且存在翻转
- 应用阴影映射后问题加剧,疑似Domain Shader或Pixel Shader存在逻辑错误
现有Shader代码
顶点着色器(Vertex Shader)
struct VertexShaderInput { float4 position : POSITION; float2 texcoord : TEXCOORD0; float3 normal : NORMAL; }; struct VertexShaderOutput { float4 position : POSITION; float2 texcoord : TEXCOORD0; float3 normal : NORMAL; }; VertexShaderOutput DeferredVertexShader(VertexShaderInput input) { VertexShaderOutput output; output.position = input.position; output.texcoord = input.texcoord; output.normal = input.normal; return output; }
外壳着色器(Hull Shader)
cbuffer TessellationBuffer { matrix world; float3 CamPos; float padding; }; struct HullShaderInput { float4 position : POSITION; float2 texcoord : TEXCOORD0; float3 normal : NORMAL; }; struct ConstantOutput { float edges[3] : SV_TessFactor; float inside : SV_InsideTessFactor; }; struct HullShaderOutput { float4 position : POSITION; float2 texcoord : TEXCOORD0; float3 normal : NORMAL; }; // PATCH OUTPUT FUNCTION ConstantOutput PatchConstantFunction(InputPatch<HullShaderInput, 3> inputPatch, uint patchId : SV_PrimitiveID) { ConstantOutput output; float3 center = (inputPatch[0].position + inputPatch[1].position + inputPatch[2].position) / 3.0f; float3 centerW = mul(float4(center, 1.0f), world).xyz; const float d = distance(centerW, CamPos); const float d0 = 0.01f; const float d1 = 10.0f; float tessellationAmount = 12.0f * saturate((d1 - d) / (d1 - d0)) + 1; output.edges[0] = tessellationAmount; output.edges[1] = tessellationAmount; output.edges[2] = tessellationAmount; output.inside = tessellationAmount; return output; } // Hull shader [domain("tri")] [partitioning("integer")] [outputtopology("triangle_cw")] [outputcontrolpoints(3)] [patchconstantfunc("PatchConstantFunction")] HullShaderOutput DeferredHullShader(InputPatch<HullShaderInput, 3> patch, uint pointId : SV_OutputControlPointID, uint patchId : SV_PrimitiveID) { HullShaderOutput output; output.position = patch[pointId].position; output.texcoord = patch[pointId].texcoord; output.normal = patch[pointId].normal; return output; }
域着色器(Domain Shader)
cbuffer MatrixBuffer { matrix world; matrix view; matrix proj; }; struct ConstantOutput { float edges[3] : SV_TessFactor; float inside : SV_InsideTessFactor; }; struct DomainShaderInput { float4 position : POSITION; float2 texcoord : TEXCOORD0; float3 normal : NORMAL; }; struct DomainShaderOutput { float4 position : SV_POSITION; float2 texcoord : TEXCOORD0; float3 normal : NORMAL; }; // DOMAIN SHADER [domain("tri")] DomainShaderOutput DeferredDomainShader(ConstantOutput input, const OutputPatch<DomainShaderInput, 3> patch, float3 barycentric : SV_DomainLocation) { float4 worldPos; float3 vertexPosition, flatPosition; DomainShaderOutput output; output.texcoord = barycentric.x * patch[0].texcoord + barycentric.y * patch[1].texcoord + barycentric.z * patch[2].texcoord; output.normal = barycentric.x * patch[0].normal + barycentric.y * patch[1].normal + barycentric.z * patch[2].normal; vertexPosition = barycentric.x * patch[0].position + barycentric.y * patch[1].position + barycentric.z * patch[2].position; float3 vecProj0 = dot(patch[0].position - vertexPosition, patch[0].normal) * patch[0].normal; float3 vecProj1 = dot(patch[1].position - vertexPosition, patch[1].normal) * patch[1].normal; float3 vecProj2 = dot(patch[2].position - vertexPosition, patch[2].normal) * patch[2].normal; float3 vecOffset = barycentric.x * vecProj0 + barycentric.y * vecProj1 + barycentric.z * vecProj2; vertexPosition += 0.5f * vecOffset; worldPos = mul(vertexPosition, world); output.position = mul(float4(vertexPosition, 1.0f), world); output.position = mul(output.position, view); output.position = mul(output.position, proj); output.normal = mul(output.normal, (float3x3)world); output.normal = normalize(output.normal); return output; }
像素着色器(Pixel Shader)
Texture2D diffuseTexture : register(t0); Texture2D specularTexture : register(t1); Texture2D ambientTexture : register(t2); SamplerState sampleTypeWrap : register(s0); cbuffer MaterialBuffer { float specularPower; float3 pad3; }; struct PixelShaderInput { float4 position : SV_POSITION; float2 texcoord : TEXCOORD0; float3 normal : NORMAL; }; struct PixelShaderOutput { float4 position : SV_Target0; float4 normal : SV_Target1; float4 diffuse : SV_Target2; float4 specular : SV_Target3; float4 ambient : SV_Target4; }; PixelShaderOutput DeferredPixelShader(PixelShaderInput input) : SV_TARGET { PixelShaderOutput output; output.position = input.position; output.position.w = 1.0f; input.normal = normalize(input.normal); output.normal = float4(input.normal, 1.0f); output.diffuse = diffuseTexture.Sample(sampleTypeWrap, input.texcoord); output.specular = specularTexture.Sample(sampleTypeWrap, input.texcoord); output.ambient = ambientTexture.Sample(sampleTypeWrap, input.texcoord); output.specular.w = specularPower; return output; }
问题根源
- GBuffer位置存储错误:像素着色器直接将裁剪空间坐标(
SV_POSITION)写入GBuffer位置目标,延迟着色需要的是世界/视图空间位置,裁剪空间坐标无法用于光照计算,导致异常光线。 - 矩阵乘法顺序错误:DirectX使用行主序矩阵,正确的顶点变换应为
mul(world, float4(vertex, 1.0f)),现有代码顺序颠倒,导致顶点位置变换错误,引发UV翻转和位置偏移。 - 法线变换逻辑错误:直接用世界矩阵变换法线,未考虑缩放影响,会导致法线方向错误,进一步加剧光照异常。
修复步骤
1. 修正Domain Shader的位置与法线变换
- 输出结构体添加世界空间位置
- 修正矩阵乘法顺序
- 使用世界矩阵的逆转置变换法线
修改后的Domain Shader关键代码:
struct DomainShaderOutput { float4 position : SV_POSITION; float2 texcoord : TEXCOORD0; float3 normal : NORMAL; float4 worldPos : TEXCOORD1; // 新增世界空间位置 }; DomainShaderOutput DeferredDomainShader(ConstantOutput input, const OutputPatch<DomainShaderInput, 3> patch, float3 barycentric : SV_DomainLocation) { // ... 原有插值代码不变 ... // 修正世界空间位置计算顺序 output.worldPos = mul(world, float4(vertexPosition, 1.0f)); // 修正裁剪空间位置计算顺序 output.position = mul(proj, mul(view, output.worldPos)); // 修正法线变换:使用世界矩阵的逆转置 float3x3 worldInvTranspose = transpose(inverse((float3x3)world)); output.normal = mul(output.normal, worldInvTranspose); output.normal = normalize(output.normal); return output; }
2. 修正Pixel Shader的GBuffer位置存储
- 输入结构体添加世界空间位置
- 用世界空间位置替换裁剪空间坐标写入GBuffer
修改后的Pixel Shader关键代码:
struct PixelShaderInput { float4 position : SV_POSITION; float2 texcoord : TEXCOORD0; float3 normal : NORMAL; float4 worldPos : TEXCOORD1; // 新增世界空间位置输入 }; PixelShaderOutput DeferredPixelShader(PixelShaderInput input) : SV_TARGET { PixelShaderOutput output; // 存储世界空间位置到GBuffer,用于后续光照计算 output.position = input.worldPos; // ... 原有法线、纹理采样代码不变 ... return output; }
3. 可选:屏幕空间UV修正(若需存储屏幕位置)
如果需要在GBuffer中存储屏幕空间位置,需对裁剪空间坐标进行透视除法并翻转Y轴:
// 在Pixel Shader中添加 float4 ndcPos = input.position / input.position.w; float2 screenUV = (ndcPos.xy + 1.0f) * 0.5f; screenUV.y = 1.0f - screenUV.y; // 修正Y轴翻转 output.position = float4(screenUV, 0.0f, 1.0f);
内容的提问来源于stack exchange,提问作者Charlie.Q
相关产品推荐
相关产品推荐

