这是摘自Unity官方文档有关优化的部分,原文链接:https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity.html
总共分为如下系列:
- 采样分析
- 内存部分
- 协程
- Asset审查
- 理解托管堆 【推荐阅读】
5.1 上篇:原理,临时分配内存,集合和数组
5.2 下篇:闭包,装箱,数组 - 字符串和文本
- 资源目录
- 通用的优化方案
- 一些特殊的优化方案
真实项目中大部分错误都是无心之过——临时的测试改动或者某个非常累的开发者不小心设置错了资源导入设置,造成资源出现大问题。
对于规模很大的项目,需要加入对抗人为错误的首条防线。例如,写几行代码确保没有人可以给工程提交了一个4K的未压缩纹理。你可能觉得很意外,但是这是一个非常常见的错误。一个4K的未压缩纹理会占据超过60MB的内存资源。在比较低端的设备上,例如iPhone4S上,超过180-200MB的内存就比较危险了。如果错误地添加了这个问题,1/4~1/3的内存就被占用了,也会造成非常难检测的内存溢出问题。
虽然在5.3之后,可以用内存剖析工具找到这个问题,但是还是最好在一开始就防止类似这样的问题发生。
使用AssetPostprocessor
Unity编辑器中的AssetPostprocessor类可以用来确定Unity工程中的资源是否满足标准。当资源被导入的时候,这个类会收到回调。使用这个类的方法只需要继承,自己实现多个OnPreprocess方法即可。重要的方法有:
- OnPreprocessTexture
- OnPreprocessModel
- OnPreprocessAnimation
- OnPreprocessAudio
具体可以参见AssetPostProcessor,可以知道更多的OnPreprocess方法。
public class ReadOnlyModelPostprocessor : AssetPostprocessor
{
public void OnPreprocessModel()
{
ModelImporter modelImporter = (ModelImporter)assetImporter;
if(modelImporter.isReadable)
{
modelImporter.isReadable = false;
modelImporter.SaveAndReimport();
}
}
}
上面是应用AssetPostprocessor的一个例子。
当项目中导入模型的时候或者模型的导入设置被更改的时候,这个方法会被调用。这个方法会检测Read/Write属性,如果属性为true,强制改成false,保存并且重新导入。
记住,SaveAndReimport方法会导致这个代码又被执行一次,因为会强制将属性改为false,所以不会进入到无限循环。
这个改动的原因可以参见模型部分。
常见的资源规则
纹理Texture
关闭read/write enabled的标志
Read/Write enabled标志导致Texture会在内存中存在两份资源:一份在GPU,一份在GPU寻址空间【因为在大部分平台,从GPU内存读取非常缓慢。从GPU内存读取纹理资源到临时缓冲区非常不划算】。在Unity中,这个选项默认被关闭,但是有可能被意外开启。
Read/Write enabled属性被打开只有在Shader外操作纹理数据才有意义(例如Texture.GetPixel和Texture.SetPixel等)所以应当尽可能避免开启。
尽可能关闭Mipmaps
如果对象相对于Camera的Z深度不变,最好关闭mipmaps可以节省大概1/3的内存。如果对象的Z深度值会变化,关闭mipmaps可能导致GPU的取样效果比较差。
通常来讲,UI纹理和在屏幕上是固定大小的纹理资源通过可以关闭掉mipmaps选项。
压缩所有的纹理
对于目标平台使用合理的纹理压缩格式可以节省内存资源。
如果目标平台的纹理压缩格式设置的不合理,Unity加载纹理资源之后会对纹理资源重新解压,会消耗大量CPU时间和内存资源。这对于Android设备很常见,通常是因为不同的芯片级支持不同的纹理格式,差异太大,难以统一。
建立纹理限制条件
虽然这个很简单,但是通常很容易忘记重新调整纹理的大小,或者不小心改变了导入设置中的纹理大小。最好是通过代码确定不同类型的纹理资源是否满足了设定的规则。
模型Model
关闭Read/Write enabled的标志
Read/Write enabled标志对于模型而言和Texture中的道理相同。但是,对于模型而言,Unity对于这个选项默认开启。
当项目会在运行过程中通过代码修改Mesh,或者Mesh被用来作为MeshCollider组件的基础时,Unity要求这个属性必须开启。如果模型没有用到MeshCollider中或者不需要通过代码修改,将这个属性改成false可以节省一半的内存资源。
对于非Character类的模型关闭rigs
默认情况下,Unity会为非Character模型导入一个原生的rig。这样会导致如果在运行过程中实例化这个模型,Animator组件会被添加。如果这个模型不需要通过Animation系统控制动画,就会增加额外的开销。因为所有处在活跃状态下的Animator会在每帧被触发。
对于不需要动画的模型最好关闭这个属性防止增加额外的Animator组件和场景中出现不需要的动画效果。
对于有动画效果的模型开启Optimize Game Objects选项
Optimize Game Object选项对于有动画的模型有非常大的性能影响。当这个选项被关闭之后,当模型被实例化的时候,Unity会创建一个大的Transform层级用来映射模型的骨架结构。这个transform的Update开销会非常大,尤其是有其他的组件(如例子系统或者碰撞器)挂接在上面的时候。这个属性同时也会影响Unity多线程处理蒙皮和骨骼动画运算的能力。
如果某个模型骨架的一部分需要被暴露出来,如某个模型的双手部分需要控制武器,那么这些部分可以列入Extra Transforms列表的白名单。
更多的细节可以参见Unity教程的模型导入部分。Model Importer
尽可能使用Mesh压缩
启用网格压缩之后,用来表示模型数据的不同频道的浮点数的位数会减少。这样虽然会引起精度的丢失,所以最终的效果需要被美术同学认可才能放入最终的工程中。
具体的浮点数的位数和压缩等级之间的关系可以参见Scripting API的ModelImporterMeshCompression.
需要注意的是,不同的频道可以使用不同的压缩等级,所以在项目中可以选择压缩切线和法线而不压缩UV和顶点位置。
注意:网格渲染的设置
当向Prefab或者GameObject添加网格渲染器的时候,需要注意设置问题。默认情况下,Unity会开启Shadow casting and receiving,Light Probe sampling,Reflecion Probe sampling和Motion Vector calculation。
如果项目中不需要使用到上面的这些特性,请用脚本确保上面的属性被关闭。任何执行添加MeshRender的运行代码也需要确定是否开启这些属性。
对于2D游戏而言,如果不小心向场景中加入的MeshRender中shadow选项未被关闭,会给渲染循环中加入额外的shadow pass过程,对于性能来讲是浪费。
音频
针对平台选择合适的压缩选项
确保对于硬件设备选择合适的压缩格式。所有的iOS设备硬件支持MP3解码,而很多Android设备原生支持Vorbis格式。
而且,向Unity中导入未压缩的音频格式,Unity当打包工程的时候会压缩这些音频文件。所以没有必要导入压缩的音频文件,然后又解压,这样只会降低最后音频的品质。
将音频强制改成单声道
很少移动设备有立体扬声器。在移动设备的工程里,将导入的音频强制改成单声道可以节省一半的资源消耗。对于不需要有立体声效果的音频也是同样处理,如UI声效。
降低音频码率
当然这个需要和负责音频部分的同事进行沟通,最大化降低音频文件的码率可以更进一步的减少内存消耗和打包的工程大小。
上篇:协程
下篇:理解托管堆...上
如果觉得文章对您有用,请点个赞呗!☺☺☺