Unity动画文件优化探究
2017-06-29 10:09
393 查看
原文链接:https://blog.uwa4d.com/archives/Optimization_Animation.html
这是侑虎科技第246篇原创文章,感谢作者舒航供稿,欢迎转发分享,未经作者授权请勿转载。当然,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:465082844)
作者博客:http://www.codershu.com. 同时,作者也是U Sparkle活动参与者哦,UWA欢迎更多开发朋友加入 U
Sparkle开发者计划,这个舞台有你更精彩!
前不久笔者在UWA群里和一些朋友们讨论动画文件精度优化的问题,参考了UWA问答上王亮的代码后,我也尝试优化了动画文件,发现如下的一个很奇怪的现象。下图中Inspector中显示的大小并没有任何变化,但是文件大小和Profiler中的内存大小确实是减小了。那么Inspector中显示的Size是什么含义呢?
下图中的动画文件不包含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减少。BlobSize是不会有任何变化的,因为每个浮点数固定占32bit。而文件大小、AB大小、Editor下的内存大小,压缩精度后不管有没有引起Curve的变化,都会变小。
裁剪动画文件的精度,意味着点的位置发生了变化,所以Constant Curve和Dense Curve的数量也有可能发生变化。由于是裁剪精度所以动画的点更稀疏了,而连续相同的点更多了。所以Dense Curve是减少了,Constant Curve是增多了,总的内存是减小了。
Constant Curve只需要最左边的点就可以描述一个曲线段。
裁剪精度前,大小为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越多优化越明显。
最后附上工具的代码和简要使用说明,选中想要优化的文件夹或文件,右键Animation->裁剪浮点数去除Scale。
感谢舒航的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:465082844)。
也欢迎大家来积极参与U Sparkle开发者计划,简称"US",代表你和我,代表UWA和开发者在一起!
这是侑虎科技第246篇原创文章,感谢作者舒航供稿,欢迎转发分享,未经作者授权请勿转载。当然,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:465082844)
作者博客:http://www.codershu.com. 同时,作者也是U Sparkle活动参与者哦,UWA欢迎更多开发朋友加入 U
Sparkle开发者计划,这个舞台有你更精彩!
前不久笔者在UWA群里和一些朋友们讨论动画文件精度优化的问题,参考了UWA问答上王亮的代码后,我也尝试优化了动画文件,发现如下的一个很奇怪的现象。下图中Inspector中显示的大小并没有任何变化,但是文件大小和Profiler中的内存大小确实是减小了。那么Inspector中显示的Size是什么含义呢?
下图中的动画文件不包含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);//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
工具代码
最后附上工具的代码和简要使用说明,选中想要优化的文件夹或文件,右键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; } } }
感谢舒航的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:465082844)。
也欢迎大家来积极参与U Sparkle开发者计划,简称"US",代表你和我,代表UWA和开发者在一起!
相关文章推荐
- Unity动画文件优化探究
- unity性能优化之降低动画文件的大小
- unity从模型中抽取动画文件(animation)
- 3dsMax导出到unity的fbx文件丢失动画
- unityios开发--unity3d 发布到ios平台 项目工程文件大小优化
- 【Unity&DragonBones】像素角色人物骨骼动画教程(一)PS文件如何导入DragonBones
- 【Unity】骨骼动画如何优化资源
- Unity - 通过降低精度减少动画文件的大小
- 在Unity中修改Fbx中的动画文件
- [Unity-26] AnimationClip压缩-动画文件压缩
- 相约八点,UWA六月直播第三弹-Unity中动画系统的性能优化方案
- unity之动画文件的设置与Apply Root Motion
- UWA 六月直播季 | 6.22 Unity中动画系统性能优化方案回顾
- unity www读取本地视频文件和外部视频文件 播放视频动画和视频声音
- Unity中国象棋(四)——悔棋、判断胜负的实现,以及动画特效和代码的优化
- 【总结】Unity 动画方面的优化
- Unity骨骼动画资源解析与优化
- [IT学堂]DLL文件基本原理及优化技巧
- 优化 Linux/Gnome桌面环境的 窗口动画效果
- 优化 Web 服务器计算机和特定应用程序的配置文件以符合您的特定需要