记录最全面的ugui优化策略

UI 资源规范(内存优化)

客户端做任何的性能优化首先想到的都是规范美术资源,前期不给美术资源定制一定的规范,后期做优化性能会非常的被动。对于 UI 资源的规范,主要是考虑的内存优化。
1.任何的 UI 图集最大 size 1024*1024(内存优化);
2.同一个界面出现的 UI 资源尽量放到一个图集,重复利用的公用资源放common(DrawCall 优化);
3.能用九宫格的尽量用九宫格来减小原图大小(内存优化);
4.美术给过来的 UI 原图 size 尽量小,对于一些全屏的 loading 原画图,原画大小是 1136*640(我们 UI 的标准分辨率),让美术按照比例高度缩小到 500,这样一张 1024*1024 的图集就可以放两张原图了,提升图集利用率。对于一些600*400 类似大小的原图,就尽量按比例把最长边压小到 500,这样出来的图集就是 512*512 而不是 1024*1024(内存优化);
5.对于特别长条的 UI 原图,例如 1000*100,如果由于加入这个长条的原图导致图集大小变大而且利用率很低的话,要把 1000*100 的原图拆分成两张图500*100,在制作界面的时候用两个 Image 拼接即可,这样可以把 1024 的图集缩小到 512(内存优化);
6.图集利用率低于 1/3 的时候,要考虑和其他同一个 size 的图集合并以提升利用率。合并的原则是不改变任何一个图集的大小,这样即可完全省掉一张图集(内存优化、安装包量优化);
7.尽量复用 UI 资源,减少不必要的原图,例如一个卡牌分了五种品质原画底图,白蓝黄绿紫,就不要使用五张大底图了,让美术同事画一个灰色原图,Image 在使用的时候直接按需求修改顶点色即可(内存优化);
8.关闭 mipmaps(内存优化)。

 

GPU 优化

谈及 GPU,我们知道它负责整个渲染流水线,从处理 CPU 传递过来的模型数据开始,进行 Vertex Shader、Fragment Shader 等一系列工作,最后输出屏幕上的每个像素。因此我们可以从两个方面着手,优化 Shader、减少 OverDraw。

Shader 优化

    我们的 UI Shader 是自己编写的,没有使用默认的 UI-Default.shader,因此Shader 优化非常有必要。
1.Fog { Mode Off },最早有一个版本我们没有关闭 Fog,导致打开全屏 UI 的时候帧率明显往下掉(我们的全屏 UI 开启后会关闭主相机,理论上渲染压力和CPU 消耗会降低),后来 Adreno Profiler 真机调式 Shader 代码后发现了此问题;
2.Fragment 剔除掉 Alpha 为 0 的像素点,减少 OverDraw;
3.尽量简化 Shader 代码,最早我们有一些 UI 效果的需求全都写在了一个Shader 里,其实 99%的 UI 不要这些东西,单独把效果需求代码拎出来独立一个 Shader。

OverDraw 优化

    在每帧绘制中,如果一个像素被反复绘制的次数越多,那么它占用的资源也必然更多。目前在移动设备上,OverDraw 的压力主要来自半透明物体。因为多数情况下,半透明物体需要开启 Alpha Blend 且关闭 ZWrite,同时如果我们绘制像 alpha=0 这种实际上不会产生效果的颜色上去,也同样有 Blend 操作,这是一种极大的浪费。

    我们的 UI 绘制是 Alpha Blend 且关闭 ZWrite,因此 UI OverDraw 的优化主要是在制作界面的时候减少 UI 重叠层级(和策划、美术 pk)。除此之外还是有一些我们程序可以控制的优化点:
1.对于九宫格的 Image,如果去掉 fillcenter 不影响最后出来的效果就要把fillcenter 去掉,可以减少中间一片的像素绘制;
2.看不见的元素且没有逻辑功能要 disable 或者挪出裁剪区域,而不要通过设置Alpha=0 来隐藏;
3.不要使用一张 Alpha=0 的 Image 来实现放大响应区域的功能;
4.UI 底层系统来控制隐藏看不见的元素,例如打开全屏 UI 的时候把下面看不见的 UI 挪出裁减区域、关闭主相机渲染。

 

