UWA

每天一点UWA:第十七周

Posted by 蔡华的博客 on December 2, 2017

AssetBundle

当一个Texture被一个material和sprite都引用的时候,将三者分别打包,出现texture内存重复问题。

  • 原文较长,总结一下就是:如果一个贴图一方面被当成Sprite使用,另一方面被当成Texture使用(譬如RawImage或者Material),就会断开引用产生多份。目前解决办法是保证用Sprite的Prefab及Material打包在一个AssetBundle。
  • 这是一个重要的问题,而且这个问题也是看unity版本的。

使用AssetBundle.Unload(true)释放AnimationClip , 但发现AnimationClip在Profiler看到还存在,请问要怎样才释放干净呢?

  • 如果这个AnimaitonClip是从AssetBundle中加载过来的,那么当这个AssetBundle调用unload(true)时,该AnimationClip理论上是会被强行卸载的。如果Profiler中查看还有,那么只有两种情况:
    • 仍然存在的AnimationClip是从其他地方加载来的,而不是来自于该卸载的AssetBundle;
    • Unity引擎的Bug,这种概率不是没有,但很小。

在打包的时候,有一部分资源是没有设置AssetBundleName的,打包的时候会和依赖它们的资源打到同一个AssetBundle包里(Unity自动完成)。在资源加载的时候,无法主动获取到这些没有设置AssetBundleName的资源。GetAllAssetNames、LoadAllAssets这些接口的返回值中都没有这部分资源。

请问,在你们看来,这种情况是Unity的设计还是Bug?如果是如此设计的话,有什么特别的意义吗? 提供一个简单的情景,如下: 1)有A、B两个Prefab,其中A上挂个脚本引用了B 2)打包的时候,设置A的AssetBundleName=”prefab.unity3d”,B的AssetBundleName=None 3)打包,只产生prefab.unity3d这一个AB包,其中包含了A、B两个资源 4)加载代码使用AssetBundle.LoadAllAssets(),返回的数组里只有一个资源(A)

  • 我们刚刚在Unity 5.5.2版本上进行了一个测试,得到与题主一样的结论。 我们更加倾向于它是一种设计,而非Bug。
  • 因为Prefab B是作为一种参数被引用在A的脚本中,所以在AssetBundle中,它将以GameObject B的形式存在,但是不会存在于AssetBundle的映射表中。
  • 而对于题主的这种打包方式而言,AssetBundle的映射表中只会有一个,就是Prefab A。而AssetBundle.LoadAllAssets虽然会把GameObject A、B及其关联的资源全部加载,但其返回的Object[]内容应该就只有一个,也就是Prefab A。
  • 所以,UWA推测,AssetBundle中在打包时是自己维护了一个map的,只有明确被设置ABName的资源才会被放入map中,也只有map中的资源才可以通过特定名称进行Load,而其关联的资源则会被打包到AssetBundle中的其他container中,是无法通过LoadAsset或LoadAllAssets API来获取到的。
  • PS:==这是一个需要注意的问题==

AssetBundle包字体bitmap A的使用到AssetBundle包Altas B时B冗余

  • 出现冗余是因为vip和betaCommon两个Prefab实际上并没有建立其依赖关系(虽然mainfest上注明了存在dependencies)。
  • 问题解决方式,只需要将BetaCommon Material设置为一个“显式”的AssetBundleName,那么冗余问题就不会存在了。
  • PS:让bitmap A的material被显示打包。

Font

Unity 5.5版本下的UGUI,我将字体设置为粗体,发现字体只是变宽不加粗,不像正常的粗体,如下图所示。另外,TestMesh Pro需要预渲染,不能用动态字体,只能用于特定范围,有像NGUI(效果还不错)那种对动态字体加粗的方法么?

  • 动态字体加粗显示的外观是和字体本身有关系的,如果字体中没有包含“粗体”的字形,那么Unity会通过拉伸来“模拟”加粗,因此得到的效果是有问题的。
  • 而这个行为在UGUI和NGUI中是一样的。因此,如果要确保加粗显示正确,则需要使用包含了“粗体”字形的字体,甚至需要考虑是否将其include到发布包中(因为某些设备中的内置字体也有可能被精简过)。

