動畫(AnimationClip)壓縮

FileSize

  1. FileInfo.Length取得的文件大小
  2. 可以在操作系統(tǒng)的文件系統(tǒng)中看到

MemorySize

  1. Profiler.GetRuntimeMemorySize取得的內存大小
  2. 可以在Profiler中通過采樣看到
  3. 分別在真機和Editor下進行了采樣

BlobSize

  1. 反射取得的AnimationClipStats.size二進制大小
  2. 顯示在AnimationClip的Inspector的面板上
image.png

紅色框內即是BlobSize柳刮,在我的理解,F(xiàn)ileSize是指文件在硬盤中占的大小痢毒,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%莺琳。

image.png
image.png

Curve減少導致內存減小

從上面的實驗可以看出來,只裁剪動畫文件的壓縮精度载慈,沒有引起Curve減少。BlobSize是不會有任何變化的办铡,因為每個浮點數固定占32bit料扰。而文件大小、AB大小拯钻、Editor下的內存大小,壓縮精度后不管有沒有引起Curve的變化,都會變小亩歹。

裁剪動畫文件的精度匙监,意味著點的位置發(fā)生了變化,所以Constant Curve和Dense Curve的數量也有可能發(fā)生變化小作。由于是裁剪精度所以動畫的點更稀疏了亭姥,而連續(xù)相同的點更多了。所以Dense Curve是減少了顾稀,Constant Curve是增多了达罗,總的內存是減小了。

Constant Curve只需要最左邊的點就可以描述一個曲線段静秆。

image.png

只裁剪精度使BlobSize減小的實例

裁剪精度前粮揉,大小為2.2kb巡李,ScaleCurve為0, ConstantCurve為4(57.1%)扶认,Stream(使用Optimal模式這部分數據將儲存為Dense)為3(42.9%)侨拦。

image.png

裁剪精度后,大小為2.1kb蝠引,ConstantCurve為7(100%)阳谍,Stream為0(0%)。裁剪完精度后導致ConstantCurve增加了3螃概,Stream(Optimal模式下即為Dense)減少了3矫夯,BlobSize減小了0.1kb。

image.png

因此吊洼,可以看出训貌,通過精度優(yōu)化降低內存的方式,其實質是將曲線上過于接近的數值(例如相差數值出現(xiàn)在小數點4位以后)直接變?yōu)橐恢旅扒希瑥亩共糠智€變?yōu)閏onstant曲線來降低內存消耗递沪。

取BlobSize代碼

AnimationClip aniClip = AssetDatabase.LoadAssetAtPath<AnimationClip> (path); 
var fileInfo = new System.IO.FileInfo(path); 
Debug.Log(fileInfo.Length);//FileSize 
Debug.Log(Profiler.GetRuntimeMemorySize (aniClip));//MemorySize 

Assembly 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;
using UnityEngine.Profiling;

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(bool scaleOpt)
        {
            Optimize(scaleOpt, 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(會刪除Clip Scale動畫,慎用)")]
        public static void OptimizeScale()
        {
            _AnimOptList = FindAnims();
            if (_AnimOptList.Count > 0)
            {
                _Index = 0;
                _Errors.Clear();
                //EditorApplication.update = ScanAnimationClip;
                ScanAnimationClip(true);
            }
        }
        [MenuItem("Assets/Animation/裁剪浮點數")]
        public static void Optimize()
        {
            _AnimOptList = FindAnims();
            if (_AnimOptList.Count > 0)
            {
                _Index = 0;
                _Errors.Clear();
                //EditorApplication.update = ScanAnimationClip;
                ScanAnimationClip(true);
            }
        }

        private static void ScanAnimationClip(bool scaleOpt)
        {
            AnimationOpt _AnimOpt = _AnimOptList[_Index];
            bool isCancel = EditorUtility.DisplayCancelableProgressBar("優(yōu)化AnimationClip", _AnimOpt.path, (float)_Index / (float)_AnimOptList.Count);
            _AnimOpt.Optimize_Scale_Float3(scaleOpt);
            _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)系作者
  • 序言:七十年代末谬莹,一起剝皮案震驚了整個濱河市檩奠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌附帽,老刑警劉巖埠戳,帶你破解...
    沈念sama閱讀 212,686評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蕉扮,居然都是意外死亡整胃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,668評論 3 385
  • 文/潘曉璐 我一進店門喳钟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屁使,“玉大人,你說我怎么就攤上這事奔则÷牛” “怎么了?”我有些...
    開封第一講書人閱讀 158,160評論 0 348
  • 文/不壞的土叔 我叫張陵应狱,是天一觀的道長共郭。 經常有香客問我祠丝,道長疾呻,這世上最難降的妖魔是什么除嘹? 我笑而不...
    開封第一講書人閱讀 56,736評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮岸蜗,結果婚禮上尉咕,老公的妹妹穿的比我還像新娘。我一直安慰自己璃岳,他們只是感情好年缎,可當我...
    茶點故事閱讀 65,847評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著铃慷,像睡著了一般单芜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上犁柜,一...
    開封第一講書人閱讀 50,043評論 1 291
  • 那天洲鸠,我揣著相機與錄音,去河邊找鬼馋缅。 笑死扒腕,一個胖子當著我的面吹牛,可吹牛的內容都是我干的萤悴。 我是一名探鬼主播瘾腰,決...
    沈念sama閱讀 39,129評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼覆履!你這毒婦竟也來了蹋盆?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,872評論 0 268
  • 序言:老撾萬榮一對情侶失蹤内狗,失蹤者是張志新(化名)和其女友劉穎怪嫌,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體柳沙,經...
    沈念sama閱讀 44,318評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡岩灭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,645評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了赂鲤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片噪径。...
    茶點故事閱讀 38,777評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖数初,靈堂內的尸體忽然破棺而出找爱,到底是詐尸還是另有隱情,我是刑警寧澤泡孩,帶...
    沈念sama閱讀 34,470評論 4 333
  • 正文 年R本政府宣布车摄,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏吮播。R本人自食惡果不足惜变屁,卻給世界環(huán)境...
    茶點故事閱讀 40,126評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望意狠。 院中可真熱鬧粟关,春花似錦、人聲如沸环戈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,861評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽院塞。三九已至遮晚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拦止,已是汗流浹背鹏漆。 一陣腳步聲響...
    開封第一講書人閱讀 32,095評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留创泄,地道東北人艺玲。 一個月前我還...
    沈念sama閱讀 46,589評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像鞠抑,于是被迫代替她去往敵國和親饭聚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,687評論 2 351

推薦閱讀更多精彩內容