CPU 优化

    在我们项目开发的过程中,遇到的大部分 UI 相关性能热点都是和 CPU 消耗相关的。大致可以分为以下几类:DrawCall、Canvas.SendWillRenderCanvases()、Canvas.BuildBatch()和其他。

DrawCall 优化

    DrawCall 是 CPU 调用底层图形接口,频繁的调用对 CPU 性能的影响是很明显的。优化思路很简单,合批绘制。UGUI 本身的动态合批机制会帮我们尽量的去优化合批,我们要做的就是弄清楚它的合批机制然后让 UI 元素尽量合批绘制。以下是我们项目组在制作 UI 过程中总结出来了的一些优化 Tips:
1.合理分配图集,同一个界面上的图尽量打到一个图集,多个界面复用的图,放到 common;
2.制作界面的时候,相邻节点尽量使用同一个图集的图片;
3.Text 本身也是用的 Font Texture,不同字体的 Text 也是来自不同的图集,所以在布局界面的时候也要尽量避免穿插打断绘制流程;
4.DrawCall 的数量不是完全由 Hierarchy 的布局决定,和 UI 的位置也有关系,这个位置不是指的 Rectranform 上面的 size 位置重叠就一定打断绘制,而是真实的三角面的位置是否重叠。这个可以在 Scene 视图下用线框模式(Texture
Wire)去观察;
5.少用 Mask 组件,Mask 实现的原理是 Stencil Buffer,往模版缓存里绘制,模版缓存里的东西才是可见的。模板缓存会打断所有的合批,Mask 的子节点和外面的节点无法合批,模板缓存自己占一个 DrawCall。Unity5.2 之后的版本建议使用 2D Rect Mask 替代。

Canvas.SendWillRenderCanvases()

真机 Profiler 会经常看到这个函数的消耗飙高,研究 UGUI 源码得知该 API 为UI 元素(Graphic 子类)自身发生变化(比如被 Enable、顶点色变化、Text 组件的文本变化等)时所产生的调用,CPU 飙高大部分情况下主要是因UnityEngine.UI.Graphic.UpdateGeometry()重新生成了所有的顶点数据,具体有哪些操作会导致Graphic重新生成顶点数据,可以搜索UGUI的源码(4.6.9)Graphic.SetVerticesDirty()。刷新顶点数据的消耗和当前 Graphic 的顶点数正
相关,所以接下来研究一下 UI 顶点数。对于 UI 来说,顶点数量主要是由 Image控件和 Text 控件产生的。对于 Image 控件,顶点的数量取决于 ImageType。
1.Simple 模式只有 4 个顶点;
2.Sliced 模式,就是我们常用的九宫格,要分两种情况:勾选了 fillcenter 的顶点数是 36 个,没勾选 fillcenter 的顶点数是 32 个;
3.Tiled 模式,就是平铺模式,这个定点数量就完全取决 Image 控件的Rectranform 设置的大小和原图大小,简单来说就是铺开了 N 张图就是 4*N 个顶点;
4.Filled 模式,一般用来做进度条、技能 cd 转圈等效果,这个顶点数就取决于Fill Method 和进度值,组合的情况比较多,我就不展开算了,具体数值大家感兴趣的自行 demo 看下,总之不少于 4 个。由此可以得出结论,Image 能用 Simple 模式的尽量用 Simple 模式。在项目组中经常遇到的一种情况是,以前的需求是这个 Image 用九宫格,后来需求变动不需要九宫格了,但是没有把 Image Type 切换会 Simple 从而导致多余的顶点数。
    其实 UI 顶点数的重灾区主要是在 Text 控件,Text 在不添加任何效果的情况下是一个字符串是 4 个顶点,单独添加 Shadow 后一个字符串顶点数是 8 个,单独添加 Outline 后一个字符串顶点数是 20 个,Shadow 和 Outline 都加一个字符串顶点数是 40 个。如果界面上有 N 个字符串,那顶点数就是 N*上面的系数,这个量级是很可怕的。因此对于Text控件尽量不要去添加 Shadow、Outline 效果,特别是Outline!这对 CPU 和 GPU 都很伤。策划和美术想要的文字凸显的效果可以用背景和文
