← ALL ARTICLES

基于深度的可见性优化

全库目录221
本文目录13

像素Shader花了很多时间画后方表面,可以尝试让深度更早拒绝它们。Early-Z改变测试时机,Depth Prepass先建立深度,Hi-Z用区域摘要减少判断。每种方法都增加不同的准备或状态要求,沿着遮挡数据怎样生成和消费来比较,才能知道额外几何是否划算。

Early-Z

如果 GPU 在 Pixel Shader 之前就能确定深度失败,就可以跳过这次昂贵的像素着色。这是 Early-Z 的核心收益。

Early-Z 不会提前计算法线,也不需要先执行 Pixel Shader。它利用已有深度和光栅器产生的候选深度做测试。

可能阻碍或限制 Early-Z 的情况:

  • Pixel Shader 修改深度;
  • 使用 discard/clip,硬件不能提前确认样本最终存在;
  • Shader 有某些可见副作用,例如 UAV 写入;
  • 深度比较或写入状态不适合提前执行;
  • 绘制顺序导致深度缓冲尚未包含近处遮挡物。

具体硬件可能仍有 Conservative Early-Z 等优化,不能仅凭 Shader 代码断言一定开启或关闭。

失效的代价往往不止当前这一次绘制。硬件可能改变提前测试或写入策略,影响范围和恢复条件要看目标实现,不能把清除深度作为通用恢复规则。是否影响后续绘制需用目标架构的状态恢复与计数器证据判断。

由近及远绘制有利于提前拒绝被遮挡片元,但效果取决于深度写入、材质操作和硬件实现。排序也占 CPU 时间,需要与减少的片元工作一起测量。硬件隐面消除可能降低排序带来的增益;比较排序开关时应同时记录 CPU 排序时间、提交批次数与 GPU 片元成本。

Depth Prepass

Depth Prepass 先用简单 Shader 写一遍不透明深度,再执行主材质 Pass。它可以提高重像素 Shader 场景的 Early-Z 命中率,也能提前提供深度给后续效果。

代价是多画一遍几何,增加 Draw、顶点处理和深度带宽。材质很轻、遮挡少或移动端带宽敏感时不一定划算。

两个 Pass 的状态配置

以普通Z为例,第一个Pass开启Depth Write并使用Less,ColorMask 0关闭颜色输出。纯不透明材质可使用简单深度路径;Alpha Test仍须采样透明度并裁剪。反向Z则将比较方向改为Greater。第二个 Pass 关闭 Depth Write,比较函数改为 Equal。

比较函数改为 Equal 是这套方案的关键。深度已经是最终结果,仅有深度相等的候选通过测试;在允许提前测试时,可以省去其他候选的重着色。同深度重叠表面、MSAA覆盖、辅助调用和着色器副作用都可能使调用数大于最终像素数。

Equal 比较要求两个 Pass 算出完全一致的深度值。两个Pass必须生成一致的深度和覆盖;通常复用相同变换与不变量约束,避免编译器重排造成数值差异。微小差异也可能让部分样本落选。顶点动画、风场扰动或不同的优化级别都可能破坏这个前提。

全局通道调度

先为全部目标对象建立深度,再执行材质通道,后方对象才有机会利用其他对象的遮挡。如果对每个对象依次执行自己的深度和颜色通道,尚未绘制的前景对象无法帮助拒绝当前背景的着色。额外Draw和状态准备由实际批次决定,不能仅由Shader文件是否包含多个Pass推断。

SRP Batcher主要减少兼容绘制之间的常量与状态准备,通常不会把多个Draw合成一个。是否采用它与是否建立深度预通道是不同决策,应分别记录CPU准备时间和GPU几何、片元成本。

成本条件

Depth Prepass 不是普遍成立的优化。它把一次额外的几何遍历换取 Overdraw 的减少,收益取决于两者的相对大小:

  • 像素着色器复杂、Overdraw 严重、难以按距离排序时,可节省的着色工作通常较多,仍需扣除预通道成本;
  • 材质轻、场景平坦、几何密集时,额外的顶点处理与深度带宽可能超过节省的像素着色。

移动端还要单独权衡。具有硬件隐面消除的分块架构可能已拒绝大量遮挡片元;额外几何遍历、参数存储与分块成本可能抵消预通道收益。具体能力和限制按目标 GPU 架构确认。

URP的Depth Priming把预通道深度提供给后续不透明绘制。目标版本是否启用、哪些对象参与以及排序如何调整,需结合渲染器配置和Frame Debugger检查。收益可能来自少做重着色或减少排序,额外几何与深度访问也会增加成本;透明合成仍按其自身深度和混合规则处理。

Alpha Test 材质的分层处理

Alpha Test可能限制提前深度写入,但一些硬件仍能提前拒绝已被遮挡的样本。需要预通道时,深度通道和颜色通道应采用相同的纹理LOD、裁剪阈值、顶点位移和覆盖规则。

以树叶硬边裁剪为例,预通道写入通过clip的覆盖,主通道复用同一clip规则并使用Equal。若还需柔和透明边缘,将其作为独立透明层,测试已有深度并按所选合成方式处理;最后一层是否写深度不能仅由“正面”或“背面”决定。

