文章目录
剔除
提升代码效率的最佳办法,就是不执行它。提升渲染效率最佳的办法,就是不渲染它。
所以Unity提供了多种将对象从渲染列表中剔除的方法。
视锥体frustum裁剪
利用场景管理算法(2D用4叉树,3D用8叉树),快速收集在摄像机视锥体内的对象,作为潜在可见集。该算法的时间开销比较大,但是无法避免。但是,如果我们在场景中有镜子,水面等,希望反射的对象,则需要为这些对象建立反射摄像机,也要进行昂贵的裁剪。当然为了解决这个问题,Unity为我们提供了Reflection Probe来离线计算静态反射数据。
视锥体裁剪是自动的,不需要我们参与。
遮挡剔除
对于进入摄像机视锥体的对象,不一定能在视口看到它,比如一堵墙后的雕像,即使雕像在视锥体内,最终也会被墙挡住,所以渲染它是浪费。Unity提供了遮挡剔除,来解决该问题。将场景拆分成许多小的单元格子,一种用来表示渲染格子(Target Cells),用来容纳可渲染对象,一种表示摄像机格子(View Cells),用来定义摄像机可能到达的区域,然后离线的,建立View Cells和Target Cells之间的关系,将每个View Cell可见的 Target Cells存储起来,运行时直接查询收集可渲染对象。
摄像机距离剔除
摄像机距离剔除,是为每个Layer定义摄像机可见距离,渲染每个Layer的对象时,查询摄像机针对该Layer的渲染距离,超出该距离,就不渲染了。
该方式适用于场景中的装饰物体,如地上的小石头,草,各种Decoration对象。
启用该功能很简单,为摄像机设置相关值就行了:
Camera camera = GetComponent<Camera>();
float[] distances = new float[32];
distances[LayerMask.NameToLayer("Npc")] = 11; //每一层的剔除距离
camera.layerCullDistances = distances;
//好处:在相机转动时,不会影响哪些对象可见或不可见,也就是不会转动中,有些原来显示的对象被隐藏
camera.layerCullSpherical = true;
遮挡剔除 Occlusion Culling
遮挡剔除用于在渲染时,剔除对被其它对象遮挡,摄像机无法观察到的对象的渲染。由于很多时候,距离摄像机远的对象先被渲染,近的后渲染时就会在远的上渲染,这导致一个像素被绘制多次,而且之前的很多次,实际上都是不可见的,这就叫overdraw。
下图展示了没有frustum裁剪时,渲染了所有的对象:
第二幅图展示了视锥体裁剪后,被渲染的对象少了很多:
第三幅图,使用遮挡剔除后,只有被摄像机看到的对象渲染,少了很多
遮挡剔除的处理过程,是用一个虚拟的摄像机遍历场景,建立潜在可见对象集合的层级关系。运行时摄像机利用这些数据标识哪些对象可见,哪些不可见。利用这些数据,Unity可以确保仅将可见的对象送入渲染管线,降低DrawCall,提升效率。
遮挡数据由一些单元格组成,每个单元格是整个场景的包围体的细分,这些单元格构建成一颗二叉树。遮挡剔除用了2棵树,一棵是View Cells(静态对象),和一棵Target Cells(移动对象)。View Cells映射到定义可见静态对象的索引列表,从而为静态对象提供更精确的筛选结果。
创建对象时,需要在对象的尺寸和Cell的尺寸间取得一个好的平衡点。不能定义相对于对象太小的Cell,也不应该创建覆盖太多Cells的对象。有时,将一些大的对象拆分成小对象,可以提升裁剪效果。同样的,可以将一些小的在同一个Cell里的对象合并来降低DrawCall。
可以在Scene视图中选择"OverDraw"渲染模式来查看OverDraw,在Game视图的Stats信息面板中查看总的渲染三角形数量,以及渲染批次。下面2张图,对比了是否开启遮挡剔除的效果及数据
该图未开启遮挡剔除,注意Scene视图中的OverDraw,墙后面的大量的房间的渲染,造成了较高的OverDraw,这些房间在游戏视图中是看不到的。
应用遮挡剔除后,远处的房间不再渲染,OverDraw也低了很多。绘制的三角形数量和渲染批次也显著降低了。
建立遮挡剔除
首先,将关卡打散到合适的尺寸,这样有利于高效地被那些大的对象遮挡住,以剔除,如墙,大的建筑。例如如果一个房间里有很多家具,这些家具应该各自进行剔除,而不是合并到一起,虽然合并到一起降低了drawcall。
为了让对象参与遮挡剔除,需要将对象的tag设置为Occluder Static或者Occludee Static。可以选择多个对象一次进行设置。
Occludee Static:那些小的,会被其它对象挡住的对象,设置为 Occludee Static。
Occluder Static:大的物体,会遮挡其它对象的对象,设置为 Occluder Static 。
当使用了LOD group时,只有LOD0的对象会被当做遮挡对象。
遮挡剔除窗口
从 Window>Rendering>Occlusion Culling 打开遮挡剔除窗口。
在该窗口,可以处理遮挡Mesh和遮挡区域。
在Object面板,如果在场景里选择了Mesh,可以修改它们的遮挡类型:遮挡或者被遮挡,或者2者都设置
如果时选择了遮挡区域(Occlusion Area),则可以设置遮挡区域属性
注意:如果没有创建裁剪区域,那么裁剪将会被应用到整个场景。
注意:一旦摄像机不在裁剪区域内,遮挡剔除将会失效,所以设置遮挡区域完全覆盖摄像机的可达区域非常重要。但是过大的裁剪区域也需要更长的数据烘焙时间。
遮挡剔除烘焙 - bake
set default parameters 按钮,重置参数为Unity默认参数,以适应大多数情况。当然可以调整参数,适应我们的场景。
参数 | 含义 |
---|---|
Smallest Occluder | 执行遮挡剔除时,会遮挡其他对象的对象的最小尺寸。小于改尺寸将不会遮挡其它对象。选择一个合适的值来平衡裁剪精度和裁剪数据尺寸。 |
Smallest Hole | 该值表示摄像机视线能穿过的,2个几何体之间的最小距离。该值表示能通过该孔的对象的直径。 |
Backface Threshold | Unity通过背面测试来减少不必要的细节,来优化遮挡数据。默认值100非常简单地,不从数据集合中移除背面。将参数设置为5,可以根据可见背面的位置,大大降低数据量。例如,当摄像机在地形下面,或者实体对象内部,这些摄像机不会到达的区域。距离背面大于该参数的位置将不会生成遮挡数据。 |
Occlusion Culling - Visualization
窗口最下方,时Clear和Bake按钮。点击Bake按钮,开始生成遮挡数据。生成后,可以切到 Visualization面板,预览测试遮挡剔除。如果觉得结果不满意,点击Clear按钮,清除数据,然后调整参数,再重新烘焙。
场景中的所有对象都会影响包围体的大小,所以要尽量让它们都在场景的可见范围内。
烘焙完成后,可以看到蓝色的立方体,每个立方体是一个裁剪区域。
Occlusion Area 裁剪区域
为了对移动对象进行遮挡,需要创建 Occlusion Area ,并修改它的尺寸来适应移动对象能到达的区域。可以为一个空的对象添加 Occlusion Area 组件来创建。
创建完后,选择 Is View Volume 来遮挡移动对象。(移动对象与摄像机不在同一个遮挡区域内,不可见?)
创建完遮挡区域后,需要查看是如何将盒子拆分到Cells中的。要看遮挡区域是如何计算的,在 Occlusion Culling Preview Panel 中,选择 Edit,并选中View Volumes复选框。
测试效果
设置好遮挡后,可以在 Occlusion Culling Preview Panel 的 Visualize mode 中,选中 Occlusion Culling,在Scene窗口中移动摄像机来进行测试。
当移动摄像机时(无论是否在play模式),会看到一些对象被禁掉了。在这里,可以查找遮挡数据的错误。当你移动摄像机时,看到一个对象突然跳出来,说明有错误发生。当发生这种情况,可以改变分辨率,或者调整对象位置来解决。可以将摄像机移动到出问题的对象处,进一步检查问题。
数据生成后,视图中会有一些带颜色的立方体。蓝色的表示Target Volume(被遮挡对象)的Cell划分,白色的是View Volume(摄像机位置包围框)的Cell划分。如果参数设置正确,可以看到一些对象不被渲染,可能不在视锥体内,也可能是被遮挡剔除掉了。
如果你发现场景中没有任何对象被遮挡,那么打碎你的对象,让这些碎片完全包含在一个Cell内。