三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Unity遮挡剔除:Occluder与Occludee核心概念与配置实战

Unity遮挡剔除:Occluder与Occludee核心概念与配置实战

1. 项目概述:从“乱勾Static”到精准优化

在Unity项目性能优化的工具箱里,Occlusion Culling(遮挡剔除)绝对是一把利器,但也是一把容易用错的“双刃剑”。很多开发者,尤其是刚接触大型场景优化的朋友,看到Static下拉菜单里的“Occluder Static”和“Ocludee Static”两个选项,常常会陷入困惑:“这俩到底啥区别?是不是全勾上就完事了?” 于是,一个常见的“性能优化”操作就变成了:选中场景里所有静态物体,然后无脑勾上这两个复选框。结果呢?烘焙时间长得离谱,运行时剔除效果诡异,甚至可能因为错误的设置导致性能不升反降。这篇文章,我们就来彻底掰扯清楚Occluder和Occludee这两个核心概念,让你告别“乱勾Static”的懵懂阶段,真正理解并驾驭遮挡剔除,为你的游戏或应用带来实实在在的帧率提升。

简单来说,Occlusion Culling解决的核心问题是:摄像机看不到的物体,就不应该送去渲染。这听起来理所当然,但Unity的默认渲染流程(视锥体剔除)只负责剔除摄像机视锥体之外的物体,对于视锥体内但被前面物体(比如一堵厚墙)完全挡住的物体,它无能为力。遮挡剔除就是用来解决这个问题的。而Occluder和Occludee,正是定义“谁挡谁”、“谁能被挡”的关键角色。理解它们,是配置高效遮挡剔除方案的第一步,也是避免踩坑、让烘焙数据真正发挥作用的前提。无论你是正在为开放世界地图卡顿发愁,还是想优化室内复杂场景的渲染,搞懂这两个标签,都能让你的优化工作事半功倍。

2. 核心概念拆解:Occluder与Occludee的本质区别

要正确使用,必须先理解定义。很多文档的解释过于学术化,我们用人话和实际场景来重新梳理一遍。

2.1 Occluder:场景中的“挡板”

你可以把Occluder(遮挡物)想象成舞台上的实心背景板或者厚重的幕布。它的核心职责是:阻挡视线,让后面的东西“消失”在摄像机的视野里。

  • 核心特性:它是一个“主动”的角色。在Unity进行遮挡计算(无论是实时计算还是预烘焙)时,系统会检查这些Occluder物体的几何形状(通常是其包围盒或简化后的网格),并将其视为不透明的、坚实的障碍物。如果一个物体被标记为Occluder,Unity就会用它来判断它后面(相对于摄像机)的物体是否被完全挡住。
  • 生活化例子:一栋大楼、一座山、一面厚厚的承重墙、一个巨大的集装箱。这些都是典型的Occluder。摄像机在这边,它们能完全挡住后面的小房子、树木或NPC。
  • 技术细节:在烘焙Occlusion Culling数据时,Occluder的网格会被用于生成场景的“遮挡体”信息。这个信息决定了从不同视角看,哪些空间区域是被填满(遮挡)的。

2.2 Occludee:场景中的“演员”

Occludee(被遮挡物)则是舞台上的演员、道具。它的核心特性是:它可以被前面的“挡板”挡住,但它自己通常不(或无法)去挡住别人。

  • 核心特性:它是一个“被动”的角色。标记为Occludee意味着这个物体有资格被其他Occluder物体遮挡。如果它完全位于某个Occluder后面,Unity的遮挡系统就会在运行时将它从渲染队列中剔除,节省宝贵的GPU资源。
  • 生活化例子:放在房间里的桌子、椅子、散落在地上的武器、一个NPC角色、一棵树。它们自己可能很薄(如一张纸)或者有缝隙(如栅栏),不足以可靠地遮挡后方物体,但它们完全可能被一堵墙整个挡住。
  • 技术细节:只有被标记为Occludee的物体,才会被纳入遮挡剔除的“可剔除对象”列表。如果一个物体连Occludee都没勾选,那么无论它前面有多少Occluder,Unity都不会尝试去遮挡它,它永远会被渲染(只要在视锥体内)。

