遮挡剔除
在3D渲染引擎中,遮挡剔除 是将被遮挡物体移除的过程。
你会在这个页面中学习到:
遮挡剔除的优缺点有哪些。
怎样在 Godot 中设置遮挡剔除。
解决 Godot 中控制器的问题 。
参见
你可以使用 遮挡剔除和网格 LOD 演示项目 来了解遮挡剔除的实际工作原理。
为什么要使用遮挡剔除
这个示例场景中有数百个房间相邻堆叠,一个动态物体(红色球体)隐藏在明亮房间的墙后面(门的左侧):
遮挡剔除关闭后,光亮房间后的所有房间和动态物体都需要被渲染:
遮挡剔除打开后,只有真正可见的房间被渲染,动态物体被墙遮挡住,因此也不用被渲染:
由于引擎只有更少的工作要做(更少的顶点和更少的 draw calls),因此只要在场景中有足够多的遮挡剔除机会,性能就会提高。所以说遮挡剔除在室内场景中最有效,尤其是在很多较小的房间而不是较少更大的房间中。将此与 网格的细节级别(LOD) 与 可见范围(HLOD) 结合起来可以进一步提高性能效益。
备注
当使用集群 Forward 渲染后端时,引擎已经执行了 深度预处理 。这包括在渲染场景的实际材质之前渲染场景的仅深度(depth-only)版本。该技术用于确保每个不透明像素仅着色一次,从而显着降低过度绘制的成本。
使用 Forward Mobile 渲染后端时可以观察到最大的性能优势,因为出于性能原因,它并没有深度预处理。因此,遮挡剔除将主动减少渲染后端的着色过度绘制。
尽管如此,即使使用深度预处理,复杂 3D 场景中的遮挡剔除仍然有明显优势。然而,在遮挡剔除机会很少的场景中,遮挡剔除可能不值得其增加的额外设定和 CPU 使用率。
Godot中的遮挡剔除如何运作
备注
“occluder” refers to the shape blocking the view, while “occludee” refers to the object being hidden.
在 Godot 中,遮挡剔除是通过将场景的遮挡几何体光栅化到低分辨率的缓存中实现的。这是使用了软光追库 Embree 来完成的。
The engine then uses this low-resolution buffer to test the occludee’s AABB against the occluder shapes. The occludee’s AABB must be fully occluded by the occluder shape to be culled.
因此,较小的物体相较于较大的物体更可能被有效地剔除。较大的遮挡物(如墙壁)往往比较小的遮挡物(如装饰道具)更有效。
设置遮挡剔除
使用遮挡剔除的第一步是在项目设置中启用 渲染>遮挡剔除>使用遮挡剔除。(确保在项目设置中启用 高级设置 选项,否则可能无法找到该设置。)
项目设置会立即应用,无需重启编辑器。
在启用项目设置后,你仍然需要创建一些遮挡物。出于性能原因,引擎不会自动将所有可见物体视为遮挡剔除的基础。相反,引擎需要只有静态物体被烘焙的简化场景表示。
有两种方法可以在场景中设置遮挡器:
自动烘焙遮挡物(建议)
备注
当前 遮挡物 烘焙过程中仅考虑 MeshInstance3D 节点。烘焙遮挡物时, 不 考虑 MultiMeshInstance3D、GPUParticles3D、CPUParticles3D 和 CSG 节点。如果你希望将它们也视为遮挡物,则必须手动建立以(大致)匹配其几何形状的遮挡物形状。
该限制不适用于 被遮挡者。任何继承自 GeometryInstance3D 的节点类型都可以被遮挡。
启用上述遮挡剔除的项目设置后,向你包含 3D 关卡的场景中新增 OcclusionrInstance3D 节点。
选择 OccluderInstance3D 节点,然后单击 3D 编辑器视口顶部的 烘焙遮挡器 。在烘焙后,OccluderInstance3D 节点将包含一个 Occluder3D 资源,用于存储关卡几何体的简化版本。此遮挡物几何体在 3D 视图中显示为紫色线框线(只要在 透视 菜单中启用 查看小工具 )。然后使用该几何体为静态和动态被遮挡物提供遮挡剔除。
烘焙后,你可能会注意到动态对象(例如玩家、敌人等)也被包含到了烘焙的网格中。为了防止这种情况发生,请在 OccluderInstance3D 上设置 Bake > Cull Mask 属性,以排除某些视觉层的烘焙。
例如,你可以禁用剔除蒙版上的第 2 层,然后将动态对象的 MeshInstance3D 节点配置为位于可视层 2(而不是第 1 层)上。为此,请选择相关的 MeshInstance3D 节点,然后在 VisualInstance3D > Layers 属性上,取消选中图层 1,再选中图层 2。配置剔除蒙版和图层后,按照上述过程再次烘焙遮挡物。
手动放置遮挡器
这种方法更适合专门的用例,例如为 MultiMeshInstance3D 设置或 CSG 节点创建遮挡(由于上述限制)。
启用上述遮挡剔除项目设置后,将 OcclusionrInstance3D 节点添加到包含 3D 关卡的场景中。选择 OccluderInstance3D 节点,然后选择要添加到 Occlusionr 属性中的遮挡器类型:
QuadOccluder3D(一个单平面)
BoxOccluder3D(一个立方体)
SphereOccluder3D(一个球形遮挡器)
PolygonOccluder3D(一个具有任意数量点的 2D 多边形)
此外还有 ArrayOccluder3D,其点无法在编辑器中修改,但可用于通过脚本进行程序生成。
预览遮挡剔除
可以启用调试绘制模式来预览遮挡剔除实际“看到”的内容。在 3D 编辑器视口的左上角,单击 透视 按钮(或 正交 ,具体取决于当前的相机模式),然后选择 显示高级… > 遮挡剔除缓冲 。这将显示引擎用于遮挡剔除的低分辨率缓冲区。
在同一菜单中,还可以启用 查看信息 和 查看帧时间 来查看右下角的绘制调用和渲染图元(顶点+索引)的数量,以及右上角渲染的每秒帧数。
如果在显示此信息时在项目设置中切换遮挡剔除,你可以看到遮挡剔除对场景性能的改善程度。请注意,性能优势在很大程度上取决于 3D 编辑器相机的视角,因为遮挡剔除仅在相机前面存在遮挡器时才有效。
要在运行时切换遮挡剔除,请在根视口上设置“use_occlusion_culling”,如下所示:
get_tree().root.use_occlusion_culling = true
在运行时切换遮挡剔除,对于比较正在运行的项目的性能很相当有用。
性能方面的考虑
在构建关卡时考虑遮挡
这是最重要的指导原则。 好的关卡设计并不只是满足游戏性的需求,也应该同时考虑遮挡。
对于室内环境,添加不透明的墙壁以定期“打断”视线,并确保一次不会看到太多场景。
对于大型开放场景,请尽可能使用类似金字塔的结构来确定地形的标高。与任何其他地形形状相比,这种设计提供了最大的剔除机会。
避免在游戏过程中移动 OcclusionInstance3D 节点
这包括移动 OccluderInstance3D 节点的父节点,因为这将导致节点本身在全局空间中移动,因此需要重建 BVH。
切换 OccluderInstance3D 的可见性(或其父级之一的可见性)并不那么耗费性能,因为更新只需要发生一次(而不是连续发生)。
举个例子,如果你有一扇滑动门或旋转门,则可以使 OccluderInstance3D 节点不是门本身的子节点(以便遮挡器永远不会移动),但可以在门开始打开后隐藏 OccluderInstance3D 可见性。一旦门完全关闭,你就可以重新显示 OcclusionrInstance3D。
如果你绝对必须在游戏过程中移动 OcclusionrInstance3D 节点,请为其使用原始 Occlusionr3D 形状,而不是复杂的烘焙形状。
使用尽可能简单的遮挡器形状
如果发现复杂 3D 场景中性能低下或卡顿,则可能意味着 CPU 由于渲染(过于)详细的遮挡器而过载。请选择 OccluderInstance3D 节点,增加 Bake > Simplification 属性,然后再次烘焙遮挡器。
请记住,保持合理的简化值。对于关卡几何形状来说,太高的值可能会导致发生不正确的遮挡剔除,如 我的被遮挡物在不应该被剔除的情况下被剔除 中所示的那样。
如果这仍然没有让 CPU 使用率低下来,你可以尝试调整 渲染 > 遮挡剔除 > BVH 构建质量 项目设置和/或减少 渲染 > 遮挡剔除 > 每线程遮挡光线 。你需要在“项目设置”对话框中启用 高级设置 开关才能查看这些设置。
故障排除
我的被遮挡物在应该被剔除的时候却没有被剔除
在遮挡器一侧:
首先,仔细检查 OccluderInstance3D 中的 Bake > Cull Mask 属性是否设置为允许烘焙你想要的网格。 MeshInstance3D 节点的可见性层必须存在于剔除遮罩中,网格才能包含在烘焙中。
此外请注意,遮挡器烘焙仅考虑具有 不透明 材质的网格。表面将 透明 材质将 不 包含在烘焙中,即使应用在其上的纹理是完全不透明的。
最后,请记住,烘焙遮挡器时 不 考虑 MultiMeshInstance3D、GPUParticles3D、CPUParticles3D 和 CSG 节点。作为一种解决方案,你可以手动为这些节点添加 OccluderInstance3D 节点。
在被遮挡物一侧:
确保 Extra Cull Margin 设置为尽可能低(通常应为 0.0
),并且在对象的 GeometryInstance3D 部分中禁用 Ignore Occlusion Culling 。
另外,检查 AABB 的大小(选择节点时由橙色框表示)。此轴对齐的边界框必须被遮挡器形状 完全 遮挡,才能隐藏被遮挡物。
我的被遮挡物在不应该被剔除的情况下被剔除
造成这种情况的最可能的原因是,遮挡器烘焙中包含的对象在烘焙遮挡器后已被移动。例如,当移动关卡几何体或重新排列其布局时,就可能会发生这种情况。要解决此问题,请选择 OccluderInstance3D 节点并再次烘焙遮挡物。
这种情况也可能发生,因为动态对象包含在烘焙中,尽管它们不应该包含在内。请使用` 遮挡剔除调试绘制模式 查找不应出现的遮挡器形状,然后 相应地调整烘焙剔除蒙版 。
最后一个可能的原因,是在遮挡器烘焙过程中使用了过于激进的网格简化。选择 OccluderInstance3D 节点,减少 Bake > Simplification 属性,然后再次烘焙遮挡器。
作为最后的手段,你可以启用被遮挡者的 Ignore Occlusion Culling 属性。这将抵消该对象的遮挡剔除的性能改善,但对于永远不会被剔除的对象(例如第一人称视图模型)来说这样做是有意义的。