Bootstrap

Unity性能调优手册4:资源优化,Texture,Mesh,Material,Animation,ParticleSystem,Audio,ScriptableObject

翻译自https://github.com/CyberAgentGameEntertainment/UnityPerformanceTuningBible/
游戏制作涉及处理大量不同类型的资产,如纹理、网格、动画和声音。本章提供了有关这些资产的实用知识,包括调优性能时要记住的设置。

Texture

图像数据作为纹理的来源,是游戏制作中不可缺少的一部分。另一方面,它消耗相对大量的内存,因此必须对其进行适当配置

Import Settings

在这里插入图片描述

Read/Write

默认情况下,该选项是禁用的。如果禁用,纹理只在GPU内存中扩展。如果启用,它不仅会被复制到GPU内存,还会被复制到主内存,从而使消耗增加一倍。因此,如果您不使用诸如
Texture.GetPixel 或Texture.SetPixel 的api,且仅使用Shader访问纹理,确保禁用它们。
同样,对于在运行时生成的纹理,将makeNoLongerReadable设置为true

texture2D.Apply(updateMipmaps, makeNoLongerReadable: true)

译者增加部分
Q什么时候需要开启Read/Write
A在做模型贴花时
下面为使用Paint3D制作VR书法
https://www.bilibili.com/video/BV1pa411H7fQ/?vd_source=b13d231c1d8955afe06699a120afae27
PS:频繁new texture需要主动调用Destroy
https://blog.csdn.net/linxinfa/article/details/108347571

Generate Mip Maps

启用Mip Map设置会使纹理内存使用量增加约1.3倍。
此设置通常用于3D对象,以减少远距离对象的锯齿和纹理传输。对于2D精灵和UI图像来说,它基本上是不必要的,所以应该禁用它。
译者增加部分
Q:为什么是增加1.3倍
A:【腾讯文档】Mipmap-内存多三分之一
https://docs.qq.com/doc/DWnlrS2drYWdkaU1N
计算公式
1.S为多出来部分:1/4 + 1/16+····+1/(4^n)
2.4S:1+ 1/4+···+1/(4^(n-1))
3.4S-S = 3S = 1 -1/(4^n)
4.n趋于无穷大,3S = 1,所以多出来的内存占用为 1/3

Aniso Level

Aniso Level是一个在物体以浅角度渲染时渲染纹理而不模糊的函数。此函数主要用于扩展较远的对象,例如地面或地板。Aniso Level值越高,它提供的好处越多,但性能成本也越高。
在这里插入图片描述

Aniso级别可以从0到16设置,但它有一个稍微特殊的规格。
•0:无论项目设置如何,始终禁用
•1:基本禁用。但是,如果项目设置为“强制开启”,则该值被限制为9~16。
•其他:设置为该值
当导入纹理时,该值默认为1。因此,除非您的目标是高规格设备,否则不建议使用强制开启设置。强制开启可以在“项目设置->质量”中的“各向异性纹理”中设置。
在这里插入图片描述

确保对没有效果的对象没有启用Aniso Level设置,或者对有效果的对象没有设置得太高。
Aniso Level的效果不是线性的,而是按步骤切换的。作者验证了它的变化分为4步:0-1、2 - 3、4-7、8或更高。

Compression Settings

纹理应该被压缩,除非有特殊的原因。如果您在项目中发现未压缩的纹理,则可能是人为错误或缺乏规则。
我们建议使用TextureImporter自动执行这些压缩设置,以避免人为错误。

using UnityEditor;
public class ImporterExample : AssetPostprocessor
{
 private void OnPreprocessTexture()
 {
     var importer = assetImporter as TextureImporter;
     // Read/Write settings, etc. are also possible.
     importer.isReadable = false;
     var settings = new TextureImporterPlatformSettings();
     // Specify Android = "Android", PC = "Standalone
     settings.name = "iPhone";
     settings.overridden = true;
     settings.textureCompression = TextureImporterCompression.Compressed;
     // Specify compression format
     settings.format = TextureImporterFormat.ASTC_6x6;
     importer.SetPlatformTextureSettings(settings);
 }
 }

