一、合批规则
合批:把渲染时使用相同材质、相同贴图的网格合并在一起,成为一个大网格,然后再调用一次Draw Call,直接渲染这一个大网格。这样做可以降低Draw Call的数量,以优化性能。
Unity是如何确定哪些网格可以进行合批的呢?
- 首先,合批是以Canvas为单位进行的。也就是说在不同Canvas下的UI元素是无法合批的。
- 接下来需要计算Canvas下所有UI元素的深度值。深度值计算规则如下:
- 如果当前UI元素不进行渲染,则Depth = 1
- 如果当前UI元素没有其他UI元素与其相交,则Depth = 0
- 如果当前UI元素与其他UI元素相交,则判断它们能否进行合批(Material ID、Texture ID是否相等),如果满足合批的条件,则它们的Depth相同;如果不满足合批条件,则取下方UI的最大深度值,并在其基础上+1。
- 计算完深度值后,按照「深度值->Material ID->Texture ID->Renderer Order(Hierarchy面板上的顺序)」对UI元素进行排序,剔除深度值为-1的元素,形成一个队列。
- 根据得到的队列,判断相邻元素之间的材质和贴图是否相同,只要相同就可以进行合批。注意这里不会对深度值进行判断,即使相邻元素深度值不同,只要满足上述条件也可以进行合批。
下面我们通过一个具体的示例加深理解
在上图中,白色Image位于所有UI元素的最底层,所以Depth = 0。接下来的文本位于白色Image的上层,所以Depth = 1。这里需要注意,UI元素进行深度判断是通过网格进行的,而不是通过rect。
接下来对红色Image进行判断。因为下层的UI元素有白色的Image和Text两个,所以取它们之间深度值最大的那个。又因为Text和红色Image的材质与贴图都不相同,所以肯定无法进行合批。所以红色Image的Depth为Text的深度值+1,即Depth = 2。
再上层的黄色Image因为与下层的红色Image材质与贴图相同,可以进行合批,所以其深度值与红色Image的深度值相同,即Depth = 2。蓝色Image同理。
接下来就是按照前面的规则对UI元素进行排序,生成排序后的队列,并确定哪些元素可以合批。通过Profiler窗口,我们可以很方便地查看当前场景的合批情况。并且会列出打断合批的原因。
二、遮罩组件
2.1 Mask
UGUI中的Mask组件有两个特点:
- 一个Mask组件除了绘制自身产生一次Draw Call外,还会额外产生两次Draw Call
- Mask会打断外部UI元素与内部UI元素的合批
Mask的额外两次Draw Call
Mask在渲染之前,会遍历所有子元素并计算一次模板缓存。将需要绘制的部分的模板缓冲值设置为1,不需要绘制的部分设置为0。这步操作需要调用一次Draw Call。同时,在执行过程中,Mask会被设置一个特殊的材质。这也是导致其内部元素无法与外部元素合批的原因。
绘制完成后,还需要将模板缓冲值恢复,这就又造成了一次Draw Call。
不同Mask间合批
虽然Mask会导致外部的元素无法与内部的元素进行合批,但由于不同的Mask添加的特殊材质是相同的,所以不同的Mask之间可以进行合批。我们来看下面这个例子
当场景中存在一个Mask时,总的Batches数量为4。当我们再添加另一个Mask,可以发现Batches并没有发生变化
被剔除的元素仍会参与计算
虽然被Mask遮挡的UI元素没有被绘制出来,但它仍然会参与合批的计算。我们尝试将一个Mask拖拽到另一个Mask剔除的UI元素区域。可以看到Batches增加到了7。这是因为被剔除的UI元素参与了合批计算,影响了深度值排序,从而导致无法进行合批。
2.2 RectMask2D
RectMask2D组件是基于自身Rect范围对下方的子元素进行裁剪实现的遮罩效果。这种实现方式有两个优点:
首先,RectMask2D本身不产生任何Draw Call。当场景中只有一个RectMask2D,且自身不挂载Image的情况下,场景中的Batches只有1(摄像机产生的Draw Call)
其次,处于遮罩范围外的子元素会被直接裁剪,不会影响合批计算。我们拖动遮罩下的子元素,可以看到顶点数量和Batches的变化
完全移出Mask范围的UI元素也不会参与合批计算
不同Mask间无法合批
RectMask2D也并不是完全没有缺点,它有个致命的问题是不同的Mask间无法进行合批。下图所示的情况,在Mask内部的UI元素材质、贴图完全相同的情况下仍然存在4次Draw Call。
将内部的UI元素隐藏,可以发现剩余两个Draw Call。这是因为RectMask2D本身挂载的Image是可以进行合批的。
2.3 Mask还是RectMask2D?
由此可见,Mask和RectMask2D各有优劣,并没有谁一定比谁的性能更优这一说,最重要的是要结合应用场景进行取舍。比如当一个场景中需要用到许多个遮罩,且内部的UI元素都是可以进行合批的,那么显然Mask要更适用。而如果一个场景中只需要一个遮罩,那么本身不占用Draw Call且不容易打断合批的RectMask2D要更适合些。
三、参考资料
[1]. https://www.laowangomg.com/?p=488
[2]. https://www.cnblogs.com/moran-amos/p/13878493.html
[3]. https://www.cnblogs.com/moran-amos/p/13883818.html
[4]. https://www.sikiedu.com/course/538