Unity中动画资源优化

这个是转自侑虎的文章

下图中的动画文件不包含Scale曲线,所以这次优化中只压缩了浮点数精度。

我分别对比了动画文件优化前和优化后的大小。

FileSize

FileInfo.Length取得的文件大小

可以在操作系统的文件系统中看到

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变为7.4KB,所以BlobSize减小了27%。


Curve减少导致内存减小

从上面的实验可以看出来,只裁剪动画文件的压缩精度,没有引起Curve减少。BlobSize是不会有任何变化的,因为每个浮点数固定占32bit。而文件大小、AB大小、Editor下的内存大小,压缩精度后不管有没有引起Curve的变化,都会变小。

裁剪动画文件的精度,意味着点的位置发生了变化,所以Constant Curve和Dense Curve的数量也有可能发生变化。由于是裁剪精度所以动画的点更稀疏了,而连续相同的点更多了。所以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。

因此,可以看出,通过精度优化降低内存的方式,其实质是将曲线上过于接近的数值(例如相差数值出现在小数点4位以后)直接变为一致,从而使部分曲线变为constant曲线来降低内存消耗。


总结

隔壁项目组对他们项目中所有的动画文件都进行了优化。其中文件大小从820MB->225MB, ab大小从72MB->64MB, 内存大小从50MB->40MB。总的来说动画文件的scale越多优化越明显。


取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

工具代码

最后附上工具的代码和简要使用说明,选中想要优化的文件夹或文件,右键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("优化AnimationClip", _AnimOpt.path, (float)_Index / (float)_AnimOptList.Count);            _AnimOpt.Optimize_Scale_Float3();            _Index++;            if (isCancel || _Index >= _AnimOptList.Count)            {                EditorUtility.ClearProgressBar();                Debug.Log(string.Format("--优化完成--    错误数量: {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;        }    }}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 两周年快乐 两条平行线在730天之前因为某种引力交叉在了一起。 有过迷失,有过陌路。 还好我们回头了。 你的画,你...
    傻大个的小胖子阅读 198评论 0 0
  • 传说,女孩I与女孩Y是一对很要好的朋友,I要结婚了,Y还特意为她调配了一瓶香水,并在婚礼上送给了她。众人揶揄身为好...
    良人DAN阅读 637评论 0 0
  • “我学美发的,想开个自己的店,但是又觉得好麻烦” “开店太难了,还是打工比较轻松,你一个女孩子...” 在清晨回武...
    _小胡老师阅读 403评论 0 0