Unity3D UGUI 加载效果 unity加载界面制作_Unity3D UGUI 加载效果


前言

在游戏项目中,制作UI界面,是大部分客户端工程师需要做的一项工作。一般的工作流是,工程师捋清策划的需求点,拼UI、写逻辑代码,修复逻辑bug,让游戏能正常运行起来,好像工作就到此完成了。但这样的工作流其实是不完备的,整个流程中缺少了对性能的考虑,更好的习惯是,工程师从一开始着手工作时,就将任务拆分为实现功能和性能分析两个子任务。将优化提前到一开始投入生产的阶段,减少后期返工优化。

性能分析检查点大致总结为:

  • DrawCall方面

1、合理设置UI元素的层级,减少DrawCall合并的打断
2、分析界面操作,动静分离,根据操作引起的变化,对界面元素进行适当分组拆分panel

  • 加载和卸载方面

1、根据UI界面出现频次,确定加载和卸载策略
2、确定界面内UI元素是否需要延迟或分帧加载,比如大贴图、粒子特效、大量元素加载
3、分析界面操作,对操作所需数据进行适当的前置缓存

  • OverDraw方面

1、利用Scene视图中的OverDraw模式检查界面OverDraw
本文将结合以上检查点,介绍笔者使用NGUI制作UI的相关优化实践。

这篇文章适合的读者:
对于NGUI制作UI界面感兴趣,或者正在开展相关工作。该文章会介绍一些最佳实践,可以在实际工作中作为参考。

DrawCall方面

理解DrawCall,首先应该知道,在Unity中,每次CPU准备数据并通知GPU绘制的过程称为一次Draw Call。当我们谈DrawCall优化时,是为了减少CPU在调用图形接口上的开销,因为每一次的调用CPU都需要做很多工作。Widget是NGUI中负责界面显示的基础单位,并由Widget继承出许多UI元素,每个Widget都必从属于一个Panel。在NGUI下,统一使用Depth来进行渲染顺序的控制,Panel之间会基于Panel的Depth进行一次排序,Panel内部也会基于Widget的Depth进行一次排序。NGUI的实际渲染流程,就是把Widget组件生成的缓存,做成UIDrawCall之后,生成mesh来渲染的过程。

优化Drawcall主要的目的是尽可能的合并指令或者让更新时只更新必要的变化,以此降低CPU的负担。

降低界面的渲染开销

降低界面的渲染开销,也就是界面的整体DrawCall,能合并的就合并。做到:
一、尽量少穿插使用不同图集,减少DrawCall合并的打断。因为图集将密切相关的图打到一起,使用同一个材质,使得存在DrawCall合并的可能,但如果没有控制好渲染顺序,依然达不到好的优化效果。
二,UITexture尽量只在大图的情况下用,能用图集就是用图集。
三、不同的字体都使用一个Depth,并且这个Depth最好不要与图片Sprite有交叉,最好放在最上层。

具体的检查操作是,结合NGUI Panel Tool工具和NGUI DrawCallTool工具,通过PanelTool找到高DrawCall的界面,再打开NGUI DrawCallTool工具,选定指定的Panel, 查看间隔的DrawCall是否使用了相同的材质球, 再查看具体的widgets,是否存在可改动的层级设置。如下图所示,发现有部分Label层级设置问题,导致使用UI_New图集的widget无法合并DrawCall。 通过修改Label的widget的层级,将打断解除,使得UI_New都在一个DC中。


Unity3D UGUI 加载效果 unity加载界面制作_加载_02


降低界面的更新开销

除了通过合并DC来降低界面的渲染开销之外,还应该分析界面操作,减少界面重建的出现,降低界面的更新开销。做到动静分离,将频繁变化的动态UI元素存放于独立的Panel下,这虽然会多造成DrawCall,但相比更新的开销,渲染的开销可以忽略不计。NGUI更新开销最主要是UIPanel.LateUpdate的开销,一般认为UIPanel.LateUpdate超过3ms到4ms,可以认为这部分的更新开销是比较高的,而UIPanel的更新机制分为更新panel中的某单个UIDrawCall(UIPanel.FillDrawCall)和更新panel的所有UIDrawCall(UIPanel.FillAllDrawCalls),要避免触发第二种情况,一出现就通常有峰值。FillAllDrawCalls一般是在UI重建时触发,往往一些很不起眼的代码,也会导致FillAllDrawCalls的出现,比如元素的隐藏显示,新增元素穿插到panel中,NGUI没有把DrawCall合并起来,就很可能会触发Panel重建的启动。关于FillAllDrawCalls的优化稍显复杂,而动静分离是降低其出现的一个折衷策略,尽量保证局部重建而不影响整个Panel,而拆分比较细可以提高容错率,即使做了一些敏感操作,导致整个UIPanel或者Canvas发生了重建,也对整个界面的影响相对小些。更细致的优化,可以借助UWA的GOT Online和Unity的Profiler进行分析。

