合批:把渲染时使用相同材质、相同贴图的网格合并在一起,成为一个大网格,然后再调用一次Draw Call,直接渲染这一个大网格。这样做可以降低Draw Call的数量,以优化性能。
Unity是如何确定哪些网格可以进行合批的呢?
下面我们通过一个具体的示例加深理解
在上图中,白色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窗口,我们可以很方便地查看当前场景的合批情况。并且会列出打断合批的原因。
UGUI中的Mask组件有两个特点:
Mask在渲染之前,会遍历所有子元素并计算一次模板缓存。将需要绘制的部分的模板缓冲值设置为1,不需要绘制的部分设置为0。这步操作需要调用一次Draw Call。同时,在执行过程中,Mask会被设置一个特殊的材质。这也是导致其内部元素无法与外部元素合批的原因。
绘制完成后,还需要将模板缓冲值恢复,这就又造成了一次Draw Call。
虽然Mask会导致外部的元素无法与内部的元素进行合批,但由于不同的Mask添加的特殊材质是相同的,所以不同的Mask之间可以进行合批。我们来看下面这个例子
当场景中存在一个Mask时,总的Batches数量为4。当我们再添加另一个Mask,可以发现Batches并没有发生变化
虽然被Mask遮挡的UI元素没有被绘制出来,但它仍然会参与合批的计算。我们尝试将一个Mask拖拽到另一个Mask剔除的UI元素区域。可以看到Batches增加到了7。这是因为被剔除的UI元素参与了合批计算,影响了深度值排序,从而导致无法进行合批。
RectMask2D组件是基于自身Rect范围对下方的子元素进行裁剪实现的遮罩效果。这种实现方式有两个优点:
首先,RectMask2D本身不产生任何Draw Call。当场景中只有一个RectMask2D,且自身不挂载Image的情况下,场景中的Batches只有1(摄像机产生的Draw Call)
其次,处于遮罩范围外的子元素会被直接裁剪,不会影响合批计算。我们拖动遮罩下的子元素,可以看到顶点数量和Batches的变化
完全移出Mask范围的UI元素也不会参与合批计算
RectMask2D也并不是完全没有缺点,它有个致命的问题是不同的Mask间无法进行合批。下图所示的情况,在Mask内部的UI元素材质、贴图完全相同的情况下仍然存在4次Draw Call。
将内部的UI元素隐藏,可以发现剩余两个Draw Call。这是因为RectMask2D本身挂载的Image是可以进行合批的。
由此可见,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