Contents

遮挡剔除

在游戏场景中,根据游戏类型的不同,有数量不等的 object,如果这些物件同时渲染的话,会对性能造成很大影响。所以对于大部分游戏来说,将 object 提交到渲染流程之前,剔除掉看不到的 object,是非常必要的一个步骤。看不见的 object 可以两类,一类是在相机范围之外的,另一类是被其他 object (比如墙壁,地形等)挡住的。通过判断 object 是否在相机的视锥体外。判断 object 是否在相机的视线范围内有简单的计算方式,但是判断 object 是否被遮挡则相对比较困难。

因为遮挡物有可能是不规则的,一个 object 挡住另一个可能是像素级别的,不能像相机视锥剔除一样,通过计算规则几何体的相交来得出结果。这个问题的解决思路和渲染的深度测试本质上类似,将 object 光栅化到屏幕空间中,进行逐像素的深度比对。

解决这个问题目前大致有三种方式:软件剔除,硬件剔除,预计算方式。

硬件剔除

Hardware Occlusion Queries

图形硬件 API 通常会提供 Hardware Occlusion Queries 的功能,它可以统计出一个 object 在渲染时会有多少个像素会被看到。打开查询功能后,object 的渲染并不会实际写入 frame buffer 或者 depth buffer,它只是简单的进行光栅化后,和 depth buffer (通常是用前一帧)进行深度测试,然后返回通过深度测试的像素数量,读回到内存中,我们可以根据像素的数量,来决定后续是不是真正渲染这个 object。为了性能考虑,通常只渲染 object 的外包围盒,这是一种保守的估计,在简化计算的同时,不会影响结果的正确性。

一般步骤如下:

  1. 初始化遮挡查询

  2. 关闭对帧缓冲区和深度缓冲区的写入

  3. 渲染一个简单但保守的复杂对象近似体,通常是一个包围盒

  4. 结束遮挡查询

  5. 获取查询结果

优势:

  • 方案比较简单,无需额外的开发

  • 结果比较精确

劣势:

  • 对硬件有一定要求(ES 3.1/Vulkan/DirectX 9)

  • 因为需要从 GPU 将结果回读到 CPU 端,一般会采用流水线的方式,会造成 1 到 2 帧的延迟。

  • 增加 draw call 的数量,增加 gpu 的负担

  • 只能以 draw call 为颗粒度,如果多个 object 通过一个 instance 渲染,那么不区分单个 object 的遮挡关系。

Hierarchical Z-Buffer Occlusion

和 Hardware Occlusion Queries 类似,不同的地方是,它将 depth buffer 生成 mipmap,让被遮挡物的包围盒在屏幕空间中只匹配到更低精度的 depth buffer ,比如 depth buffer 就能覆盖包围盒,这种方法巧妙的避免了对 object 进行光栅化的过程,也减少了对纹理的读取开销。

优势:

  • 减少纹理的读取开销

  • 避免对被遮挡物进行光栅化

劣势:

  • 有一些 corner case,实现相对复杂

  • 这样会减少 cull 的 object 数量

GPU driven

使用 compute shader 等更高级的 api 特性,将遮挡剔除的结果直接在 gpu 内使用,避免了从 gpu 回读到 cpu 过的过程,但是一般实现较为复杂,对硬件有一定的要求。目前 UE 和 unity 并没有提供现成的解决方案。

软件剔除

软件剔除的方式和硬件剔除是类似的,只不过是在 CPU 上进行光栅化。

首先将遮挡 object 光栅化到 depth buffer,然后对被遮挡物的包围盒进行光栅化,将光栅化的结果和 depth buffer 进行对比,判断 object 是否被遮挡。光栅化可以采用传统的扫描线算法。

一般来讲,CPU 上模拟光栅化速度比硬件要慢,这个方案能成立的前提是它对硬件没有要求,另外可以通过多线程,SIMD 等优化方式来提升运算速度。

优点:

  • 对硬件没有要求

  • 不用从 GPU 读取数据到 CPU 端,延迟相对较小。

  • 在 GPU 成为性能瓶颈的时候,可以减轻 GPU 的负担。

  • 可以将 culling 运算放入单独的线程,减少对性能的影响。

目前可以参考的开源的软件剔除方案有:

预计算

这种方案是在烘培的阶段进行可见性的计算,运行时再利用预计算的结果来加速剔除的判断,这种方案通常会消耗更多的内存。UE 和 Unity 都提供了这种方案。

UE

在相机可能经过的路径上放置一些 cell(Precomputed Visibility Volumes),预先计算从 cell 出发所能看到的物件,并存储在对应的 cell 中。当摄像机在某个 cell 中时,直接读取结果即可。 Pastedimage20240115202402.png

优势:

  • 计算速度较快

劣势:

  • 无法处理超出 cell 的部分,只能适用于相机路径比较固定的游戏。

  • 无法判断动态的 object,需要同时使用其他的辅助方案

  • 需要占用额外的内存

Unity

unity 采用的是第三方的商业化方案 Umbra,相比 UE 提供的多种方案,unity 仅仅只提供了这一种。相比 UE 需要提前在相机可能出现的位置上布置 cell,Unity 的系统会自动处理摄像机在任何位置的遮挡剔除,不需要提前布置 cell。

这套方案会对场景静态几何体进行分析,创建一个空间数据库,这个数据库能够在运行时用来快速判定物体是否可能被遮挡。具体实现方式可以参考 Introduction to Occlusion Culling | by Umbra 3D | Medium

优势:

  • 可以查询动态 object 是否被遮挡

劣势:

  • 需要指定 object 作为遮挡物, 并且不支持动态 GameObjects 作为遮挡物

  • 需要占用额外的内存