2.3 关键误区澄清:为什么可以“既是又是”?

这是最让人困惑的点。从字面意思看,一个物体怎么能同时是“挡板”又是“演员”呢?这岂不是矛盾?

一点也不矛盾,这恰恰是大多数静态物体的正确状态。我们回到舞台的比喻:一个高大的书架(Occluder),它能挡住后面书桌上的台灯(Occludee)。但同时,这个书架本身,也可能被它前面更厚的一堵墙(另一个Occluder)完全挡住。此时,对于墙来说,书架就成了“被遮挡物”(Occludee)。

所以,一个物体的“身份”是相对的、取决于观察视角的:

  • 对于它后面的小物体而言,它是Occluder
  • 对于它前面的更大物体而言,它是Occludee

因此,对于一个典型的、实心的、不透明的静态物体(比如场景中的建筑、岩石),最合理的设置就是同时勾选“Occluder Static”和“Occludee Static”。这意味着:“我既可以挡住别人,也可以被别人挡住,请把我纳入遮挡计算的双方考量中。”

那么,什么情况下应该只勾选一种呢?

  • 仅Occluder(不勾选Occludee):这种物体巨大无比,在游戏的所有可能视角下都不可能被其他任何物体完全挡住。例如,作为远景一直存在的、贴在天球上的天空盒网格,或者一个包裹整个游戏世界的、巨大的不可见碰撞体(如果用它做遮挡)。标记它为仅Occluder可以告诉Unity:“只用我来挡别人,不用费心计算有没有东西能挡我,因为不可能有。” 这能轻微优化烘焙和运行时计算。但这种情况非常少见,需要谨慎判断。
  • 仅Occludee(不勾选Occluder):这种物体本身不适合作为可靠的遮挡物。主要有三类:
    1. 透明或镂空物体:比如铁丝网、栅栏、窗户(除非是脏玻璃这种半透遮挡物)。它们无法完全阻挡视线,如果被当作Occluder,会导致它们后面的物体被错误地剔除,造成渲染错误(本该看到的物体消失了)。
    2. 非常薄或面积很小的物体:比如一张纸、一片树叶、一根电线。它们的遮挡能力极不稳定,从稍微侧一点的角度看就可能失效,依赖它们做遮挡会导致剔除结果闪烁(物体时隐时现),弊大于利。
    3. 动态物体:虽然动态物体一般不标记Static,但这里提一下原理。一个移动的NPC,如果你把它当Occluder,那么它一走动,背后的剔除数据就全乱了,会导致严重的渲染错误。所以动态物体通常通过其他方式(如动态遮挡剔除插件)处理,绝不标记为Static Occluder。

注意:Unity的官方文档和社区回答中,确实存在一些历史版本表述不清的问题,导致了“Occluder和Occludee互斥”的误解。现在的共识和实践已经非常明确:对于绝大多数实心静态物体,双勾选是最佳实践。

3. 静态标记(Static Flags)的深度解析与配置策略

理解了概念,我们再来看看操作界面——Inspector窗口顶部的Static复选框。点击下拉箭头,你会看到一列选项,“Occluder Static”和“Occludee Static”就在其中。这个Static系统是Unity管理优化功能的核心。

3.1 Static标记的真正含义

给一个物体打上Static标记,并不仅仅是“让它不动”那么简单。它的深层含义是:向Unity引擎承诺,这个物体在运行时(Runtime)的变换(位置、旋转、缩放)永远不会改变。这是一个非常重要的契约。

基于这个契约,Unity就可以在编辑期(Editor time)或构建时(Build time)放心地针对这个物体进行一系列预计算,将运行时的计算开销转移到编辑期,从而提升游戏性能。Occlusion Culling数据烘焙就是其中最典型的预计算之一。其他还有Lightmapping(光照烘焙)、Navigation Static(导航网格生成)、Batching Static(静态合批)等。

