Unity中動畫資源優(yōu)化

這個是轉自侑虎的文章

下圖中的動畫文件不包含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;        }    }}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末萍歉,一起剝皮案震驚了整個濱河市侣颂,隨后出現的幾起案子,更是在濱河造成了極大的恐慌枪孩,老刑警劉巖憔晒,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異蔑舞,居然都是意外死亡拒担,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門攻询,熙熙樓的掌柜王于貴愁眉苦臉地迎上來从撼,“玉大人,你說我怎么就攤上這事钧栖〉土悖” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵拯杠,是天一觀的道長掏婶。 經常有香客問我,道長潭陪,這世上最難降的妖魔是什么雄妥? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任最蕾,我火速辦了婚禮,結果婚禮上老厌,老公的妹妹穿的比我還像新娘瘟则。我一直安慰自己,他們只是感情好枝秤,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布醋拧。 她就那樣靜靜地躺著,像睡著了一般宿百。 火紅的嫁衣襯著肌膚如雪趁仙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天垦页,我揣著相機與錄音雀费,去河邊找鬼。 笑死痊焊,一個胖子當著我的面吹牛盏袄,可吹牛的內容都是我干的。 我是一名探鬼主播薄啥,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼辕羽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了垄惧?” 一聲冷哼從身側響起刁愿,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎到逊,沒想到半個月后铣口,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡觉壶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年脑题,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铜靶。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡叔遂,死狀恐怖,靈堂內的尸體忽然破棺而出争剿,到底是詐尸還是另有隱情已艰,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布秒梅,位于F島的核電站旗芬,受9級特大地震影響,放射性物質發(fā)生泄漏捆蜀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辆它。 院中可真熱鬧誊薄,春花似錦、人聲如沸锰茉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽飒筑。三九已至片吊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間协屡,已是汗流浹背俏脊。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留肤晓,地道東北人爷贫。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像补憾,于是被迫代替她去往敵國和親漫萄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,304評論 25 707
  • 發(fā)現 關注 消息 iOS 第三方庫盈匾、插件腾务、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,121評論 4 61
  • 兩周年快樂 兩條平行線在730天之前因為某種引力交叉在了一起。 有過迷失削饵,有過陌路岩瘦。 還好我們回頭了。 你的畫葵孤,你...
    傻大個的小胖子閱讀 209評論 0 0
  • 傳說担钮,女孩I與女孩Y是一對很要好的朋友,I要結婚了尤仍,Y還特意為她調配了一瓶香水箫津,并在婚禮上送給了她。眾人揶揄身為好...
    良人DAN閱讀 650評論 0 0
  • “我學美發(fā)的,想開個自己的店赡模,但是又覺得好麻煩” “開店太難了田炭,還是打工比較輕松,你一個女孩子...” 在清晨回武...
    _小胡老師閱讀 413評論 0 0