从CPU和GPU出发的UGUI优化

UGUI优化

 

就在前几天去B站面试实习岗位,发现学习的方向出现了一些小问题

对UGUI的优化理解出现了不小问题,面试之后,查询了不少的文章,并吸取他们的知识,对UGUI有了一定的理解,希望可以避免再出现类似的问题

下面我便记录一下UGUI

 

首先作为Unity自带的UI系统,它具有以下的优点:

    1.可视化编程:在Scene窗口可编辑

    2.智能的Sprite packe可以讲图片按tag自动生成图集而不需要人工维护,生成的图集合并方式合理,无冗余资源

    3.UGUI的精灵切图功能十分方便,可以把一张图集分割成一张一张可以直接使用的图片

    4.渲染的顺序与GameObject的Hierarchy顺序有关,靠近根节点显示在底层,而靠近叶子结点的在顶层;这样渲染使调整UI层级更加放便直观

    5.RetTranForm组件以及锚点系统更适合2D平面布局,并适应多分辨率屏幕的自适应

     在这里我引入一张UGUI与NGUI的对比图

从CPU和GPU出发的UGUI优化

 

下面说到优化:可以从以下几点来说明:

    1.UI的制作规范和指导方法

    2.从CPU角度的优化

    3.从GPU角度的优化

 

1.首先是UI的制作规范和指导方法

    很多时候都是细节决定成败,减少一些不必要的资源往往会有意想不到的效果。其实性能问题往往是资源的不合理使用造成的。

    下面总结了几点规范的方法

    1.合理的分配图集

    同一个UI界面的图片放在一个图集种

    公共的图片放在一个图集中(通用的弹框和按钮;相同功能的图片放到一个图集,例如装备图标和英雄头像等;这样可以降低切换界面的加载速度)

    2.resources目录应该只保存prefab文件,其他非prefab文件(动画,贴图,材质等)应该放在resource之外

    因为随着项目的迭代,可能会导致部分资源(动画,贴图)失效,如果这些文件放到resource目录下,在打包时,unity会讲resource目录下文本全部打成一个大的AssetBundle包(非resource目录下的文件只有在引用的时候才会打到包中),从而出现冗余,增加不必要的存储空间和内存占用

    3.关卡内的UI资源不要与外围系统UI资源混用

    在关卡内,需要加载大量的角色以及场景资源,内存吃紧,一般再进入关卡时,都会手动释放外围系统的资源,以便使关卡内有更多的内存可以使用。如果战斗内的UI与外围系统的UI使用相同图集里的图片,则有可能会使外围系统的图片释放不成功,对于关卡内与外围共用的UI资源需要特殊处理,一般来说,复制出来一份专门给关卡内使用是比较好的选择

    4.适当的降低图片的尺寸

    降低UI系统的背景可能会使用全屏大小的图片,比如Iphone上使用1136*640大小的图片;使用这样尺寸的图片代价是昂贵的。可以与美术商量适当的降低图片的精度,使用更低尺寸的图片

    5.在android设备上使用etc格式的照片

    目前,几乎所有的Android都支持etc1格式的图片,etc1的好处是第个像素点只占用0.5个字节而普通的rgba32的图片每个像素点占四个字节,也就是说同样一张1024*1024的图片如果使用rgba32的格式占用字节为而etc1格式的八倍,但是etc1格式的图片有两个限制--长和宽必须是POT的(2的N次方)并且不支持alpha通道,因此使用etc1的时候需要额外的一张图来存储alpha通道,并且使用特殊的shader来对alpha采样

    具体的细节可参考:http://malideveloper.arm.com/resources/sample-code/etcv1-texture-compression-and-alpha-channels/

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    在这里这个网址我并没有进去(加载不出来),但是我对这句话里面的一些名词并不清除其中的含义做了一下简单的调查

   etc1和rgba32:图片的两种格式,Unity5.3之后,Android平台的默认压缩纹理改为ETC2。ETC2支持半透明,使用也很方便,不过问题是尚未普及,尤其是低端机是不支持ETC2的。Unity对不支持ETC2的机子,在加载图片的时候会解压为RGBA,不过这样就会造成极大的内存浪费,也会严重影响加载速度。

    对于没有透明色且为2的整次幂的正方形图片,Unity会转为ETC1,所以模型的纹理是可以用默认的压缩格式。

    需要注意的是非2的整次幂的图片,以及含有半透明的图片。这里主要是UI图片资源。Unity提供了ETC1+Alpha的支持。在图片分页进行如图所示的设置

    从CPU和GPU出发的UGUI优化

这里要注意几点:

1、图片必须设置好Sprite Tag,即进行图集打包,才能支持ETC1+Alpha。

2、BuildSetting里面 Texture Compression要选成 ETC(default)。如果选成其他的,则会使用ETC2,这样在低端机可以显示,但是效率很低。