具体检查操作是,利用GOT,观察UI模块中,找到高堆内存分配的帧。发现是由一个点击操作造成的,打开profiler,在相应界面停留,找到对应帧,查看UICamera.Update和UIPanel.LateUpdate函数相关数值。


Unity3D UGUI 加载效果 unity加载界面制作_UI_03


Unity3D UGUI 加载效果 unity加载界面制作_缓存_04


复现点击操作,从profiler中观察,看到一个点击操作后,观察DrawCall,发现点击前为25,点击后变为26;同时UIPanel.LateUpdate中出现了GameObject.Deactivate和Active,这是UIPanel完全重建的表现。因此猜测,点击操作导致界面UI元素发生了变化,并且新增的UI元素,由于depth穿插问题,打破了原有panel中DrawCall。


Unity3D UGUI 加载效果 unity加载界面制作_缓存_05


优化结果是点击操作后,在profiler选择指定帧,观察产生的GameObject.Activate,都没有出现大量的GameObject.Active和GameObject.Deactive。具体的改动,是将新增的UI元素合入到现有DrawCall中,并优化了NGUI代码新增和删除Widget的操作,不在此展开。

加载和卸载方面

在制作界面时,需要考虑到界面的使用频率,对于不常用界面需要注意缓存级别。
一、日常经常使用的UI,预先加载,不卸载;
二、一旦加载就可能会在后面经常用到的UI,使用时加载,不卸载;
三、很少使用的UI,使用时加载,用完卸载。
此外,界面内部元素的加载和卸载也应该注意, 加载的时候,如果不是一定需要绘制的重资源,尽量动态加载(使用时再加载)。卸载的时候,对于大贴图是否应该清空引用,是常常会被忽略的问题。
比如第一时间不会绘制的大贴图、第一时间不会播放的特效,制作界面的占位图,都不要直接挂载在界面预制体上。而对于打开会造成卡顿明显的UI元素,可以考虑用分帧的方式去加载,比如,大贴图和背包道具,如果策划同意视觉上会有一个依次出现的过程,那么对打开时的性能压力会有所减少。
最后,对于数据的缓存,应该更多的关注到,如果是一个频繁的操作,计算能够前置的就尽量前置,将结果提前缓存,代替多次的频繁计算,因为往往这样的计算可能会有new的操作,造成不必要的浪费。

OverDraw方面

以上部分谈到的,主要是针对CPU端,而针对UI界面的GPU端的优化,需要关注的OverDraw。
所有被Canvas绘制的图形都会被放到透明渲染队列中,图形上的每一个像素都会被采样,即使它被另一个不透明的图形完全覆盖。Overdraw表示单位像素的重新绘制次数,Unity提供了查看Overdraw的视图,在Scene视图中选择RenderMode→Overdraw。直观上看,越亮的区域表示Overdraw的程度越高,也就越消耗GPU。
一般来说,同一时刻只有一个全屏显示,当打开全屏的时候,关闭上一个全屏页面,这样带来的好处是降低了OverDraw。


Unity3D UGUI 加载效果 unity加载界面制作_打开不出现界面_06


Unity3D UGUI 加载效果 unity加载界面制作_Unity3D UGUI 加载效果_07


如上图,可以观察到,如果打开全屏界面B时,没有关闭界面A,则overdraw模式下,界面变得更亮,overdraw变高了。

另外, 对于可九宫切图的底图,如果上方有其他图片遮挡住中间区域,可以根据情况设置该图片的fill center属性为invisible,这样中心区域就镂空不渲染,重合面积也会小。


Unity3D UGUI 加载效果 unity加载界面制作_Unity3D UGUI 加载效果_08