Hi-Z

Hierarchical Z 把深度缓冲构造成层级摘要。粗层级表示一块区域的保守深度范围,可以快速拒绝整块图元、Occlusion Query、SSR/Ray March 步进或 GPU Culling 中的不可见区域。

Hi-Z 是分层加速,不等于 Early-Z 本身。Early-Z 描述测试时机,Hi-Z 描述利用层级深度加速判断的方法。

保守分层归约

以常规深度“近为小、远为大”为例,判断包围体完全遮挡需使用区域最远深度,较粗 mip 用 max 归约。反向深度则使用 min。下例为单层 2×2 归约的 HLSL 轮廓,实际调度处理 mip 尺寸和边界。

hlsl
float ReduceDepth(float4 samples, bool reversedDepth)
{
    if (reversedDepth)
    {
        return min(min(samples.x, samples.y), min(samples.z, samples.w));
    }
    return max(max(samples.x, samples.y), max(samples.z, samples.w));
}

空背景、未覆盖像素和屏幕外区域必须保守处理。选 mip 时要覆盖包围体投影全部范围;漏掉一个未遮挡角落就可能误删对象。历史 Hi-Z 还需处理相机变化和新生物体。

包围体的保守查询

设普通Z深度图一块2×2区域为[0.2,0.2;0.2,1.0]。其中1是未覆盖背景,max归约为1。若对象投影覆盖这整块区域,且对象最近深度为0.6,就不能用0.2认定它全部被挡:右下角可能看见对象。使用max后,0.6>1为假,正确地保留候选。把背景改成0.4,则最远遮挡深度为0.4,0.6>0.4说明该区域所有深度都在对象最近点前,可以拒绝。

实际查询先保守投影包围体,得到屏幕矩形和最近深度;跨近平面时不能只投影八角后取最小最大,应裁剪或直接保留。对整数矩形范围,在第m级把最小坐标向下取整、最大坐标向上覆盖到对应块,枚举所有相交mip纹素,再取这些纹素的max。m由预算选择,选更粗级通常减少查询数量,却纳入矩形外的背景,降低剔除率。只有证明四角查询覆盖了全部相交块,才可以把枚举缩成四次读取。

反向Z用min摘要,对象最靠近相机的深度是最大值,完全遮挡条件反为objectNearest < regionMin。等号和量化误差采用保守容差,无法证明被挡时继续绘制。前帧摘要还带有相机、物体和遮挡物的时间条件;静态二维比较正确不能保证历史遮挡结果有效。

分组生成多级深度

构建深度金字塔(每级取若干子像素的最远深度)的朴素做法是每级一次 Dispatch,十级就是十次。

利用共享内存可以在一次 Dispatch 内完成多级。思路是每轮让活跃线程数降到四分之一,用位掩码筛选哪些线程继续工作:

hlsl
gs_Depth[localIndex] = mipDepth;
GroupMemoryBarrierWithGroupSync();

// 0x9 = 二进制 001001,筛出 x、y 均为偶数的线程
if ((localIndex & 0x9) == 0)
{
    // 合并右、下、右下三个邻居
    mipDepth = MaxDepth(float4(mipDepth,
                               gs_Depth[localIndex + 0x01],
                               gs_Depth[localIndex + 0x08],
                               gs_Depth[localIndex + 0x09]));
    _OutMip1[dispatchId.xy >> 1] = mipDepth;
    gs_Depth[localIndex] = mipDepth;
}
// 所有组内线程在下一轮读取前再次执行GroupMemoryBarrierWithGroupSync
// localIndex = x + 8*y;下一轮用0x1B筛选,最终由localIndex==0写出

8×8的组把输入的8×8值依次归约为4×4、2×2、1×1,产生三级较粗结果;若同时写出输入级则共四个级别。这个示意只覆盖一个8×8组的局部归约。全图剩余级需要额外调度或有证明的跨组协调,Dispatch数量由输入级别、输出级别和尺寸决定。

两个实现细节:

反向 Z 的方向。金字塔要保存“最远”深度,但反向 Z 下最远对应数值最小,比较函数必须跟着翻转。这类符号错误不会报错,表现为遮挡剔除把该显示的物体剔掉。

输出资源绑定。每个实际写入的mip都需合法UAV及尺寸。未使用描述符能否保持未绑定取决于API和功能启用情况;通用实现可绑定兼容占位资源并保证不访问,不能假定空描述符在所有后端有效。

验证方法

  • 比较近到远与远到近排序下的片元调用和整帧时间。
  • 加入透明裁剪与顶点位移,检查预通道和主通道深度一致。
  • 可视化每级 Hi-Z,使用正确的保守归约并测试反向深度。

相关主题

实现状态:ReduceDepth与组内归约为HLSL机制片段,需补线程组、边界加载和UAV绑定。等深度与Hi-Z保守性由正文条件限定;本轮未执行GPU预通道、深度金字塔或性能测量。

参考资料