Unity 纹理,动画,网格加载分析及优化技巧

Unity加载模块深度解析(纹理篇)

认为目前加载模块中最为耗时的性能开销可以归结为以下几类:资源加载、资源卸载、Object的实例化和代码的序列化等。今天,我们先为大家带来资源加载中纹理资源的加载性能分析。
资源加载是加载模块中最为耗时的部分,其CPU开销在Unity引擎中
主要体现在Loading.UpdatePreloadingLoading.ReadObject两项中

Loading.UpdatePreloading,这一项仅在调用类似LoadLevel(Async)的接口处出现,主要负责卸载当前场景的资源,并且加载下一场景中的相关资源和序列化信息等。下一场景中,自身所拥有的GameObject和资源越多,其加载开销越大。

在很多项目中,存在另外一种加载方式,即场景为空场景,绝大部分资源和GameObject都是通过OnLevelWasLoaded回调函数中进行加载、实例化和拼合的。对于这种情况,Loading.UpdatePreloading的加载开销会很小。

Loading.ReadObject,这一项记录的则是资源加载时的真正资源读取性能开销,基本上引擎的主流资源(纹理资源、网格资源、动画片段等等)读取均是通过该项来进行体现。可以说,这一项很大程度上决定了项目场景的切换效率。

资源加载性能测试代码

以下为我们测试时所使用的测试代码,我们将每种资源均制作成一定大小的AssetBundle文件,并逐一通过以下代码在不同设备上进行加载,以期得到不同硬件设备上的资源加载性能比较。

IEnumerator LoadABs(string name)
{
	float currentTime = Time.realtimeSinceStartup;
	string bundlePath = Application.streamingAssetsPath + "/texture_res/" + name;
	WWW www = new WWW(bundlePath);
	while(!www.isDone) yield return null;
	
	AssetBundle bundle = www.assetBundle;
	if(bundle!=null)
	{
		bundle.LoadAllAssets();
	}
	time = Time.realtimeSinceStartup -currentTime;
	bundle.Unload(false)
}

纹理资源是项目加载过程中开销占用最大的资源之一,其加载效率由其自身大小决定。目前,决定纹理资源大小的因素主要有三种:分辨率、格式和Mipmap是否开启

1. 分辨率 & 格式
分辨率和格式是影响纹理资源加载效率的重要因素,因为这两项的设置对纹理资源的大小影响很大。因此,我们对这两种因素进行了详细的测试:

测试1:相同格式、不同分辨率的加载效率测试
我们选取了两张分辨率为2048x2048的普通纹理资源,并在打成AssetBundle时,将其分辨率最大值分别设置为512x512、1024x1024和2048x2048,纹理格式均设置为ETC1(Android)和PVRTC(iOS)、且关闭Mipmap功能。所以,三组纹理的内存占用分别为256KB、1MB和4MB,其对应AssetBundle大小为156KB、531KB和1.92MB(对于Android平台)、175KB、628KB和2.4MB(对于iOS平台)。Unity 版本为5.2,压缩格式为默认的LZMA压缩。

Android平台测试纹理:
请输入图片描述

我们在五种不同档次的机型上加载这些纹理资源,为降低偶然性,每台设备上重复进行十次加载操作并将取其平均值作为最终性能开销。具体测试结果如下表所示。
请输入图片描述
通过上述测试,我们可以得到以下结论:

1、纹理资源的分辨率对加载性能影响较大,分辨率越高,其加载越为耗时。设备性能越差,其耗时差别越为明显;

2、设备越好,加载效率确实越高。但是,对于硬件支持纹理(ETC1/PVRTC)来说,中高端设备的加载效率差别已经很小,比如图中的红米Note2和三星S6设备,差别已经很不明显。

测试2:不同格式、相同分辨率的加载效率测试
我们选取了两张分辨率为1024x1024的普通纹理资源,并在打成AssetBundle时,根据不同平台将其纹理格式分别设置不同格式用于打包。对于Android平台,我们使用ETC1、ETC2、RGBA16和RGBA32四种格式,对于iOS平台,我们使用PVRTC 4BPP、RGBA16和RGBA32三种格式,同时,对于每张纹理均关闭Mipmap功能。所以,三组纹理的内存占用分别为1MB、1MB、4MB 和 8MB(Android平台)/1MB、4MB 和 8MB(iOS平台)。

