UI输入
Q1:在使用NGUI时,我们通常会将很多小图打成一个大的图集,以优化内存和Draw Call。而在UGUI时代,UI所使用的Image必须是Sprite;Unity提供了SpritePacker。 它的工作流程和UGUI Atlas Paker有较大的差别。在Unity Asset中,我们压根看不到图集的存在。 问题是:
1. SpritePacker大概的工作机制是什么样的?
2. 如果Sprite没有打包成AssetBundle,直接在GameObject上引用,那么在Build时Unity会将分散的Sprite拼接在一起么?如果没有拼接,那SpritePacker是不是只会优化Draw Call,内存占用上和不用SpritePacker的分离图效果一样?
3. 如果将Sprite打成AssetBundle,AssetBundle中的资源是分散的Sprite吗?如果不是,不同的AssetBundle中引用了两张Sprite,这两张Sprite恰好用SpritePacker拼在了一起,是不是就会存在两份拼接的Sprite集?
4. 如果想使用NGUI Atlas Packer的工作流程,改如何去实现?
-
简单来说,UGUI和 NGUI 类似,但是更加自动化。只需要通过设定 Packing Tag 即可指定哪些 Sprite 放在同一个 Atlas 下。
-
可以通过 Edit -> Project Settings -> Editor -> Sprite Packer 的 Mode 来设置是否起效,何时起效(一种是进入 Play Mode 就生效,一种是 Build 时才生效)。所以只要不选 Disabled,Build 时就会把分散的 Sprite 拼起来。
-
可以认为 Sprite 就是一个壳子,实际上本身不包含纹理资源,所以打包的时候会把Atlas 打进去。如果不用依赖打包,那么分开打两个 Sprite 就意味各自的AssetBundle 里都会有一个 Atlas。
-
可以通过第三方工具(如 Texture Packer)制作 Atlas,导出 Sprite 信息(如,第 N 个 Sprite 的 Offset 和 Width,Height 等),然后在 Unity 中通过脚本将该 Atlas 转成一个 Multiple Mode 的 Sprite 纹理(即一张纹理上包含了多个 Sprite),同时禁用 Unity 的 Sprite Packer 即可。
两种做法各有利弊,建议分析一下两种做法对于自身项目的合适程度来进行选择。
UI输入
Q2:我们在编译安卓版本时,在某些设备上(Sony L36h,小米4)调试时,发现有一个时间消耗项叫Graphics.PresentAndSync,该函数对性能的消耗会特别夸张(渲染20毫秒,这个能达到50毫秒)。查了相关文档,发现该函数好像和安卓设备的垂直同步有关,但是大部分安卓设备的垂直同步是不可以关闭的。请问有什么好的办法解决吗?
A:概括来说,该值很高表示 GPU 负担很重,可以从降面,或者简化shader入手。
Graphics.PresentAndSync 是指主线程进行Present时的等待时间和等待垂直同步的时间。该参数在Profiler中CPU占用通常较高,且仅在发布版本中可以看到。究其原因,其实是CPU和GPU之间的垂直同步(VSync)导致的,主要是与项目是否开启多线程渲染有关。当项目开启多线程渲染时,你看到的则是Gfx.WaitForPresent;当项目未开启多线程渲染时,看到的则是Graphics.PresentAndSync。
其中的原理,可以参照我们之前对其函数的详细解释:扒一扒Profiler中这几个“占坑鬼”。
UI输入
Q3:我们使用UGUI,Sprite一般都选择默认的格式了,Compressed,但是在iOS设备下就会糊,用TrueColor就很大,请问一般该如何处理呢?
A:这确实是没办法的,如果选 Compressed (默认会选择 PVRTC 格式)比较糊,那么可以尝试 RGB16,如果还是觉得糊,只能尝试 RGB32。
一般来说,渐变色在压缩时会明显失真,可以尝试避免,这其实是内存和效果的权衡,并没有最优的方案。
UI输入
Q4:UWA性能检测报告中的Shared UI Mesh表示什么呢?
Shared UI Mesh是在Unity 5.2 版本后UGUI系统维护的UI Mesh。在以前的版本中,UGUI会为每一个Canvas维护一个Mesh(名为BatchedMesh,其中再按材质分为不同的SubMesh)。而在Unity 5.2版本后,UGUI底层引入了多线程机制,而其Mesh的维护也发生了改变,目前Shared UI Mesh作为静态全局变量,由底层直接维护,其大小与当前场景中所有激活的UI元素所生成的网格数相关。
一般来说当界面上UI元素较多,或者文字较多时该值都会较高,在使用UI/Effect/shadow和UI/Effect/Outline时需要注意该值,因为这两个Effect会明显增加文字所带来的网格数。
UI输入
Q5:能否对提升NGUI的渲染效率提供一些思路?
开发团队可以从以下几点入手:
1、通常一个Panel会产生1个或多个Draw Call,以一个Panel为单位,Draw Call 的数量通常由当前 Panel 中使用的Atlas、Font的数量所决定。2、要降低UI渲染时的 Draw Call数量则需要对 Atlas 的制作进行合理的规划,即在保证使用较少的 Atlas 的同时,还需要保证 Atlas之间不会存在交叉遮挡。
3、要注意UI Texture的使用,每个UITexture自身会占用一个Draw Call,同时如果其Depth值穿插在了其他来自相同Atlas的UISprite中,还会导致Draw Call的打断,造成不必要的额外Draw Call。
4、另外还可以借助Panel Tool和Draw Call Tool来对UI部分的Draw Call进行分析,前者可以显示每个UIPanel包含了多少个Draw Call,而后者可以显示每个Draw Call由哪些UIWidget组成。
UI输入
Q6:我用的是UGUI Canvas,Unity 5.3.4版本,请问如何查看每次Rebuild Batch影响的顶点数, Memory Profiler是个办法但是不好定位。
由于Unity引擎在5.2后开始使用Shared UI Mesh来存储UI Mesh,所以确实很难查看每次Rebuild的UI顶点数。但是,研发团队可以尝试通过Frame Debugger工具对UI界面进行进一步的查看。
UI输入
Q7:在UI界面中,用Canvas还是用RectTransform做根节点更好?哪种方法效率更高?
Canvas划分是个很大的话题。简单来说,因为一个Canvas下的所有UI元素都是合在一个Mesh中的,过大的Mesh在更新时开销很大,所以一般建议每个较复杂的UI界面,都自成一个Canvas(可以是子Canvas),在UI界面很复杂时,甚至要划分更多的子Canvas。同时还要注意动态元素和静态元素的分离,因为动态元素会导致Canvas的mesh的更新。最后,Canvas又不能细分的太多,因为会导致Draw Call的上升。我们后续将对UI模块做具体的讲解,尽请期待。
UI输入
Q8: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);
从而卸载这部分多余的内存。
UI输入
Q9:UGUI里的这个选项 ,应该是ETC2拆分Alpha通道的意思,但是在使用中并没起作用?请问有没有什么拆分的标准和特别要求呢?
据我们所知,目前 alpha split 的功能只对 Unity 2D 的 Sprite(SpriteRenderer)有完整的支持,而UI的支持仍在进行中。因此暂时我们不建议大家在 UGUI中尝试该功能。
UI输入
Q10:动静分离或者多Canvas带来性能提升的理论基础是什么呢?如果静态部分不变动,整个Canvas就不刷新了?
在UGUI中,网格的更新或重建(为了尽可能合并UI部分的DrawCall)是以Canvas为单位的,且只在其中的UI元素发生变动(位置、颜色等)时才会进行。因此,将动态UI元素与静态UI元素分离后,可以将动态UI元素的变化所引起的网格更新或重建所涉及到的范围变小,从而降低一定的开销。而静态UI元素所在的Canvas则不会出现网格更新和重建的开销。