重要概念
- Canvas Batch:Canvas下的UI元素最终都会被Batch到同一个Mesh中,而在Batch前,会根据这些UI元素的材质(通常就是Atlas)以及渲染顺序进行重排,在不改变渲染结果的前提下,尽可能将相同材质的UI元素合并在同一个SubMesh中,从而把DrawCall降到最低。Batch的结果会被缓存复用,知道这个Canvas被标记为dirty.
- Unity官方的重要提示:当给定Canvas上的任何可绘制UI元素发生更改时,Canvas必须重新执行合批过程。此过程重新分析Canvas上的每个可绘制UI元素,不管它是否被修改。注意:更改是指影响UI元素外观的任何变动,包括修改sprite、transform的Position 和Scale、文本网格的text等等。
- Canvas嵌套:Canvas可以嵌套使用,一个子Canvas下dirty的子物体不会触发父Canvas的rebuild
网格重建因素
UI顶点属性变化会引发网格更新
- 修改Image、Text的Color属性会改变顶点的颜色UIVertex.color
- 修改RectTransform的Size、Anchor、Pivot等,会改变顶点的位置UIVertex.position
- 注意:在UGUI中颜色的变化是通过修改顶点色实现的,避免生成了新的DrawCall
- 注意:UIVertex.position记录的是本地空间下的坐标
网格重建流程图:
流程图说明: - 该过程由CanvasUpdateRegistry监听Canvas的WillRenderCanvases上图中的1而执行,主要是对当前标记为dirty的layout和graphic执行rebuild
- 在rebuild layout前会对Layout rebuild queue中的元素依据他们在hierarchy中的层次进行排序(上图2),排序的结果是越靠近根节点越会被优先处理。
- rebuild layout (上图3)主要是执行ILayoutElement和ILayoutController接口中的方法来计算位置,rect的大小等布局信息。
- rebuild graphic(上图4)主要是调用UpdateGeometry重建网格的顶点数据(上图5)以及调用UpdateMaterial更新CanvasRender的材质信息(上图6)
合批原理
UGUI的合批规则是进行重叠检测,然后分层合并。
- 第一步计算每个UI元素的层级号:如果由一个UI元素,它所占的矩形范围内,如果没有任何UI在它的底下那么它的层级号就是0(最底下);如果有一个UI在其底下且该UI可以和它batch,那么它的层级号与它底下的UI层级号一样;如果有一个UI在其底下但是却无法与它batch,那么它的层级号为它底下UI的层级+1;如果有多个UI都在其下面,那么按前两种方式遍历计算所有层级号,其中最大的那个作为自己的层级号。
- 第二步合并相同层级中可以batch的元素作为一个批次,并对批次进行排序:有了层级号之后,unity会将每一次嗯的所有元素进行一排序(按照材质、纹理等信息),合并掉可以batch的元素成为一个批次。经过以上排序,就可以得到一个有序的批次序列了。这个时候Unity会再做一个优化,即如果相邻间的两个批次正好可以batch的话就会进行Batch。合批的batch数据,最后会分别放在CanvasMesh的SubMesh里
UGUI开发过程中常见问题
- 过多的GPU片段着色器使用率(如屏幕填充率过高)
- 过多的CPU时间开销再重建一个画布上
- 过多的CPU时间开销再生成顶点上(通常是文本)
- 过多的画布重建次数
优化策略
- 网格重建优化策略
- 屏幕填充率优化策略
- 合批优化策略
- 字体优化策略
- 滚动视图优化策略
- 其他优化策略
网格重建优化策略
- 使用尽可能少的UI元素:在制作UI时,一定要仔细检查UI层级,删除不必要的UI元素,这样可以减少深度排序的时间以及Rebuild的时间。
- 减少Rebuild的频率:将动态UI元素(频繁改变例如顶点、alpha、坐标、和大小等元素)与静态UI元素分离出来,放在特定的Canvas中。
- 谨慎使用UI元素的SetActive操作:因为他们会触发耗时较高的rebuild
- 谨慎使用Canvas的Pixel Perfect选项
- 谨慎使用Tiled类型的Image
屏幕填充率优化策略(OverDraw)
- 禁用不可见面板:比如当打开一个系统时如果完全挡住了另外一个系统,则可以将被遮挡住的系统面板禁用。(可以通过又该Canvas的Layer隐藏面板)
- 不要使用空的Image做按键响应:可以实现一个只在逻辑上相应Raycast但是不参与绘制的组件
using UnityEngine;
using UnityEngine.UI;
public class Empty4Raycast : MaskableGraphic
{
protected Empty4Raycast()
{
useLegacyMeshGeneration = false;
}
protected override void OnPopulateMesh(VertexHelper toFill)
{
toFill.Clear();
}
}
将以上组件添加至没有Image的Obj上即可
- Polygon Mode Sprites:如果图片边缘有大片留白就会产生很多无用填充。Unity和TexturePacker目前都支持PolygonMode。也就是说将原来的举行Sprite用更加紧致的Polygon来描述。
合批策略优化(DrawCall)
- 相同层级原则:父节点下所有子节点,精良保持相同的层次结构。相同层级下的UI元素可以Batch.
- Mask组件:mask组件使用的模板缓存,mask中的UI元素无法与外界UI元素合批,Mash组件还会额外增加2个DrawCall
- 影藏的Image:Image组件中的sprite为空,都是占用DrawCalll渲染的,并且还会打断前后元素的合批。
- Screen Space-Camera模式:一个Canvas中的任何一个元素只要在屏幕中,则这个Canvas中的其他元素鸡是再屏幕外DrawCall任然不会减少 也就是说仍然会绘制。
- Hierarchy穿插重叠问题:不同图集图片尽量不要重合覆盖 会打乱合批。
字体优化策略
- Text的网格重建:Text组件被重新启用的时候,会重建Text的网格。如果含有大量文字,会造成严重的CPU开销。
- 提前生成动态字体
- 使用美术数字:游戏的分数减血等可以使用美术数字(精灵图片)来代替Text组件。
- 谨慎使用Text的Best Fit选项
- 减少长文本Tex的变动,慎用UI/Effect:描边和阴影效果都会增大斯贝德定点数。
其它优化策略
- 禁用无用的Raycast:UGUI的touch处理消耗也可能成为性能热点。因为UGUI在默认情况下会对所有可见的Graphic组件调用Raycast.对于不需要接受touch时间的Graphic一定要禁用raycast
- OverrideSorting:子Canvas中的OverrideSorting属性将会造成Graphic Raycast测试停止遍历Transform层级。
- UI对象的坐标Z值:Z值不为零的时候会影响对象渲染顺序并不能合批
- CanvasGroup的使用
优化工具
- Profiler
- FrameDebug