Texture

UGUI自动打包图集时,有时候同一个Tag会自己打出多个Group图集,导致DrawCall增加,有什么解决方法吗?这个分Group的原理是什么?

  • 产生Group的原因主要有两种:
    • 纹理的格式不同 。举例来说,有这样四张纹理格式分别为:RGB24,ETC1,RGBA32和ETC2,那么设置一样的Tag后,对应的图集就会有四个Group。
    • 纹理量太大。一个Group放不进,这个原因是容易理解的,就不解释了。
    • 其中特别容易忽略的一点是,某些小纹理可能没有Alpha通道,导致了图集被分成两个Group,引起DrawCall的增高,这种情况下可以直接修改纹理,也可以强制设定为Aalpha通道的格式。

UI

请问UGUI Image 在切换Sprite(网络图片) 的时候有卡顿,从Profiler中看到具体卡在SpriteMeshGenerator.TraceShape 这个方法上,如下图所示,有没有什么优化的方法呢?

image

  • 图中的==SpriteMeshGenerator.TraceShape/Decompose/Simplify==的函数,通常出现在加载或者创建(Sprite.Create)SpriteMeshType为==Tight类型==的Sprite时,==Tight类型的Sprite在加载或创建时,需要检测图片的alpha区域从而生成多边形==。计算量较大,建议将其改为FullRect模式。

工具

Unity Profiler中如下函数的耗时异常高,请问是什么原因导致呢?这些函数分别是什么意思,有什么具体优化方法呢?

  • Profiler.FinalizeAndSendFrame: 这个是Unity Profiler在记录和传输性能数据的开销,研发团队可忽略,因为在release版本中,该项并不存在;
  • WaitForJobGroup:是主线程在等待子线程完成的耗时开销,如果该项较高,那么说明该帧中某子线程的开销很大。就目前我们优化过的项目而言,绝大部分均为UGUI在子线程的开销所致。更多的参考资料建议研发团队参考UWA问答之前的记录: https://answer.uwa4d.com/question/search?q=waitingforjob
  • Camera.Render:是Unity引擎的主要渲染函数,其中负责了绝大部分场景的渲染工作

Gfx.ProcessCommands包含哪些行为操作?

  • 该行为是在RenderThread中,导致该行为耗时的主要原因是DrawCall。
  • 另外过程耗时统计还包括顶点、材质贴图以及Shader等从内存到GPU的IO时间。
  • 即使是多线程渲染,图形API的调用也需要在同一个线程中。其一,是Android系统的EGLContext一般不是线程独共享的,也就是只有一个线程能向同一个EGLContext里面发送GL指令。其二,如果使用共享的EGLContext,多个线程都能提交图形API,渲染的结果很难保证正确性。比如:如何保证线程A绑定了VBO或者Texture之后,线程B提交DrawCall时一定是它需要的呢。综上所述,我们认为这些行为应该都在一个线程里面执行的。

模型和动画

New出来的Mesh好像都是可读写的,在我填充完数据以后,有什么办法能把这个Mesh改为只读么?

  • 通过public void UploadMeshData(bool markNoLogerReadable);来设置。

请问我一个MeshRender的显示层级能不能夹在一个Canvas下的两个UI节点下?我通过设置MeshRender.sortingOrder数值在两个Image的Render的sortingOrder数值之间 ,是否能做到呢?

  • Image的sortingorder设置通常是无效的,因为UGUI会对DrawCall进行合并,调整其渲染顺序。因此,需要将Mesh前后的UI元素拆分到不同的Canvas中,从而直接设置Canvas的sortingorder。

物理

