一、研究动画精简的原因
最近做一个3D卡牌项目,角色非常的多,每个角色的动作也比较多。
项目打包之后,发现包体容量比较大,然后分析了AssetBundle资源,发现里面的动画文件非常的大,有些角色一个动画文件打包AssetBundle之后居然有1M多,这样一个角色单纯是动画文件就占了超过10M了。
由于这次的美术团队是新合作的,一开始也没有留意动画的制作方式是否有问题,既然发现存在异常了,于是就对美术资源进行分析。
经过一番尝试之后,成果还是很明显的,在保持原有动画效果的基础上,能把原来1m多的的AssetBundle文件减小到不到200k。
二、动画文件容量分析
1、动画文件的导入问题
一般复杂的角色动画,都不会是在Unity里面k出来的,而是从比如3DsMax之类的软件制作,然后导出fbx文件到Unity里面使用,在导入的时候,Unity有针对动画的压缩选项,Anim.Compression:
具体有3种不同的选项:
(1).Off
(2)Keyframe Reduction
(3)Optimal
具体来研究一下这三个选项对产生的动画文件的区别在哪里:
(1).Off:
顾名思义,就是完全不压缩。但实际上,他还有另外的一个作用,就是烘焙关键帧。
举个例子:
我们有这么一段动画,正常的情况下,关键帧的分布情况是这样的。
现在我们把动画压缩的选项改为Off
现在再看关键帧,就变成这样了。
在选择了Off之后,引擎对于动画的每一个节点的每一帧,都做了计算并生成了关键帧,这样做的好处是,动画会变得非常的准确,不再因为动画的曲线过渡,或者打忘记打前后关键帧而导致各种动画的滑步、抖动、动画切换某些节点异常等问题。
不过说句我个人的看法。如果动画真的出现了各种异常,本身就是k动画的过程中的疏忽或者错漏导致的,正常的解决办法应该是去修改原始动画文件,而不是靠烘焙关键帧这种操作来避免错误。
从肉眼可以看出当选择了Off之后,由于关键帧大量增多,所以动画文件也会变得巨大无比。
(2)Keyframe Reduction
选择这种方式,从单词直译,就是减少关键帧。这种情况下,Unity会保持动画在导出Fbx时的关键帧信息,并做一定量的关键帧优化。选择这个选项后,动画关键帧看起来是不会有什么区别的。
(3)Optimal
从单词直译,这种方式叫做最优的,最佳的。从命名上可以看出,Unity是非常推荐这种压缩方式的。实际上,从关键帧的分布看,Optimal和KeyframeReduction是一样的。从Unity的官方文档看,Optimal是在KeyframeReduction的基础上,进一步选择最优的动画曲线,最终减少内存的占用。
Optimal实际上进行的操作,应该是进一步的去掉重复和多余的关键帧,以最少关键帧作为曲线过渡的形式做插值计算。
单纯从Unity的Api描述,还有实际看Animation时间轴的关键帧分别,我们就可以知道,从容量来说,肯定是Off>KeyframeReduction>Optimal的。所以按道理来说,这里应该是选择Optimal作为动画的压缩方式。
在我的这个项目里面,我发现很多动画选择了Off,我问了一下动画师,原来是因为有些角色的动画从3DsMax里面导入到Unity,发现播放时角色的脚出现了滑步,而原始动作的脚应该是固定的。所以动画师选择了把动画压缩设置Off的方式,想用大量的关键帧去解决这个问题。
这里我想说一个纯美术人员和技术人员之间的明显分歧,篇幅有点长,如果不敢兴趣可以直接跳过到下一小节。
从动画师的角度看,最终动画的确不会滑步了,感觉问题已经解决了,所以也不会去再找技术去解决这个问题。但从技术的角度看,虽然看起来的动画表现好像是没问题的,但实际上产生了容量巨大的动画文件,占用的包体容量和内存也会大幅度的升高。
我一直觉得,美术团队一定要有可靠的技术美术(TA)作为支撑,不能靠纯美术人员凭经验来想办法解决问题。因为所谓的经验,一般就是,以前某个项目是怎样怎样做没问题。但实际的情况是,不同的项目,使用资源的方式和遇到的重点问题可能都不一样,如果在遇到问题的时候,没有从学术性上的理论支撑,而单凭经验来解决问题,其实有很大的运气成分,可能真的解决了问题,也可能根本没有解决,甚至产生了新的问题。
有人可能会说,项目很紧急,来不及思考这么多,就先随便动手解决了。但如果连产生问题的根本原因都不知道的情况下,贸然动手往往是浪费更多人力成本而没有达到目的。花时间做实际的研究分析,虽然前期可能会花更多时间,但实际动手的过程会很快,往往是写几行代码批处理一下就行了,并且问题解决得彻底。我个人习惯是比较喜欢每件事情都搞清楚原因和原理,才去动手做的。
当然了,如果某些团队没有合格的技术人员作为支撑,也的确没有查找问题的能力,到最后就只能碰运气了。
回到我的项目里,遇到了精简关键帧之后出现动画异常的问题时,我认为正确的解决办法是动画师通过分析关键帧过渡出错的地方,然后在正确的位置加关键帧来解决。但这种方式需要动画师有比较高的水平和经验,我这边的美术团队暂时没有这个能力。所以只能我来想办法通过技术手段来解决问题了。
2、anim文件的分离
先说一个在Unity使用资源的习惯问题。
一般导入3D模型到Unity里面,都是使用fbx格式的模型。在Unity里面,我们可以直接看到Fbx文件展开之后,里面可能有网格模型(Mesh),有动画片段,也可能有贴图,有导入时Unity自动生成的默认材质球等等。
我们是可以直接用这些Fbx里面的资源的一部分,比如只有Mesh,只用动画。但在打包的时候,Unity会默认引用了整个Fbx文件,把我们没有用到的一些资源也作为引用给打包进去了。如果我们不想引用多余的Fbx文件部分,只是想要使用某些东西,我们可以把这部分的内容复制出来。
比如我这里有一个测试技能动画的fbx文件,我可以在展开fbx之后,把里面的skill动画选中,然后按Ctrl+d快捷键,把skill动画片段复制出来。
由于接下来要分析三种不同压缩设置对文件容量的实际影响,所以我分别设置了Off、KeyframeReduction和Optimal三种情况,并用Ctrl+d把文件复制出来:
可以看到,原始的fbx文件只有2m左右,Off模式的anim文件竟然达到了差不多22m,而KeyframeReduction和Optimal小一点,也达到了8m多。
看着这个容量,可能大家会说,原来fbx文件这么小,Ctrl+d之后反而变大了,是不是有问题?其实,我们使用引擎资源要习惯不要单纯看文件本身的内存大小。这个就好比使用贴图,不管你用jpg、png把图片压缩得多小,但实际上进入了Unity之后,它还是会还原成RGBA32,实际容量是在引擎里面再次调整的。
这里也是一样的,fbx文件小的原因是他使用二进制格式来存储并压缩。而我们Ctrl+d出来的anim文件,是以纯文本的方式记录数据的,anim文件容量虽然大,但实际上在Unity真正使用的时候,两者是一样的。
复制出来anim之后,我们已经把动画文件和fbx脱离关系了,而且由于它是纯文本,所以我们可以进行各种直接的编辑。
随便打开一个anim文件看看,里面很容易看懂,就是记录了这段动画的各种曲线,每条曲线里面有各个关键帧的数据
3、不同的导入设置对分离anim文件的具体影响
由于刚才说了,anim文件本身的容量并不能准确表达最终动画的容量,所以我们先把3段动画打包成AssetBundle,然后对比一下容量
这里可以很直观的看出,Off的动画容量是惨不忍睹的,占了1.4m,KeyframeReduction的容量也达到了674k,而Optimal的是262k。
如果从这样的角度看,只要设置成Optimal,是不是可以很轻松的解决了动画容量的问题呢?答案并没有这么简单。因为策划设置成Off的原因,是有些动画出现了滑步问题。
我们这里对比一下容量,只是为了搞清楚3种不同的动画压缩设置,实际上产生了什么差异而已。我们可以通过对比3个anim文件,看看他们的容量为什么差别这么大。
4、影响文件容量的因素
先看skill_Off和skill_KeyframeReduction这两个anim文件的对比
Off的容量很大,是因为记录了非常多的关键帧信息,包括了各种位移旋转缩放的曲线、编辑器用的曲线,都是每个物体节点每隔0.03333秒都会记录一个关键帧,这部分数据比KeyframeReduction大很多倍,所以直接导致了文件容量也大很多。其他部分两个文件是一样的。
再来看skill_KeyframeReduction和skill_Optimal这两个anim文件的对比。这两个文件容量是几乎一样的
通过对比,发现两个文件基本上是一样的,只有m_UseHighQualityCurve这个选项。在KeyframeReduction里面这个值是1,在Optimal里面这个值是0。
我个人的猜测是,当这个值开启的时候,Unity会尽量保留原有的动画曲线数据,当关闭时,Unity会对动画曲线做精简,去掉Unity认为没必要的曲线数据,选择最优的曲线数据保留。由于anim文件其他部分是一样的,所以这个设置的生效阶段,应该是在动画真正使用的时候才会触发,在编译AssetBundle的时候,也会触发。
三、对动画文件进行精简尝试
基于以上的分析我们可以对anim文件做一下的处理
1、去掉重复的帧数
Off的anim产生的文件大,是因为它记录的帧很多,但实际上,大部分的帧都是重复无用的,所以其实可以通过一定的方法,把重复的帧去掉。
我的处理方式是,在anim文件里面进行字符串处理,找到同一个物体的Curve段,然后每个Curve段找到不同的time的段,判断下一个time的各种参数是否和上一个time完全一样,如果完全一样,就把中间的time段全部去掉,只保留一头一尾的time的关键帧信息。
2、修改HighQualityCurve项
匹配m_UseHighQualityCurve: 1字符串,如果存在,就替换成m_UseHighQualityCurve: 0。
3、精简浮点数位数
由于Unity默认生成的anim文件里面的各种浮点数都是保留了8位小数,但实际上太精确的数值作用并不是很大,我认为保留3-4位小数就基本上能满足动画表现了。所以可以通过匹配字符串,找到浮点数,并把小数位保留成4位。
这些操作,我都实现了,先说一下结果:
1、手动去掉帧数的操作,在KeyframeReduction和Optimal模式下,并没有很大的效果,这应该是因为本身这两种模式就会精简关键帧,所以一些很明显的重复的关键帧,本身就会被去掉,所以自己手动去掉的关键帧的意义不是很大。
而且由于有曲线数据的存在,有些看着像是重复的关键帧,实际上是不能直接去掉的,比如原来中间有一帧被删掉了,前后的帧的曲线应该相应调整数值才能达到原有的动画效果。如果直接去掉,动画的效果就可能被改变了。
所以这个操作被我放弃了。
2、手动修改UseHighQualityCurve,是很有效果的,等于是在anim文件脱离了Fbx后,还能再进行一次曲线优化的设置。
3、精简浮点数。这个操作也是很有效果,我猜测的是,在关掉UseHighQualityCurve后,Unity会优化曲线,而精简了浮点数之后,把一些原本就非常接近的关键帧,让Unity认为是可以进行优化的曲线,并进行了合并。
于是,我对之前3个anim文件,进行了字符串修改,得到了下面的结果,后面加x的就是处理过的文件
从文本对比看,就是在原有的基础上改了曲线模式和小数点位数
发现文件并没有小很多,我们再打包AssetBundle看看:
可以看到精简之后,不论原来设置了哪种动画压缩方式,包括off,最终也变成了100多k的容量了。
我们把生成的动画播放一下,发现用Off模式生成出来的anim文件经过精简之后,并没有出现滑步的问题。
最后,我们把6个文件通过AssetBundle加载到实际运行里面,看看运行的内存占用情况:
可以看出,没有带x的,符合我们的预期,是Off>KeyframeReduction>Optimal,
然后带x的,就算是Off_x也比不带x的Optimal要占用得小。在精简之后,KeyframeReduction和Optimal占用的内存是一样的。
到这里,试验成功,可以些批量工具把整个项目的anim文件全部处理一下,就完事了。
四、结论
1、关于精简的操作
通过AssetBundle文件的容量、播放的效果、占用内存的大小这3方面的对比,可以看出,如果没有特殊情况,动画压缩选择Optimal,并且精简浮点小数位数,是最优的选择。
如果出现脚步滑动等状况,可以选择Off,并且关闭HighQualityCurve,精简浮点小数,也能得到较小的AssetBundle容量和内存占用。
本来我是想写工具,在anim导入的时候,自动修改压缩选项并精简浮点数。不过由于有些情况还是需要使用Off选项,所以后来并没有这么做,而是做了根据文件夹寻找anim文件并批量修改的工具来处理。文章来源:https://www.toymoban.com/news/detail-434993.html
2、关于字符串处理的一些经验
我这里没有直接给出精简anim文件的工具代码,因为这些毕竟是公司项目在使用的工具。如果有这方面疑问,可以私下联系我进行交流。
我可以提供一些我做替换时遇到的问题,各位可以自己注意一下:
一开始,我是使用正则匹配来查找和替换文本的,这样代码写起来很简单。但在实际的运行中,发现有些anim文件实在很大,纯文本可能达到上百万行。这样巨大的文本,用正则匹配速度有点慢,不过也还勉强能接受。但到了替换这一步,如果直接操作巨大的字符串,会发现不论是用String自带的Replace方法,还是正则的Replace方法,都会非常非常的慢,甚至会卡死。
后来我改了实现的方式,改为了把巨大的字符串通过’\n’来分割为多行,然后每一行单独的处理查找和替换工作,最后,使用string.Join方法,把多行数据用’\n’重新拼接起来,这样的速度就非常的快,上百万行的文件,2-3秒就能处理完。
在匹配小数的时候,还要留意有没有小数是用科学计数法来表达的,也要把这种情况给匹配出来。文章来源地址https://www.toymoban.com/news/detail-434993.html
到了这里,关于Unity动画文件(AnimationClip)精简容量的研究的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!