Unity3D性能优化最佳实践(四)资源审查

时间:2020-12-20 18:04:14

Asset auditing - 资源审查

许多项目发生效能问题的真正原因只是由于人员操作不当或是试东试西,而不小心改到导入设定影响到导入的资源。(例如最近的gitlab*)

对于较大规模的项目,最好准备一道自动的防线防范人为失误。例如写一段简单的检查程序确保没有任何人能在项目加入一张没压缩的 4K 贴图。

或许你会觉得不可能,但这问题我们真的很常见。没有压缩的 4K 贴图会占用大约 60mb 的内存空间,在低端的手机设备(例如 iPhone 4s)上,整个项目用掉超过 180mb~200mb 就会很危险。有时候你的游戏在好的手机上跑没问题,在差的手机上跑会宕机,不一定是硬件的问题。如果犯这种错误,这张贴图会无端占用应用程序四分之一到三分之一的可用内存,造成很难追踪的内存不足错误。

虽然上面有提到可以用 Unity 5.3 的 MemoryProfiler 来追踪这样的问题,但最好养成良好开发习惯排除这样的可能。

使用资源后处里器(Asset Postprocessors)
Unity 编辑器里的 AssetPostprocessor 类别可以用在 Unity 项目上实行某些基本限制。这个类别在资源导入时会收到一个回调。使用方法即继承 AssetPostprocessor 并实作一个或多个 OnPreprocess 方法,重要的包含:

OnPreprocessTexture

OnPreprocessModel

OnPreprocessAnimation

OnPreprocessAudio

请查询 AssetPostprocessor API 文件以了解更多关于 OnPreprocess 方法的数据。

1
2
3
4
5
6
7
8
9
10
public class ReadOnlyModelPostprocessor : AssetPostprocessor {
   public void OnPreprocessModel() {
        ModelImporter modelImporter = (ModelImporter)assetImporter;
         
        if(modelImporter.isReadable) {
            modelImporter.isReadable = false;
            modelImporter.SaveAndReimport();
        }
    }
}

这是一个简单的 AssetPostprocessor 限制规则范例

每当导入模型到项目或模型的导入设定(Import settings)被修改时会呼叫这个类别,这里程序只是检查可否读写模型的设置 isReadable 属性,如果是 true 就会改为 false,存盘后重新导入资源。

请注意,呼叫 SaveAndReimport 会导致这段程序会被再次呼叫!但由于设置已经被改为 false,所以不会无穷递归下去。

至于为何要拿掉写入的设置会在底下的模型章节解释。

常见资源规则

Textures - 材质

关闭 Read/Write Enabled 设置

这个 Read/Write Enabled 的设置会造成贴图在内存里变成两份,一份在 GPU 上一份在 CPU 可以寻址的内存上。这是因为大多数平台,把数据从 GPU 内存读回 CPU 很慢。从 GPU 内存读取一张贴图到暂存区给 CPU 程序用(例如:Texture.GetPixel)会导致效能很差。这个设定在 Unity 里预设是关闭的,但要避免误勾这个选项。

Read/Write Enabled 只有在 Shader 以外的地方存取贴图数据(例如:Texture.GetPixel 和Texture.SetPixel 这样的 API)时才会需要,但尽量避免使用这个功能比较好。

如果可以,尽量不要用 Mipmaps 

对于对象相对于镜头之间的 Z 轴深度不太会有变化的对象,关闭 Mipmaps 可以省下约三分之一的内存空间,但如果对象在镜头看出去的深度会有变化,关闭 Mipmaps 会影响到 GPU 贴图取样的效率。

一般来说,关闭这个选项对于 UI 和不太会变大变小的贴图很有帮助。

压缩全部贴图

针对各个不同的平台选用该平台适合的压缩格式压缩贴图,这是节省内存的首要之务。

如果选到目标平台不支持的格式,Unity 会在 CPU 上解压缩贴图,消耗 CPU 时间和很多内存。这类问题比较常发生在Android 上,开发者得注意 Android 装置上各种不同的芯片各支持哪种压缩格式。

