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);
}
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 可寻址内存中删除 -
默认情况下,此选项处于禁用状态。在大多数情况下,要节省运行时内存使用量,请禁用此选项
而对于模型本身来说,尽量避免模型留有多余的面数。尤其是移动端。因为高精度模型除了本身所带来的压力外,在其他方面也有诸多的性能挑战
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 来解决这样的问题。
当然这种技术本身也是有相当大的缺陷的,首先就是会增大包体的体积,同时也会增加美术的工作量。所以在实际开放中,一般只会对一些重要的对象使用该技术
总结
上面所介绍的关于资源影响游戏性能的一些常用的点,都是游戏开发者日常接触最多的,更深入,更底层的就需要根据项目的特点进行专门的适配与调整。
|