字配色来实现,效果实在无法接受的可以少量使用 Shadow。对于频繁变动的Text 文本(例如聊天),更要慎重考虑是否使用 Shadow、Outline。除了优化 UI 顶点数以外还有一些设置是会影响此函数消耗的,查看源码得知如果勾选了 Canvas 下的 pixelPerfect,在刷新顶点的时候会做一些额外的操作RectTransformUtility.PixelAdjustRect,像素对齐调整,此操作很费,所以不要勾选 Canvas 下的 pixelPerfect

Canvas.BuildBatch()

    理解该函数的消耗之前先来说一下 UGUI 的动态合批机制(减 DrawCall),UGUI是以 Canvas 为合批的根节点,首先 Canvas 与 Canvas 之间是没法合批的,Canvas 下所有节点的顶点数据会生成一张大的 BatchedMesh,再根据材质纹理、渲染顺序等把这个 Canvas 下能合批的 Mesh 生成 SubMesh,一个SubMesh 就是一个绘制批次。每当这个大的 BatchedMesh 中任意一个顶点数据发生变化的时候,UGUI 目前的实现方式很粗暴,就会直接重新生成BatchedMesh , Canvas.BuildBatch() 干 的 就 是 这 个 事 情 。 由 此 可 见Canvas.BuildBatch()消耗飙高的因素有两点:Canvas 下的顶点数量多和Canvas 下的顶点发生变化。

    通常之前所提到的 Canvas.SendWillRenderCanvases()的调用都会引起Canvas.BuildBatch()的调用。理解了该函数的原理之后,优化的思路就清晰了:减少一些频繁变动的 UI 元素的 Canvas 下的顶点数量。关于优化 UI 顶点数量上面说的比较多了,接下来重点说下顶点变化。先来看下上面提到的 BatchedMesh该 Mesh 包含了顶点色,所以修改顶点色、缩放、位移等导致 Mesh 数据变化的操作,都会引起 Canvas.BuildBatch()。游戏中难免有 UI 频繁移动、缩放、变颜色的需求,既然一定要变动,那我们就让这些频繁变动的 UI 元素单独放在一个 Canvas 下,且尽量减少这个频繁变动的 Canvas 下的顶点数量。简单的概括一句优化思路就是:动静分离。当然如果 Canvas 分离得太细也有另外一个问题,DrawCall 数量提升,所以要做一个权衡。

 

其他 UI 卡顿优化建议

    除了以上三点,UI 方面造成 CPU 消耗飙高的原因还有很多,就不一一展开详细解释了。
1.精简拆分 UI Prefab 文件,把打开界面瞬间看不见的 UI 元素尽量拆分,功能逻辑触发其他 UI 元素时再动态加载,例如:背包、练鼎根据子页签内容拆分,这样可以减少打开 UI 时卡顿的感觉;
2.对于 ScrollRect 里存在非常多元素的界面,避免一次全部 Initiation,回收重复里节点元素,刷新数据即可(可以实现一个公用组件);
3.避免频繁 Initiation、Destroy 某个 UI 子元素,类似小地图的 SceneActor 圆点、寻路路径、YLIconBox 的子节点,使用对象池维护起来重复利用;
4.序列化保存Prefab的时候,尽量让active的状态和大部分情况下出现的active状态一致,可以避免切换 active 状态带来的性能消耗;
5.在 UI 布局位置不会动态调整的情况下,不要偷懒用 Layout 组件实现自动布局,直接 Rectranform 写死坐标位置即可,Layout 动态计算有开销;
6.UGUI对比 NGUI有个不好的地方是所有的 Graphic 组件默认都是参与接受射线检测,响应 EventSystem 事件的,这样在在长时间按屏幕输入的时候参与遍历检测的 UI 节点会很多,界面复杂到一定程度会导致 EventSystem.Update 消耗飙升。建议用 5.2 之后版本的可以让所有没有响应需求的 Graphic 组件Raycast Target 默认不勾选,4.x 也是可以添加 Canvas Group 然后去掉 Block
Raycasts 和 Interactable;
7.不要使用 Text 的 Best Fit 选项,有额外开销;
8.对部分 UI 有频繁显示和隐藏需求的,建议不要直接调用 SetActive,上面有提到这样会导致顶点数据重新生成,过于频繁调用有性能问题。有两个建议:挪出裁减区域或者禁用 CanvasRender,具体采用什么方案要看需求,因为禁用CanvasRender 还是会响应事件检测。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值