這個是轉自侑虎的文章
下圖中的動畫文件不包含Scale曲線,所以這次優(yōu)化中只壓縮了浮點數精度。
我分別對比了動畫文件優(yōu)化前和優(yōu)化后的大小凌节。
FileSize
FileInfo.Length取得的文件大小
可以在操作系統(tǒng)的文件系統(tǒng)中看到
MemorySize
Profiler.GetRuntimeMemorySize取得的內存大小
可以在Profiler中通過采樣看到
分別在真機和Editor下進行了采樣
BlobSize
反射取得的AnimationClipStats.size二進制大小
顯示在AnimationClip的Inspector的面板上
紅色框內即是BlobSize杉适,在我的理解,FileSize是指文件在硬盤中占的大小亮曹,BlobSize是從文件反序列化出來的對象的二進制大小。Editor下的MemorySize不僅有序列化后的內存大小,還維護了一份原文件的內存乾忱。就像我們在Editor下加載一張Texture內存是雙份一樣讥珍,而真機下就約等于BlobSize。真機下的MemorySize和Inspector里的BlobSize非常接近窄瘟,BlobSize可以認為是真機上的內存大小衷佃,這個大小更有參考意義。
同時蹄葱,我也對去除Scale曲線的方法進行了實驗氏义。下圖這個動畫文件原來Inspector中Scale的值為4,即有Scale曲線图云,原始文件BlobSize為10.2KB惯悠,去除Scale曲線后,Blob Size變?yōu)?.4KB竣况,所以BlobSize減小了27%克婶。
Curve減少導致內存減小
從上面的實驗可以看出來,只裁剪動畫文件的壓縮精度丹泉,沒有引起Curve減少情萤。BlobSize是不會有任何變化的,因為每個浮點數固定占32bit摹恨。而文件大小筋岛、AB大小、Editor下的內存大小晒哄,壓縮精度后不管有沒有引起Curve的變化睁宰,都會變小。
裁剪動畫文件的精度寝凌,意味著點的位置發(fā)生了變化柒傻,所以Constant Curve和Dense Curve的數量也有可能發(fā)生變化。由于是裁剪精度所以動畫的點更稀疏了硫兰,而連續(xù)相同的點更多了诅愚。所以Dense Curve是減少了,Constant Curve是增多了劫映,總的內存是減小了。
Constant Curve只需要最左邊的點就可以描述一個曲線段刹前。
只裁剪精度使BlobSize減小的實例
裁剪精度前泳赋,大小為2.2kb,ScaleCurve為0喇喉, ConstantCurve為4(57.1%)祖今,Stream(使用Optimal模式這部分數據將儲存為Dense)為3(42.9%)。
裁剪精度后,大小為2.1kb千诬,ConstantCurve為7(100%)耍目,Stream為0(0%)。裁剪完精度后導致ConstantCurve增加了3徐绑,Stream(Optimal模式下即為Dense)減少了3邪驮,BlobSize減小了0.1kb。
因此傲茄,可以看出毅访,通過精度優(yōu)化降低內存的方式,其實質是將曲線上過于接近的數值(例如相差數值出現在小數點4位以后)直接變?yōu)橐恢屡陶ィ瑥亩共糠智€變?yōu)閏onstant曲線來降低內存消耗喻粹。
總結
隔壁項目組對他們項目中所有的動畫文件都進行了優(yōu)化。其中文件大小從820MB->225MB, ab大小從72MB->64MB, 內存大小從50MB->40MB草巡∈匚兀總的來說動畫文件的scale越多優(yōu)化越明顯。
取BlobSize代碼
AnimationClip aniClip = AssetDatabase.LoadAssetAtPath<AnimationClip> (path);var fileInfo = new System.IO.FileInfo(path);Debug.Log(fileInfo.Length);//FileSizeDebug.Log(Profiler.GetRuntimeMemorySize (aniClip));//MemorySizeAssembly asm = Assembly.GetAssembly(typeof(Editor));MethodInfo getAnimationClipStats = typeof(AnimationUtility).GetMethod("GetAnimationClipStats", BindingFlags.Static | BindingFlags.NonPublic);Type aniclipstats = asm.GetType("UnityEditor.AnimationClipStats");FieldInfo sizeInfo = aniclipstats.GetField ("size", BindingFlags.Public | BindingFlags.Instance);var stats = getAnimationClipStats.Invoke(null, new object[]{aniClip});Debug.Log(EditorUtility.FormatBytes((int)sizeInfo.GetValue(stats)));//BlobSize
工具代碼
最后附上工具的代碼和簡要使用說明山憨,選中想要優(yōu)化的文件夾或文件弛饭,右鍵Animation->裁剪浮點數去除Scale。
//****************************************************************************//// File: OptimizeAnimationClipTool.cs//// Copyright (c) SuiJiaBin//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A// PARTICULAR PURPOSE.////****************************************************************************using System;using System.Collections.Generic;using UnityEngine;using System.Reflection;using UnityEditor;using System.IO;namespace EditorTool{ class AnimationOpt { static Dictionary<uint,string> _FLOAT_FORMAT; static MethodInfo getAnimationClipStats; static FieldInfo sizeInfo; static object[] _param = new object[1]; static AnimationOpt () { _FLOAT_FORMAT = new Dictionary<uint, string> (); for (uint i = 1; i < 6; i++) { _FLOAT_FORMAT.Add (i, "f" + i.ToString ()); } Assembly asm = Assembly.GetAssembly (typeof(Editor)); getAnimationClipStats = typeof(AnimationUtility).GetMethod ("GetAnimationClipStats", BindingFlags.Static | BindingFlags.NonPublic); Type aniclipstats = asm.GetType ("UnityEditor.AnimationClipStats"); sizeInfo = aniclipstats.GetField ("size", BindingFlags.Public | BindingFlags.Instance); } AnimationClip _clip; string _path; public string path { get{ return _path;} } public long originFileSize { get; private set; } public int originMemorySize { get; private set; } public int originInspectorSize { get; private set; } public long optFileSize { get; private set; } public int optMemorySize { get; private set; } public int optInspectorSize { get; private set; } public AnimationOpt (string path, AnimationClip clip) { _path = path; _clip = clip; _GetOriginSize (); } void _GetOriginSize () { originFileSize = _GetFileZie (); originMemorySize = _GetMemSize (); originInspectorSize = _GetInspectorSize (); } void _GetOptSize () { optFileSize = _GetFileZie (); optMemorySize = _GetMemSize (); optInspectorSize = _GetInspectorSize (); } long _GetFileZie () { FileInfo fi = new FileInfo (_path); return fi.Length; } int _GetMemSize () { return Profiler.GetRuntimeMemorySize (_clip); } int _GetInspectorSize () { _param [0] = _clip; var stats = getAnimationClipStats.Invoke (null, _param); return (int)sizeInfo.GetValue (stats); } void _OptmizeAnimationScaleCurve () { if (_clip != null) { //去除scale曲線 foreach (EditorCurveBinding theCurveBinding in AnimationUtility.GetCurveBindings(_clip)) { string name = theCurveBinding.propertyName.ToLower (); if (name.Contains ("scale")) { AnimationUtility.SetEditorCurve (_clip, theCurveBinding, null); Debug.LogFormat ("關閉{0}的scale curve", _clip.name); } } } } void _OptmizeAnimationFloat_X (uint x) { if (_clip != null && x > 0) { //浮點數精度壓縮到f3 AnimationClipCurveData[] curves = null; curves = AnimationUtility.GetAllCurves (_clip); Keyframe key; Keyframe[] keyFrames; string floatFormat; if (_FLOAT_FORMAT.TryGetValue (x, out floatFormat)) { if (curves != null && curves.Length > 0) { for (int ii = 0; ii < curves.Length; ++ii) { AnimationClipCurveData curveDate = curves [ii]; if (curveDate.curve == null || curveDate.curve.keys == null) { //Debug.LogWarning(string.Format("AnimationClipCurveData {0} don't have curve; Animation name {1} ", curveDate, animationPath)); continue; } keyFrames = curveDate.curve.keys; for (int i = 0; i < keyFrames.Length; i++) { key = keyFrames [i]; key.value = float.Parse (key.value.ToString (floatFormat)); key.inTangent = float.Parse (key.inTangent.ToString (floatFormat)); key.outTangent = float.Parse (key.outTangent.ToString (floatFormat)); keyFrames [i] = key; } curveDate.curve.keys = keyFrames; _clip.SetCurve (curveDate.path, curveDate.type, curveDate.propertyName, curveDate.curve); } } } else { Debug.LogErrorFormat ("目前不支持{0}位浮點", x); } } } public void Optimize (bool scaleOpt, uint floatSize) { if (scaleOpt) { _OptmizeAnimationScaleCurve (); } _OptmizeAnimationFloat_X (floatSize); _GetOptSize (); } public void Optimize_Scale_Float3 () { Optimize (true, 3); } public void LogOrigin () { _logSize (originFileSize, originMemorySize, originInspectorSize); } public void LogOpt () { _logSize (optFileSize, optMemorySize, optInspectorSize); } public void LogDelta () { } void _logSize (long fileSize, int memSize, int inspectorSize) { Debug.LogFormat ("{0} \nSize=[ {1} ]", _path, string.Format ("FSize={0} ; Mem->{1} ; inspector->{2}", EditorUtility.FormatBytes (fileSize), EditorUtility.FormatBytes (memSize), EditorUtility.FormatBytes (inspectorSize))); } } public class OptimizeAnimationClipTool { static List<AnimationOpt> _AnimOptList = new List<AnimationOpt> (); static List<string> _Errors = new List<string>(); static int _Index = 0; [MenuItem("Assets/Animation/裁剪浮點數去除Scale")] public static void Optimize() { _AnimOptList = FindAnims (); if (_AnimOptList.Count > 0) { _Index = 0; _Errors.Clear (); EditorApplication.update = ScanAnimationClip; } } private static void ScanAnimationClip() { AnimationOpt _AnimOpt = _AnimOptList[_Index]; bool isCancel = EditorUtility.DisplayCancelableProgressBar("優(yōu)化AnimationClip", _AnimOpt.path, (float)_Index / (float)_AnimOptList.Count); _AnimOpt.Optimize_Scale_Float3(); _Index++; if (isCancel || _Index >= _AnimOptList.Count) { EditorUtility.ClearProgressBar(); Debug.Log(string.Format("--優(yōu)化完成-- 錯誤數量: {0} 總數量: {1}/{2} 錯誤信息↓:\n{3}\n----------輸出完畢----------", _Errors.Count, _Index, _AnimOptList.Count, string.Join(string.Empty, _Errors.ToArray()))); Resources.UnloadUnusedAssets(); GC.Collect(); AssetDatabase.SaveAssets(); EditorApplication.update = null; _AnimOptList.Clear(); _cachedOpts.Clear (); _Index = 0; } } static Dictionary<string,AnimationOpt> _cachedOpts = new Dictionary<string, AnimationOpt> (); static AnimationOpt _GetNewAOpt (string path) { AnimationOpt opt = null; if (!_cachedOpts.ContainsKey(path)) { AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip> (path); if (clip != null) { opt = new AnimationOpt (path, clip); _cachedOpts [path] = opt; } } return opt; } static List<AnimationOpt> FindAnims() { string[] guids = null; List<string> path = new List<string>(); List<AnimationOpt> assets = new List<AnimationOpt> (); UnityEngine.Object[] objs = Selection.GetFiltered(typeof(object), SelectionMode.Assets); if (objs.Length > 0) { for(int i = 0; i < objs.Length; i++) { if (objs [i].GetType () == typeof(AnimationClip)) { string p = AssetDatabase.GetAssetPath (objs [i]); AnimationOpt animopt = _GetNewAOpt (p); if (animopt != null) assets.Add (animopt); } else path.Add(AssetDatabase.GetAssetPath (objs [i])); } if(path.Count > 0) guids = AssetDatabase.FindAssets (string.Format ("t:{0}", typeof(AnimationClip).ToString().Replace("UnityEngine.", "")), path.ToArray()); else guids = new string[]{}; } for(int i = 0; i < guids.Length; i++) { string assetPath = AssetDatabase.GUIDToAssetPath (guids [i]); AnimationOpt animopt = _GetNewAOpt (assetPath); if (animopt != null) assets.Add (animopt); } return assets; } }}