UWA

每天一点UWA:第四周

Posted by 蔡华的博客 on August 27, 2017

AssetBundle

Resources.UnloadAsset仅能释放非GameObject和Component的资源,比如Texture、Mesh等真正的资源。对于由Prefab加载出来的Object或Component,则不能通过该函数来进行释放。

用的是Resource的加载方式,并且已经预加载好了材质所在的Prefab ,但是为什么在第一次显示材质的时候还要Load这个材质?

  • 我的理解是prefab只是个资源GUID的合集,并不包含真正的资源。所以要使用真正的资源(mesh\texture等时要加载。)
  • 通过 Resources 加载和通过 AssetBundle 加载是有所区别的。Resources.Load 和 Instantiate 操作都不会立刻加载其依赖的材质,因此在loadPrefab = Resources.Load之后,其依赖的材质(包括相关的 Shader 和纹理)实际并没有被加载到内存中,在实例化后也是一样,直到某个 Camera 需要对其进行渲染时(调用了 Material.SetPastFast),才发现该材质还没进内存,此时才开始进行加载。
  • 因此,在使用 Resources 加载的情况下,如果希望提前加载该 Material 以及相关的 Shader 和纹理,可以尝试通过调用 Resources.Load 直接加载该材质和纹理,并通过 Shader.Find 来加载 Shader。也可以尝试通过 Player Settings 中的 Preload Shaders(配合 Graphics Settings 下的 Preloaded Shaders) 和 Preloaded Assets 来批量预加载。

Build

EditorOnly tag

  • prefab打上EditorOnly tag,但是放到了Resources文件夹下一样会打包进入,因为理论上只对场景中的GameObject有效。- 不放到Resources文件夹下,然后在使用EditorOnly tag,打包时就会认为没有场景中的物件引用prefab,就不会打包了。

内置的shader怎么打包?

  • 通常有两种方式对内置的Shader进行打包:
  • 将其添加到Graphics Settings中的Always Included Shaders 中,此时添加后的内置Shader就不会被打入AssetBundle包中;
  • 在http://unity3d.com/cn/get-unity/download/archive下载内置的 Shader,将其导入项目,并替换成非内置的材质球,从而可以直接通过脚本来控制其打包的方式。

GC.MarkDependencies的CPU消耗有过高,虽然在退出战斗的时候调用了Resources.UnloadUnusedAssets(); 可是卡顿还是很明显

  • GC.MarkDependencies的消耗是由Resources.UnloadUnusedAssets引起的。该函数的主要作用是查找并卸载不再使用的资源。游戏场景越复杂、资源越多,该函数的开销越大,一般在300~2000 ms范围内。
  • 对于该函数的优化,我们建议一方面控制场景中不必要的资源量,同时通过UnloadAsset来及时卸载不再使用的资源,以减少Resources.UnloadUnusedAssets的耗时压力。

打包时候AssetBundle的md5总变化(被打包的东西没变)

  • 对于Unity 4.x版本的AssetBundle文件,其md5值在某些情况下确实会前后不一致(哪怕是完全一样的内容进行打包)。对于该系列版本,仅能建议开发团队建立配置文件来对AssetBundle进行管理。
  • 而对于Unity 5.x版本,则可以在打包时开启 AppendHashToAssetBundleName 选项,这样Unity引擎会在每个AssetBundle文件后生成一个唯一的HashID(显示地放在文件名后),开发团队可以通过该ID来判断对应的AssetBundle文件是否发生改变。

CPU

第一次执行GameObject.Instantiate一些资源的时候会卡

  • Instantiate的卡顿与三部分开销相关:相关资源加载、脚本组件的序列化和构造函数的执行,并且绝大部分原因均是相关资源加载导致。所以,我们的建议如下:
    • 通过 Profiler 查看 Instantiate 具体的CPU分配情况;
    • 如果是资源加载导致的性能瓶颈,则一方面通过简化资源来缓解CPU耗时压力,另一方面通过 AssetBundle 依赖关系打包将资源预先加载,即将此处 Instantiate 的总体耗时拆分,平摊到之前帧进行执行(比如切换场景处等),从而让 Instantiate 实例化操作的局部耗时更加平滑;
    • 如果是脚本组件序列化导致的性能瓶颈,则可尝试减少脚本中的序列化信息; -如果是构造函数的执行导致的性能瓶颈,一般只能在策略上进行规避,比如降低 Instantiate 的调用频率等。

UI

本周的UI有很多不错的技术知识

