延迟着色时出现环形伪影的原因排查与技术求助
实时渲染带状(环形)伪影问题
我终于在软件中实现了真实感实时渲染,但在执行实际着色时遇到了“带状”(环形)伪影。测试场景为一个平面和一个点光源:
- 伪影效果:平面上出现环形的明暗过渡异常带
- 放大效果:能清晰看到明暗交界的阶梯状异常过渡
您能看到这些异常过渡吗?可能的问题根源是什么?
以下是着色阶段的实现代码:
float4 main(VS_OUTPUT input) : SV_TARGET { const float4 wsPos = positionWsOccludedTex[input.position.xy]; if (wsPos.w == 0.0f) discard; const float3 P = wsPos.xyz; const float3 V = normalize(EyePosition.xyz - P); float3 N = normalWsTex[input.position.xy]; const float NoV = dot(N, V); if (NoV < 0.0f) { N *= -1.0f; } const float4 albedoShininess = albedoShininessTex[input.position.xy]; float4 pixelColor = 0.0f; for (int i = 0; i < NbLights; ++i) { float3 L = Lights[i].position.xyz - P; const float distance= length(L); L /= distance; const float NoL = max(0.0f, dot(N, L)); // Diffuse contribution float4 color = albedoShininess * InvPI; // Lambert cosine law color *= NoL; // Multiply by light color color *= Lights[i].color; // Multiply by light falloff const float fallOff = 1.0f / (distance * distance); color *= fallOff; // Finally add light contribution to pixel color pixelColor += color; } return float4(pixelColor.xyz, 1.0f); }
注释以下两行代码可修复该伪影:
//color *= NoL;
以及
//color *= fallOff;
着色阶段输入的GBuffer渲染方式如下:
顶点着色器
struct VS_INPUT { float3 position : POSITION; float3 normal : NORMAL; float3 tangent : TANGENT; float3 bitangent : BITANGENT; float2 texCoord: TEXCOORD; }; struct VS_OUTPUT { float4 position: SV_POSITION; float3 worldPosition : POSITION; float3 tangent : Tangent; float3 bitangent : Bitangent; float3 normal : NORMAL; float2 texCoord: TEXCOORD; }; VS_OUTPUT main(VS_INPUT input, uint instanceID : SV_InstanceID) { VS_OUTPUT output; const float4x4 modelMat = meshGroupDatas[instanceID].transform; const float4 worldPosition = mul(float4(input.position, 1.0f), modelMat); output.worldPosition = worldPosition.xyz; output.position = mul(worldPosition, vpMat); output.texCoord = input.texCoord; output.normal = normalize(mul(float4(input.normal, 0.0f), modelMat)); output.tangent = normalize(mul(float4(input.tangent, 0.0f), modelMat)); output.bitangent = normalize(mul(float4(input.bitangent, 0.0f), modelMat)); return output; }
像素着色器
GBufferPSOut main(VS_OUTPUT input) { const float3 P = input.worldPosition; input.texCoord *= Material.uvStretch; const float3 N = computeNormal(Material, normalTex, tSampler, input.normal, input.tangent, input.bitangent, input.texCoord); float4 matBaseColor = Material.baseColor; if (Material.hasBaseColorTex) { matBaseColor *= baseColorTex.Sample(tSampler, input.texCoord); } GBufferPSOut psOut; psOut.positionWsOccluded = float4(P, 1.0f); psOut.normalWs = float4(N, 1.0f); psOut.tangentWs = float4(input.tangent, 1.0f); psOut.bitangentWs = float4(input.bitangent, 1.0f); psOut.albedoShininess = float4(matBaseColor.xyz, Material.shininess); psOut.specularAnisotropy = Material.specular; return psOut; }
对应的GBuffer纹理信息:
- 世界位置纹理:纹理颜色过渡均匀,准确反映平面的世界位置信息
- Albedo纹理:纯色纹理,无噪点或颜色异常
- 法线纹理:纹理颜色统一,对应平面的一致法线方向
我显然存在操作错误,但问题根源是什么?恳请协助!
问题根源分析
这是典型的精度不足导致的量化伪影,核心原因:
- GBuffer纹理格式精度不够:你存储世界位置、法线的GBuffer大概率用了低精度格式(比如
RGBA8而非RGBA16F/RGBA32F)。低精度格式会让位置、法线的采样值出现离散误差,而1/(distance*distance)的平方倒数衰减会大幅放大这种误差,形成环形阶梯状伪影。 - NoL的精度损失:法线存储精度不足时,
dot(N,L)的计算结果会出现离散化的阶梯值,乘以颜色后就会呈现明暗带状过渡。
修复方案
- 将GBuffer的世界位置纹理和法线纹理改为高精度格式(
RGBA16F或RGBA32F),从根源避免位置、法线的采样误差。 - 若必须使用低精度格式,可对
distance和NoL结果做轻微平滑处理(比如邻近像素采样后取平均),但精度格式升级是最彻底的解决办法。
内容的提问来源于stack exchange,提问作者TheChamp
相关产品推荐
相关产品推荐

