UWA

每天一点UWA:第三周

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

内存

资源复用

  • 如果一个prefab被简单的多复制几次,里面的mesh、Texture、AnimationClip、Material这些都是被复用的,在profile中可以看到。但是如果prefab从AssetBundle中获取,并且多次的加载、实例化、卸载可能造成内存泄漏,因为unload(false)并没有真正卸载内存中的已经实例化的prefab,再次load时PersistentManager会认为是一个新的对象。

UI在多次开关后出现profile中资源重复

  • 刚进游戏时获取的图中出现的“重复”资源可能并不是冗余,因为 Atlas的一个 Group 中可能包含多张一样大小的Page(即纹理),而这几个Page在内存中的名字是一样的。
  • 但是,如果同一UI界面多次开启后,内存中出现了更多同样的资源,则说明UI的管理方式存在一定问题。对于频繁使用的UI,我们建议在加载之后通过缓冲池对其进行缓存,后续使用时,直接通过缓冲池获取即可。而不要每次均通过AssetBundle进行加载,这种做法既会造成更大的CPU占用,同样会很大几率造成资源的冗余。
  • 同时,如果多次开启的是不同UI界面,并且造成内存中同种资源的增加,则很有可能是UI在AssetBundle打包时形成了冗余(这种情况在目前的UGUI系统中较为常见)。对此,如果开发团队使用的是UGUI,那么我们的建议如下:
    • 对于使用Unity 5.x的新AssetBundle打包系统,则打包时尽可能将同种Atlas的UI界面打成一个AssetBundle文件,否则将很有可能出现资源冗余的情况;
    • 对于使用Unity 4.x的老AssetBundle打包系统,则可以将一个含有Atlas的Prefab(或其他Object)先打包,其他UI元素对其进行依赖即可。

物理

RigidBody + CapsuleCollider替换CharacterController

  • 高效一些,因为 move 函数本身会进行较复杂的模拟运算。

Static Collider.Move (Expensive Delayed cost)警告

  • Static Collider.Move (Expensive Delayed cost) 意味着场景中有静态碰撞体(不带有RigidBody的Collider)发生了移动,而该操作在Unity 4.x版本的PhysX 中会在后续的模拟中产生较高的开销,但在 5.x 中理论上开销并不明显。如果要消除该问题,只需定位到发生位移的Collider并挂上RigidBody 组件,打开其is kinematic 选项即可。

渲染

什么在影响lightmap尺寸

  • 首先确认下Lightmap的类型,Single类型只生成一张,而Dual和Directional会生成两张。
  • 其次,确认下当前的发布平台,Android下的Lightmap会比Standalone更小。因为不同平台采用的压缩格式不同。此外,Lightmapping中的Lock Atlas,Resolution,Padding等选项也会影响最后烘焙光照图的大小。

原先的Lightmap不再影响合并后的Mesh

  • 总结一句就是不要讲使用了多个lightmap的mesh进行合并。

关于抗锯齿和BLOOM,有什么好的优化方案或者优秀插件推荐?

  • 通常在中低端的设备上,抗锯齿并没有比较高效的方案;而对于中高端的设备,可尝试直接使用 Unity 内置的 MSAA 功能,但也只推荐使用 2x。 关于Bloom效果,以下是适用于移动端,且评价较好的一款插件:BloomPro

AssetBundle

5.x后设置好AssetBundle Name就可以实现依赖资源的独立打包

4.x时代不同版本之间的AssetBundle不能通用

unload顺序

  • 在 Resources.UnloadUnusedAssets() 时,如果还没有进行AssetBunlde的Unload 操作,那么从AssetBunlde中加载的资源依然会因为被AssetBunlde引用而无法被卸载。
  • 开发团队可以尝试 Destory 后做 AssetBunlde的Unload,最后进行 Resources.UnloadUnusedAssets()。

生成AssetBundle的时候每个文件会多生成一个Manifest文件,这个文件也需要一起随着AssetBundle上传吗,在资源加载的时候具体怎么用呢?

  • 每个文件多生成的Manifest 文件是不需要上传的,其作用就是供开发人员查看AssetBundle 中的依赖关系等信息。
  • 但除了每个文件多生成的 Manifest 以外,根目录下还会有一个与根目录同名的AssetBundle 以及 Manifest 文件,通过运行时加载这个AssetBundle,可以得到一个 AssetBundleManifest 对象,然后就可以通过这个对象得到AssetBundle直接的依赖关系。

选择移动平台后,editor下shader显示错误

  • 是Unity已知的一个问题,Android 和 iOS 的部分Shader在打包后,在Editor 下无法正常显示。 主要原因是在打包时,只会把对应平台的Shader预编译代码(如 gles )打入包中,因此在 Editor 下会执行失败(通常 Editor 是 d3d 驱动)。 因此,目前只能尝试在Editor下重新指定Shader来绕过这个问题。

Texture

