Unity3D性能优化之资源原理科普篇
一、Unity的资源(Asset)和对象(UnityEngine.Objects)
资源(Asset):是硬盘中的文件,存储在Unity工程的Assets文件夹内。例如,纹理(Texture),材质(Material)和FBX文件等,它们都是资源。一些资源的数据格式是Unity原生支持的,例如材质。有些资源则需要转换为原生的数据格式后才能被Unity使用,例如FBX文件。
对象(UnityEngine.Object):代表序列化数据的集合,表示某个资源的具体实例。它可以是Unity引擎使用的任何类型的资源,例如网格,Sprite,音频剪辑或动画剪辑。所有的对象(Object)都是UnityEngine.Object基类的子类。
几乎所有的对象(Object)类型都是内建的,其中有两种比较特殊的类型。
ScriptableObject为开发者提供了一套便捷的系统,供开发者自定义数据类型。这些类型可以被Unity直接序列化或反序列化,并在Unity编辑器的检视器窗口中进行操作。
MonoBehaviour提供了链接MonoScript的容器。MonoScript是一种内部数据类型,Unity用它保存对某个特定程序集和命名空间中特定脚本类的引用,MonoScript本身不包含任何实际的可执行代码。
资源(Asset)与对象(Object)是一种一对多的关系,即一个资源文件可能会包括多个Object。
二、对象之间的引用
所有UnityEngine.Objects都可以引用其他的UnityEngine.Objects。这里“其他的Object”可能存在于相同的资源文件中,或需要从其他资源文件导入。例如,一个材质Object通常有一个或多个纹理Object的引用。这些纹理Object一般是从一个或多个纹理资源文件中导入的(例如PNG或JPG文件)。
序列化后,这些引用由两部分数据组成:文件GUID和本地ID。文件GUID用于识别资源(Asset)文件中目标资源(Resource)的存储位置。而本地唯一的ID负责识别单个资源文件中的Object,因为一个资源文件可能会包含多个Object。
比如一个特效做成的Prefab,直接用文本打开prefab和prefab.meta后缀的两个文件。
GUID:存储于.meta文件中。Unity会在首次导入资源文件时生成.meta文件,并和资源文件一起存储在相同的目录中。GUID提供了文件存储位置的抽象,这样一个文件GUID就对应一个具体的文件,因此我们才能随意移动这个文件而不破坏所有相关Object对这个文件的引用。
fileFormatVersion: 2
guid: 87160fe309c6cd4458c5f56188b57684
timeCreated: 1568165766
licenseType: Pro
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
本地ID:是唯一的,使用文本编辑器打开prefab文件,可以看下关于这个预设的所有属性都在配置里面,100100000就是本地ID。任何资源(Asset)文件中都可能含有(或通过导入产生)多个UnityEngine.Object资源(Resource),因此需要一个本地ID来对其中的Object做明确区分。一个大的预设里面会有多个gameobject,相当于总的预设会记录子物体的本地ID,这样才能关联到每一个子物体。
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1001 &100100000
Prefab:
m_ObjectHideFlags: 1
serializedVersion: 2
m_Modification:
m_TransformParent: {fileID: 0}
m_Modifications: []
m_RemovedComponents: []
m_ParentPrefab: {fileID: 0}
m_RootGameObject: {fileID: 1891991147743598}
m_IsPrefabParent: 1
--- !u!1 &1053155390742798
总结:Guid相当于指向一个资源路径;本地ID相当于指向于具体的游戏对象Object
三、资源引用分析
我们知道了资源和对象的关系后,对资源引用过程已经清楚了。那么怎么分析资源的引用关系呢?
AssetDatabase.AssetPathToGUID(path)
AssetDatabase.GUIDToAssetPath(guid)
看代码就大概了解,这两个方法可以将guid和path进行互相转换,因此可以根据预设里引用到的所有guid值,进而可以找到这个资源所引用到对象。
四、Unity资源引用的机制
我们在Unity如果创建一个Materials,然后指定一个有两张图片属性的Shader,接着引用了两张纹理。这时候打开材质可以看到是有引用到两个纹理m_Texture。可以看下原先的属性:
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_Name: 5400_fangkuai
m_Shader: {fileID: 4800000, guid: b5f72fe4d91f50b47920a7498eeaf32a, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _AmitTex:
m_Texture: {fileID: 2800000, guid: 35a133778d8d5f64788367aa21dd2589, type: 3}
m_Scale: {x: 0.5, y: 1}
m_Offset: {x: 0, y: 0}
- _FlowTex:
m_Texture: {fileID: 2800000, guid: 4cccfa2e1ba52a342b545da00de76a29, type: 3}
m_Scale: {x: -2, y: -2}
m_Offset: {x: 0, y: 0}
m_Floats:
- _AmitIntensity: 2
- _FlowIntensity: 4
- _FlowTexUSpeed: 0.5
- _FlowTexVSpeed: -0.8
m_Colors:
- _Color: {r: 0.32352942, g: 0.5521299, b: 1, a: 1}
- _FlowColor: {r: 0, g: 0.28539556, b: 0.61764705, a: 1}
如果这时我改一下Shader,而且这个Shader只引用一张纹理,我不赋值。这个材质引用的纹理就变成了三张,前面Shader引用到的纹理依旧在引用。
m_TexEnvs:
- _AmitTex:
m_Texture: {fileID: 2800000, guid: 35a133778d8d5f64788367aa21dd2589, type: 3}
m_Scale: {x: 0.5, y: 1}
m_Offset: {x: 0, y: 0}
- _Amitex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _FlowTex:
m_Texture: {fileID: 2800000, guid: 4cccfa2e1ba52a342b545da00de76a29, type: 3}
m_Scale: {x: -2, y: -2}
m_Offset: {x: 0, y: 0}
总结:这个引用机制就是每次改变时,只增加新的引用,旧的引用不会去删除。
这个机制的好处:是当又改成之前的shader时,会自动引用到纹理和设置属性。
这个机制的坏处:资源会导致冗余,打包时会打到多余的资源对象,加载时也会加载多余的资源对象。
五、Unity资源冗余
我们知道了引用机制,那么可以发现一些应该避免和注意的问题。
- 当有对象被删除时,引用此对象的依赖依然存在,应该处理。
- 材质:材质会引用纹理,而纹理是根据材质所引用的Shader决定,所以应该针对所有材质进行处理纹理的引用。
- 粒子系统(Particle System):粒子系统可以引用FBX和材质,在Renderer中可以引用到FBX网格,这部分也会引起FBX和纹理引用冗余。
六、资源打包冗余
打包冗余是指相同的对象,比如纹理被重复打进多个AB包,这样会造成包体变大,加载重复资源的问题。
所以我们应该根据这些特性或者说机制,来分析资源引用的关系,再来进行引用的优化。所以针对这种情况会不同的资源先进行分析,最后再根据分析的情况来进行优化。