译者增加部分
【腾讯文档】unity图片压缩格式
https://docs.qq.com/doc/DWlBMQUdrUHBCaEZV
纹理内存大小(字节) = 纹理宽度 x 纹理高度 x 像素字节
像素字节 = 像素通道数(R/G/B/A) x 通道大小(1字节/半字节)(不压缩情况下)
所以要对图片进行压缩:
ASTC 4x4:每个像素1byte,无2次幂要求,支持透明通道,限制OpenGL ES 3.0
如果不要求低端设备运行,UI统一采用ASTC格式。ASTC4X4 作为所有通用图集的压缩格式,icon使用 5x5; Loading图使用 6x6
用于模型上的贴图,使用ASTC6x6
并非所有纹理都需要采用相同的压缩格式。例如,在UI图像,具有整体渐变的图像往往由于压缩而显示出明显的质量损失。在这种情况下,建议只对部分目标图像设置较低的压缩比。另一方面,对于3D模型等纹理,很难看到质量损失,因此最好找到合适的设置,例如高压缩比。

Mesh

以下是在处理导入Unity的网格(模型)时要记住的几点。可以根据设置来改进导入模型数据的性能。应注意以下四点。
•已启用读写Read/Write Enabled
•顶点压缩Vertex Compression
•网格压缩Mesh Compression
•优化网格数据Optimize Mesh Data

Read/Write Enabled

第一个网格注释是Read/Write Enabled。模型检查器中的这个选项在默认情况下是禁用的。
在这里插入图片描述

如果你不需要在运行时访问网格,你应该禁用它。具体来说,如果模型被放置在Unity中并且只用于播放AnimationClip, Read-/Write Enabled可以禁用。
启用读/写将消耗两倍的内存,因为CPU可访问的信息存储在内存中。请检查一下,因为简单地禁用它将节省内存。

Vertex Compression

顶点压缩是一个将网格顶点信息的精度从float改为half的选项。这可以在运行时减少内存使用和文件大小。
在这里插入图片描述

但是,请注意,在以下条件下顶点压缩是禁用的
•已开启读写功能
•Mesh Compression是启用的
•启用动态批处理和可适应的网格(少于300个顶点和少于900个顶点属性)

Mesh Compression

网格压缩允许你改变网格的压缩比。压缩比越高,文件大小越小,占用的存储空间越少。压缩后的数据在运行时解压缩。因此,运行时的内存使用不会受到影响。
网格压缩提供了四种压缩设置。
•Off:未压缩
•Low:低压缩
•Medium:中等压缩
•High:高压缩
在这里插入图片描述

Optimize Mesh Data

优化网格数据是一个功能,自动删除不必要的顶点数据从网格。不必要的顶点数据将根据使用的着色器自动确定。这将减少运行时的内存和存储。
可以在“Project Settings -> Player ”下的“Other ”中进行设置。
在这里插入图片描述

这个选项很有用,因为它会自动删除顶点数据,但要注意,它可能会导致意想不到的问题。例如,在运行时切换材质和着色器时,访问的属性可能会被删除,导致不正确的渲染结果。当只绑定网格资源时,不正确的材质设置可能会导致不必要的顶点数据。这在只提供网格参考的情况下很常见,例如在粒子系统中。

Material

材质球是决定物体如何渲染的重要功能。虽然这是一个熟悉的特性,但如果使用不当,它很容易导致内存泄漏。

简单地访问一个参数将复制它

关于Material,最重要的是要记住,它们可以简单地通过访问它们的参数来复制。而且很难注意到它正在被复制。
材质球被复制的例子

Material material;
void Awake()
{
    material = renderer.material;
    material.color = Color.green;
}

这是一个简单的过程,将材质的颜色属性设置为color .green。
渲染器的材质是重复的。复制的对象必须是显式的
使用实例删除重复的Material