强制贴图大小 

这虽然是件小事但很多人会忘记调整贴图大小或不小心从改到导入设定里的贴图大小上限。先决定各种贴图的合理大小上限然后用程序去强制限制它。

大多数的手机游戏用 2048x2048 或 1024x1024 来存放图集(Atlas)就够了,3D 手机游戏的模型贴图用到 512x512 应该就够好了。

Models - 模型

关闭 Read/Write Enabled 设置 

这个选项的运作原理和贴图一样,只不过预设是打开的。

当项目执行时想用程序来修改 Mesh,或者如果 Mesh 要用作 MeshCollider 的话,这里需要打勾。反之如果模型没用在MeshCollider,也没用程序来修改 Mesh 的话,关闭这里可以省下一半的内存。

非角色模型关闭骨架功能 

在预设情况下,Unity 会帮非角色的模型放入一个通用骨架(Generic rig),如果这个模型执行时被实例化,会导致被加上一个 Animator 组件。如果这个对象没有用到 Mecanim 动画系统就会产生不必要的开销,因为所有启动中的 Animator 每帧都会被触发一次。

关闭没有动画的对象的骨架来确保他们不会在执行期间被加上 Animator 组件,造成不必要的消耗。

帮带有动画的对象开启 Optimize Game Objects 选项 

OptimizeGame Objects 选项对于带动画的模型有明显的效能影响。如果这里没打勾,在初始化模型时 Unity 会把整个模型的骨架结构对应的 GameObject 阶层全部产生出来。这个巨大的 Transform 组件结构更新起来自然很耗效能,尤其是如果结构带有其他组件(例如粒子系统或碰撞体)。它也会拉低 Unity 多线程对蒙皮(Mesh skinning)和骨架动画的计算能力。

打勾后所有的骨架对应的 Transform 结构都会被移除,如果模型骨架结构中有特定的部位需要露出方便控制(例如模型的手部要用来握住武器),则可以把它列在“ExtraTransforms”白名单中。

更多数据可以参阅官方手册关于Model Importer 的部分。

如果可以,尽量使用Mesh压缩 

开启 Mesh compression 选项会缩短用来表示模型数据不同信道的浮点数字元长度,这会移除一定的精确度并可能造成可见的变化,使用这个之前最好先让美术检查过这种损失在允许范围内。

各个压缩等级使用的位长度在 ModelImporterMeshCompression 脚本里有介绍。

请记得,可以针对不同信道使用不同等级的压缩,所以项目可以只针对切线向量(Tangent)和法向量(Normal)压缩但不压缩 UV 和顶点位置。

追记:Mesh Renderer 的设定 

当加入一个 Mesh Renderer 到 Prefab 或 GameObjec t时,请小心上面的设定。预设情况下 Unity 会开启 Shadow casting、Shadow receiving、Light Probe 取样、Reflection Probe 取样和动态向量(Motion Vector)的计算。

假如项目不需要上述的功能,记得写个编辑器脚本关掉素材上的选项,执行期用程序加 MeshRenderer 到对象上也要记得执行相同的规则。

对于一个2D游戏来说,不小心加了一个带有 Shadowcasting 的 MeshRenderer 到场景里会触发整个阴影计算循环,这通常是浪费效能。

Audio - 音效

采用平台支持的压缩设定 

采用硬件支持的音源压缩格式。所有的iOS设备都有 MP3 硬件解压缩能力,而大多数的 Android 设备都有支持 Vorbis。

此外,可以直接将未压缩的声音文件导入 Unity 里,因为 Unity 会在打包项目时会重新压缩。所以不需要先压缩再导入Unity,这只会降低音效质量。

强制音效用单声道 

只有少数的手机装置真的有立体声喇叭,而将音效强制设定为单声道能让内存的消耗减半。就算游戏会输出部份的立体声,有些单声道像是 UI 音效还是可以开启这个选项。

降低音频的取样

调低取样能进一步降低内存消耗和最终项目的大小,可以和音效设计师协调找出最小最能接受的音源质量。参考SetCompressionBitrate。