(转)Unity 之 UGUI 小总结

转自:http://www.jianshu.com/p/5b6f5022662e

 开发过程中对UGUI的一个小总结。

   首先从原画师拿到效果图,美术切图,拿到碎图后打成大图。

    我们先来说一下图:RGBA8888:每一个通道占8位。大图:1024*1024。高端    机:2048*2048。

   我们通常从美工那里拿来碎图,歪歪使用的一个工具texturepacker 把碎图打成大图,导出成 .tpsheet  .png格式。其次我们要做的是,在Unity3d中导入插件texture import(此插件会自动把大图打成图集).然后把大图导入U3D。

  在UI优化中较为明显而众所周知的就是降低DrawCall(注释:DrawCall CPU向GPU发送的一次渲染指令)。而降低DrawCall我们会采用静态合批和动态合批。(注释:合批必须同类型 Mesh 同材质。)DrawCall的标准 RGP 类 小于 150。

   然而,工作中我们优化大概分为UI层级计算,UI重建和多层级渲染。接下来我们来说一下:

UI层级 计算 :

1,计算层级:

1,如果有一个UI元素,它所占的屏幕范围内(通常是矩形),如果没有任何UI在它的底下,那么它的层级号就是0(最底下);

2,如果有一个UI在其底下且该UI可以和它Batch,那它的层级号与底下的UI层级一样;

3,如果有一个UI在其底下但是无法与它Batch,那它的层级号为底下的UI的层级+1;

4,如果有多个UI都在其下面,那么按前两种方式遍历计算所有的层级号,其中最大的那个作为自己的层级号。

合并批次原则;

同层级{同材质球,}

{0 : { image   , image, text  }}

1,Unity会将每一层的所有元素进行一个排序(按照材质、纹理等信息),合并掉可以Batch的元素成为一个批次

2,Text组件会排在Image组件之前渲染。

3,,Unity会再做一个优化,即如果相邻间的两个批次正好可以Batch的话就会进行Batch(这么处理,可以合成一个批次,如下:)

{0 : { text   image  }}textàimage

{1 : { text  ,image }}

0: textàimage    1: textàimage

下面我们来看一个列子,这样会更加的清晰:

一个层级为0的ImageA,一个层级为1的ImageB(2个Image可Batch)和一个层级为0的TextC,

textCàimageAàimageB

一个层级为0的TextD,一个层级为1的TextE(2个Text可Batch)和一个层级为0的ImageF,

0 : textD –>imageFà1 : textEàimage h    3:

0 : textD –>imageFà1 : textEàimage h

总结:

1,有相同材质和纹理的UI元素是可以Batch的,可以Batch的UI上下叠在一块不会影响性能,但是如果不能Batch的UI元素叠在一块,就会增加Drawcall开销

2,尽量让同一个材质球上的东西 放在同一级上

3,有些情况可以考虑人为增加层级从而减少Drawcall,比如一个Text的层级为0,另一个可Batch的Text叠在一个图片A上,层级为1,那此时2个Text因为层级不同会安排2个Drawcall,但如果在第一个Text下放一个透明的图片(与图片A可Batch),那两个Text的层级就一致了,Drawcall就可以减少一个。

Text0 –》Imageàtext 1

4,应该尽量避免使用Mask,其实Mask的功能有些时候可以变通实现,比如设计一个边框,让这个边框叠在最上面,底下的UI移动时,就会被这个边框遮住;

5, z值 保持为0

UI重建:

1,动静分离:

经常发生变动的ui单独放在一个Canvase

2,删除不必要的元素。

3,CanvasRender.setDisable .

4,Text的Best Fit选项.

5,Canvas的Pixel Perfect选项

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

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


 

多层级渲染:

1,canvasRender .setdisable()

2,只挂载button替代  空的image

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

3. public class NoDrawingRayCast : Graphic

4. {

5.     public override void SetMaterialDirty()

6.     {

7.     }

8.     public override void SetVerticesDirty()

9.     {

10.     }

11.     protected override void OnFillVBO(List vbo)

12.     {

13.         vbo.Cslear();

14.     }

}

性能检测工具:

1profile

常见问题:

Q1:我在UGUI里更改了Image的Color属性,那么Canvas是否会重建?我只想借用它的Color做Animation里的变化量。

如果修改的是Image组件上的Color属性,其原理是修改顶点色,因此是会引起网格的Rebuild的(即Canvas.BuildBatch操作,同时也会有Canvas.SendWillRenderCanvases的开销)。而通过修改顶点色来实现UI元素变色的好处在于,修改顶点色可以保证其材质不变,因此不会产生额外的Draw Call。

Q2:Unity自带的UI Shader处理颜色时,改_Color属性不会触发顶点重建吗?

在UI的默认Shader中存在一个Tint Color的变量,正常情况下,该值为常数(1,1,1),且并不会被修改。如果是用脚本访问Image的Material,并修改其上的Tint Color属性时,对UI元素产生的网格信息并没有影响,因此就不会引起网格的Rebuild。但这样做因为修改了材质,所以会增加一个Draw Call。

Q:动静分离或者多Canvas带来性能提升的理论基础是什么呢?如果静态部分不变动,整个Canvas就不刷新了?

在UGUI中,网格的更新或重建(为了尽可能合并UI部分的DrawCall)是以Canvas为单位的,且只在其中的UI元素发生变动(位置、颜色等)时才会进行。因此,将动态UI元素与静态UI元素分离后,可以将动态UI元素的变化所引起的网格更新或重建所涉及到的范围变小,从而降低一定的开销。而静态UI元素所在的Canvas则不会出现网格更新和重建的开销。

