概述
NGUI:在远古时期,UGUI出现之前,约国内八成左右的商业项目,UI都是使用NGUI来实现的,可以说NGUI是元老了。
NGUI是严格遵循KISS原则并用C#编写的Unity(适用于专业版和免费版)插件,提供强大的UI系统和事件通知框架。其代码简洁易懂,程序员可以很容易地扩展NGUI的功能或调节已有功能,也意味着更容易优化其性能及更低的学习难度。
另外的优点是,组件属性集成到了Inspector面板中, 在编辑视图就能看到在游戏视图中的效果(所见即所得)。
基于组件的、模块化的特性。添加新的功能,只需为其附加相应的组件,不需要额外编码。
UGUI:从Unity4.6版本开始官方推出了UGUI。UGUI是Unity 官方的UI系统,在兼容性和官方支持上来说,官方的 UI系统具有一定优势。
例如: Canvas.BuildBatch(),合批Canvas下所有网格,这个功能在 5.2 版本后挪到了子线程去完成,减轻了主线程的压力,而NGUI 作为一个插件没法做到这一点,网格合批的性能热点还是耗在主线程的UIPanel.LateUpdate();
八卦一下:NGUI和UGUI的作者是同一个人,ArenMook(后来又因个人原因已从Unity离职)。
NGUI和UGUI之元素更新
NGUI元素更新:
查看NGUI的源码,发现NGUI的UIPanel采用的是调用LateUpdate的方式进行更新。每帧都会调用UpdateSelf,做的事情就是更新当前UIPanel内所有组件的位置、图层关系、甚至重建信息。值得指出的是,因为是每帧执行,即使UI没有任何改变,是静态的UI仍然会有一定的开销。
UGUI元素更新:
查看UGUI的源码,发现UGUI已经不是通过每帧调用来产生更新了。而是通过Canvas.SendWillRenderCanvas添加全局委托,再通过m_LayoutRebuildQueue和m_GraphicRebuildQueue,这两个队列分别记录Layout和外观发生变化的UI元素。在渲染前,会在这个回调函数里去处理这两个队列里的元素,分别进行Rebuild。因此,如果在UGUI中如果UI元素是静态的,则不会有持续开销。
优化策略:
NGUI中可直接通过Color.alpha = 0去隐藏。这样UI元素的顶点数可被移除掉,也可避免掉SetActive的开销。
注意!不能用Scale = 0 ,这仅仅把网格Scale为0,但顶点数还在,DC依旧存在。
UGUI中设置Scale = 0,或者添加 Canvas Group 组件设置Alpha = 0,这两种均可去掉元素的顶点,也可避免SetActive的开销。
注意!UGUI中不能使用Color.alpha = 0 这种方式,因为这相当于贴了一个透明面片还是会被画到场景中,对DC和OverDraw无影响。
NGUI和UGUI之Drawcall合批( Batch )
NGUI是通过手动调整Depth值,根据值的大小以UIPanel为单位进行一个排序,Depth接近,中间没有材质不同的对象打断时,相同材质的对象进行合批。注意在实际调整UIPanel的Depth时,请将动态文本单独设置一个Depth,如果有动效则分别单独设置其Depth,然后再将相同材质的UI分别设置为同一个Depth。
UGUI的合批规则,则是自动计算元素的层级
1.首先排除掉active=false,scale=0,Canvas Group=0
2.遍历所有元素,根据包围盒重叠关系分组
3.根据图层顺序得到depth,最下面的元素为depth=0,依次往上递增=1,=2,=n
4.同一分组,根据图层的顺序,如相邻depth的元素是同一材质的对象时,进行合批处理。
5.特别要注意下图所示情况,因为分组是根据包围盒重叠来判断的,下图上半部分,被判断为2个分组,因为他们所属不同材质,所以产生了七个drawcall,而下半部分,因为是1个分组,所以只有4个drawcall。这点是在UGUI里需要特别注意的。
NGUI和UGUI之网格更新
NGUI中Batch是以UIPanel为单位来改变的,UIPanel内单个元素变化则FillDrawCall 即更新单个DrawCall,如果发生重建Rebuild,则 FillAllDrawCall 更新所有DrawCall。值得特别注意的NGUI的网格更新是每帧进行的,在元素更新时已经说过。
UGUI的话,因为网格重建的实现是在C++里实现的(因此网格合并比NGUI要快),所以不能找到其源码来分析下,但是不影响分析出其更新原理。
在 UGUI 中,Batch是以Canvas为单位的,即在同一个Canvas下的UI元素最终都会被Batch到同一个Mesh中。而在Batch前,UGUI会根据这些元素的材质,以及渲染顺序进行分组重排,根据分组将相邻depth并且相同材质的UI元素合并在同一个SubMesh中,从而把DrawCall降到最低。而Batch的操作只会在UI元素发生变化时才进行,且合成的Mesh越大,操作的耗时也就越大。
因此,我们建议尽可能把频繁变化(位置,缩放,旋转,颜色等)的UI元素从复杂的面板中(UIPanel/Canvas)分离出来,放到独立的面板里面,从而避免频繁重建,也就是所谓的动静分离。
优化界面的技巧
看完这两套UI的更新原理、合批机制和网格更新方式。那么降低界面渲染的技巧,其实也就变成了如何降低UI中产生的Drawcall,让更新的网格降低的技巧。
1.尽可能的把频繁变化(位置,缩放,旋转,颜色等)的UI元素从复杂的面板中(UIPanel/Canvas)分离出来,放到独立的面板里面,从而避免频繁重建,也就动静分离。
2.减少UI层叠关系,将同一个材质的图层放到同一层,或者同一个图集能大量降低Drawcall
3.善于利用工具调试查看Drawcall
NGUI:DrawCall Tool,Frame Debugger
UGUI: Frame Debugger
4.NGUI中,当用SetActive或alpha = 0的时候,NGUI的规则是从UIPanel中移除元素,这样会引发FillAllDrawCalls重建整个网格,通过设置scale = 0 或 alpha 接近0来隐藏(因为这样网格还在,DrawCall也在,不会打乱)的话,则只会重建单个网格,在优化复杂面板时尤为有效。
5.NGUI中,在UIPanel上如果勾选了Static选项,意思是把该面板变为静态面板,所有的子对象都无法缩放、旋转、移动(可以更改这些数值,但是不会有效果)。勾选了这个选项,可以减少轮询的开销。
总结
NGUI优势:
1.开放源码,NGUI的源码是开放的。使得开发者可以随意的查看代码并修改,可以随意修改其易用性或优化性能。
2.配套工具链和积累的组件。NGUI的先发优势使得开发者积累了大量的开发组件和工具,经过项目的验证,已经融入了开发的工作流中。
UGUI优势:
1.性能优势,作为Unity的亲儿子,网格计算是C++下实现的、而且还可以放在子线程完成,性能天花板比NGUI高出了不少。
2.持续的更新和官方的支持。作为官方UI,支持和后续的优化,肯定是会持续进行的。
在UGUI刚出来不久时,大部分团队都还是在使用NGUI,因为积累的工作流都比较成熟,是久经考验的。而近两年,使用UGUI的团队变得原来越多,一是UGUI的工具链也越来越成熟好用了,二是因为UGUI的天花板确实要比NGUI要高。慢慢的使用UGUI的团队也就越来越多了。