原文链接:https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity4.html
我们发现在实际项目中发生的许多问题都是由于开发者的“无心之过”——一个疲劳的开发者的临时“测试”更改或是错误的点击会添加低性能的资产,或是改变已经存在的资产的设置。
对于任何大规模的项目来说,最好有第一道防线来应对人为的错误。相对简单的方法是,编写一小段代码来确定没有人可以添加一张4K未压缩的纹理到项目中。
然而,这是一个令人惊讶的常见问题。一张4K未压缩纹理占用60MB内存。在低端移动设备,比如iPhone4S,消耗超过180-200MB内存是非常危险的。如果这张纹理被错误的添加,那么这张纹理将会占用到这个应用程序内存预算的三分之一到四分之一,并且导致难以诊断的内存不足的问题。
虽然现在可以通过5.3内存分析器排查出这些问题,但是我们认为最好是应该确保这些问题一开始就不可能发生。
使用AssetPostprocessor
Unity编辑器中的AssetPostprocessor类可以用于在一个Unity项目中执行某些最低标准。这个类在资源被导入时收到回调函数。要使用它,就继承AssetPostprocessor并且实现并且实现一个或更多个OnPreprocess方法。重要的一些有:
·OnPreprocessTexture
·OnPreprocessModel
·OnPreprocessAnimation
·OnPreprocessAudio
要获取更多关于OnPreprocess函数的信息,请查看AssetPostprocessor的脚本参考(链接见原网页)。
public class ReadOnlyModelPostprocessor : AssetPostprocessor {
public void OnPreprocessModel() {
ModelImporter modelImporter =
(ModelImporter)assetImporter;
if(modelImporter.isReadable) {
modelImporter.isReadable = false;
modelImporter.SaveAndReimport();
}
}
}
这是一个使用AssetPostprocessor对一个项目进行规范的例子。
这个类会在模型被导入项目时或是模型的导入设置被更改时被马上调用。这段代码仅仅检查看一下是否Read/Write enabled标志位被设置为true(通过isReadable属性表示)。如果是true,那么会使这个标志位强制变为false,然后保存后重新导入该资源,
请注意,调用SaveAndReimport会导致这段代码被再次调用。然而,由于我们现在可以保证isReadable是false,这段代码不会造成一个无限重新导入的循环。
在下文的“模型”部分中,会讨论要进行这种改变的原因。
通用资源规则
纹理
禁用read/write enabled标志位
Read/Write enabled标志位会导致一张纹理在内存中持有两份, 一个在GPU,另外一个在可寻址的CPU内存中(请注意:这是由于在大多数平台上,从GPU内存中回读是非常慢的。从GPU内存中读取一张纹理到一个临时缓冲区用于CPU代码(比如Texture.GetPixel)是性能非常差的。)在Unity中,默认是禁用的,但是它可能会被意外打开。
Read/Write Enabled只在shader外对纹理数据进行操作时才是必要的(比如使用Texture.GetPixel和Texture.SetPixel这种API),并且应该避免这种情况的发生。
如果可能禁用Mipmaps
对于相对于摄像机有相对不变的Z轴深度的对象,可以禁用Mipmaps来节省加载纹理时所需的三分之一的内存。如果对象会改变Z轴深度,禁用Mipmaps会导致GPU纹理采样的性能更差。
总体来说,这对于UI纹理和在屏幕上有恒定尺寸显示的纹理非常有用。
压缩所有纹理
使用一种对于项目目标平台合适的纹理压缩格式是对节省内存至关重要的。
如果选中的纹理压缩格式不适用于目标平台,Unity会在加载完纹理后解压纹理,消耗CPU时间和大量的内存。这通常是安卓设备的一个问题,由于其通常因为芯片组的不同而支持非常不同的纹理压缩格式。(详情参阅:https://docs.unity3d.com/Manual/class-TextureImporterOverride.html)
执行合理的纹理大小限制
虽然这很简单,但是也很容易忘记重新设置纹理的大小或是无意中改变纹理大小的导入设置。对于不同类型的纹理确定一个合理的最大尺寸,并且将其通过代码来执行。
对于大多数应用程序来说,2048x2048或是1024x1024足以用于纹理图集,512x512足以用于3D模型的纹理贴图。
模型
禁用Read/Write enabled标志位
Read/Write enabled标志位对模型的作用与上文中其对纹理的作用描述相同。然而对于模型来说它是默认打开的。
当一个项目会通过脚本在运行时动态修改网格或是要使用网格作为MeshCollider组件的基础,Unity会需要这个标志位启用。如果这个模型不用网格碰撞体并且不会通过脚本修改,那么禁用这个标志位来节省一半的模型内存。
在非角色模型上禁用rig
默认情况下,Unity对非角色动画会导入一个通用的rig。这就会造成当运行时生成模型时会给其添加一个Animator组件。如果这个模型不是通过动画系统进行运动,那么这个添加操作会给动画系统添加无必要的消耗,因为所有激活的Animator一定会每帧被记录。
通过禁用不会动画的模型来避免自动添加Animator组件并且可以避免无意的在一个场景中添加不需要的Animator。
对于要进行动画的模型启用Optimize Game Objects选项
Optimize Game Objects选项(在Rig选项卡中)对于要进行动画的模型来说有显著的性能影响。在这个选项被禁用时,当模型被生成的时候Unity会创造大量的transform层级来对应模型的骨骼构造。transform层级的update是非常昂贵的,尤其是还有其他组件(比如说粒子系统或者碰撞体)关联到其上。它还会限制Unity对多线程网格蒙皮和骨骼动画的计算能力。
如果模型骨骼构造的特定位置需要被暴露(比如说要暴露一个模型的手以便动态的与武器模型相关联),那么这些位置可以在Extra Transforms列表中被指定的列入白名单。
一些额外的细节可以在Unity手册的模型导入(https://docs.unity3d.com/Manual/FBXImporter-Rig.html?_ga=2.160227261.1276288412.1550042523-677452303.1550042523)一文中找到。
如果可能使用网格压缩
启用网格压缩可以减少模型数据所表现的不同通道的浮点数字的位数。这可能会导致较小的精度损失,并且在最终应用于项目之前,美术人员应该检查这种不精确带来的影响。
指定压缩等级的特定位数使用在ModelImporterMeshCompression脚本参考(https://docs.unity3d.com/ScriptReference/ModelImporterMeshCompression.html)中有详细说明。
请注意如果可能的话对不同的通道使用不同的压缩等级,所以一个项目可以选择只压缩切线和法线而保留UV和顶点位置不压缩。
请注意:网格渲染器设置
当添加网格渲染器到预制体或者游戏物体上时,请注意其组件设置。Unity默认开启阴影投射和接收,光照探针采样,反射探针采样,以及移动矢量计算。
如果一个项目不需要这些功能,可以通过一个自动脚本来确认它们被关闭。任何运行时添加网格渲染器的代码也需要开关这些设置。
对于2D游戏来说,无意中添加一个阴影选项开启的网格渲染器到场景中会添加一整个阴影pass到渲染循环中,这通常是一个性能的浪费。
音频
适应平台的压缩设置
使用一个匹配可用硬件的压缩格式。所有的iOS设备都包含一个MP3解压缩器,很多安卓设备都原生的支持Vorbis。
更进一步讲,导入不压缩的音频文件到Unity中,Unity总是会在构建一个项目时重新压缩音频。无需导入一个压缩的音频然后再次压缩它,这只会造成降低最终音频的片段的质量。
强制音频片段为单声道
很少有移动设备真的有立体声扬声器。在移动平台上,使导入的音频片段强制变为单声道可以减少其内存消耗的一半。这个设置也适用于所有没有立体声效果的音频,比如说大多数UI声音效果。
减少音频比特率
这个需要咨询音频设计师,尝试最小化音频文件的比特率来进一步节省内存消耗和减小项目大小。