Material material;
void Awake()
{
    material = renderer.material;
    material.color = Color.green;
}
void OnDestroy()
{
    if (material != null)
    {
        Destroy(material)
    }
}

以这种方式销毁重复的材料可以避免内存泄漏。
译者增加部分
Q:如何正确给材质赋值
A:通过MaterialPropertyBlock
【腾讯文档】材质MaterialPropertyBlock
https://docs.qq.com/doc/DWnhRZ09Za2xzTVBY

彻底清理生成的材质球

动态生成的材料是导致内存泄漏的另一个常见原因。
确保在使用完生成的材料后销毁它们。

Material material;

void Awake()
{
material = new Material(); // Dynamically generated material
}

void OnDestroy()
{
 if (material != null)
 {
 Destroy(material); // Destroying a material when you have finished using it
 }
 }

材料应该在使用完后销毁(OnDestroy)。
根据项目的规则和规范,在适当的时间销毁材料。
译者增加部分
如果项目中无法避免new材质球,可以使用对象池管理材质球

Animation

调整skin weights 的数量

在内部,运动通过计算每个顶点对每个骨骼的影响程度来更新每个顶点的位置。在位置计算中考虑到的骨骼数量称为skinweight (皮肤权重)或influence count (影响数)。因此,可以通过调整蒙皮权重的数量来减少负载。然而,减少皮肤重量的数量可能会导致奇怪的外观,所以一定要验证这一点调整皮肤重量时的数量。
皮肤权重的数量可以在“Project Settings ->Quality ”下的“Other”中设置
在这里插入图片描述

这个设置也可以从脚本中动态调整。因此,可以将低规格设备的Skin Weights设置为2,高规格设备的Skin Weights设置为4,以此类推,以便进行微调。

// How to switch QualitySettings entirely
// The argument number is the order of the QualitySettings, starting with 0.
QualitySettings.SetQualityLevel(0);

// How to change only SkinWeights
QualitySettings.skinWeights = SkinWeights.TwoBones;

减少Keys

动画文件依赖于键的数量,这可能会在运行时消耗存储和内存。减少键数的一种方法是使用
Anim. Compression 特性。这个选项可以通过从模型导入设置中选择Animation选项卡找到。当Anim. Compression 开启,在导入资产过程中会自动删除不需要的Keys。
在这里插入图片描述

关键帧减少在值变化不大时减少键。具体来说,当键与前一条曲线相比处于误差范围内时,键将被删除。这个误差范围可以调整。
在这里插入图片描述

误差设置有点复杂,但误差设置的单位因项目而异。旋转以度为单位,而位置和比例以百分比为单位。捕获图像的旋转公差为0.5度,位置和比例公差为0.5%。详细的算法可以在Unity文档https://docs.unity3d.com/Manual/class-AnimationClip.html#tolerance
中找到。
Optimal更令人困惑,但它比较了两种简化方法,即Dense Curve格式和Keyframe reduction,并使用具有较小数据的方法。要记住的关键点是,密集曲线的大小比关键帧减少要小。然而,它往往是嘈杂的,这可能会降低动画质量。在理解了这个特性之后,让我们直观地检查实际的动画,看看它是否可以接受。

减少更新频率

默认情况下,即使动画不在屏幕上,Animator也会更新每一帧。
有一个名为Culling Mode的选项允许您更改此更新方法。
在这里插入图片描述

每个选项的含义
在这里插入图片描述

关于每个选项都有几点需要注意。首先,在设置完全剔除时,要小心使用根移动。例如,如果你有一个动画从屏幕外帧,动画将立即停止,因为它是在屏幕外。因此,动画将永远不会帧。下一步是剔除更新变换。这似乎是一个非常有用的选项,因为它只跳过更新转换。但是,如果您有抖动或其他依赖于transform的过程,则要小心。例如,如果一个角色出了帧,那么就不会从那个时候的姿势进行更新。当角色再次进入帧时,它将被更新为一个新的姿势,这可能会导致摇晃的物体明显移动。在更改设置之前,最好了解每个选项的优缺点。
此外,即使有了这些设置,也不可能动态地改变动画更新的频率。例如,您可以通过将距离相机较远的对象的动画更新频率减半来优化动画更新的频率。在这种情况下,你需要使用AnimationClipPlayable或停用Animator并调用Animator.Update 。两者都需要编写自己的脚本,但后者比前者更容易实现。

