UWA

每天一点UWA:第十八周

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

AssetBundle

最近的项目使用了AssetBundle的资源管理方案,对于Shader的部分全部放到了Always include里面(这里不仅是系统内置的Shader,还包括自定义的Shader),然后打Bundle资源的目录里是不包含任何Shader的。但是现在发现,Shader还是被作为依赖关系打进了最终的Bundle里,而且造成了冗余,问问大家有没有遇到这个情况呢?我的Unity版本是5.3.4f1。

  • 首先,只要资源在工程里并且需要用到它,就会被打包进AssetBundle。
  • 然后Always include里built-in Shader不会被打包进AssetBundle,这是一个特例。这个特例仅限于没有放到工程里的部分,有些项目会自己下载built-in Shader放到工程里,这样的Shader和自己写的没有区别,也会被打包。
  • 最后,你遇到的问题是正常现象,需要你们通过资源管理的策略避免掉。

自定义Shader放入Always include里面,造成Bundle 资源冗余。项目中的某一个自定义的Shader 使用了Shader feature ENABLE_CLIP,Shader被打进最终的Bundle包的时候,这个Shader feature 不起作用了,是因为这个Shader Variant没有被打入最终包吗,官方文档里的说明是:

the only difference is that unused variants of shader_feature shaders will not be included into game build. So shader_feature makes most sense for keywords that will be set on the materials, while multi_compile for keywords that will be set from code globally.

但是我确实有个材质是使用了这个Shader Feature的,为什么这个Shader Variant 没有进入最终的Bundle包呢?

  • 据了解,Unity官方有在英文论坛里提到Asset Bundle team正在解决这个问题。如果不将Shader对应的Material与Shader一同打包,当前另外的解决方案包括:
    • 使用dummy materials / ShaderVariantCollection 与shader打在同一ab内。
    • 使用multi_compile替换shader_feature。

我把FBX和粒子系统分开打包了。加载的时候先加载FBX文件,然后再加载粒子系统,结果那些粒子系统是Mesh模式的时候,它们的UV会消失。怎么办呢?开了Read/Write able就不会消失。不分开打包也不会消失,这种情况如何破?

  • 建议考虑在 FBX 的 AssetBundle 里再放一个带粒子系统,并且引用这个 Mesh 的 Prefab(但不去加载,也不去使用它),只是为了让 UV 可以被正确获得。 理论上这样做的话,基本不需要修改原来的加载方式,也不用开Read/Write。

Gameplay

我写了一个顶点动画的Shader,对物体顶点位置进行了修改,使其能够跟随相机移动,并总是出现在相机前面。当游戏运行后,开始时物体能够正常显示。相机在场景中移动一段距离后,物体便不再显示,但相机换一下朝向,物体又能显示。目前排除了面剔除的原因,在Shader中已经关闭了Cull(Cull Off),请问还可能是什么其他原因呢?

  • 消失的原因应当是Unity的Frustum Culling引起。因为顶点动画是在shader中修改的顶点位置,而Frustum Culling是根据顶点修改之前的Mesh的bounds进行的,因此随着相机移动,Mesh实际已经出了Frustum的范围。最简单的解决方法是直接将Mesh的bounds设置足够大,让Unity始终不对其进行Culling:
mesh.bounds = new UnityEngine.Bounds(transform.position, new Vector3(float.MaxValue, float.MaxValue,
float.MaxValue));

