遮挡剔除
Occlusion Culling
遮挡剔除尽早剔除被其他物体挡住的物体,避免它们在 z-buffer 中被光栅化、深度比较甚至着色。本页覆盖遮挡剔除的分类与通用框架、GPU 遮挡查询、层次 Z 缓冲 (HZB) 及其在现代引擎中的简化变体(含两遍法、软件光栅化遮挡),以及在 CPU/GPU 上按物体—簇—三角形多粒度组合剔除并用间接绘制提交的剔除系统。
为什么需要(19.7 节)
z-buffer 能正确解决可见性,但它是蛮力的。视线方向排成一列的 10 个球,最终只看到 1 个,中间像素的深度复杂度为 10:后到前绘制会有 9 次多余的像素着色;即使前到后绘制,10 个球的三角形仍要全部光栅化并做深度比较。雨林、发动机、城市、摩天楼内部这类密集场景都是如此。背面剔除可以看作最简单的遮挡剔除。
分类与通用框架
- 基于点 vs 基于单元:基于点是从单一视点判断;基于单元针对一个区域(盒或球)内所有视点,物体必须从单元内任何一点都看不见。后者算一次可在视点留在单元内的若干帧复用,但计算更贵,常作为预处理。二者类似点光源与面光源:物体不可见相当于位于本影中。
- 图像空间 / 物体空间 / 光线空间:实时图形中图像空间算法最常用。
通用算法(Zhang)维护遮挡表示 OR 和潜在遮挡物集合 P:
flowchart TD
A["取下一个通过视锥剔除的物体 g"] --> B{isOccluded g, OR}
B -- "是" --> C["跳过"]
B -- "否" --> D["渲染 g 并加入 P"]
D --> E{"P 足够大?"}
E -- "是" --> F["把 P 合并进 OR, 清空 P"]
E -- "否" --> A
F --> A
C --> A如上图,被判为遮挡的物体直接跳过,未被遮挡的物体渲染后成为新的遮挡物。性能强烈依赖绘制顺序:先画车引擎盖,发动机很可能被剔除;先画发动机则什么都剔不掉,因此粗略的前到后排序收益显著。小物体也可能是好遮挡物——足够近时一个火柴盒能挡住金门大桥。
遮挡查询(19.7.1 节)
GPU 的特殊渲染模式:光栅化一组三角形(通常是复杂物体的 BV,如盒或 k-DOP),与当前 z-buffer 比较,返回可见像素数 ,但不写任何颜色和深度。
- 还需保证视锥可见近平面的任何部分都不在 BV 内,才能安全剔除; 小于阈值时可以近似丢弃(以质量换速度), 也可用来选择更低的 LOD。
- 只需布尔结果时可用
ANY_SAMPLES_PASSED(OpenGL 3.3 / DirectX 11 起),一有片元通过就能提前结束;OpenGL 4.3 的ANY_SAMPLES_PASSED_CONSERVATIVE允许只与粗深度缓冲比较,只要保守即可。 - 延迟很长,期间可渲染成百上千个三角形,所以只在包围盒内物体很多、遮挡很多时划算。查询模型是异步的:CPU 发出任意多个查询,定期检查结果队列而不停顿。谓词/条件渲染把查询与对应绘制调用一起提交,GPU 仅在查询可见时执行该绘制,大大提高了实用性。
- 调度:被遮挡的物体每帧都测,可见的物体往往保持可见,可以少测(按多帧统计调整频率);无条件渲染时可以批量合并查询、用多个小盒代替一个大盒、对之前可见的物体做时间抖动采样调度。
- 难点:如果一切都可见,遮挡算法只增加开销;要快速察觉算法无效并收手;视锥内最先绘制的物体必然可见,对它们查询是浪费;何时以何种顺序测试很难决定。
层次 Z 缓冲 HZB(19.7.2 节)
原始算法把场景放进八叉树,把 z-buffer 维护为图像金字塔 z-pyramid:最细层就是 z-buffer,其他层每个值是下一层对应 窗口中的最远 z。z-buffer 被改写时向上传播。按粗略前到后遍历八叉树,把节点包围盒在能包住其投影的最粗 z-pyramid 单元上测试,若盒的最近深度 比该单元更远则被遮挡,否则下降到更细层直到判定遮挡或到达底层(可见);可见节点继续向下遍历并把几何渲染进 HZB,供后续测试使用。完整 HZB 如今已不用,但它是 GPU 硬件 z-culling 的基础,也启发了大量基于计算着色器或 CPU 软件光栅化的自定义剔除。
现代简化流程(大多数实现不用八叉树,也不在渲染后更新 z-pyramid,因为太贵):
- 用某种遮挡物表示生成完整 z-pyramid。
- 把物体 BV 投影到屏幕,用投影后最长边 (像素)选择 mip 层:
其中 是 z-pyramid 的层数,max 防止负层级,min 防止越界。(原文取整符号在提取中丢失;书中说明该式选择使投影 BV 至多覆盖 个深度值的最低整数层,此处按向上取整写出。)这样每次测试最多读 4 个深度值,成本可预测;而且大物体本就更可能可见,没必要读更多。 3. 计算 BV 的最小深度 (视空间 AABB 直接取最小深度;OBB 把顶点投影到视线方向取最小;球取 ),若它比这至多 4 个深度值都大则遮挡。相机在 BV 内时直接渲染。可选地再维护一个存最小深度的金字塔:BV 的 比其中所有值都小则一定可见;两者重叠时再到更细层测试。 比较类似百分比渐近过滤,可直接用带比较的双线性采样实现。
遮挡物来源:最近的 n 个"最佳"遮挡物、美术制作的简化遮挡体、上一帧可见物体统计,或直接用上一帧 z-buffer(不保守,快速运动时物体会突然冒出)。Haar 与 Aaltonen 把最佳遮挡物与上一帧 1/16 分辨率重投影深度结合,用 GPU 生成金字塔,有的实现借助 AMD GCN 的 HTILE 加速。
两遍法(Haar 与 Aaltonen;Kubisch 与 Tavenrath 类似)保证所有可见物体都被渲染:
flowchart LR A["用上一帧 z-pyramid 测试所有物体"] --> B["渲染判为可见的物体"] B --> C["由本帧深度重建 z-pyramid"] C --> D["重测第一遍被剔除的物体"] D --> E["渲染未被剔除者"]
即使相机或物体快速运动,结果也完全正确。
其他变体:
- Doghramachi 与 Bucci:把被遮挡物的 OBB 光栅化到降采样并重投影的上一帧深度上,强制 early-z,可见片元按物体 ID 在缓冲中标记可见,比 mip 层测试剔除率更高。
- Collin: 浮点 z-buffer(非层次)在 CPU 或 PS3 的 SPU 上用 SIMD 软件光栅化美术遮挡体,测试物体屏幕 AABB 的 ,只把幸存物体发给 GPU;分辨率低于帧缓冲,所以不保守。Wihlidal 建议再用它的 预填 GPU 的 HiZ(如 HTILE),或生成计算剔除用的 z-pyramid。
- 遮罩层次深度缓冲 MHDB(Hasselgren 等):每个 瓦片每像素 1 位掩码加两个 ,共每像素 3 位;两个深度分别给前景和背景,能更好处理深度不连续。保守、可做 剔除,每个三角形都更新,软件光栅化很快;可配合 AABB 树分层测试,或用堆按前到后遍历整个场景。开源代码针对 AVX2 深度优化。
- 中间件:Umbra 等专用剔除框架已集成进多款引擎。
剔除系统(19.8 节)
现代系统在多个粒度上组合剔除,有的完全在 GPU 计算着色器中进行,有的 CPU 粗剔、GPU 细剔。
flowchart LR A["物体/实例剔除"] --> B["簇/块剔除"] B --> C["三角形剔除"] C --> D["压缩幸存三角形列表"] D --> E["间接绘制"]
- 簇 (cluster/chunk) 是物体三角形的子集,例如 64 顶点的三角形带或 256 个三角形一组。物体级可用小三角形、细节、视锥、遮挡剔除;簇更小、更易被剔除,可用细节、视锥、成簇背面、遮挡剔除。
- 三角形级:除以 w 后与 比较的视锥剔除、背面、退化三角形、小三角形,可能还有遮挡。幸存者压缩成紧凑列表,计算着色器把三角形数写入 GPU 缓冲,用间接绘制(OpenGL 的 multi-draw indirect、DirectX 的 execute indirect)让 GPU 给自己下绘制命令,无需 CPU 往返。
- 代表工作:Shopf 等人把角色 AI 放在 GPU 上,位置只存在显存中,因此用计算着色器做剔除与 LOD,影响了后续大多数系统;《刺客信条:大革命》(Haar 与 Aaltonen);Frostbite(Wihlidal);配合可见性缓冲的系统(Engel);大量零件的大模型渲染(Kubisch 与 Tavenrath,用几何着色器生成包围盒可见面再靠 early-z 剔除)。
- 最佳组合尚无定论,取决于目标架构和内容。
取舍
| 方法 | 优点 | 缺点 |
|---|---|---|
| 硬件遮挡查询 | 精确、易用,可条件渲染 | 延迟长,结果常需下一帧使用 |
| HZB 计算剔除 | 全 GPU、成本可预测,可配合间接绘制 | 依赖遮挡物质量;用上一帧深度需两遍法保证正确 |
| CPU 软件光栅化 | 不占 GPU,早剔除减少提交 | 低分辨率不保守,占 CPU |
| MHDB | 保守、逐三角形更新 | 实现复杂,依赖 SIMD |
GEA 的视角:PVS 与 HiZ(卷 II 11.5.1.3、11.6.8.6)
- 何时值得做:地面视角的拥挤环境中物体互相遮挡很多,遮挡剔除极其重要;场景稀疏或从高处俯视时遮挡少,剔除的开销可能超过收益(卷 II 11.5.1.3)。
- 潜在可见集 (PVS):对任意相机位置列出可能可见的物体,宁可多列不可见的,也不漏掉会出现在画面中的。常见实现是把关卡切成区域,每个区域附一张"在此区域内能看到哪些区域"的列表。可以由美术或策划手工指定,更常见的是离线工具根据用户划定的区域自动生成:在区域内随机分布的视点上渲染场景,每个区域的几何用不同颜色编码,扫描帧缓冲统计出现的颜色即得可见区域列表。自动工具不完美,通常允许手动放置测试视点,或手动指定某区域 PVS 中必须包含或排除的区域。
- HiZ 与两遍法:GEA 在 Nanite 一节介绍了层次 Z 缓冲的构建(每级存下层四像素的最大深度)、按簇屏幕范围选 mip 级的单次采样测试,以及"先重画上一帧可见簇建 HZB、再测其余"的两遍法(卷 II 11.6.8.6)。这与本页「层次 Z 缓冲 HZB」和「两遍法」一致,细节见 Nanite 虚拟化几何体 中的 GEA 部分。
- 入口与反入口见 剔除技术 中的 GEA 部分。
出处
- realtime_rendering_4ed.pdf 第 19 章 Acceleration Algorithms
- Game_Engine_Architecture_4th_Edtion_Volume_2_-_Jason_Gregory.pdf
- Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 11 章 11.5.1.3、11.6.8.6 节