[Unity] UGUI 性能优化简述

1.UGUI合批

为什么要合批

合批就是将多个DrawCall合并为同一个DrawCall并放入命令缓冲区中,每当CPU调用DrawCall时需要从用户模式切换到内核模式,并且需要调用DX\GL的驱动接口,该操作耗时较长,我们可以通过合批来解决这个问题,自Unity5.2以后,对于UGUI的合批底层C++代码改为多线程操作,当前移动端CPU基本都是多核,各个Driver接口优化也越来越好,所以也有些人说UI动静分离对性能提升已经没有那么明显

合批原理

合批以Canvas为单位,并在子线程执行,基本条件为Canvas下的两个UI材质贴图相同,但并不是材质贴图相同就一定会产生合批

在Canvas下,引擎会先计算UI的Depth(注:不是Image的那个)

Depth计算规则:

按照从上至下的顺序依次遍历Canvas中的所有元素 (int i = 0; i < Canvas.ChildCount, ++i)

如果Child[i]不渲染,Child[i].Depth = -1

如果Child[i]渲染,且Child[i - 1]没有和Child[i]的Mesh重叠或者Count == 1,Child[i].Depth = 0

如果Child[i]渲染,在Count == 2的情况下Child[i - 1]和Child[i]的Mesh重叠,判断两个UI的材质贴图是否完全一致,一致则Child[i].Depth = Child[i - 1].Depth,不一致则Child[i].Depth = Child[i - 1].Depth+1

如果Child[i]渲染,在Count > 2的情况下有多个UI元素和当前UI的Mesh重叠,那么Child[i]的Depth等于所有重叠UI的Depth中最大的Depth

在得出Canvas下所有UI元素的Depth后,将UI元素根据Depth从小到大进行排序

排序完成后再根据这些UI元素的Material ID进行从小到大排序

排序完成后再根据这些UI元素的Texture ID进行从小到大排序

排序完成后再根据这些UI元素在Hierarchy中的顺序排序

即Depth相同则Material ID小的元素排前面

Depth,Material ID都相同那么Texture ID小的元素排前面

如果Depth,Material ID,Texture ID都相同,那么在Hierarchy中谁在前面谁就排前面

那么我们现在得到了经过乱七八糟计算后的一个List,在这个List中,相邻的元素如果材质和贴图相同,那么就可以进行合批

至此我们可以得出一个结论,当一个Canvas下的节点越多,合批需要的时间就越多

Rebatch

我们已经了解了合批(Batch)的原理,每当Canvas下的元素Mesh发生改变时,Unity就会帮助我们重新进行合批,也就是Rebatch,会造成Rebatch的操作有:SetActive,改变UI位置,改变UI颜色,改变文本内容,改变UI材质,改变UI贴图等

Rebatch触发条件

1.所有会让Mesh发生改变的操作

2.改变材质贴图

3.Rebuild

Rebuild

我们之前的合批都是在Unity底层通过C++代码完成的,而Rebuild是在UGUI的C#层实现的,在某些操作下,UGUI会将组件打上脏标记,在渲染Canvas的前一帧,会去判断是否有UI被打上了脏标记,如果是顶点脏标记就会重建网格,材质脏标记就会重建材质,在重建后,Unity会重新进行合批,即我们做了某些操作触发了C#层的Rebuild,Rebuild后Unity底层再进行Rebatch

Rebuild触发条件

Text: 文本内容改变,设置是否支持富文本,更改换行模式,设置字体最大最小值,变更文本使用的对齐锚点,设置是否通过几何对齐,变更字体大小,变更是否支持水平及垂直意出,修改行间距,变更字体样式如斜体,加粗

Image:颜色变化,变更显示类型如Simple,Sliced,变更是否应该保留Sprite宽高比,FillCenter属性变更,变更填充方式,变更图像填充率,变更图像顺逆时针填充类型,变成填充过程的原点

RawImage:设置Texture,变更纹理使用的UVRect

Shadow:改变距离,颜色,变更useGraphicAlpha

Mash:调用showMaskGraphic,enable发生变化

所有继承MaskableGraphic的控件:设置此图型是否允许被遮盖,enable发生变化,父节点发生变化,在Hierachy面板上发生变化

UI材质发生变化 贴图发生变化

还有很多,基本上修改了UI就会产生Rebuild,Rebuild后底层就会Rebatch

动静分离

既然我们已经知道每当修改UI就会触发Rebuild和Rebatch,Unity底层就会进行合批操作,而合批操作消耗的性能和Canvas下的节点数量是线性增长的,那么我们就需要使用动静分离来进行一个UI优化,

即将不会进行频繁修改的UI放入一个Canvas,会频繁修改的UI放入另一个Canvas,这样就减少了Canvas下的节点数量,也就减少了合批算法的时间

2.图集

静态图集

前文我们得知Unity的合批基本规则为两个UI元素要有相同的材质和相同的贴图,在实际的UI界面中,我们的一些UI比如技能图标之类的往往具有相同的材质,却是不同的贴图,那这样每一个Image都会产生一个DrawCall,我们可以将这些技能图标的贴图打进同一张图集中,这样它们的Texture ID就为相同,也就可以一次DrawCall就解决他们了

动态图集

在前文的静态图集中,我们提到的例子为技能图片,在像MOBA类游戏中,每一轮游戏玩家选择的英雄不同,技能图标也不同,我们不可能提前将所有技能图标都打进一张图集里,图集过大会占用更多内存,在游戏准备阶段,我们可以将这一轮游戏的玩家技能图标,角色头像,动态的打进一张图集中,具体实现这里不过多赘述

图集策略

一张图集最好不要超过1024*1024

尽量不要留有空白

将公用UI打入一张图集中

将一个功能的UI打入一张图集中

3.UI预加载

在加载某些节点数量多的UI时,需要将UI数据存入内存,进行合批计算,在游戏中,如果等待玩家操作后再进行加载UI的话,可能会导致卡顿,解决方案为,在游戏初始化阶段,就将此类UI先加载至内存中

4.显示与隐藏

在游戏中,经常需要用到显示隐藏UI的时候,大部分人会使用SetActive来进行此操作,结合前文我们已知晓,在SetActive时会触发Unity合批,并且对于Text控件来说,SetActive会触发它的OnEnable,在Text的OnEnable方法中会将文本的网格信息存储在内存中,文本信息越多,内存占用越大,产生GC

解决方案:

禁用Canvas 缺点:依然有SendWillRenderCanvases的开销

移出摄像机视锥体 缺点:依然有CPU裁剪的消耗

设置Canvas Group的alpha为0 缺点:依然有DrawCall,但不会Rebatch

设置Scale为0 缺点:依然有DrawCall,依然Rebatch

设置一个Canvas为父节点,将其Layer设置为相机屏蔽等级,同时禁用GraphicsRaycaster,当需要隐藏某个界面时将其移动到该Canvas下 缺点:有SetParent的消耗 [推荐]

canvaseRender.cull 缺点:只能在隐藏某个Graphic类的情况下使用 [推荐]

注意在使用这些方法隐藏UI后,UI的update依然会执行

写累了,未完待续

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值