目录
第4章 着手处理艺术资源
有一些变通方法能用于实现性能提升,但是这些方法往往会为了提高速度而降低质量。
无论是试图最小化运行时的内存占用,保持尽可能小的可执行文件大小,最大限度地提高加载速度,还是保持帧速率的一致性,都有许多选项可供探索。
本章探索入如何提高下列资源类型的性能:
- 音频文件
- 纹理文件
- 网格和动画文件
- AssetBundle和Resource
对于每个资源类型,我们将研究Unity在应用程序的构建和运行时如何存储、加载和维护这些资源,将解释在面对性能问题时的观念,以及如何避免可能产生性能瓶颈的行为。
4.1 音频 (P97)
游戏行业的两方往往都忽视了音频。
过度压缩、过多的音频操作、过多的活动音频组件、低效的内存存储方法和访问速度都是导致内存和CPU性能低下的原因。
4.1.1 导入音频文件
音频文件的一些设置:加载行为、压缩行为、质量、采样率、以及是否支持双声道音频。
4.1.2 加载音频文件
3种设置:
Preload Audio Data决定了音频数据是在场景初始化期间自动加载还是在以后加载。警用此选项将加快场景初始化,但这也意味着第一次播放文件时,CPU需要立即访问磁盘,检索文件,将其加载到内存,解压缩并播放。这是一个同步操作,它将阻塞主线程直到其完成。
Load In Background确定此活动是在完成之前阻塞多线程,还是在后台异步加载。
调用AudioClip.LoadAudioData()可以实现音频加载,但是,此活动仍然会阻塞主线程。此时应使用Load In Background选项,将加载改为异步任务。通过AudioClip.loadState属性来复查AudioClip组件的当前加载状态。
Load Type设置定义了将什么类型的数据拉入内存,以及一次拉入多少数据。
现代游戏通常在关卡中实现方便的停止点,以执行诸如加载或卸载音频数据之类的任务。例如,几乎不会发生任何操作的楼梯间电梯或长走廊。
LoadType选项的3种选择:
- Decompress On Load
- Compressd in Memory
- Streaming
Decompress On Load:压缩磁盘上的文件以节省空间,在首次加载时解压缩到内存。
Compressed in Memory:加载音频只是将其直接从磁盘复制到内存中,播放时才会在运行期间进行解压缩。
Streaming:将在运行时加载、解码、播放文件,具体是逐步将文件推过一个小缓冲区,在缓冲区中一次只存在整个文件的一小部分数据。
音频加载的优化目的是,减少场景加载时间,和播放音频时能够马上播放没有延迟。
默认情况下,启动Preload Audio Data,禁用Load In Background以及使用Decompress On Load的加载类型会导致场景加载时间过长,但可以确保在需要时立即准备好场景中引用的每个音频剪辑。折中方法是,为以后才需要的音频剪辑启用Load In Background,然后通过AudioClip.LoadAudioData()和UnloadAudioData()手动控制音频数据的加载。
4.1.3 编码格式和品质级别 (P101)
Unity支持3种编码格式,由Compression Format选项决定:
- Compressed
- PCM
- ADPCM
导入Unity引擎中的音频文件可以是许多流行的音频文件格式之一,例如Ogg Vorbis、MPEG-3(MP3)和Wave,但是捆绑到可执行文件中的实际编码将转换为不同的格式。与Compressed设置一起使用的压缩算法取决于目标平台独立的应用程序和其他非移动平台将文件转换为Ogg Vorbis格式,而移动平台使用MP3格式。
PCM格式是一种无损的、未压缩的音频格式,提供接近模拟音频的效果。
ADPCM格式在大小和CPU消耗方面比PCM高效得多,但是压缩会产生相当大的噪音。
Compressed格式将导致小文件的质量低于PCM,但明显优于ADPCM,代价是要使用额外的运行时CPU。
4.1.4 音频性能增强 (P103)
1.最小化活动音源数量
限制可以同时播放音频剪辑的实例数。这涉及通过一个中介发送音频播放请求,该中介控制着音频源,从而对一个音频剪辑可以同时播放的实例数设置硬性的上限。
UnityAssetStore中几乎所有可用的音频管理资产都实现了某种类型的音频限制功能(通常称为音频池)。由于这些工具通常提供许多更微妙的性能增强功能,因此建议使用预先存在的解决方案,而不是推出自己的解决方案。
2.为3D声音启用强制为单声道
在立体声音频文件上启动Force to Mono(强制为单声道)设置会将来自两个音频通道的数据混合到一个通道中,文件的总磁盘和内存空间使用量有效地降低了50%。
3.重新采样到低频
将导入的音频文件重新采样到较低的频率将减低文件和运行时内存占用。为此可以将音频文件的SampleRate设置为OverrideSampleRate,此时可以通过SampleRate选项设置采样率。22050Hz是一个常见采样率。
4.考虑所有的压缩格式
在适当的情况下,可以对不同的文件使用不同的编码格式,在内存占用、磁盘占用、CPU使用和音频质量方面做出一些妥协。
5.注意流媒体
Streaming加载类型的优点是运行时内存成本低,因为它分配了一个小的缓冲区,文件想数据队列一样连续地推入缓冲区。这看起来很有吸引力,但是从磁盘流式传输文件应该仅限于大型的单实例文件,因为它需要运行时的硬盘访问,这是我们可用的最慢的数据访问形式之一(仅次于通过网络拉取文件)。
6.通过混音器组应用过滤效果以减少重复
过滤效果可用于修改通过音频源播放的音效,并可通过FilterEffet组件完成。一个文件可以通过一组不同的过滤器进行调整,以产生完全不同的声音效果。
由于有额外的开销,在场景中过度使用过滤器效果会导致性能上的严重后果。更好的方法是利用Unity的音频混音器实用程序(Window|Audio Mixer)生成通用的过滤效果模板。
7.谨慎地使用远程内容流
可以使用Unity通过Web动态加载游戏内容,这是减少应用程序磁盘占用的一种有效方法,因为需要打包到可执行文件中数据文件更少,还提供了一种使用Web服务呈现动态内容的方法,以确定在运行时向用户呈现的内容。素材流可以通过WWW类或UnityWebRequest类完成的。
WWW类提供audioClip属性,访问此属性将在每次调用时分配一个全新的AudioClip资源,类似于其他WWW资源获取方法。一旦不再需要此资源,就必须使用Resources.UnloadAsset()方法释放它。
不像托管资源,丢弃引用(将引用设置为null)不会自动释放这些资源,因而它会持续占用内存。因此,应当仅通过audioClip属性获取一次AudioClip,此后仅使用该AudioCllip引用,当不再需要时释放它。
在Unity2017中,WWW类已被UnityWebRequest类替代,它使用了新的HLAPI和LLAPI网络层。
UnityWebRequestMultimedia.GetAudioClip()
DownloadHandlerAudioClip.GetContent()
新版本的API旨在更有效的存储和提供请求的数据,通过DownloadHandlerAudioClip.GetContent()多次重新获取音频剪辑不会导致额外的分配。相反,它只会返回对最初下载的音频剪辑的引用。
8.考虑用于背景音乐的音频模块(Audio Module)文件
音频模块文件也称为音轨模块(Tracker Module),是节省大量空间,并且没有任何明显质量损失的绝佳方式。如果有机会为音乐文件使用音轨模块版本,就应好好探索一番。
4.2 纹理文件 (P106)
在游戏开发中经常混淆纹理和精灵的概念。精灵是网格的2D等价物,用于渲染面向当前相机的平面。
网格和精灵都使用纹理,将图像渲染到它的表面。在运行时,纹理图像文件加载进内存,推送到GPU的显存,并在给定的DrawCall期间,由着色器渲染到目标精灵或网格上。
4.2.1 纹理压缩格式
有一些可用的导入设置,通过一些微调提升纹理的品质和性能。
可以以多种常见格式(如jpg和png)导入纹理文件,但应用程序中内置的实际压缩格式可以根据给定平台的GPU,从很多不同的纹理压缩格式中选择理想适配的一种。
修改压缩的简单方式是使用Compression纹理导入选项:None,LowQuality,NormalQuality,HignQuality。
根据平台的不同,Unity将尝试选择匹配选项的压缩格式。
还有一个Crunch Compression设置,它在DTX压缩格式之上应用额外级别的有损压缩。
4.2.2 纹理性能增强
1.减少纹理文件的大小
给定的纹理文件越大,推送纹理所消耗的GPU内存带宽就越多。如果每秒的总内存超过图形的总内存带宽,就会产生瓶颈,因为在下一个渲染过程开始之前,GPU必须等待所有纹理都上传完毕。
2.谨慎地使用Mip Map
Mip Map通过提前生成相同纹理的低分率替代品,保证它们占据相同的内存空间。运行时,GPU根据透视图中的表面大小选择相应的Mip Map级别(大体基于对象渲染时的纹理到像素的比例)。
通过开启Generate Mip Maps设置,Unity自动处理纹理的这些低分辨率副本的生成,提前生成的。
Mip Map最初的总体目的是故意降低质量,以提高性能。
这些纹理会打包到一起以节省空间,最终的纹理文件比原始图像大33%。这将消耗一些磁盘空间和用于上传的GPU的带宽。
纹理只有需要在距离相机很远或很久的地方渲染时,Mip Mapping才是有用的。
------------------------------
参考:Unity中关于 Mipmap(https://blog.csdn.net/u010019717/article/details/91352180),这里更加详细,也介绍了一些工具。
发现HDRP下的Scene中没找到Mipmaps
3.从外部管理分辨率的降低
可以习惯性避免在Unity项目中使用.PSD和.TIFF文件(在其他地方保存它们,将缩小版本导入Unity中),或者只是偶尔执行一些测试。
4.调整Anisotropic Filtering级别
Anisotropic Filtering是一项在非常倾斜(类似浅滩)的角度观察纹理时提升纹理品质的特性。
与Mip Mapping很像,Anisotropic Filtering很昂贵,有时没有必要使用。
没有实际看到这个的效果,项目中。
5.考虑使用图集
图集是一种技术,它将许多较小的、独立的纹理合并到一个较大的纹理文件中,从而最小化材质的数量,因此最小化所需使用的DrawCall数量。这是利用动态批处理的有效方法。
每种独特的材质都需要额外的DrawCall,但是每种材质只支持单一的主纹理。将多个主纹理合并到一个大的纹理文件中,渲染共享这个纹理的对象时,可以最小化所使用的DrawCall数量。
需要做的额外工作是修改网格或精灵对象的UV坐标,只采集大纹理文件中所需的部分。
图集是在UI元素和包含许多2D图形的游戏中应用的一种常见策略。当使用Unity开发移动游戏时,图集是必不可少的技术,因为在这些平台上,DrawCall会成为最常见的瓶颈。
若3D游戏具有简单纹理的分辨率,或是扁平着手的低多边形风格,都可以这种方式使用图集。
在一个扁平着色、低面艺术风格的游戏中,每个对象使用通用的颜色调色板,为整个游戏世界、对象和角色使用单一的纹理,可以节省一些空间。
我有个项目,大部分模型都是纯色的,不同颜色的钢筋,感觉其实可以用图集,一块一个颜色,修改模型的UV,使用一个纹理,一种材质,来显示不同颜色的效果。(调研并测试方案)
图集的缺点主要是开发时间和工作流成本。要彻底检查现有的项目才能使用图集,这需要花费大量的精力,只是为了辨别是否值得使用图集就需要做很多工作。
纹理总是从VRAM和较低级缓存中提取。
图集显然不是一个完美的解决方案,如果不清楚它是否能带来性能提升,就应该小心,不要在它的实现上浪费太多时间。
如果移动游戏尝试使用高质量的资源或任何类型的3D图形,就应该尽可能在开始开发时集成图集。
不管什么产品,如果由于DrawCall太多而受限于CPU,也尝试过其他替代技术,那么图集在多数情况下是非常有效的性能提升方案。
6.调整非方形纹理的压缩率
避免非正方形和/或非2的n次幂的纹理
7.Sparse Textures
SparseTextures也称为Mega-Textures或Tiled-Textures,提供了一种运行时从磁盘传输纹理数据流的方式。
相对而言,如果CPU以秒为单位执行操作,那么磁盘将以天为单位执行操作。因此,通常的建议是,应该尽可能避免游戏运行时的硬盘访问。因此,一般的建议是,在游戏过程中应该尽可能避免硬盘访问,因为此类技术可能造成超出可用磁盘访问的风险,从而导致应用程序陷入停顿。
然而,如果明智地在需要之前开始传输纹理的部分数据,则Sparse Textures提供了一些有趣的性能提升技术。
需要大量的场景准备工作。
游戏世界需要以纹理交换量最小的方式创建。
在游戏业中,它是一项高度专业化的技术,但没有被广泛采用,部分原因是它需要专业的硬件和平台支持,另一部分原因是它难以完美实现。
文档:http://docs.unity3d.com/Manual/SparseTextures.html
8.程序化材质
程序化材质也称为Substances,是一种在运行时通过自定义数学公式混合小型高质量的纹理样本,通过程序化方式生成纹理的手段。程序化材质的目标是在初始化期间以额外的运行时内存和CPU处理为代价,极大地减少应用程序的磁盘占用,以便通过数学操作而不是静态颜色数据来生成纹理。
纹理文件有时是游戏项目中最大的磁盘空间消耗者。
http://docs.unity3d.com/Manual/ProceduralMaterials.html
9.异步纹理上传 (P115)
禁用Read/Write Enable选项的好处是纹理可以使用Asynchronous Texture Uploading特性,该特性有两个优势:纹理从磁盘异步上传到RAM中;且当GPU需要纹理数据时,传输发送在渲染线程,而不是主线程。
最终,这减少了每帧准备渲染状态所花费的时间,允许将更多的CPU资源花在游戏玩法、物理引擎等逻辑模块中。
由于异步纹理上传特性仅适用于明确导入到项目中且在构建时存在的纹理,因为该特性仅在纹理打包到可流式传输的特殊资源中才会生效。
相关设置:Edit|Project Settings|Quality|Other菜单下的AsyncUploadTimeSlice和AsyncUploadBufferSize。