Q:UWA建议“尽可能将静态UI元素和频繁变化的动态UI元素分开,存放于不同的Panel下。同时,对于不同频率的动态元素也建议存放于不同的Panel中。”那么请问,如果把特效放在Panel里面,需要把特效拆到动态的里面吗?

通常特效是指粒子系统,而粒子系统的渲染和UI是独立的,仅能通过Render Order来改变两者的渲染顺序,而粒子系统的变化并不会引起UI部分的重建,因此特效的放置并没有特殊的要求。

Q:多人同屏的时候,人物移动会使得头顶上的名字Mesh重组,从而导致较为严重的卡顿,请问一下是否有优化的办法?

如果是用UGUI开发的,当头顶文字数量较多时,确实很容易引起性能问题,可以考虑从以下几点入手进行优化:

1.尽可能避免使用UI/Effect,特别是Outline,会使得文本的Mesh增加4倍,导致UI重建开销明显增大;

2.拆分Canvas,将屏幕中所有的头顶文字进行分组,放在不同的Canvas下,一方面可以降低更新的频率(如果分组中没有文字移动,该组就不会重建),另一方面可以减小重建时涉及到的Mesh大小(重建是以Canvas为单位进行的);

3.降低移动中的文字的更新频率,可以考虑在文字移动的距离超过一个阈值时才真正进行位移,从而可以从概率上降低Canvas更新的频率。

三、界面切换

Q1:游戏中出现UI界面重叠,该怎么处理较好?比如当前有一个全屏显示的UI界面,点其中一个按钮会再起一个全屏界面,并把第一个UI界面盖住。我现在的做法是把被覆盖的界面SetActive(False),但发现后续SetActive(True)的时候会有GC.Alloc产生。这种情况下,希望既降低Batches又降低GC Alloc的话,有什么推荐的方案吗?

可以尝试通过添加一个Layer如OutUI, 且在Camera的Culling Mask中将其取消勾选(即不渲染该Layer)。从而在UI界面切换时,直接通过修改Canvas的Layer来实现“隐藏”。但需要注意事件的屏蔽,禁用动态的UI元素等等。

这种做法的优点在于切换时基本没有开销,也不会产生多余的Draw Call,但缺点在于“隐藏时”依然还会有一定的持续开销(通常不太大),而其对应的Mesh也会始终存在于内存中(通常也不太大)。

以上的方式可供参考,而性能影响依旧是需要视具体情况而定。

Q2:通过移动位置来隐藏UI界面,会使得被隐藏的UIPanel继续执行更新(LateUpdate有持续开销),那么如果打开的界面比较多,CPU的持续开销是否就会超过一次SetActive所带来的开销?

这确实是需要注意的,通过移动的方式“隐藏”的UI界面只适用于几个切换频率最高的界面,另外,如果“隐藏”的界面持续开销较高,可以考虑只把一部分Disable,这个可能就需要具体看界面的复杂度了。一般来说在没有UI元素变化的情况下,持续的Update开销是不太明显的。

Q3:如图,我们在UI打开或者移动到某处的时候经常会观测到CPU上的冲激,经过进一步观察发现是因为Instantiate产生了大量的GC。想请问下Instantiate是否应该产生GC呢?我们能否通过资源制作上的调整来避免这样的GC呢?如下图,因为一次性产生若干MB的GC在直观感受上还是很可观的。

 


 

准确的说这些GC Alloc并不是由Instantiate直接引起的,而是因为被实例化出来的组件会进行OnEnable操作,而在OnEnable操作中产生了GC,比如以上图中的函数为例:

上图中的Text.OnEnable是在实例化一个UI界面时,UI中的文本(即Text组件)进行了OnEnable操作,其中主要是初始化文本网格的信息(每个文字所在的网格顶点,UV,顶点色等等属性),而这些信息都是储存在数组中(即堆内存中),所以文本越多,堆内存开销越大。但这是不可避免的,只能尽量减少出现次数。

因此,我们不建议通过Instantiate/Destroy来处理切换频繁的UI界面,而是通过SetActive(true/false),甚至是直接移动UI的方式,以避免反复地造成堆内存开销。

四、加载相关

Q1:UGUI的图集操作中我们有这么一个问题,加载完一张图集后,使用这个方式获取其中一张图的信息:assetBundle.Load (subFile, typeof (Sprite)) as Sprite;这样会复制出一个新贴图(图集中的子图),不知道有什么办法可以不用复制新的子图,而是直接使用图集资源 。

 


 

经过测试,这确实是Unity在4.x版本中的一个缺陷,理论上这张“新贴图(图集中的子图)”是不需要的,并不应该加载。 因此,我们建议通过以下方法来绕过该问题:

在assetBundle.Load (subFile, typeof (Sprite)) as Sprite;之后,调用

Texture2D t = assetBundle.Load (subFile, typeof (Texture2D)) as Texture2D;

Resources.UnloadAsset(t);

从而卸载这部分多余的内存。

Q2:加载UI预制的时候,如果把特效放到预制里,会导致加载非常耗时。怎么优化这个加载时间呢?

UI和特效(粒子系统)的加载开销在多数项目中都占据较高的CPU耗时。UI界面的实例化和加载耗时主要由以下几个方面构成:

纹理资源加载耗时

UI界面加载的主要耗时开销,因为在其资源加载过程中,时常伴有大量较大分辨率的Atlas纹理加载,我们在之前的Unity加载模块深度分析之纹理篇有详细讲解。对此,我们建议研发团队在美术质量允许

1,1024*768    1024*1024

2, 640*480      512*512



作者:歪歪小花鹿
链接:http://www.jianshu.com/p/5b6f5022662e
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

posted @ 2017-08-24 09:50  Quintinz  阅读(2187)  评论(0编辑  收藏  举报