所以,第一个大坑就是:如果你给一个之后会移动、旋转或缩放的物体标记了Static,然后又去动态修改它的Transform,不仅相关的优化会失效(比如光照错乱),还可能引发难以排查的渲染或物理问题。

3.2 如何正确配置场景物体的Static属性

面对一个复杂的场景,我们不应该全选然后无脑勾选。一个科学的配置流程能节省大量烘焙时间,并得到更准确的剔除结果。

1. 分类筛选物体:

  • 大型、实心、不透明建筑/地形:选中这些物体,勾选Occluder StaticOccludee Static。通常它们也同时是Lightmap Static(接受光照烘焙)和Navigation Static(参与导航网格计算)。
  • 中型静态道具(如箱子、雕像、大型家具):同样,双勾选Occluder StaticOccludee Static。它们既能挡住更小的物品,也可能被墙壁挡住。
  • 小型静态道具(如杯子、书本、武器):通常也双勾选。但如果你有成千上万个这样的小物体,需要考虑它们作为Occluder的价值。有时为了简化遮挡数据,可以只勾选Occludee Static,不让它们参与遮挡计算,以提升烘焙速度。
  • 透明/镂空物体(栅栏、玻璃窗、链条):只勾选Occludee Static绝不勾选Occluder Static。确保它们能被墙挡住,但不会错误地挡住后面的东西。
  • 纯装饰性面片(公告板树木、贴花):通常只勾选Occludee Static。它们很薄,不适合做遮挡物。
  • 永远在视野最前层的物体(UI Canvas、始终在摄像机近裁剪面附近的特效):不要勾选任何Occlusion Static。因为它们不可能被遮挡,参与计算纯属浪费。
  • 动态物体(NPC、可移动机关、玩家角色):保持所有Static复选框未勾选。它们的遮挡需要通过其他技术(如动态遮挡剔除、层次Z缓冲等)处理。

2. 利用图层(Layer)进行批量管理:这是专业工作流的关键。不要手动一个一个点选物体。

  • 在Tags & Layers中创建专用的图层,例如Static_OccluderAndOccludeeStatic_OccludeeOnlyDynamic
  • 将对应类别的物体分配到这些图层。
  • 然后你可以使用Unity的搜索过滤功能,或者编写简单的编辑器脚本,批量修改同一图层下所有物体的Static属性。这能极大提升场景配置效率,并保持一致性。

3. 烘焙前的检查清单:

  • ✅ 确认所有标记为Static的物体在运行时真的不会动。
  • ✅ 确认透明/镂空物体没有错误标记为Occluder。
  • ✅ 确认动态物体没有标记任何Static。
  • ✅ 使用场景视图的“Occlusion Culling”可视化模式,预览遮挡效果是否合理。

4. Occlusion Culling工作流与烘焙参数详解

配置好Static标签只是第一步,接下来需要通过Window > Rendering > Occlusion Culling打开面板进行烘焙。这个面板里的参数决定了烘焙数据的质量和大小。

