很多童鞋的Unity作品完成后,发现场景卡的要死,尽管在模型阶段采用了很多优化建模方法,但还是卡顿。电脑端都这么卡,发布到移动端就更不用说了。遇到这种情况,很多童鞋急得团团转,不知如何是好。此时,就必须对场景进行深度优化,当然了,Unity深度优化的方法很多,本文将和大家一起探讨Unity3d最重要也是最有效的场景优化方法-遮挡剔除(Occlusion culling),希望对遇到场景卡顿的童鞋有所帮助。当然,对于机器配置高的童鞋,虽然暂时没遇到卡顿,可别忘了,你的作品最终不是在你自己的机器上运行,而是要在配置不高的用户机或移动端运行的,所以,场景的深度优化是每个人绕不过的一道坎。
那么什么是“遮挡剔除Occlusion culling”呢?通俗地讲,就是将距离相机当前位置最近的一组非透明物体完全遮挡(看不到)的物体,动态地禁用渲染(禁用渲染一些物体,游戏视图的渲染负担就会减轻,作品跑起来就会流畅一些),这就是“遮挡剔除”。简单地说,就是把被遮挡住的物体剔除掉。都被挡住看不见了,还不剔掉,留着这些物体,除了因增加系统渲染负担而导致场景卡顿外,没有任何意义。这只是对遮挡剔除的文字描述,为了让童鞋们直观地理解,下面我们用图解诠释。
在认识“遮挡剔除”之前,得先了解什么是“视锥剔除( Frustum Culling)”。大家都知道,摄相机视野的空间形态是一个四棱锥,Unity称之为“视锥(Frustum)”,默认情况下,Unity会和人的眼睛一样,自动将视锥以外的物体动态地禁止渲染,也就是只渲染视锥以内的物体,这就是“视锥剔除”。一般情况下我们是看不到“视锥剔除”现象的,但“视锥剔除”在运行时会自动启用。
下图左上角几条线封围的部分就是相机的视锥(俯视),因为未使用遮挡剔除,所以场景中的所有物体均可见。
下图为添加遮挡剔除并使用其可视化预览功能的情况。在右下角弹出的可视化预览面板中,激活“Camera Volumes(相机体积)”,取消“Occlusion Culling(遮挡剔除)”复选框的勾选,目的是先禁用遮挡剔除,只预览“视锥剔除”效果。说到这里,有些童鞋肯定有些晕,不是说添加了遮挡剔除,怎么又禁用了呢?因为是想先给大家直观地展示一下视锥剔除的效果,可一般情况下大家是看不到的,而遮挡剔除使用后,视锥剔除和遮挡剔除的效果都可以用可视化预览,并且取消预览面板中的“Occlusion Culling”复选,所预览到的就只是视锥剔除的效果了,这下明白了吧?由下图可见,视锥剔除后,只有视锥以内的物体可见,视锥以外的物体都被剔除掉了。对比上一张图和下图右下角游戏视图的预览,我们发现视锥剔除后,游戏视图中所看到的内容没有任何变化,这也就是说,视锥剔除不会改变游戏视图的视觉内容,但是由于剔除了视锥以外的内容,游戏视图相机的渲染量被大幅降低,如果不剔除,运行时游戏视图的相机会因渲染负担沉重而卡顿。视锥剔除是Unity3d的特性,无需特别设定,运行时会自动启用。说到这里,一定有童鞋说,既然这是Unity3d特性,那你费什么话,啰里啰嗦说半天视锥剔除?因为搞清楚了视锥剔除的原理,对于理解遮挡剔除是非常有帮助的。
好了,接下来我们就来讨论遮挡剔除。前面我们取消了预览面板中的“Occlusion Culling”复选,现在我们勾选这一项(如下图所示),也就是在Scene视图中除了预览视锥剔除的效果,也预览遮挡剔除的效果。勾选后,由下图可见,刚才视锥内保留的好多物体又消失了,这是因为相机处于室内,室内墙不透明,视锥内的室外物体都被室内墙挡住看不见,使用遮挡剔除后,那些物体也被剔除掉了,所以不可见了,但是对比下图右下角游戏视图的内容和前面没有任何剔除以及视锥剔除后游戏视图的内容,完全一样,也就是说,遮挡剔除后,也不会改变游戏视图的内容,但是在视锥剔除的基础上,又进一步大幅度降低了相机的渲染量,运行时作品会更加流畅,因为被剔除物体的几何、材质、光照贴图、粒子系统等都不再被渲染了。
通过上面的静帧图解,大家对视锥剔除和遮挡剔除有了形象的理解,但是运行时,相机的位置和方位角是动态变化的,所以被剔除和被保留的物体也是动态变化的。前面为了简化问题,我用了俯视视角图解了视锥剔除和遮挡剔除,下面我们用动态GIF图像来看看实际遮挡剔除和视锥剔除,由于这里对图像大小有限制,所以GIF只能截取很短的片段,能说明问题就好。
下图左侧是游戏视图,右侧是Scene视图,从Scene视图可见,应用遮挡剔除后,当相机移动时,很多物体被视锥和因遮挡被动态地剔除了,但是游戏视图始终是完整的,根本看不到哪些物体被剔除。这里说明一下,右侧视图当中地面深色的东西是地面的光照贴图,不是物体,剔除后保留的物体只有相机前方很少的一部分,场景的全貌如第一帧所示,大家可以仔细观察右侧视图中保留物体的动态变化。
由于上图右侧视图太小,下面我们拉近并手工拖动相机观察一下遮挡剔除和视锥剔除的综合效果(如下图所示)
到这里,相信大家对什么是遮挡剔除和视锥剔除有了直观的认识和理解,需要说明的是,如果将整个场景合并为一个物体,遮挡剔除和视锥剔除没有任何效果,为什么呢?因为视锥剔除是根据物体的最大外轮廓(或者说是物体的边界框)是否在视锥以内来确定是否被剔除,如果合并成一个物体,相机怎么移动,物体的边界框都在视锥以内。对于遮挡剔除来说,都合并成一个物体了,哪还有被遮挡的物体呢?所以要通过遮挡剔除和视锥剔除优化场景,严禁将所有物体合并为一个物体,除非场景规模非常小。需要说明的是,这只是剔除静态物体的例子,对于动态物体的遮挡剔除,后面再说。
遮挡剔除的过程是使用一个虚拟相机构建潜在可见物体集合的层级结构数据,好让每个摄像机在运行时使用这个数据来识别哪些物体可见,哪些物体不可见。 有了这些数据,Unity的相机将只对可见物体渲染,不仅降低了绘制调用(draw calls)的次数,并可提高游戏的性能。
遮挡剔除的数据由许多单元格(前面GIF图像中Scene视图中的蓝色3d线框)组成,每个单元格是整个场景边界体积的细分,具体地说就是这些单元格构成了一个二叉树结构。遮挡剔除使用了两个树结构,一个用于视图单元(静态物体),另一个用于目标单元(动态物体象)。 所有视图单元映射到定义可见静态物体的索引列表,从而为静态对象提供更准确的剔除效果。
在建模时要记住很重要的一点,即要考虑在物体大小和遮挡剔除的单元格大小之间求得最佳平衡。理想情况下,不应该有比物体小太多的单元格,同样地,也不应该让物体的尺度比单元格大到跨越了许多单元格。可以考虑在建模软件中将大的物体分解成较小的多个物体来改善剔除,但有时也需要考虑将小的物体合并在一起以减少绘制调用(draw calls),并且只要它们都属于同一个单元格,就不会影响遮挡剔除。
可以使用场景视图的“overdraw(绘制重叠)”模式(如下图所示),定性地来查看绘制重叠(之所以发生绘制重叠,是因为物体出现了相互遮挡)的情况,也就是查看场景中物体的相互遮挡情况。
我们还是以前面的场景为例,将Scene视图由Shader改为overdraw模式,如下图所示,这是使用未遮挡剔除的场景。overdraw模式用来查看场景中物体的遮挡情况,由下面中间图可见,矩形框所标注的区域亮度很高,说明这个区域物体重叠严重,也就是被遮挡的物体很多。另外,可通过游戏视图的状态信息面板,定量地查看游戏场景的状态,如下面右图右上角所示。从状态信息可见没有遮挡剔除时,批次数量(Batches)为296(这个值越高,越卡顿,2017版之前用Drawcall来表示),三角面约42万,顶点数约62万。
下图是使用遮挡剔除后的情况。由下面中间图可见,矩形框所标注的区域亮度明显降低,说明这个区域物体重叠已经消除,也就是被遮挡的物体已经被剔除。由下面右图可见,批次数量降至62,三角面降至约15万,顶点数降至约21万。这里说明一下,批次数量(batches)的值是游戏是否运行流畅的重要标志,场景优化的目标就是降低批次数量、三角形数量、顶点数量,这些值越低,作品运行越流畅,否则会很卡顿。
由上面的图解再一次说明,遮挡剔除对于优化场景性能非常重要,也非常有效。
遮挡剔除对模型的要求:要使用遮挡剔除,模型必须在建模软件中被分解成合理尺寸的大小,以适应unity中遮挡剔除的单元格细分尺寸。在建模软件中尽可能把模型分解成比较小的几何体,且被比较大的物体(例如墙壁,建筑物等)遮挡的布局对于unity中的遮挡剔除优化是很有帮助的。
遮挡剔除的一般设置:在检视面板中,将要参与遮挡剔除计算的静态物体标记为Occluder Static(静态遮挡物)或Occludee Static(静态被遮挡物),如下图所示。Occluder是遮挡物,也就是遮挡其他物体的物体,Occludee是被遮挡物,也就是被其他物体遮挡的物体。有的物体需要同时勾选Occluder Static和Occludee Static,也就是这个物体既遮挡别的物体,也会被别的物体遮挡。有的物体只需勾选Occluder Static,例如比较大的物体,它是永远不会被其他物体遮挡。而有的物体只需勾选Occludee Static,例如比较小的物体,因为它遮挡其他物体的可能性很小,单独勾选Occluder Static或Occludee Static,可减少遮挡剔除的计算量,即可加快遮挡剔除的烘焙。如果将场景中比较大且不可能被其他物体遮挡的物体设置Occludee Static,或者将比较小且不可能遮挡别的物体的物体设为Occluder Static,除了浪费烘焙时间和增大遮挡剔除数据文件外,毫无意义,所以根据物体的实际情况,合理地对其进行遮挡剔除设置,对加快遮挡剔除烘焙和优化遮挡剔除数据非常重要。另外补充一点,很多人在烘焙场景时,按照官方的要求,将要烘焙的静态物体直接设置为“Static(静态)“,也就是将检视面板右上角的“Static”复选框直接勾选,可当展开“Static”后面的下拉菜单的三角符时,发现后面有一堆这静态那静态的选项,如果直接勾选Static,就等于把这个下拉菜单里的所有选项(除Noting外)都勾选了,我认为,在烘焙场景时,不要直接勾选“Static”,而只需勾选“Lightmap Static”,全部勾上会给烘焙增加多余的计算负担和时间,做什么计算,就勾什么选项,全部勾上,进行遮挡剔除计算时不分青红皂白,会浪费很多时间,原因前面已经说过了,这里再次提醒一下。
这里特别说明一下,当使用LOD Group优化时,只有LOD0可作为遮挡物。
设置物体的Occluder Static和Occludee Static,也可以在遮挡剔除面板中完成,即单击打开菜单Window > Rendering > Occlusion Culling,在弹出的面板(如下图所示)中选择“Object”,并且在场景中选择“网格渲染器(也就是场景中的物体)”,遮挡剔除面板下方即会出现Occluder Static和Occludee Static两个复选框,在这里勾选,和前面设置物体的Occluder Static和Occludee Static属性异曲同工。遮挡剔除面板各按钮的功能见下图。
注意一点,只有相机在遮挡区域里面才会生效