Unity 性能优化:资源篇

Posted 心之凌儿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity 性能优化:资源篇相关的知识,希望对你有一定的参考价值。

Unity性能优化

大的方面来说,通过Unity对于项目的性能优化大概可以分为下面几个部分:

  • 资源
  • 渲染
  • 程序
  • 项目配置

而在这个部分中,资源的性能优化属于最基础、最有效的优化手段,也是游戏开发者日常开发最需要注意的一部分,所以本篇文章就简单的介绍一下对资源进行操作时需要关注哪些点

一、纹理(简单来说就是图片)

纹理的资源优化主要集中于下面的几点:

  • 纹理大小
  • 压缩格式
  • 导入设置

通常,在游戏运行时,大部分内存都是用在了纹理上,因此你的导入设置非常关键,我们可以在Inspector面板看到图片资源的信息,类似于下面这张图:

从性能优化的角度看,纹理导入需要遵循如下的原则:

  • 降低最大分辨率
  • 采用二次幂压缩格式:
  • 制作纹理图集
  • 取消勾选Read/Write Enabled
  • 禁用多余的Mip MapMip Map贴图在2D精灵和UI图形这类大小始终一直的纹理上并无用处

1、降低最大分辨率

很好理解,纹理分辨率越大,在游戏运行时,占用的内存资源越大,并且占用的内存量是与其分辨率的平方成正比的。也就是说,分辨率变为原来的二倍,那么内存量就需要消耗四倍。因此需要从资源优化的角度来讲,需要对其进行一定的限制,简单的来说,一个Button的纹理通常低于128X128,如果使用1024X024规格的大小就造成了性能的浪费

如下面的图,我们可以在图片资源的Inspector面板中最下面的属性中看到它:Max Size,同时可以看到可以根据不同平台来选择不同的最大分辨率:

第一张图片:Max Size调整为1024,纹理资源大小为4.9MB

第二张图片:Max Size调整为512,图片资源大小为1.3MB

2、对于纹理的压缩

在你主动选择压缩格式之前,Unity本身会对图片做一些处理,无论你放入的是PNGJPGPSD或者TGAUnity都会手动帮助我们调整为Texture 2D,这是一种简单的调度策略:

那么既然Unity本身都已经智能转换好了,为什么还要给予开发者选择压缩格式的选择呢,直接对Texture 2D封装好压缩方法不就可以了吗?

其实Unity相对于其他开发引擎有一个很明显的优势,就是多适配性,那么为了实现这种多适配性,对于单一平台的针对性就会相对减弱,而不同平台的性能表现又不尽相同,就需要开发者来根据平台特点来选择针对游戏平台的压缩格式。

同样,即使在同一平台,也会根据不同的情况有着不同的压缩需求,比如有一些关键的主页面图片,玩家感知强的地方,就对图片的质量要求高些,一些边角的辅助图片,可能要求就低一些,如果都使用高质量压缩,性能方面就造成了浪费,但若都是用低质量压缩,质量又跟不上。所以,同样需要根据实际需求选择不同的压缩格式。

在说到Unity图片压缩时,经常会看到这样一张图,来介绍不同压缩格式的特点与适用场景,我也是百度图片直接爬取的这张图片,大家可以参考着来看(如果有侵权,请告知我,我会立刻删除)

在理解上面这张图之前做一个简单的计算,一张10241024RGBA32格式图片的占用存储空间为:

由于RGBA32一个像素每一个颜色值是由两个16进制的数组成,也就是8位,那个一个像素就是8位乘以4个颜色值RGBA得到的是4个字节,即4B,然后乘上1024*1024个像素,最终得到的大小为4MB,也就是4兆。很明显这是很恐怖的一个数字,要知道现在移动端手机的运行内存大概6G,除去系统的占用内存,真正给游戏用的最多也就4G`,再分配给GPU|的显存就更少了,而若这些内存被用来大量加载贴图很明显是不能被接受的。同时,这样数据量的图片加载也会给内存

基于上面原生纹理带来的包体与内存问题,就需要根据不同的情况采取一些压缩策略,由于本人基本没有美术功底,所以对与各种压缩格式的美术呈现效果不是很了解,主要是从功能性与性能的角度来分析各种压缩格式的适用场景:

  • 高品质压缩格式:RGBA32作为一种高保真的压缩格式,能够极大的保证图片质量

  • 中品质压缩格式:RGBA16 + Dithering一听就是RGBA32的阉割版,简单来说,相比于RGBA32其色彩细分程度大,可以明显的看出阶梯感,视觉表现相对于RGB32不够平滑

  • 低品质压缩格式: ETC1+Alpha/PVRTC4这些压缩格式往往是移动段最常用的压缩格式,其相对于其他压缩格式有着无可比拟的性能优势

注意:

  • 除了由于压缩逻辑不同带来的加载带宽减少之外,同时还需要了解像ETC1PVRTC4等这类在内存中不需要进行解压,而是可以直接被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,也就是最小的纹理也会额外的产生三层低精度纹理:16X164X41X1,这就是为什么很多文章介绍到使用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,其实应该在渲染这一段来讲,但是这个技术又与模型网格有很大的关系,所以提前介绍一下

LODLevels of Detail,翻译过来就是多层次细节,类似与纹理渲染的Mip map技术,同样是一种根据渲染距离设置渲染精度的一种技术。其实现方式是在游戏开发时,美术根据不同的渲染距离制作一组不同精度的模型,导入到Unity通过LOD组件连接其这一组模型,并设置相关参数。这样在游戏运行时,就会在不同的距离有不同的渲染精度:

这是Unity官方文档的一个案例,可以看出,随着渲染距离的增加,渲染精度逐渐下降,直到最终被剔除,这样做的优势是保证游戏画面表现的同时,可以最大程度降低渲染压力。简单的理解,如果不采用LOD,随着距离增加,物体占用的屏幕像素就会越少,那么单位像素的三角面数就会越多。单位的渲染压力就会增大。画面表现需求不高的地方渲染压力反而更高,这显然是不合理的。所以就需要通过LOD来解决这样的问题。

当然这种技术本身也是有相当大的缺陷的,首先就是会增大包体的体积,同时也会增加美术的工作量。所以在实际开放中,一般只会对一些重要的对象使用该技术

总结

上面所介绍的关于资源影响游戏性能的一些常用的点,都是游戏开发者日常接触最多的,更深入,更底层的就需要根据项目的特点进行专门的适配与调整。

以上是关于Unity 性能优化:资源篇的主要内容,如果未能解决你的问题,请参考以下文章

Unity3D性能优化之资源导入标准和属性设置篇

Unity性能优化之字体篇

Unity优化篇 | Unity脚本代码优化策略,空引用快速检索使用合适的数据结构禁用脚本和对象等 性能优化方法

Unity加载模块深度解析(网格篇)

Unity 优化篇| 优化的基本概念/意义,Unity Profiler工具的使用 以及 性能分析的方法精华收藏

Unity 优化篇| 优化的基本概念/意义,Unity Profiler工具的使用 以及 性能分析的方法精华收藏