Android平台测试纹理:
请输入图片描述
与测试1相同,我们在五种不同档次的机型上重复进行十次加载操作并将取其平均值作为最终性能开销。具体测试结果如下图所示。
Android设备:
请输入图片描述
iOS设备:
请输入图片描述
通过上述测试,我们可以得到以下结论:

1、纹理资源的格式对加载性能影响同样较大,Android平台上,ETC1和ETC2的加载效率最高。同样,iOS平台上,PVRTC 4BPP的加载效率最高。

2、RGBA16格式纹理的加载效率同样很高,与RGBA32格式相比,RGBA16其加载效率与ETC1/PVRTC非常接近,并且设备越好,加载开销差别越不明显;

3、RGBA32格式纹理的加载效率受硬件设备的性能影响较大,ETC/PVRTC/RGBA16受硬件设备的影响较低。

注意事项:这里需要指出的是测试中所使用的ETC1和ETC2纹理均为RGB 4Bit格式,所以对于半透明纹理贴图,需要两张ETC1格式的纹理进行支持(一张RGB通道,一张Alpha通道)。逐一加载两张ETC1格式的纹理,其加载效率要低于RGBA16格式,但可以通过加载方式来进行弥补。这一点我们将在后续文章中进行详细说明。

开启Mipmap功能会导致资源加载更为耗时,且设备性能越差,其加载效率影响越大。

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

网格资源

网格资源与纹理资源一样,在加载时同样会造成较高的CPU占用,且其加载效率由其自身大小(网格数据量)决定。因此,我们通过选择不同数据量的网格资源来详细分析其加载效率。   

 

IEnumerator LoadABs(string name)
{
	float currentTime = Time.realtimeSinceStartup;
	string bundlePath = Application.streamingAssetsPath + "/Mesh/" + name;
	WWW www = new WWW(bundlePath);
	while(!www.isDone) yield return null;
	
	AssetBundle bundle = www.assetBundle;
	if(bundle!=null)
	{
		bundle.LoadAllAssets();
	}
	time = Time.realtimeSinceStartup -currentTime;
	bundle.Unload(false)
}

测试1:不同面片数的网格资源加载效率测试
我们选取了四种不同面片数的网格资源,含有的面片数分别为1K、5K、10K和50K,且不含有Tangent顶点属性。四组网格资源的内存占用分别为195KB、0.8MB、1.4MB和3.9MB,其对应AssetBundle大小为43KB、108KB、178KB和507KB。

测试网格:
请输入图片描述

我们在三种不同档次的机型上加载这些网格资源,为降低偶然性,每台设备上重复进行十次加载操作并将取其平均值作为最终性能开销。具体测试结果如下表所示:

Mesh_TriangleCount.jpg

1、资源的数据量对加载性能影响较大,面片数越多,其加载越为耗时。设备性能越差,其耗时差别越为明显;

2、随着硬件设备性能的提升,其加载效率差异越来越不明显。


测试2:相同面片数、不同顶点属性的加载效率测试

我们选择测试1中的网格资源做为该测试的样本数据,并在打包时加入Tangent顶点属性。则四组网格资源的内存占用分别为287KB、1.2MB、2.1MB和5.8MB,其对应AssetBundle大小为72KB、228KB、376KB和937KB。与测试1相同,我们在三种不同档次的机型上重复进行十次加载操作并将取其平均值作为最终性能开销。具体测试结果如下图所示:
请输入图片描述
请输入图片描述
通过上述测试,我们可以得到以下结论:

1、顶点属性的增加对内存和AssetBundle包体大小影响较大。与测试1中未引入Tangent顶点属性的网格数据相比,测试2中的网格数据在内存上均大幅度增加(增加量与网格顶点数有关),且AssetBundle大小同样有成倍(1~2)的增加。

2、顶点属性增加对于加载效率影响较大,且顶点数越多,影响越大。