4.1 烘焙流程步骤

  1. 对象过滤(Object Filtering):在Occlusion窗口的Object标签页,确认场景中参与烘焙的物体范围。通常保持默认即可,它会自动包含所有标记了Occluder StaticOccludee Static的物体。
  2. 烘焙设置(Bake Settings):这是核心环节,切换到Bake标签页。
    • Smallest Occluder最重要的参数之一。它定义了能被当作有效遮挡物的最小物体尺寸(世界单位)。比如设置为1,那么任何尺寸小于1x1x1的物体,即使标记了Occluder Static,在烘焙时也会被忽略其遮挡作用。这能有效防止大量小物件(如石子、小草)产生巨量无用的遮挡数据,极大减少数据量和烘焙时间。设置原则:比你这个尺寸小的物体,你认为它不足以可靠地遮挡住后面的物体。
    • Smallest Hole:定义遮挡物中可以被“看穿”的最大孔洞尺寸。如果一个洞的直径小于这个值,遮挡系统会认为这个洞不存在,依然会剔除洞后面的物体。对于有窗户的建筑,需要将此值设置为小于窗户的尺寸,否则窗户后面整个房间都会被错误剔除。通常需要根据场景中典型孔洞(如门、窗)的大小来调整。
    • Backface Threshold:背面剔除阈值。当摄像机看到一个物体的背面(通常不可见)超过这个百分比时,该物体将不会被当作遮挡物。对于封闭的室内场景,墙的内侧是背面,这个设置可以防止室内墙体被当作遮挡物,避免错误剔除。通常保持默认值100即可,意味着只有完全看到背面时才不作为遮挡物。
  3. 执行烘焙(Bake):点击Bake按钮。这个过程可能会很耗时,取决于场景复杂度、Smallest Occluder的设置和烘焙分辨率。烘焙完成后,会在场景文件同级目录生成一个OcclusionCullingData文件。
  4. 可视化与调试(Visualization):烘焙后,在Scene视图左上方的下拉菜单中,选择Occlusion Culling。然后在Visibility面板中,你可以用鼠标拖动预览摄像机,实时看到哪些物体被剔除了(显示为红色线框或完全消失)。这是验证烘焙结果是否正确的最直接方法。

4.2 参数设置经验谈

  • Smallest Occluder:从场景中典型小型遮挡物(如路灯柱、稍大的石头)的尺寸开始尝试。可以先设一个稍大的值(如2.0),烘焙快但可能粗糙。如果发现有些该被挡住的物体没被挡住,再逐步调小这个值。在复杂场景中,从1.0到0.5的调整可能会让烘焙时间成倍增加,需权衡利弊。
  • 烘焙分辨率:在Bake标签页的Settings折叠栏下。更高的分辨率(如Width/Height设为1024或更高)会产生更精确的遮挡数据,但数据量更大,加载可能稍慢。对于大型开放世界,可能需要使用较低的分辨率(如512)来平衡精度和内存。对于小型室内场景,可以使用高分辨率(如1024)来获得像素级精确的剔除。
  • “保守”与“激进”Smallest Occluder调大、Smallest Hole调小,属于“保守”策略,烘焙快、数据小,但可能漏掉一些遮挡机会(该剔除的没剔除)。反过来则是“激进”策略,剔除更积极,但烘焙慢、数据大,且容易发生过度剔除(不该剔除的剔除了)。通常从“保守”开始,逐步向“激进”调整,直到在视觉正确性和性能提升之间找到最佳平衡点。

5. 实战案例:室内场景与开放世界配置对比

理论说再多,不如看实际案例。我们分别看一个室内FPS场景和一个开放世界RPG场景该如何配置。

5.1 案例一:室内FPS场景(如反恐精英地图)

场景特点:空间分割明确(多个房间、走廊),遮挡物多为厚实的墙壁和门,视线转折多,剔除潜力巨大。

配置策略

  1. Static标记
    • 所有墙体、地板、天花板、大型固定家具(书架、柜子):勾选Occluder StaticOccludee Static。它们是完美的遮挡物。
    • 窗户玻璃只勾选Occludee Static。确保玻璃能被墙挡住(比如从隔壁房间看),但它本身不能挡住房间内的物体。
    • 铁丝网、栅栏门只勾选Occludee Static
    • 小型道具(弹药箱、油桶):双勾选。它们在走廊里也能形成有效遮挡。
    • 玩家、可移动道具、门(动画):不勾选任何Static。
  2. 烘焙参数
    • Smallest Occluder: 0.5。室内道具尺寸相对统一且较小,需要较精细的遮挡。
    • Smallest Hole: 0.8。略小于标准门宽(假设1.0),确保角色透过门缝能看到隔壁时,不会因为遮挡剔除而让整个隔壁房间消失。
    • Backface Threshold: 100。室内墙体背面明确,保持默认。
  3. 预期效果:当玩家在一个房间内时,厚实的墙壁会将隔壁所有房间的渲染负载完全剔除,帧率得到显著提升。透过门缝或窗户,能看到隔壁房间的内容,符合预期。

