You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

延迟着色时出现环形伪影的原因排查与技术求助

实时渲染带状(环形)伪影问题

我终于在软件中实现了真实感实时渲染,但在执行实际着色时遇到了“带状”(环形)伪影。测试场景为一个平面和一个点光源:

  • 伪影效果:平面上出现环形的明暗过渡异常带
  • 放大效果:能清晰看到明暗交界的阶梯状异常过渡

您能看到这些异常过渡吗?可能的问题根源是什么?

以下是着色阶段的实现代码:

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纹理:纯色纹理,无噪点或颜色异常
  • 法线纹理:纹理颜色统一,对应平面的一致法线方向

我显然存在操作错误,但问题根源是什么?恳请协助!


问题根源分析

这是典型的精度不足导致的量化伪影,核心原因:

  1. GBuffer纹理格式精度不够:你存储世界位置、法线的GBuffer大概率用了低精度格式(比如RGBA8而非RGBA16F/RGBA32F)。低精度格式会让位置、法线的采样值出现离散误差,而1/(distance*distance)的平方倒数衰减会大幅放大这种误差,形成环形阶梯状伪影。
  2. NoL的精度损失:法线存储精度不足时,dot(N,L)的计算结果会出现离散化的阶梯值,乘以颜色后就会呈现明暗带状过渡。

修复方案

  • 将GBuffer的世界位置纹理和法线纹理改为高精度格式(RGBA16F或RGBA32F),从根源避免位置、法线的采样误差。
  • 若必须使用低精度格式,可对distance和NoL结果做轻微平滑处理(比如邻近像素采样后取平均),但精度格式升级是最彻底的解决办法。

内容的提问来源于stack exchange,提问作者TheChamp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 21:19:51