在游戏里没有使用物理(Physics)相关功能时,怎么把物理相关的性能消耗降到最低?我们游戏里完全没用到物理相关的功能,只是因为需要做点击碰撞检测,所以GameObject上需要加上BoxCollider组件,但是Profile时会时不时看到Physics.Processing的消耗有点高,也不算非常高,但是理论上如果完全没用物理相关功能的话,总感觉这部分消耗是可以完全干掉的,对于Profiler中Physics模块中给的几个统计不太清楚是怎么计算的,目前使用的Unity版本是5.3.6f1版本。

  • 就我们目前的分析来看,在Unity5.4版本之前,物理模块是会每帧都运行的。而在Unity5.4版本之后,如果没有使用任何物理相关的功能时(Rigidbody、CharacterController、Rogdoll、Cloth模拟等等),物理模块会被关闭。所以,就你的问题而言,物理模块的开销只能降低,而不能完全消除。
  • 对于物理模块的开销,我们建议从宏观上进行了解,当你的项目完全没有使用物理模块时,那就需要从它的调用次数入手了。
  • Physics.Processing调用次数高:可以看到,很多时候,该函数的CPU耗时其实并不是物理开销过高,而是调用次数过多。这主要是因为Unity 5.x默认设置的Fixed Timestep为0.02,Maximum Allowed TimeStep为0.333。也就是说,物理模块每20ms更新一次,所以如果某一帧很卡(200ms),那么物理模块会被调用10次,这样耗时就直接上去了。而0.333表示如果该帧CPU开销超过333ms了,那么就不会再调用物理模块,所以上图中调用次数中最大是17次。
  • 所以,如果想进一步降低物理模块的开销,在完全没有使用物理的情况下,可以将Fixed Timestep设置为0.05或0.1均可,降低它被调用的频率。同时,尽可能优化其他模块耗时,让每帧的总体耗时尽可能降低。
  • 另外,需要注意的是,修改Fixed Timestep也会影响FixedUpdate的调用,在修改之前一定要检测项目中是否有使用FixedUpdate。

性能综合

MonoBehaviour中包含了被加载出来的Prefab上的组件(通过Load接口加载出来的),而这部分组件只能在Prefab被销毁的时候才会释放。

工程里还是选择只使用 OpenGL ES 2.0吗?iOS 是否该加上 Metal?

  • 目前都是用的Auto,主要有两个原因:
    • Metal测试下来CPU Overhead会比GLES低很多
    • GLES3能够有tex2Dlod支持 兼容性上来说只有实在老的机器和模拟器会fallback到GLES2,可能效果上会略差一些不过我们测试下来都可以接受。
    • 选ES3.0还有一个原因,就是发现AssetBundle打出来的资源会比选Auto小很多。
    • PS:如果游戏面对的是高端机玩家可以放弃ES2

切换UI场景使用AssetBundle加载慢,怎么优化界面进入速度,是应该预加载吗?

  • 界面加载慢主要的原因有,图集纹理的加载以及大量UI元素的实例化操作。
  • 针对图集的加载,可以尝试合理的规划图集,尽量控制界面所引用的图集数量(即使用到了某个图集中的一个Sprite,也会加载整个图集);
  • 可以尝试对公共图集进行预加载,通常公共图集较大,且被使用的概率很大。
  • 针对大量UI元素的实例化,这项开销大通常只发生在背包等复杂的界面中,而对于这类复杂界面可以考虑进行分步实例化,即首先实例化如外框、容器等部分的UI元素,然后分帧实例化背包中的UI元素,从而提高界面打开的速度以及流畅性。
  • PS: ==分帧是关键。在FPS60的情况下,只要100ms内响应即可。一帧是16.7,也就是说5~6帧做完就可以了。==

渲染

Unity官方半透明Shader代码在iOS上运行出现问题

  • PS:遇到了看https://blog.uwa4d.com/archives/TechSharing_70.html