5.2 案例二:开放世界RPG场景(如野外森林与城镇)

场景特点:视野开阔,遮挡物多为山脉、大型建筑群,但也有大量树木、岩石等中型遮挡物。视线遮挡不如室内那么彻底。

配置策略

  1. Static标记
    • 远山、大型山脉、巨型岩石:勾选Occluder StaticOccludee Static。它们是世界级遮挡物。
    • 城镇建筑、城堡:双勾选。建筑群之间互相遮挡效果明显。
    • 树木需要谨慎处理。对于树干粗壮、树冠茂密的树,可以双勾选。对于树叶稀疏、树干很细的树,建议只勾选Occludee Static,避免因其形状复杂和不规则导致剔除错误和烘焙数据膨胀。
    • 小型岩石、灌木丛:通常只勾选Occludee Static。它们数量极多,作为Occluder价值有限且会大幅增加计算量。让它们被大山或建筑遮挡即可。
    • 地面地形:通常作为Occludee Static参与,但它作为Occluder的意义不大(除非是深谷悬崖)。有时为了简化,地形可以不参与Occlusion烘焙,依靠视锥体剔除和LOD。
    • NPC、野生动物、可采集物:不勾选Static。
  2. 烘焙参数
    • Smallest Occluder: 2.0 - 5.0。开放世界尺度大,忽略小物体的遮挡作用,聚焦于山脉、建筑等大型遮挡物,能极大加速烘焙和减少数据量。
    • Smallest Hole: 2.0。对应建筑之间的街道、峡谷等空隙。
    • 考虑使用分块烘焙(Bake Cells):在Occlusion窗口的Object标签下,可以设置View Cell Size。将大世界分割成多个单元格分别烘焙和管理,可以优化运行时数据加载。
  3. 预期效果:当玩家在山谷中时,两侧的山脉能有效剔除山谷外的广大区域。在城镇中,密集的建筑能互相剔除背后的建筑。对于开阔平原,遮挡剔除效果有限,此时应主要依赖视锥体剔除和层次细节(LOD)系统。

6. 常见问题、性能陷阱与调试技巧

即使配置正确,在实际项目中还是会遇到各种问题。这里记录一些典型的坑和解决方法。

6.1 常见问题速查表

问题现象可能原因排查与解决思路
物体闪烁(时隐时现)1. 物体被错误地当作Occluder,但其形状(如栅栏)导致剔除不稳定。
2.Smallest Hole设置过大,小孔洞后的物体被错误剔除,摄像机微动时又出现。
3. 遮挡数据分辨率过低,边界计算不精确。
1. 检查闪烁物体及其附近物体的Static标记,确保镂空/透明物体未标记Occluder。
2. 适当减小Smallest Hole值,或确保有孔洞的物体不被当作Occluder。
3. 尝试提高烘焙分辨率重新烘焙。
该被挡住的物体依然被渲染1. 遮挡物未标记Occluder Static
2. 遮挡物尺寸小于Smallest Occluder设置。
3. 摄像机与物体间存在多个遮挡物,但数据烘焙不连续。
4. 物体本身未标记Occludee Static
1. 确认遮挡物勾选了Occluder Static
2. 测量遮挡物尺寸,调整Smallest Occluder
3. 在Scene视图用Occlusion可视化模式逐步移动摄像机,观察剔除过程,检查遮挡链是否完整。
4. 确认被遮挡物勾选了Occludee Static
烘焙时间过长1.Smallest Occluder设置过小,大量微小物体参与计算。
2. 场景中标记为Occluder的物体过多或过于复杂(高面数)。
3. 烘焙分辨率设置过高。
1. 增大Smallest Occluder,过滤掉小物体。
2. 审查场景,将不适合作为Occluder的物体(小道具、植物)改为仅Occludee。
3. 对于大型场景,尝试降低烘焙分辨率,或使用分块烘焙。
烘焙数据文件巨大同上,原因类似。此外可能使用了过高的Backface Threshold精度。同上。另外检查是否对大量复杂网格(如树木)进行了Occluder标记,考虑用简化的代理碰撞体代替复杂网格进行遮挡烘焙(高级用法)。
移动平台(如Android/iOS)上效果差或出错1. 遮挡数据过于复杂,加载或计算开销大。
2. 可能使用了不支持的烘焙设置或特性。
1. 采用更“保守”的烘焙策略(更大的Smallest Occluder,更低的分辨率)。
2. 确保所有参与烘焙的物体都使用了移动平台支持的Shader和渲染状态。复杂植被、粒子系统等可能不适合参与静态遮挡。