ios上的ASTC格式

  • ASTC在 iOS 的高端机上是被支持的,因此理论上在 Editor 下不会强制把 ASTC 转为 RGBA32,建议尝试设置为 ASTC 后打包,从 Editor.log 中或者直接从包体大小上可以看出是否确实使用了ASTC。
  • 一般来说,如果 RGBA16 的效果可以接受的话,建议使用 RGBA16,虽然打包时相对大一些,但是内存中相比 RGBA32 能够减半,但使用 ASTC 的话,虽然打包时比较小,但是在普通机型上会被处理成 RGBA32,导致过大的内存开销。

UI

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会明显增加文字所带来的网格数。

Build

打包注意

  • 打包的时候Resources文件夹下所有的内容都会被打入resources.assets,要想包体小就删除不要的资源。而且Resources目录下所以资源启动时参与persistentmanager的初始化,内容多了会卡。

static选项

  • 如果在Editor中进行勾选,则会在项目中生成一个较大的VBO,Runtime时通过该VBO来进行渲染,优点是有效减少了Draw Call,缺点是增大了发布游戏包的体积。
  • 如果在Runtime通过脚本来进行Batching,则相当于把拼合的时间由Editor中搬到了Runtime,所以加载时间(一般在场景加载时执行Batching)会稍有增加,但游戏包的体积将相应减少。

动画

Optimize GameObject

  • 在选择Optimize GameObjects后,可在Extra Transforms中加入你想挂载特效的骨骼结点,这样该骨骼结点将不会进行优化处理,即其Transfrom将不会消失。

工具

WaitForTargetFPS

  • 该参数一般出现在CPU开销过低,且通过设定了目标帧率的情况下(Application.targetFrameRate)。当上一帧低于目标帧率时,将会在本帧产生一个WaitForTargetFPS的空闲等待耗时,以维持目标帧率。
  • 该项在Unity引擎的主循环中其实是最早执行的。

Gfx.WaitForPresent & Graphics.PresentAndSync

  • 这两个参数在Profiler中经常出现CPU占用较高的情况,其实是CPU和GPU之间的垂直同步(VSync)导致的,之所以会有两种参数,主要是与项目是否开启多线程渲染有关。当项目开启多线程渲染时,你看到的则是Gfx.WaitForPresent;当项目未开启多线程渲染时,看到的则是Graphics.PresentAndSync。
  • Gfx.WaitForPresent其真实的意思应该是为了在渲染子线程(Rendering Thread)中进行Present,当前主线程(MainThread)需要等待的时间。
  • 最后,如何优化并降低这两个参数的CPU占用呢? 那就是,忽略Gfx.WaitForPresent 和 Graphics.PresentAndSync这两个参数,优化其他你能优化的一切!WTF!
  • PS:这篇内容相当不错,建议看原文

editor下出现AssetDatabase内存过高可以无视

Canvas.SendWillRenderCanvases

  • Canvas.SendWillRenderCanvases为UGUI中非常重要的接口,经常会出现较高的性能开销。当Canvas中的UI元素出现了长、宽或Alpha变化时,UGUI会更新其所在Canvas中所有UI元素的Transform、状态等等。Canvas中UI Mesh顶点较多的话,则该项将会出现较高的CPU开销。

Loading.UpdatePreloading

  • Loading.UpdatePreloading为Unity引擎的主要加载函数。场景中的资源加载(包括Texture、Mesh、Shader、AnimationClip等)和相关序列化操作均在其中体现。因此,如果该值开销较高,建议研发团队对资源进行进一步的优化和控制。

脚本

如果脚本引用了GameObject,那转换场景的时候脚本和GameObject都没了,还会产生堆内存的吗?

  • 如果脚本是MonoBehaviour,而且在切换场景后所挂的Game Object被释放了,那么这个脚本对象所引用的堆内存就会在GC的时候被释放。 但有一种例外,如果是通过Static变量引用的堆内存,那么依然是释放不掉的,除非手动解开引用,比如变量置Null,数组Clear等等。

粒子

粒子动态合并

  • 粒子系统的Draw Call动态拼合与半透明物体的动态拼合机制相当(粒子基本都是半透明材质)。而对半透明物体,由于其渲染顺序的限制(必须从后向前渲染,以保证渲染结果的正确性),动态拼合只能对渲染顺序相邻且材质相同的物体有效。而在决定半透明物体的渲染顺序时,Unity首先会按Shader中的RenderQueue进行排序;其次(相同RenderQueue时),会根据每个半透明物件到屏幕的距离,距离大的优先渲染。
  • 因此,需要尽可能地将相同材质的粒子系统放在比较接近的深度下,才能更多地使动态拼合生效。但通常由于相机的运动、粒子系统的分散分布等原因造成粒子系统之间的穿插,能够动态拼合的数量往往都是很少的。
  • 目前粒子系统已经不再进行 Draw Call 的拼合,因为在新版本5.3 中已通过多线程进行更新,暂时无法支持拼合,但性能已经得到提升。