Particle System

游戏效果对于游戏呈现来说是必不可少的,Unity经常使用粒子效果
系统。在本章中,我们将从性能调优的角度介绍如何使用粒子系统,以及如何避免错误。
以下两点很重要。
•保持低颗粒数量。
•注意Noise模块消耗大的

减少粒子数量

粒子的数量与负载有关,由于粒子系统是CPU驱动的(CPU粒子),粒子越多,CPU负载就越高。作为基本策略,将粒子数量设置为必要的最小值。根据需要调整粒子的数量。
有两种方法可以限制粒子的数量。
•限制发射系统的发射数量particles emitted
•在Max particles主模块中限制最大粒子数
在这里插入图片描述

限制发射模块的发射数量图
•Rate over Time时间速率:每秒发出的粒子数量
•Bursts>Count:粒子在Burst时间发射的数量
调整这些设置以达到所需粒子的最小数量。
在这里插入图片描述

限制发射的最大粒子数量图
另一种方法是在主模块中使用Max Particles。在上面的例子中,超过1000的粒子将不会被释放
小心次级发射器
在减少粒子数量时,还应考虑Sub - Emitters模块。
在这里插入图片描述

子发射器模块在特定时间产生任意粒子系统(在创建时,在生命结束时等)根据子发射器的设置,粒子的数量可能会立即达到峰值数量,所以在使用此模块时要小心。

注意Noise模块消耗大

https://docs.unity3d.com/cn/2022.2/Manual/PartSysNoiseModule.html
在这里插入图片描述

噪声模块的质量很容易过载。噪声可以表达有机颗粒,常被用来方便地增加质量效果。因为它是一个经常使用的函数,所以应该注意它的性能。
• Low (1D)
• Midium (2D)
• High (3D)
质量尺寸越高,负载越高。如果你不需要噪音,关闭噪音模块。如果需要使用噪点,请先将“质量”设置为“低”,然后根据需要增加“质量”。

Audio

导入声音文件的默认状态在性能方面有一些改进点。有以下三种设置。
• Load Type
• Compression Format
• Force To Mono

Load Type

有三种加载声音文件(AudioClip)的方法。
在这里插入图片描述

• Decompress On Load
• Compressed In Memory
• Streaming

Decompress On Load

解压加载加载未压缩的视频到内存。它的cpu密集度较低,因此可以用较少的等待时间执行回放。另一方面,它使用了大量的内存。
建议用于需要立即播放的短声音效果。BGM和长语音文件使用大量内存,因此在使用此功能时应小心。

Compressed In Memory

压缩内存将AudioClip以压缩状态加载到内存中。这意味着它在播放时被解压缩。这意味着
CPU负载很高,很可能出现播放延迟。
它适用于不希望直接解压缩到内存中的文件大小较大的声音,或者不受轻微播放延迟影响的声音。它常用于语音对话。
Streaming
流媒体,顾名思义,是一种加载和播放声音的方法。它使用更少的内存,但cpu更密集。建议长时间使用BGM。
在这里插入图片描述

PCM
未压缩且占用大量内存。不要设置这个,除非你想要最好的音质
ADPCM
使用的内存比PCM少70%,但质量更低,CPU负载比Vorbis小得多。CPU负载比Vorbis低得多,这意味着解压缩的速度更快,使其适合即时播放和大量播放的声音。对于脚步声、碰撞、武器等嘈杂的声音来说尤其如此,因为这些声音需要快速且大量地回放。
Vorbis
作为有损压缩格式,质量比PCM低,但文件大小更小。这是唯一一种允许对音质进行微调的格式。它是所有声音(背景音乐,音效,声音)最常用的压缩格式。
在这里插入图片描述

