资源导入优化
随着项目越来越大,资源越来越多,有一套资源导入自动化设置很有必要,它不但可以减少你的工作量,也能更好的统一管理资源,保证资源的导入设置最优,还不会出错。
AssetPostprocessor
在Unity中除了引擎自己创建的资源,更多的是外部导入进来的,像贴图纹理、模型动画、音效视频,这些导入的外部资源都会经过Unity引擎转换成一种Unity内部格式的资源,存储在Library下。
AssetPostProcessor是一个编辑器类,用来管理资源导入,当资源导入之前和之后都会发送通知,可以根据不同的资源类型,在导入之前和之后做不同的处理,来修改Untiy内部格式资源。一般我们通过这个类中OnPreprocess和OnPostprocess消息处理函数来修改资源数据和设置,这两者的区别可以简单理解为:
- OnPreprocess用来改变资源报错在meta中的序列化信息,也就是说大部分是Inspector视图可见的选项。
- OnPostprocess为导入过程中的一些处理,结果保存在最终的Library,是和序列化无关的。
AssetPostprocessor 常用API介绍:
using UnityEngine;
using UnityEditor;
public class MyAssetPostprocessor : AssetPostprocessor
{
//模型导入之前调用
public void OnPreprocessModel() { }
//模型导入之后调用
public void OnPostprocessModel(GameObject go) { }
//Texture导入之前调用,针对Texture进行设置
public void OnPreprocessTexture() { }
//Texture导入之后调用,针对Texture进行设置
public void OnPostprocessTexture(Texture2D tex) { }
//导入Audio后操作
public void OnPostprocessAudio(AudioClip clip) { }
//导入Audio前操作
public void OnPreprocessAudio() { }
//所有的资源的导入,删除,移动,都会调用此方法,注意,这个方法是static的
public static void OnPostprocessAllAssets(string[] importedAsset, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
foreach (string filePath in importedAsset)
{
Debug.Log("importedAsset:" + filePath);
}
foreach (string filePath in deletedAssets)
{
Debug.Log("deletedAssets:" + filePath);
}
foreach (string filePath in movedAssets)
{
Debug.Log("movedAssets:" + filePath);
}
foreach (string filePath in movedFromAssetPaths)
{
Debug.Log("movedFromAssetPaths:" + filePath);
}
}
}
纹理导入设置示例:
public void OnPreprocessTexture()
{
//获得importer实例
TextureImporter textureImporter = assetImporter as TextureImporter;
Debug.Log("OnPreprocessTexture: " + textureImporter.assetPath);
//设置Read/Write Enabled开关,不勾选
textureImporter.isReadable = false;
if (textureImporter.assetPath.StartsWith("Assets/UI"))
{
//设置UI纹理Generate Mipmaps
textureImporter.mipmapEnabled = false;
//设置UI纹理WrapMode
textureImporter.wrapMode = TextureWrapMode.Clamp;
}
}
也可以使用OnPostprocessAllAssets,导入音效设置示例:
public static void OnPostprocessAllAssets(string[] importedAsset, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
foreach (string filePath in importedAsset)
{
if (filePath.EndsWith(".wav") || filePath.EndsWith(".mp3"))
{
var Importer = AssetImporter.GetAtPath(filePath);
if (Importer != null && Importer.GetType() == typeof(AudioImporter))
{
var mImporter = Importer as AudioImporter;
mImporter.forceToMono = true;
var set = mImporter.defaultSampleSettings;
if (set.sampleRateOverride != 22050)
{
set.sampleRateSetting = AudioSampleRateSetting.OverrideSampleRate;
set.sampleRateOverride = 22050;
mImporter.defaultSampleSettings = set;
}
}
}
}
}
Preset Manager
除了AssetPostprocessor,Unity还提供了Preset Manager,也可以对Unity的资源设置进行统一管理。Preset Manager通过创建Preset(预设),保存对象的属性信息,以此为模板可应用到新创建的组件或新导入的资源中,使它们有相同的属性设置。
Preset Manager: 是允许管理在将组件添加到游戏对象或将新资源添加到项目时创建的自定义预设以指定默认属性。您定义的默认预设会覆盖Unity的默认设置。 注意:不能为项目设置、首选项设置或原生资源(Materials, Animations, or SpriteAtlas)设置默认属性。 除了在创建component和导入资源时使用默认Presets外,在Inspecrot窗口中使用 Reset 按钮时,Unity还会应用默认Presets设置.
- Filter 使用 Filter 字段来定义预设应用于哪些组件或导入器。
- Preset 使用 Preset 字段设置要使用的预设。默认情况下,预设会在您创建后应用于该预设类型的所有组件或资源导入器。如果您只想将其应用于特定组件或资源类型,请使用 Filter 字段来定义何时应用预设。
- Add Default Preset 选择此按钮选择要为其添加预设的导入器、组件或者 ScriptableObject 。如果您选择导入器或者组件,选择要为其创建预设的资源导入器或组件的类型。
创建Preset
以音效为例,当你有一个音效文件,导入Unity并设置好你理想的属性,在Inspector栏点击保存预设的按钮,然后点击SaveCurrentTo,保存为一个新的Preset。
你可以创建三种类型的Preset,导入器、组件、ScriptableObject
创建好Preset后,在Preset Manager窗口点击Add Default Preset,则可为默认设置指定预设。
你可以添加各种各样的Preset,同一种组件,同一种导入器还可以有多个Preset,可以使用Filter(过滤器)规则来定义何时应用预设,可以按文件名、目录和文件扩展名进行过滤。
Preset Manager 虽然方便,但灵活性有所欠缺。以音效为例,如果要区分长音效短音效应用不同的设置,Preset Manager则无法满足,但AssetPostprocessor则可以。
纹理优化
纹理可以说是Unity中最常用的资源了,也是资源占比最多的,纹理使用不当将给项目带来严重的性能问题。
纹理压缩格式的选择
纹理压缩是一种图像压缩,用于在保持视觉质量的同时减小纹理数据大小。我们平时看到的纹理都是png、jpg、tga等格式,但在Unity中所说的纹理压缩和这些格式几乎没有关系,纹理导入Unity后,会生成Unity能识别的Texture类型资源。
Unity 支持许多常见的图像格式作为导入纹理的源文件(例如 JPG、PNG、PSD 和 TGA)。但是,3D 图形硬件(例如显卡或移动设备)在实时渲染期间不会使用这些格式。这种硬件要求纹理以专门格式进行压缩,这些格式针对快速纹理采样进行了优化。不同的平台和设备分别都有自己的专有格式。
为什么需要纹理压缩呢
主要考虑内存+带宽,尤其在移动设备上,内存带宽有限。假如我们直接使用RGB 16、RGBA 32 这些未经压缩的纹理格式,如一张RGBA32格式512*512的纹理贴图,一个像素占4Byte,512x512x4B=1048576B=1M,但如果使用ETC2,只有256KB,相差4倍。内存是一方面,另一个重要的是数据传输时的带宽,带宽是发热的元凶,特别在移动设备上。
因此我们需要一种内存占用既小又能被GPU读取的格式——压缩纹理格式。纹理压缩对应的算法是以某种形式的固定速率有损向量量化将固定大小的像素块编码进固定大小的字节块中。
为什么不直接使用png、jpg、tga这类常见的压缩格式?
尽管png、jpg、tga的压缩率很高,但并不适合纹理,主要问题是不支持像素的随机访问,这对GPU相当不友好,GPU渲染时只使用需要的纹理部分,我们总不能为了访问某个像素去解码整张纹理吧?不知道顺序,也不确定相邻的三角形是否在纹理上采样也相邻,很难有优化。这类格式更适合下载传输以及减少磁盘空间而使用,平时我们能在图像软件浏览这些格式的图片,是经过CPU解压后,再传给GPU渲染的。
移动平台纹理压缩格式选择
ASTC
ASTC是在OpenGL ES3.0出现后在2012年中产生的一种业界领先的纹理压缩格式,它的压缩分块从4x4到12x12最终可以压缩到每个像素占用1bit以下,压缩比例有多种可选。ASTC格式支持RGBA,且适用于2的幂次方长宽等比尺寸和无尺寸要求的NPOT(非2的幂次方)纹理。
ASTC在Android、IOS、WebGL都支持,在压缩质量和容量上有很大的优势。如一张512*512的纹理贴图,ASTC 6x6 block压缩格式,内存占用为115.6KB,只有ETC2的一半,但质量和ETC2相差无几。
不管Android、IOS都首推ASTC,都2023年了,国产Android手机基本都支持了,IOS在iphone6以上的设备都支持。
Android RGBA Compressed ETC2
如果你的项目考虑到兼容老旧设备,或者海外手机的情况下,Android RGBA Compressed ETC2是首选
Android RGBA Crunched ETC2
Crunch 压缩是一种基于 DXT 或 ETC 纹理压缩的有损压缩格式(压缩过程中会丢失部分数据)。在运行时,纹理在 CPU 上解压缩为 DXT 或 ETC,然后上传到 GPU。Crunch 压缩有助于纹理在磁盘上使用尽可能少的空间,但对于运行时内存使用量没有影响。Crunch 纹理可能需要很长时间进行压缩,但在运行时的解压缩速度非常快。
RGBA Crunched ETC2,虽然质量方面有些丢失,但包体小很多,对包体有要求或者网络下载有要求都可考虑。
IOS RGBA Compressed PVRTC 4
如果你的项目考虑到兼容iphone5s以及之前的设备,只能选择RGBA Compressed PVRTC 4了,PVRTC压缩质量一般会比较差,可以考虑拆分贴图,另外PVRTC要求纹理的长宽相等并且要是2的整次幂。
选择纹理压缩格式需要在文件大小、质量和压缩时间之间取得平衡。