UWA

每天一点UWA:第七周

Posted by 蔡华的博客 on September 10, 2017

UI

NPOT采用 ToLarger 的模式拉成POT,如果是UI界面(开启Pixel Perfect)的话,可能显示时会有较大视觉损失。

在Unity 5.x版本下,我们在用UGUI的过程中发现它把图集都打进了包里,这样就不能自动更新了,请问图集怎么做自动更新呢?

  • 在Unity 5.x中UGUI使用的Atlas确实是不可见的,因此无法直接将其独立打包。
  • 建议,可以把Packing Tag相同的源纹理文件,打到同一个AssetBundle中(设置一样的AssetBundle Name),从而避免Atlas的冗余。同时这样打包可以让依赖它的Canvas的打包更加自由,即不需要把依赖它的Canvas都打在一个AssetBundle中,在更新时直接更新Atlas所在的AssetBundle即可。

ScrollRect在滚动的时候,会产生Canvas.SendwillRenderCanvases,有办法消除吗?

  • ScrollRect在滚动时,会产生OnTransformChanged的开销,这是UI元素在移动时触发的,但通常这不会触发Canvas.SendWillRenderCanvases。
  • 如果观察到Canvas.SendWillRenderCanvases耗时较高,可以检查下ScrollRect所在的Canvas是否开启了Pixel Perfect的选项,该选项的开启会导致UI元素在发生位移时,其长宽会被进行微调(为了对其像素),而ScrollRect中通常有较多的UI元素,从而产生较高的Canvas.SendWillRenderCanvases开销。因此可以尝试关闭Pixel Perfect看效果是否可以接受,或者尝试在滚动过程中暂时关闭Pixel Perfect等方式来消除其开销。

对NGUI字体错乱有什么好的解决方案吗?

  • 有这么几种可能:
    • 一次展开文字太多了。这种情况在部分高通机型和Unity早期版本上都经常出现,现在也偶尔有,究其原理是FontTexture的扩容操作做得不够快或者收到了硬件驱动的限制。
    • 一般来说有两种方法可以解决:(1)减少面板中的字体内容;(2)一开始就用超大量的字体去扩容,将动态字体的FontTexture扩大到足够大;文字渲染与开发团队编写的多线程渲染发生了冲突。这种情况也常有发生,特别是通过GL.IssuePluginEvent方式来开启多线程渲染的项目,就会容易出现问题。
  • 就我们的优化经验来看,第一种情况发生的可能性比较大。

工具

Draw Call和Setpass Call,这两个指标主要是看哪一个?关于这点众说纷纭,很多地方都是说看SetPass Call,但是在UWA的性能测试中,还是把Draw Call当成唯一指标。

  • 在 Unity 5.x 中,SetPass Call与 Draw Call相比,SetPass Call的指标与性能相关性更大(比如Static Batching的开启不影响Draw Call数,而SetPass Call通常会明显下降)。但 SetPass Call在某些情况下也同样存在问题,比如往一个场景中添加任意个相邻且材质相同的大网格物体(使Dynamic Batching失效)时,SetPass Call并不会变化。因此在UWA中,我们所使用的是类似Profiler 中 Total Batches 这一项指标,通常该数值与 Frame Debugger 中的数值基本一致,因此可以通过该工具来查看每个Batch的内容,从而更有针对性地进行优化。

脚本

Editor 下,当调用GetComponent() 且 T组件并不在当前的GameObject 上时,确实会出现GC Alloc。

  • 首先在发布后是不会出现的。
  • 这是因为在Editor下,Unity的MissingComponentException实现所致,在出现以上情况时,Unity 并不是直接返回一个 NULL,而是返回一个代理 Object用来储存一些相关信息,在后续被访问时可以给出更详细的报错信息。

EventSystem.Update()占用过高怎么优化?

  • EventSystem.Update 的开销分为两个部分:
    • 来自被触发的响应事件,这部分的开销实际上并不属于事件系统本身,比如:某次点击触发了一个开销很高的逻辑函数,尤其是涉及到资源的加载和实例化。对于这类开销,我们建议通过Profiler.Begin/EndSample或者UWA提供的UWA API,对这些逻辑函数进行进一步的分析。

    • 事件检测的开销,在默认情况下所有的UI元素都是事件检测的目标,因此当界面上UI元素数量很多,且长时间接触屏幕时,会有较高的持续开销。对于这类开销,我们建议通过以下几种方式来优化:

      • 合理划分子Canvas,只有需要检测事件的界面才需要挂上GraphicRaycaster组件。
      • 在Unity5.2之后的版本中,尽可能将不需要参与事件检测的UI元素的 Raycast Target 属性的勾选去掉。而对于Unity 4.x的版本,则可以尝试对UI部分的源码来进行修改以达到类似的效果。

模型&动画

动态替换Animator Controller后删除旧的Animator Controller,但是其中的动画片段资源没有被删除。

  • 销毁Animator Controller并不会释放其内所引用的AnimationClip资源的,所以内存没有明显下降是正常的。建议按照以下方式做个试验:
  • 销毁Animator Controller 后,通过Unity Profiler立刻真机Take Sample,查看Animation Clip的Ref count是否为0;
  • 如果不是,则需要进一步查看这些资源的索引出处;如果为0,则可以通过UnloadUnusedAssets或UnloadAsset来将其从内存中去除。
  • PS:在unity中很多对象只是个包装器,或者只是包含了其它资源的引用,因此消耗这些包装器并不会真正的销毁资源对象。

渲染

我使用Shader.WarmupAllShaders操作,在后续加载资源还是有CreateGPUProgram出现。(Shader都在一个AssetBundle文件中,都是常驻内存的,不会删掉)是必须使用ShaderVariantCollection来加载Shader吗?

  • WarmupAllShaders仅能对当前内存中的Shader进行warm up。后续如果又有Shader加载进来,则仍然会出现CreateGPUProgram操作。
  • 所以如果出现问题中的现象应该还是有shader打包到别的AssetBundle中了。

Material.SetPassFast占用很高,这是在第一次实例化一个特效,但是第二次实例化就不会出现高值了,请问能怎么优化吗?

  • 该过程是在处理Shader,Unity 5.3以后在第一次显示时才会将Shader进行Warmup,所以就会造成一次峰值卡顿。
  • 上面那个问题就很好的解决了这个问题,加载完shader后可以主动调用一次Shader.WarmupAllShaders函数。

在Unity 3D中,shader是何时编译的,在何时加载入显存中的(本条来自知乎)?

  • Editor中:修改shader并保存时立即编译。
  • Runtime下,无论哪个平台,都是在进入场景时加载shader object内容到内存,但是首次实际调用渲染时才编译,编译完成之后会cache下来。
  • 有两种优化方法:
      1. 调用Shader.WarmupAllShaders(),自动编译该场景中用到的所有shader。该方法在Unity5中已经废弃。
      1. 在项目设置的GraphicsSettings中,可以导出ShaderVariantCollection,并在Preloaded Shader中导入,这样的话就可以在载入场景时一并编译需要预加载的shader,这样的优化在移动平台上用得比较多。
    • 具体见