6.2 高级调试技巧

  1. Scene视图可视化:这是最基本的调试工具。除了看物体的显示/隐藏,还可以在Occlusion Culling窗口的Visualization下,开启Show PortalsShow Visibility Lines,能看到摄像机视锥体如何被遮挡体切割,以及可见性的计算路径,对于理解复杂遮挡关系非常有帮助。
  2. Frame Debugger:在游戏运行时,打开Window > Analysis > Frame Debugger。逐步查看每一帧的绘制调用(Draw Calls),你可以清晰地看到哪些物体因为遮挡剔除而被跳过。这是验证运行时剔除是否生效的终极工具。
  3. 脚本查询:可以通过编写简单的调试脚本,在运行时输出某个物体是否被当前摄像机遮挡。使用Renderer.isVisible属性需要注意,它综合了视锥体和遮挡剔除的结果。更精确的遮挡查询可以使用Camera的相关API,但通常可视化工具已足够。
  4. 烘焙代理体(Bake Proxy):对于极其复杂但形状规则的物体(如一棵细节丰富的树),可以创建一个简单的长方体或胶囊体碰撞器,将其标记为Occluder Static,而将原始的高面数树模型标记为Occludee Static。这样,遮挡计算基于简单的代理体,快速且稳定,而渲染的依然是精美的模型。这是一种平衡精度和性能的高级手段。

6.3 性能陷阱提醒

  • 动态物体是盲区:静态遮挡剔除对动态物体无效。一个在墙后跑动的NPC依然会被渲染。对于大量动态物体,需要结合其他技术,如:
    • 动态遮挡剔除插件:如Unity的Entity Occlusion Culling (ECS),或第三方解决方案。
    • 基于距离的LOD与裁剪:对于远处的动态物体,直接根据距离隐藏或简化。
    • 手动管理:对于已知的、移动范围受限的动态物体(如房间内的NPC),可以通过触发器(Trigger)或逻辑判断来手动启用/禁用其渲染器。
  • 过度剔除的代价:过于激进的剔除设置(很小的Smallest Occluder,很大的Smallest Hole)可能导致物体在应该被看到的时候突然“弹出”(Pop-in),破坏游戏体验。这种视觉瑕疵比多渲染几个三角形更糟糕。永远优先保证视觉正确性,再考虑性能优化。
  • 内存与加载时间:复杂的遮挡数据会增加场景的磁盘大小和内存占用,也可能略微增加场景加载时间。对于需要流式加载的开放世界,需要精心设计分块策略和数据压缩。

理解Occluder和Occludee,并正确配置Static标签,是掌握Unity遮挡剔除的基石。它不是一个“勾上就有魔法”的选项,而是一套需要结合具体场景进行思考和调优的精细工具。从区分“挡板”和“演员”开始,到有策略地批量标记,再到耐心调整烘焙参数并反复调试验证,这个过程本身就是一次对场景空间结构和渲染管线的深度理解。别再乱勾Static了,从现在开始,让你的每一次勾选都有的放矢,让遮挡剔除真正成为你项目性能优化的坚实支柱。

← 返回列表