欢迎光临
我们一直在努力

延迟管线中的半透明渲染痛点:混合 Forward Pass 的排序与开销

延迟管线中的半透明渲染痛点:混合 Forward Pass 的排序与开销

封面信息图

延迟着色(Deferred Shading)通过将几何体属性(法线、Albedo、材质参数、深度)解耦写入 G-Buffer,并在屏幕空间统一计算光照,完美解决了成百上千动态光源下的计算复杂度问题。然而,延迟管线存在一个从数学架构上无法原生解决的痛点:半透明物体(Alpha Blending)的渲染。

在单个像素位置,G-Buffer 的固定槽位只能记录单一物体的材质属性与深度值。若将半透明物体写入 G-Buffer,后方被遮挡的不透明物体信息将被彻底覆盖,导致透视关系破坏。因此,所有延迟渲染管线都必须在完成 G-Buffer 不透明光照之后,追加一个混合前向通路(Mixed Forward Transparency Pass)。

痛点 1:CPU 几何排序与拓扑自相交困境

半透明渲染依赖传统的 Alpha 混合公式:$$Color_{dst} = Color_{src} \\times \\alpha + Color_{dst} \\times (1 – \\alpha)$$

该混合操作不满足乘法交换律,意味着渲染顺序必须严格从后向前(Back-to-Front)。

摄像机视角: [Camera] ──> [玻璃杯前表面 (A)] ──> [杯中液体 (B)] ──> [玻璃杯后表面 (C)] ──> [桌子(不透明)]
正确混合顺序: 桌子 ──> C ──> B ──> A

在 CPU 端,引擎只能按照 Mesh 对象的包围盒中心到摄像机的距离进行粗粒度排序(Object-level Sorting)。一旦出现以下情况,排序必将失效:

  • 凹多面体或复杂网格:单个物体的后表面必须先于前表面绘制。
  • 多物体空间穿插:两个半透明网格的包围盒相互重叠交叉,无法在对象层级定出绝对前后。
  • 粒子系统排序开销:同屏数万个半透明粒子若每帧在 CPU 上执行 O(N log N) 的快速排序,主线程耗时将直接飙升。
  • 痛点 2:光照计算孤岛与 Forward+ 光源分块

    在延迟阶段,不透明物体只需在屏幕空间读取光照缓冲区;而半透明物体在 Forward Pass 中,必须在自身的片段着色器中重新遍历所有影响它的光源。

    若没有额外的光源剔除机制,半透明着色器要么退化为只支持 1 盏主方向光 + 少量逐顶点光源,要么在片段着色器内执行沉重的动态光源循环,导致 GPU 发生灾难性的 Overdraw(过度绘制) 与算力暴增。

    现代管线的标准做法是将半透明 Forward Pass 升级为 Cluster / Forward+ 架构:利用 Compute Shader 将视锥体在三维空间切分为 3D 网格(Clusters),提前将光源索引烘焙进 3D 列表。半透明着色器根据当前像素的屏幕坐标与深度值,直接索引对应 Cluster 内的局部光源子集。

    方案对比:Alpha Test vs 软粒子 vs WBOIT

    方案原理优点缺点
    Alpha Test / Cutout 阈值剔除片段,写入深度 可写 G-Buffer,享受完全延迟光照 边缘锯齿严重,无法表达半透明过渡
    Forward Sorted Pass 开启 Z-Test,关闭 Z-Write,CPU 粗排序 效果标准,通用性高 穿插错乱,高 Overdraw 带宽压力大
    加权无序半透明 (WBOIT) 像素空间深度加权累加,单 Pass 完成 无需任何几何排序,彻底解决相交穿插 极厚堆叠时颜色可能泛白,需额外 2 张 RT

    现代解法:加权无序半透明(WBOIT)实现

    加权无序半透明(Weighted Blended Order-Independent Transparency)由 McGuire 等人提出。其核心思想是:利用深度加权函数(Depth Weight Function)为不同深度的半透明片段赋予权重,离相机越近权重越大,在两个独立的离屏渲染目标上进行无序累加,最后在全屏复合 Pass 中统一重建最终颜色。

    1. WBOIT 积累着色器(HLSL Accumulation Pass)

    在此 Pass 中,关闭深度写入(ZWrite Off),开启加权混合:

    struct PixelOutput
    {
    float4 accum : SV_Target0; // RGBA16F: rgb * a * weight, a 累加
    float reveal : SV_Target1; // R8_UNORM: 累乘 (1 – a)
    };

    // 深度加权函数(McGuire 经典曲线)
    float ComputeWBOITWeight(float z, float alpha)
    {
    // z 为观察空间线性深度
    return clamp(pow(min(1.0, alpha * 10.0) + 0.01, 3.0) * 1e8 * pow(1.0 – z * 0.9, 3.0), 1e-2, 3e3);
    }

    PixelOutput WBOIT_Accum_PS(v2f i)
    {
    PixelOutput output;

    float4 color = SampleTextureAndEvaluateLight(i); // 计算半透明颜色与 alpha
    float linearDepth = i.linearDepth; // 观察空间深度
    float weight = ComputeWBOITWeight(linearDepth, color.a);

    // Accumulation Target (Blend: One, One)
    output.accum = float4(color.rgb * color.a, color.a) * weight;

    // Revealage Target (Blend: Zero, OneMinusSrcColor)
    output.reveal = color.a;

    return output;
    }

    2. WBOIT 全屏复合着色器(Composite Pass)

    在不透明背景上将累积的无序颜色与透过率正确混合:

    Texture2D _AccumTexture;
    Texture2D _RevealTexture;
    SamplerState sampler_PointClamp;

    float4 WBOIT_Composite_PS(float2 uv : TEXCOORD0) : SV_Target
    {
    float4 accum = _AccumTexture.Sample(sampler_PointClamp, uv);
    float reveal = _RevealTexture.Sample(sampler_PointClamp, uv).r;

    // 避免除以 0
    if (reveal >= 1.0)
    {
    discard; // 无半透明像素覆盖
    }

    // 还原平均颜色
    float3 averageColor = accum.rgb / max(accum.a, 0.00001);

    // 最终混合:以 (1 – reveal) 作为整体不透明度
    return float4(averageColor, 1.0 – reveal);
    }

    工程落地选型建议

    在延迟管线中治理半透明,通常采取分层策略:

    • 树叶、草地、铁丝网:坚决使用 Alpha Test (Cutout) + TAA/MSAA (Alpha to Coverage) 写入 G-Buffer,享受廉价的不透明延迟光照。
    • 普通烟雾、爆炸粒子:走 Forward Pass + 软粒子深度淡出(Soft Particles),关闭深度写入并按发射器粗排序。
    • 复杂玻璃座舱、建筑内外层半透明、水体折射:采用 WBOIT 或 Cluster Forward 专用 Pass,以可预测的固定带宽消耗彻底终结多层穿插引起的闪烁与穿模。
    赞(0)
    未经允许不得转载:171主机测评 » 延迟管线中的半透明渲染痛点:混合 Forward Pass 的排序与开销
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址