使用SystemInfo.graphicsDeviceName获取的GPU信息不太详细。比如ARM系列的Mali,T880 MP2与T880 MP12相差甚远,但是只能获取到T880,无法获取MP的信息。各位是否有办法?

  • 对于获取GPU名称信息,在Android的Java层中可以利用API:GLES20.glGetString(GLES20.GLRENDERER)获取显示设备名。可以尝试在Unity中利用AndroidJavaClass(https://docs.unity3d.com/ScriptReference/AndroidJavaClass.html)来调用该API,尝试获取更详细的型号。
  • 如果获取GPU名称的目的是判断GPU性能,可以看下SystemInfo中其他graphicsDevice相关的GPU性能信息是否够用。

物理

两个挂了Collider的物体,一个物体挂了Rigidbody,去碰撞另一物体,为什么每次OnCollisionEnter这个函数在实际还没有碰撞的时候就调用了呢?如下图,这时OnCollsionEnter已经被调用,但从图上可以看到两物体实际还没有接触,这个函数调用之后才会真正碰撞在一起。

  • 物理系统在FixedUpdate中触发,包括OnCollisionEnter。而FixedUpdate早于Frame Rendering,因此碰撞实际已经发生,只是画面还没更新。其实在运行时这点时间差几乎应该可以忽略。

性能综合

想对场景中的静态物件做一个遮挡剔除,不知道Unity自带的Occlusion Culling对性能优化的提升有多少呢?我们用Unity官方例子测试,启用Occlusion Culling后发现DrawCall、Triangle几乎没有变化。另外也想获知,Occlusion Culling的数据能否像NavMeshData那样可以动态加载呢?

  • Occlusion Culling在使用后具体能提升多少,这个其实是没有明确数值的,甚至可能不升反降!这个只能题主在自己的项目中进行尝试。
  • 一般来说,Occlusion Culling功能特别适合第一人称或第三人称跟随的平视角相机(比如传统意义上的MMO等类似游戏),且适用于在场景中存在大量的遮挡情况。因此,在城市街道漫游、峡谷漫游等特定场景中,比较推荐开启Occlusion Culling功能。
  • 但是Occlusion Culling功能本身存在一定的性能开销,因为需要每帧均遍历烘焙的Cell来明确哪些物体需要或不需要渲染,所以场景中Cell越细,那么其查询开销也就越大。因此,如果场景中本身没有大量的遮挡关系,那么开启Occlusion Culling功能后,其节省下来的渲染耗时可能抵不上其Cell查询耗时来的大,这样就得不偿失了。
  • 最后,Occlusion Culling的Data目前并不能动态加载,只能随场景来进行加载。因此,可以尝试创造一个拥有Occlusion Culling Data的“空场景”,然后通过LoadLevelAdditive方式来进行加载,从而来达到“动态”加载的效果。

通过Profiler.GetMonoHeapSize()获取到的堆内存,在操作UI的时候,打开某个节点比较多的UI界面,根据上面接口取到的堆内存会突然涨6~8MB,但是Profiler取不到这个数据,该怎么办呢?我不是固定打开某个界面,而是随机出现在某个界面上,这样的问题该如何入手呢?

  • Mono总体堆内存一次性涨6-8MB是比较正常的。Mono内存并不是随用随分配的,而是当其发现不够用时,一次性从系统中获取一段连续的内存。在游戏运行一段时间后,这个值一般是8MB。所以,题主遇到的情况很有可能是在连续操作UI界面时,Mono发现堆内存不够用了,于是就有了题主观察到的内存分配。

渲染

下图这一项是指渲染透明物体的渲染消耗么,主要是提交DrawCall是么,现在游戏的GPU消耗在20ms以内Xcode Profiler结果。但是有些粒子系统比较耗CPU,就是这个涨得比较厉害,峰值有7~8ms,请问有没有什么优化建议?

image

  • 首先,需要说明的是,Draw Call是CPU端的耗时开销,XCode上GPU上的开销统计与其关系不大。
  • 其次,图中红框所示确实表示的是半透明物体渲染在CPU端的耗时,其不仅与Draw Call数量相关,也和渲染State的切换相关(从图中可以看出,项目使用的是Unity 4.x版本,对应的需要关注Shader.SetPass的开销)。
  • 再次,粒子系统的耗时开销很高,如果还是持续在7~8ms左右,那么研发团队关注较高耗时处的游戏场景,并控制粒子系统的使用数量,粒子系统的优化并没有“神奇”的方案,目前研发团队需尽可能控制粒子系统的使用数量。