Unity性能优化
大的方面来说,通过Unity
对于项目的性能优化大概可以分为下面几个部分:
- 资源
- 渲染
- 程序
- 项目配置
而在这个部分中,资源的性能优化属于最基础、最有效的优化手段,也是游戏开发者日常开发最需要注意的一部分,所以本篇文章就简单的介绍一下对资源进行操作时需要关注哪些点
一、纹理(简单来说就是图片)
纹理的资源优化主要集中于下面的几点:
- 纹理大小
- 压缩格式
- 导入设置
通常,在游戏运行时,大部分内存都是用在了纹理上,因此你的导入设置非常关键,我们可以在Inspector
面板看到图片资源的信息,类似于下面这张图:
从性能优化的角度看,纹理导入需要遵循如下的原则:
- 降低最大分辨率
- 采用二次幂压缩格式:
- 制作纹理图集
- 取消勾选
Read/Write Enabled
- 禁用多余的
Mip Map
:Mip Map
贴图在2D精灵和UI
图形这类大小始终一直的纹理上并无用处
1、降低最大分辨率
很好理解,纹理分辨率越大,在游戏运行时,占用的内存资源越大,并且占用的内存量是与其分辨率的平方成正比的。也就是说,分辨率变为原来的二倍,那么内存量就需要消耗四倍。因此需要从资源优化的角度来讲,需要对其进行一定的限制,简单的来说,一个Button
的纹理通常低于128
X128
,如果使用1024
X024
规格的大小就造成了性能的浪费
如下面的图,我们可以在图片资源的Inspector
面板中最下面的属性中看到它:Max Size
,同时可以看到可以根据不同平台来选择不同的最大分辨率:
第一张图片:Max Size
调整为1024
,纹理资源大小为4.9MB
第二张图片:Max Size
调整为512
,图片资源大小为1.3MB
2、对于纹理的压缩
在你主动选择压缩格式之前,Unity
本身会对图片做一些处理,无论你放入的是PNG
、JPG
、PSD
或者TGA
,Unity
都会手动帮助我们调整为Texture 2D
,这是一种简单的调度策略:
那么既然Unity
本身都已经智能转换好了,为什么还要给予开发者选择压缩格式的选择呢,直接对Texture 2D
封装好压缩方法不就可以了吗?
其实Unity
相对于其他开发引擎有一个很明显的优势,就是多适配性,那么为了实现这种多适配性,对于单一平台的针对性就会相对减弱,而不同平台的性能表现又不尽相同,就需要开发者来根据平台特点来选择针对游戏平台的压缩格式。
同样,即使在同一平台,也会根据不同的情况有着不同的压缩需求,比如有一些关键的主页面图片,玩家感知强的地方,就对图片的质量要求高些,一些边角的辅助图片,可能要求就低一些,如果都使用高质量压缩,性能方面就造成了浪费,但若都是用低质量压缩,质量又跟不上。所以,同样需要根据实际需求选择不同的压缩格式。
在说到Unity
图片压缩时,经常会看到这样一张图,来介绍不同压缩格式的特点与适用场景,我也是百度图片直接爬取的这张图片,大家可以参考着来看(如果有侵权,请告知我,我会立刻删除)
在理解上面这张图之前做一个简单的计算,一张1024
乘1024
的RGBA32
格式图片的占用存储空间为:
由于RGBA32
一个像素每一个颜色值是由两个16进制的数组成,也就是8位,那个一个像素就是8位乘以4个颜色值R
、G
、B
、A
得到的是4个字节,即4B
,然后乘上1024
*1024
个像素,最终得到的大小为4MB
,也就是4兆。很明显这是很恐怖的一个数字,要知道现在移动端手机的运行内存大概6G
,除去系统的占用内存,真正给游戏用的最多也就4G`,再分配给GPU|的显存就更少了,而若这些内存被用来大量加载贴图很明显是不能被接受的。同时,这样数据量的图片加载也会给内存
基于上面原生纹理带来的包体与内存问题,就需要根据不同的情况采取一些压缩策略,由于本人基本没有美术功底,所以对与各种压缩格式的美术呈现效果不是很了解,主要是从功能性与性能的角度来分析各种压缩格式的适用场景:
-
高品质压缩格式:
RGBA32
作为一种高保真的压缩格式,能够极大的保证图片质量 -
中品质压缩格式:
RGBA16
+Dithering
一听就是RGBA32
的阉割版,简单来说,相比于RGBA32
其色彩细分程度大,可以明显的看出阶梯感,视觉表现相对于RGB32
不够平滑 -
低品质压缩格式:
ETC1
+Alpha
/PVRTC4
这些压缩格式往往是移动段最常用的压缩格式,其相对于其他压缩格式有着无可比拟的性能优势
注意:
- 除了由于压缩逻辑不同带来的加载带宽减少之外,同时还需要了解像
ETC1
、PVRTC4
等这类在内存中不需要进行解压,而是可以直接被GPU
支持,所以相比其他压缩格式通常会有最好的性能表现
3、取消勾选Read/Write Enabled
该功能是为了使得游戏开发者可以通过C#
脚本调用对与图片的读取与写入的控制,很明显,这是由CPU来控制实现的,所以为了可以使得CPU
获取数据,需要在内存中备份一份让CPU
访问。同时为了图形渲染与显示,又会将其加载到显存中为GPU
提供数据。
简单来说,该选项会在游戏运行时,分别在CPU
内存与GPU
内存中备份出一张贴图,如果你并不需要对于纹理进行读写操作,可以尝试关闭该选项,这样就可以避免游戏运行时占用多余的内存
4、禁用多余的Mip Map
Mip Map
类似于模型的LOD
,同样是一种基于渲染距离改变渲染贴图精度的技术。其优势是在物体距离渲染距离比较远时,可以节省性能。但是使用Mip Map
时会增大内存占用量。
Mip map
的技术原理是根据原始图进行2的幂次方的递减来生成一组不同精度的图片。当游戏运行时,会将这组图片加载到内存中,然后根据渲染的距离不同,来使用不同精度的图片。
Mip map会增大多大的内存占用量呢
- 在我们使用
Mip map
时,假设大小为256X256
,并且会生成8
张不同精度的图片。根据2
的幂次方进行递减计算每张贴图大小并累加。这样最终得到的图片组的体积大概比原来的单张贴图大33%
通过一个实例来验证,假设原始图片大小为8M
,生成的第一张低精度图片的大小为2M
(分辨率减少一半,大小就会变为原来的四分之一,很好理解)这样大概递减8
次,然后累加,就会获取最终的图片组大小,通过一个简单的递归方法来计算一下:
public void Awake()
{
Debug.Log(GetMipmapSize(8, 8)/8);
}
// index:处理总层级数 imageSize:初始大小 返回增大的内存量
float GetMipmapSize(int index,float imageSize)
{
float lastSize = 0;
if (index == 1)
{
lastSize = imageSize*0.25f;
}
if (index > 1)
{
lastSize = imageSize * 0.25f + GetMipmapSize(index - 1, imageSize * 0.25f);
}
return lastSize;
}
执行程序,得到的结果为:
可以看到,求到的结果是接近于33%
,当然如果Mip Map
对一张纹理的处理不是八次,计算的结果会有偏差但是实际上,从三次往上,内存占用量的增加比例已经非常少了,基本都维持在33%
左右,将上面的代码稍微修改,做个小验证:
public void Awake()
{
for (int i = 3; i < 15; i++)
{
Debug.Log(string.Format("处理 {0} 次内存占用量为: {1}",i,GetMipmapSize(i, 8) / 8));
}
}
float GetMipmapSize(int index,float imageSize)
{
float lastSize = 0;
if (index == 1)
{
lastSize = imageSize*0.25f;
}
if (index > 1)
{
lastSize = imageSize * 0.25f + GetMipmapSize(index - 1, imageSize * 0.25f);
}
return lastSize;
}
得到的日志为:
可以发现,从三层开始,基本就维持在33%
的内存占用量,并且在层数逐渐增大的时候,内存占用量增加量就非常非常少了。
而Unity
所支持的纹理最小为32X32
,也就是最小的纹理也会额外的产生三层低精度纹理:16X16
、4X4
、1X1
,这就是为什么很多文章介绍到使用Map mip
会说其大约会增加33%
的内存占用量
虽然Mip map
本身是一种性能优化的技术,但是在2D
精灵或者UI
元素这些不会改变渲染精度的纹理上,只会占用多余的内存,所以在2D
精灵或者UI
元素上使用纹理时记得不要勾选Mip map
。
5、打包图集
图集的打包主要是优化UI图形渲染过程中Draw call的数量,其基本原理也是通过UI元素合批来减少Draw Call,进入提升CPU的性能表现,关于其具体细节,可以查看我之前的文章:
图集打包文章:
二、模型
相比与纹理,模型的性能表现更多的取决于美术规范,程序来讲没有更多可以优化的地方,但是在Unity中也有一些选项影响模型加载、渲染等方面的性能表现,我们可以在导入时看到:
1、禁用掉Reader/Write Enables:
点击模型,可以在Inspector
面板看到这些设置选项,类似于纹理,如果在游戏中,你不需要对模型进行修改,可以禁用掉Reader/Write Enables
来避免数据的备份而占用多余的内存,我们可以在Unity官方文档中找到相关介绍:
翻译过来就是:
-
启用此选项后,
Unity
会将Mesh
数据上传到GPU
可寻址内存,但也会将其保存在CPU
可寻址内存中。这意味着Unity
可以在运行时访问Mesh
数据,可以从脚本中访问它。 -
而禁用此选项后,
Unity
会将Mesh
数据上传到GPU
可寻址内存,然后将其从CPU
可寻址内存中删除 -
默认情况下,此选项处于禁用状态。在大多数情况下,要节省运行时内存使用量,请禁用此选项
而对于模型本身来说,尽量避免模型留有多余的面数。尤其是移动端。因为高精度模型除了本身所带来的压力外,在其他方面也有诸多的性能挑战
注意:
- 某些情况下禁用Reader/Write Enables会使得Mesh Collider不可用,具体信息在Unity官方文档中有提到:
2、尽量不要勾选不需要的功能选项
在Unity
中,某些功能即使你未使用到,也会 消耗一定的资源去维护其状态。类似上面的Reader/Write Enables
选项,所以用不到的功能我们可以考虑尽量的去禁用掉
3、设置一些关于质量与性能的选项
Unity
提供了一些对模型进行优化的选项,可以查阅Unity
官方文档来阅读了解他们,这里也简要的列出:
通过上面一张图片可以看出,影响模型表现与游戏性能的选项有下面几个:
Mesh Compression
:通过使用网格边界和每个组件较低的位深度来压缩网格数据,增加压缩率会降低网格的精度。最好在Mesh
看起来与未压缩版本没有太大区别的情况下将其调得尽可能高。这对于优化游戏大小很有用Optimize Mesh
:确定三角形在网格中列出的顺序以获得更好的GPU
性能,默认都会勾选Normals
:如果网格模型既不是法线贴图也不受实时光照影响,就选用None
,这样也能够很好的提升性能表现
其实,Unity
设置了一些通过程序控制模型质量来改变性能表现的选项,但是不建议使用,预期通过这些选项来调整性能表现,还不如直接让美术直接处理模型。毕竟他们更加专业,可以更好的保证模型的表现效果与性能表现的平衡。
4、使用LOD
关于LOD
,其实应该在渲染这一段来讲,但是这个技术又与模型网格有很大的关系,所以提前介绍一下
LOD
即Levels of Detail
,翻译过来就是多层次细节,类似与纹理渲染的Mip map
技术,同样是一种根据渲染距离设置渲染精度的一种技术。其实现方式是在游戏开发时,美术根据不同的渲染距离制作一组不同精度的模型,导入到Unity
通过LOD
组件连接其这一组模型,并设置相关参数。这样在游戏运行时,就会在不同的距离有不同的渲染精度:
这是Unity
官方文档的一个案例,可以看出,随着渲染距离的增加,渲染精度逐渐下降,直到最终被剔除,这样做的优势是保证游戏画面表现的同时,可以最大程度降低渲染压力。简单的理解,如果不采用LOD
,随着距离增加,物体占用的屏幕像素就会越少,那么单位像素的三角面数就会越多。单位的渲染压力就会增大。画面表现需求不高的地方渲染压力反而更高,这显然是不合理的。所以就需要通过LOD
来解决这样的问题。
当然这种技术本身也是有相当大的缺陷的,首先就是会增大包体的体积,同时也会增加美术的工作量。所以在实际开放中,一般只会对一些重要的对象使用该技术
总结
上面所介绍的关于资源影响游戏性能的一些常用的点,都是游戏开发者日常接触最多的,更深入,更底层的就需要根据项目的特点进行专门的适配与调整。