Sample Rate
质量可以通过指定采样率来调整。支持所有压缩格式。在采样率设置中可以选择三种方法。
在这里插入图片描述

Preserve Sample Rate
默认设置。使用原始声源的采样率。
Optimize Sample Rate
由Unity分析并根据最高频率分量自动优化。
Override Sample Rate
覆盖原始声源的采样率。可设置为8000 ~ 192000hz。即使采样率高于原始源,质量也不会得到改善。当您想要比原始声源更低的采样率时,使用此选项。

将声音效果设置为单声道

默认情况下,Unity播放立体声,但通过启用强制单声道,单声道播放是启用的。启用单声道播放将削减一半的文件大小和内存大小,因为没有必要有单独的数据为左和右通道。
在这里插入图片描述

单声道播放通常是很好的声音效果。在某些情况下,单声道播放也更适合3D声音。建议在仔细考虑后启用Force to Mono。性能调优效果是小题大做。如果你对单声道播放没有问题,你应该积极使用强制单声道。
尽管这与性能调整不同,但未压缩的音频文件应该导入Unity。如果你导入压缩的音频文件,它们将在Unity端被解码和重新压缩,从而导致质量损失

Resources / StreamingAssets

项目中有一些特殊的文件夹。从性能的角度来看,以下两点尤其需要注意
• Resources folder
• StreamingAssets folder
通常情况下,Unity只包含场景、材料、脚本等引用的对象。
对于上面提到的特殊文件夹,规则有所不同。存储的文件包含在构建中。这意味着即使是不实际需要的文件,如果它们被存储,也会包含在构建中,从而导致构建大小的扩展。
问题是无法从程序中进行检查。您必须直观地检查不必要的文件,这很耗时。向这些文件夹中添加文件时要小心。
然而,随着项目的进展,存储文件的数量将不可避免地增加。其中一些文件可能与不需要的文件混合在一起

Resources 文件夹减慢启动时间

在Resources文件夹中存储大量对象将增加应用程序启动时间。Resources文件夹是一种老式的便利特性,它允许您通过字符串引用加载对象。

var object = Resources.Load("aa/bb/cc/obj");

很容易过度使用Resources文件夹,因为您可以通过将脚本中的对象存储在Resources文件夹中来访问它们。
但是,如上所述,重载Resources文件夹将增加应用程序的启动时间。这样做的原因是当Unity启动时,它会分析所有资源文件夹中的结构并创建一个查找表。最好尽可能减少对Resources文件夹的使用。

ScriptableObject

ScriptableObjects是YAML资产,许多项目可能将其文件作为文本文件来管理。通过显式指定[PreferBinarySerialization]属性将存储格式更改为二进制。对于以大数据量为主的资产,二进制格式可以提高读写操作的性能。
然而,二进制格式自然更难以与合并工具一起使用。对于只需要通过覆盖资产来更新的资产,例如那些不需要检查文本更改的资产,或者在游戏开发完成后数据不再被更改的资产,建议使用。[PreferBinarySerialization] ScriptableObjects不需要与ScriptableObjects一起使用。
Tips
使用ScriptableObjects时的一个常见错误是类名和源代码文件名不匹配。类和文件必须具有相同的名称。在创建类时要小心命名,并确保.asset文件被正确序列化并保存为二进制格式。

/*
* When the source code file is named ScriptableObjectSample.cs
*/

// Serialization succeeded
[PreferBinarySerialization]
public sealed class ScriptableObjectSample : ScriptableObject
{
...
 }

 // Serialization Failure
 [PreferBinarySerialization]
 public sealed class MyScriptableObject : ScriptableObject
 {
 ...
 }

译者增加部分
Q:ScriptableObject可以用在哪些地方
A:
运用1:拆表,id整列出来为ScriptableObject文件,加入到自动打包流程。这样判断是否某个id包含在内时,不需要加载整个表,而是单独加载id的ScriptableObject文件。
运用2:技能表现编辑器的数据可以序列化为ScriptableObejct文件

;