3、Project Setting--Graphic--Always Included Shaders里面添加 UI/DefaultETC1。如果没有添加则真机上显示异常。

4、Unity5.3.0以及之前的版本,UGUI的Image是不支持ETC1的,只有SpriteRender渲染正常。  最新版本是没问题的。

5、如果自己写了某些自定义Shader,如图片灰化,那么这个也要做相应的支持。否则渲染不正常。具体支持方式参考UI/DefaultETC1

那么alpha通道是什么呢?

阿尔法通道(Alaha Channel)指一张图片的透明与半透明。例如一个使用16比特存储的位图,对与图形中的每一个像素而言,可能以5像素表示红色,五像素表示绿色,五像素表示蓝色,最后一比特就是阿尔法。在这里它要么表示透明要么就是不透明。如果是32比特的位图,他除了每八位的颜色位,还有可以表示256级半透明度的阿尔法通道

 

6.删除不必要的UI节点、动画组件及资源

    随着项目的迭代,可能有部分UI节点以及动画已经失效,对于失效的节点以及动画一定要删除,在很多项目中,有部分同学为了放便省事,只是把失效的节点和动画disable了,这样做虽然在运行时不会对cpu造成太多的负担,但是在加载时会增加不必要的加载时间以及内存占用。对于废弃的UI图片资源,虽然未放到Resource目录最终不会打到包里,但是在Editor模式下仍然会打到图集中从而影响优化策略。

    该文章的笔者写了一个扫描未使用到UI贴图资源的工具,代码地址:https://github.com/neoliang/FindUnUsedUITexture;

 

7.使用Canva为节点的UI资源组合方式

     在UGUI中,无论UI网格的更新或者重建(动态批处理)都是以Canvas为单位,但是动态,静态UI元素以Canvas为节点进行分离,可进一步降低DrawCall(粒子系统不会引起UI的重建)

    另外废弃的脚本,可能还会有某些对象持有对他的引用,而加载这样的对象就比较耗时,这个笔者也写了一个扫描废弃脚本的工具,代码地址:https://github.com/neoliang/MissingScriptFinder    (这个作者真的牛逼)

 

2.从CPU角度的优化

一般来说,优化cpu性能应该先用profiler定位到性能热点,找到消耗最高的函数,然后在想办法降低他的消耗。讲过那位笔者多次使用profiler对UGUI的分析来看,其CPU性能开销高主要的原因之一是Canvas对UI网格的重建,有很多情况会触发Canvas对网格的重建,例如:Image,Text等元素的Enable及UI元素的长,宽或Color属性的变化等。Canvas中的UI Mesh顶点较多的话,则该项目将会出现较高的CPU开销。在Unity的Profiler中则对应的是Canvas.SendWillRenderCanvases或Canvas.BuildBatch占用过多的时间。

Canvas.BuildBatch主要功能是合并Canvas节点下所有UI元素的网格,合并后的网格会缓存起来,只有其下面的UI元素的网格发生改变时才会重新合并。而UI元素的网络变化主要是因为Canvas.SendWillRenderCanvases调用时,rebuild了Layout或者craphic。该函数的调用过程时序图如下:

从CPU和GPU出发的UGUI优化

1.该过程由CanvasUpdateRegistry监听Canvas的WillRenderCanvases(上图中1)而执行,主要是对前标记为dirty的layout和craphic执行rebuild。引起layout和graphic的dirty主要原因是因为Canvas树形结构下的UI元素发生了变化(例如增加删除UI对象,UI元素的顶点,rec尺寸改变等)调用了Graphic.SetDirty(实际上最终都会调用CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild)。

 

2. 在rebuild layout之前会对Layout rebuild queue中的元素依据它们在heiarchy中的层次深度进行排序(上图中的2),排列的结果是越靠近根的节点越会被优先处理。

 

3. rebuild layout(上图中的3),主要是执行ILayoutElement和ILayoutController接口中的方法来计算位置,Rect的大小等布局信息。

 

4. rebulid graphic(上图中的4),主要是调用UpdateGeometry重建网格的顶点数据(上图中5)以及调用UpdateMeterial更新CanvasRender的材质信息(上图中6)。

 

profiler:内存分析器

 

基于UGUI的网格更新原理,我们可以做以下的优化:

1.使用尽可能少的UI元素;在制作UI时,一定要仔细检测UI层级,删除不必要的UI元素,这样可以减少深度排序的时间以及Rebuild的时间

2.减少Rebuild的频率,将动态UI元素(频繁改变例如顶点、alpha、坐标和大小等元素)与静态UI元素分离出来,放到特定的Canvas中。

3.谨慎使用UI元素的enable与disable,因为他们会触发耗时较高的rebuild,代替方案一是enable和disableUI元素的Canvasrender或者Cancas。方案二是通过添加对应的UI的layer,然后,设置Camera的CulingMask将其layer取消勾选(既不渲染此UI)达到隐藏,优点不会产生多余的DrawCall,缺点常驻内存。