能否对提升NGUI的渲染效率提供一些思路?

  • 开发团队可以从以下几点入手:
    • 通常一个Panel会产生1个或多个Draw Call,以一个Panel为单位,Draw Call 的数量通常由当前 Panel 中使用的Atlas、Font的数量所决定。
    • 要降低UI渲染时的 Draw Call数量则需要对 Atlas 的制作进行合理的规划,即在保证使用较少的 Atlas 的同时,还需要保证 Atlas之间不会存在交叉遮挡。
    • 要注意UI Texture的使用,每个UITexture自身会占用一个Draw Call,同时如果其Depth值穿插在了其他来自相同Atlas的UISprite中,还会导致Draw Call的打断,造成不必要的额外Draw Call。
    • 另外还可以借助Panel Tool和Draw Call Tool来对UI部分的Draw Call进行分析,前者可以显示每个UIPanel包含了多少个Draw Call,而后者可以显示每个Draw Call由哪些UIWidget组成。

如何查看每次Rebuild Batch影响的顶点数, Memory Profiler是个办法但是不好定位。

  • 5.2后开始使用Shared UI Mesh来存储UI Mesh,所以很难查看每次Rebuild的UI顶点数。可以尝试通过Frame Debugger工具对UI界面进行进一步的查看。

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

  • 通过移动的方式“隐藏”的UI界面只适用于几个切换频率最高的界面
  • 一般来说在没有UI元素变化的情况下,持续的 Update 开销是不太明显的。

游戏中出现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 也会始终存在于内存中(通常也不太大)。
  • PS:一个经典的场景,一个非常巧妙的答案。学到了。

在UI界面中,用Canvas还是用RectTransform做根节点更好?哪种方法效率更高?

  • 简单来说,因为一个Canvas下的所有UI元素都是合在一个Mesh中的,过大的Mesh在更新时开销很大,所以一般建议每个较复杂的UI界面,都自成一个Canvas(可以是子Canvas),在UI界面很复杂时,甚至要划分更多的子Canvas。
  • 同时还要注意动态元素和静态元素的分离,因为动态元素会导致Canvas的mesh的更新。最后,Canvas又不能细分的太多,因为会导致Draw Call的上升。
  • PS:总结来说就是不分子Canvas会导致mesh过大,分的太多dc过高,在于平衡。

使用File类来读取图片的bytes流,512x512的RGBA32格式图片使用了2MB内存

  • 首先正常的512x512的RGBA32图片的大小为4byte * 512 * 512 / 1024 / 1024 = 1MB,这个2MB是因为内存和显存各需要一份。file read的时候本身会产出一份,在向GPU发送的时候会备份一份,推测是这样。

  • 一般来说,比较建议通过AssetBundle来动态加载资源,而非通过bytes流来进行加载。如果你的项目正在使用这种方式来加载纹理,我们建议从策略上考虑将其更改。
  • 在我们目前来看,通过bytes流来生成资源,绝大部分原因是想对其进行加密,从而让资源难于破解。但其实这种加密方式用处不大,因为据我们所知,现在有很多工具可以直接通过底层显卡层来直接查看各种纹理、Mesh资源,比如Mali Graphics Debugger、Qualcomn Profiler等。因此,如果是从加密的角度来通过bytes流生成资源,那么我们建议通过AssetBundle这种直接的方式进行加载。

动画

CullCompletely

  • 使用 CullCompletely 在开启 RootMotion 时是需要注意的,比如人物有一个巡逻动画是通过 RootMotion 制作的,那么在人物走出屏幕后,其动画就停止了,即不会再走回屏幕中。

渲染

场景中一个点光源,烘焙前暗,后亮是为何?

  • 简单来说,这就是实时的直接光照和全局光照的差别。 在渲染时前者只能处理光源和单个物件之间的直接光照,而后者在烘焙时是通过光线跟踪或者辐射度等复杂算法,计算出所有物体各个表面之间相互反射的光照信息,这也是烘焙Lightmap需要较久的时间的原因 。可以发现在全局光照下,即使是物体的背面也会因为其它表面的反射而被照亮,这在直接光线下就无法实现这样的效果。

相同效果前提下,就性能而言,Shader 是用 V&F 还是Surface好?

  • V&F,Surface生成的V&F比较庞杂,分支较多,如果不注意 #pragma surface 参数的选择,容易出现不必要的开销。

大面积的面片会导致baked GI时间过久,拆分大面积的面片对渲染性能也会有所提升

Handheld.PlayFullScreenMovie

  • 首先这天放在这里似乎不合适。
  • Android上PlayFullScreenMovie 的实现实际上是通过Android原生的接口直接播放的,播放过程中Unity也是停止更新的,因此这部分的内存理论上并不会记录在 Unity 中,同样也不影响mono。