注意事项:
模型常见的顶点属性主要有Position、UV、Normal、Tangent和Color。Color属性与Tangent属性一样,如果网格顶点拥有该属性,同样会对内存、物理体积和加载性能造成影响。

在使用Draw Call Batching时,切忌将不同属性的网格模型拼合在一起举个例子 ,100个网格模型进行Static Batching,如果99个模型只有Position和UV两种属性,而剩下1个模型函数有Position、UV、Normal、Tangent和Color五种属性。那么引擎在进行拼合时,会将前99个模型的顶点属性补齐,然后再进行拼合。这样无形中会增加大量的内存占用,从而造成不必要的内存浪费。

测试3:开启/关闭Read/Write功能的加载效率测试

我们使用测试1中的网格资源数据,并关闭其Read/Write功能,从而来查看其Read/Write功能对加载效率的影响。关闭Read/Write功能后,四组网格资源的内存占用分别为104KB、454KB、0.8MB和2.3MB,其对应AssetBundle大小为38KB、94KB、152KB和428KB。与测试1相同,我们在三种不同档次的机型上重复进行十次加载操作并将取其平均值作为最终性能开销。具体测试结果如下图所示:
请输入图片描述
请输入图片描述

通过上述测试,我们可以得到以下结论:

1、关闭Read/Write功能会降低AssetBundle的物理大小,其降低量与资源本身数据量相关。同时,关闭Read/Write功能会大幅度降低网格资源的内存占用;

2、关闭Read/Write功能会略微提升该资源的加载效率。


通过以上测试和分析,我们对于网格资源的管理建议如下:

1、在保证视觉效果的前提下,尽可能采用“够用就好”的原则,即降低网格资源的顶点数量和面片数量;

2、研发团队对于顶点属性的使用需谨慎处理。通过以上分析可以看出,顶点属性越多,则内存占用越高,加载时间越长;

3、如果在项目运行过程中对网格资源数据不进行读写操作(比如Morphing动画等),那么建议将Read/Write功能关闭,既可以提升加载效率,又可以大幅度降低内存占用。


Unity加载模块深度解析之动画资源

我们对于AnimationClip资源的加载性能分析同样使用该测试代码。同时,我们将AnimationClip文件制作成一定大小的AssetBundle文件,并逐一通过以下代码在不同设备上进行加载,以期得到相应的资源加载性能比较。

IEnumerator LoadABs(string name)
{
	float currentTime = Time.realtimeSinceStartup;
	string bundlePath = Application.streamingAssetsPath + "/AnimationClip" + name;
	WWW www = new WWW(bundlePath);
	while(!www.isDone) yield return null;
	
	AssetBundle bundle = www.assetBundle;
	if(bundle!=null)
	{
		bundle.LoadAllAssets();
	}
	time = Time.realtimeSinceStartup -currentTime;
	bundle.Unload(false)
}

AnimationClip资源是项目运行时最常加载的资源之一,且其加载效率主要由其自身加载量决定,而决定AnimationClip资源加载量的主要因素则是它的压缩格式。目前,Unity引擎对导入的AnimationClip提供三种压缩格式,

Off  表示不采用压缩处理

Keyframe Reduction  表示使用关键帧进行处理

Optimal  表示Unity引擎会根据动画曲线的特点来自动选择一个最优的压缩方式,可能是关键帧压缩,也可能是Dense压缩。


50个“None Compression”资源、50个“Keyframe Reduction”资源和50个“Optimal”资源,打包成AssetBundle文件后,其文件大小分别为:2.46MB、858KB和525KB。
请输入图片描述

通过上述测试,我们可以得到以下结论:

  1. Optimal压缩方式确实可以提升资源的加载效率,无论是在高端机、中端机还是低端机上;
  2. 硬件设备性能越好,其加载效率越高。但随着设备的提升,Keyframe Reduction和Optimal的加载效率提升已不十分明显;
  3. Optimal压缩方式可能会降低动画的视觉质量,因此,是否最终选择Optimal压缩模式,还需根据最终视觉效果的接受程度来决定。
转自:https://blog.uwa4d.com/archives/Loading_AnimationClip.html

猜你喜欢

转载自blog.csdn.net/zphshiwo/article/details/80742976