4.谨慎使用Test的Best Fit选项,虽然这个选项可以动态的调整字体大小以适应UI布局而不会超框,但是代价是很高的,Unity会为用到的该元素所用到的所有字号生成图元保存在atlas里,不但增加额外的生成空间,还会使字体对应的atlas变大。

从CPU和GPU出发的UGUI优化

谨慎使用Canvas的Pixel Prefact选项,该选项会使得UI元素在发生位置变化时,造成layoutRebuild。(比如ScrollRect滚动时,如果开启了Canvas得pixel Perfect,会使得Canvas。SendWillRenderCanvas消耗高)

从CPU和GPU出发的UGUI优化

 

5.使用缓存池来保存ScrollView中的Item,对于移出或移进View外得元素,不要调用disable或enable,而是把他们放到缓存池里或者从缓存池中取出来复用。

6.除了rebuild过程之外,UGUI的troch处理消耗也可能会成为性能热点。因为UGUI在默认情况下会对所有可见的Graphic组件调用raycast。对于不需要接收touch事件的grahic,一定要禁用raycast。对于unity5以上的可以关闭graphic的Raycast Target而对unity4.6,可以给不需要接收touch的UI元素加上canvasgroup组件。

unity5.x 

从CPU和GPU出发的UGUI优化

unity4.6

从CPU和GPU出发的UGUI优化

 

3.从GPU角度的优化

一般来说,造成GPU性能优化的瓶颈主要有两个原因:复杂的vertext或pixel shader计算以及overdrow造成过多的像素填充。在默认情况下UGUI中的所有UI元素使用UI/Defaut shader,因此在优化时可以考虑Overdraw问题。Overdraw主要是因为大量的UI元素重叠引起,查看overdraw比较简单,在scenen窗口中选择overdraw模式,场景中越亮的地方表示overdraw越高。

从CPU和GPU出发的UGUI优化

为了降低overdraw,可以做如下优化:

1.禁用不可见的UI,比如当打开一个系统时如果完全挡住另一个系统,则可以将遮挡住的系统统统禁用

2.不要使用空的Image,在Unity中,RayCast使用Graphi作为元素来检测touch,在这个笔者参与的项目中很多同学使用空的Imager并将alpha设置为0来接收touch事件,这样会产生不必要的overdraw。通过如下类NoDrawingRayCast来接收事件可以避免不必要的overdraw。

 

 

 

总结

优化UGUI性能没有万能的方法,笔者这些经验总结也只能作为参考。优化性能往往是在各种选择之间做出平衡,比如drawcall与rebuild平衡、内存战胜与cpu消耗平衡以及UI图片精度与纹理大小的平衡等。每一次优化都有可能使得瓶颈出现在其它的环节上,要善于使用profiler,找到性能热点,对症下药。

 

关于资源占用问题

UI资源优化是UGUI性能优化的重点,腾讯WeTest也在资源方面提供了性能的测试。以下通过“纹理”资源,介绍腾讯WeTest性能测试在资源方面的测试情况。

1、登录http://wetest.qq.com/cube/ ,点击“Android版 下载”,或者在页面末尾扫描二维码直接下载腾讯WeTest的手游客户端性能分析工具Cube。打开工具,选择“Unity资源分析”。

2、上传测试报告后,我们可以通过测试报告,了解unity游戏的资源情况。

 

资源结论概况

进入资源数据的报告之后,首先可以看到所有资源数据的概况结果,总体上了解存在问题的数据,继续下拉,可以了解该指标的具体情况。

 

从CPU和GPU出发的UGUI优化

资源数据概况

 

下面将以“纹理资源”为例,对cube资源测试报告进行解读。

 

纹理资源

Cube测试报告的“纹理资源”,根据腾讯标准,是期望<50MB的,从下图可见,如果超出红色虚线,就说明纹理资源存在超标。

 

从CPU和GPU出发的UGUI优化

点击具体数据点,获取具体资源数据

 

另外,点击图表中的绿色线条中的具体数据点,可以看到这个点的当前数据,所有数据根据资源大小进行排序:

从CPU和GPU出发的UGUI优化

所有数据根据资源大小进行排序

 

在这个表之下,有一个“资源大小top20”的表格,罗列了资源排名前20的资源内容。其中资源大小超过建议值的会呈现红色,资源大小非2的n次幂的呈现黄色。点击任意一个资源名称,可以在图表上观察这个资源所影响的区域:

从CPU和GPU出发的UGUI优化

点击具体资源了解影响区域

 

从CPU和GPU出发的UGUI优化

了解资源调用的影响区域

 

 

说真的本来以为ugui是unity最简单的模块没有想到里面也有这么多的学问,爱死游戏制作了