在移动端项目中,内存占用是一项非常重要的指标,一般都会被严格控制其大小,否则会导致内存占用过大而影响其他功能的使用。而在移动端Unity中,由于嵌入了Unity引擎,内存占用会一下子飙升,如果控制不好内存的增长,恐怕许多项目都不会允许Unity嵌入到移动端中,在这种时候,内存优化就变得尤为重要,接下来我就在实践中一步一步地对移动端Unity项目进行内存优化,通过工具分析和多角度优化内存,让大家可以清晰地看到内存优化的效果和掌握优化手段。
为了得到清晰的优化情况对比,我们先把优化前的App内存占用情况记录一下。一般情况下,查看Unity的内存占用可以使用Unity的Profile工具,但是该工具对内存占用的情况显示不是很详细,所以这里我们使用Memory Profile进行内存分析,Memory Profile的安装使用可以查看这个链接:Memory Profile官方文档,我们打开App,然后使用Memory Profile去抓一下快照,可以看到快照信息如下:
我们这里可以对整体的内存进行分析,这里大概说下每个各个部分的含义,便于我们理解内存占用构成:
这个是设备提交的内存情况,可以用来看正在使用内存是多少,预留了多少内存
Total(总内存):表示Unity进程当前使用的总内存量。这包括所有已分配给Unity的内存,无论是实际使用中的内存还是预留的内存
In Use Memory(使用中内存):表示当前实际使用中的内存量。它反映了Unity正在使用的资源占用的内存大小,涵盖了纹理、模型、声音等加载的资源。
Reserved Memory(已预留内存):表示Unity为未来使用而预留的内存量。这些内存可能由于资源加载和运行时需求而保留,但尚未实际使用。
在这里可以看到程序各部分占用的内存,可以方便我们针对性分析和解决内存占用问题
Managed Heap:是指运行在托管环境中,由.NET运行时(例如Unity使用的Mono或IL2CPP)管理的内存堆
Virtual Machine(IL2CPP):与IL2CPP虚拟机相关的内存占用
Graphics & Graphics Driver:图形渲染和图形驱动程序内存占用,一般和画面复杂度,分辨率,纹理,各种缓冲区,各种渲染技术特效等等相关
Audio:音频部分,和音频文件数据占用内存相关
Other Native Memory:这部分内存用于存储临时数据、中间计算结果、状态信息等
Profiler:用于分析性能的,这个一般无需理会,正式版本没这个
Executabe & DLLs:可执行文件和动态链接库的内存占用
从上面这些可以看到总览的内存占比,便于我们对整个程序的内存占用情况有比较全面的了解,接下来我们再看看更详细的内存占用情况,我们点击Tree Map菜单,可以看到Memory Usage Overview界面,下面列举了各个部分占用的情况:
从这些部分中就可以找出需要优化的点,我们点击较大的方块,比如Texture2d,就能看到下面详细的内存占用情况:
从图中可以看到,第一项占用21.3 MB,其他的占用会小一点,但是在移动端这些都是可以优化的点。第一项我们无法看出命名是什么,其实这里是中文命名的乱码,因此最好不要使用中文命名,否则不知道具体是什么导致的内存占用。我们点击第一项,可以看到以下界面:
可以从这里看到引用的物体,但是由于中文问题,导致依然不知道是哪个贴图,所以大家一定不要用中文的命名。这里每一部分都点进去看看,就能看到各项的内存占用,具体每部分的情况我就不贴出来了,接下来我们开始优化内存。
一般贴图的内存占用是占Unity的内存大头的,因此我们首先需要对贴图进行优化,我们首先降低贴图的分辨率,这里为了快速就统一设置贴图最大分辨率为512了,实际工程中最好是为每个贴图设置最佳分辨率,设置最大分辨率的地方在:
我们看看优化效果:
可以看到,texture2d从74.7M降低到35.4MB,如果场景中高分辨率贴图多,那么这个优化将会更大。
一些贴图是不需要带上透明通道的,可以去除减小内存大小。还有就是mipmap,开启mipmap会增大大概1/3的内存大小。如果项目中没有远近景的切换,是用不到mipmap的,也可以关掉:
需要注意的是,这两项是否可以关闭需要根据项目实际情况而定,而且其实优化不是很大,以下是在测试项目中优化的效果(测试项目的贴图较少,优化程度很低):
只是从35.4M变成了34.1M而已,优化掉了1.3M。
除了上述这两个操作外,还有一些其他的贴图优化技术,可以根据项目情况使用,比如利用Photoshop之类的工具对贴图进行专门的优化,保持效果的同时优化内存占用。也可以使用纹理合并,把多个小纹理合并为一个大的纹理图集,减少纹理的切换和内存开销。这些就看实际项目去做具体优化了。
shader的内存占用一般和shader的编写和加载有关,通常情况下不会占用特别大,除非shader编写特别复杂,变体特别多,这里就不详细每个步骤都讲述内存变化了,在执行多个操作后再统一看内存变化。
Always Included Shaders在Unity设置的Graphics下面,是用于包含那些暂时没有引用但是在需要时动态加载的shader的,但是在这里的shader会无论用不用得到都会加载进去,不仅会增大内存,还会增加编译时间,所以尽量不要放在这里
"Always Included Shaders"和"Preload Shaders"是两种不同的Shader预加载选项,"Always Included Shaders"是一种将Shader直接包含在构建输出中的选项。这意味着不管场景中是否使用了这些Shader,它们都会被打包到构建中。而"Preload Shaders"是一种在运行时提前加载Shader的选项。这意味着只有在需要使用这些Shader时才会进行加载。这里需要填入的是一个shader变体集合,也要记得避免把集合中用不到的shader和其变体去掉,避免内存占用
在移动端尽量不要使用过于复杂的shader,可以尽量减少shader的pass,同时不要生成过多的变体,尽量做到简单高效。
如果多个如果多个对象使用相同的材质和Shader,可以考虑将它们合并为一个大的网格,以减少重复的Shader实例。这样可以节省内存并提高性能。
本次测试项目中使用到的shader不多也不复杂,通过上面这些技术,最终优化效果如下:
从一开始的38.1M降到37.2M,并且shader的数量也从54个降低到42个。
减少模型的面数可以显著降低内存占用
将多个小模型合并为一个大的网格模型
使用LOD(层次细节):根据物体与相机的距离,使用不同细节级别的模型,利用LOD功能可以降低模型的面数和纹理分辨率,以降低内存占用
使用GPU Instancing:对于需要大量重复实例的模型,使用GPU Instancing技术可以共享材质和缓冲区数据,以减少内存开销。
尽量避免使用骨骼动画:骨骼动画(Skinned Mesh Animation)通常消耗比较多的内存。如果没有必要,尽量使用其他动画技术(如顶点动画、物理动画),或者尝试使用成批处理(Batching)等技术来减少动画对象的数量。
使用顶点压缩:Unity提供了顶点压缩选项,可以减小存储每个顶点所需的内存。您可以在导入模型时选择适当的压缩选项,例如使用Half精度来减小顶点的内存消耗
对于模型Mesh部分,优化大头在于模型的制作简化这里,尽量使用低模,模型细节用别的技术模拟实现,而不要增加过多面数,测试项目中用到的模型已经是最简,这里就基本没什么优化空间了。
降低屏幕分辨率也可以减少整体的内存占用,例如从19201080降低到1280720,可以看到效果:
可以看到,RenderTexture大幅度降低,优化幅度非常大,因此在移动端,分辨率这一项设置变得非常重要,尽量使用较低分辨率。
一般项目中还会用到一些类似于后处理,抗锯齿,阴影,灯光数量等方面的效果,虽然视觉上确实会增加不少,但是内存也跟着增加上来了,所以尽量也要减少这些方面的使用。在测试项目中,后处理使用到了
Post Process全局后处理
TTA抗锯齿
Bloom
高质量阴影
lighting count为6
针对上面这些进行优化:
去掉Post Process全局后处理
TTA抗锯齿换成Unity自带的MSAA抗锯齿,设置为2x
Bloom使用Shader实现
降低阴影质量
lighting count降低为4
最终得到的画面效果其实差别不会特别大,最终内存优化如下:
可以看到,各项占用内存再次大幅度降低,特别是RenderTexture,从29.7M降到了9.7M。
一般情况下,内存占用最大的都是资源和效果,但还有一些其他的方面需要注意的,虽然本次测试项目中这些方面占比不大,但是也在这里列出来,在项目中也要注意这些方面的优化:
动画的优化:一般项目使用动画都会使用动画控制器进行播放,但如果动画控制器里设置了较多的动画,则会占用不少内存。如果可以,使用动态加载动画的方式,用不到的动画及时卸载,避免内存占用。除此之外,动画数据的也不要太过于复杂,记得去除重复帧,减少关键帧,减少动画节点,这样会使动画变得更小,占用内存也会更小
音频优化:音频部分也会占用不少的内存,可以强制使用单声道,压缩音频,重采样等方式减少内存占用
代码优化:代码逻辑中也要注意内存的占用,不要滥用单例和静态类静态属性等。
通过查看各个步骤的Memory Profile的Objects and Allocations的Total Size数据,可以得到较为准确的内存占用数据,最后得出的数据如下:
未优化 | 贴图优化后 | Shader优化后 | 降低屏幕分辨率后 | 效果内存优化后 |
---|---|---|---|---|
230.0M | 189.4M | 188.5M | 123.2M | 77.8M |
可以看到,即使是一个比较简单的项目,经过一系列的内存优化后,也能达到一个非常不错的效果,但无论如何,在内存优化的过程中也不能一味的放弃质量,因此在项目里实际优化需要因情况而定,选择最适合项目的方式,尽量保持一个性能和效果都相对比较不错的情况。