0. 写在前面
本文为个人学习的笔记整理,如有错误,望不吝指出。
1. UE4动画压缩方案
默认使用移除线性关键帧压缩
1.1 无损压缩 Least Destructive
- 设置平移数据(Translation)的压缩格式:ACF_None,不压缩
- 设置旋转数据(Rotation)的压缩格式:ACF_Float96NoW,3个32位Float来存旋转数据。骨骼旋转数据是用四元数存的,这里去掉w分量。
- 进行位压缩
1.2 位压缩 Bitwise Compress Only
-
Key和Track:
Key :关键帧数据,描述某一特定时间下单根骨骼的位置和旋转的数据。
Track :描述单根骨骼随着时间进行运动的一组关键帧。 -
对每根骨骼,将原始动画数据提取到平移、旋转、缩放三个Track中
SeparateRawDataIntoTracks( CompressibleAnimData.RawAnimationData, CompressibleAnimData.SequenceLength, TranslationData, RotationData, ScaleData ); -
去除冗余的Track数据:当一根骨骼的Track数据没有发生变化时(即该骨骼在这个动画下不参与平移或旋转或缩放,除了第一帧其他全部去掉。
FilterTrivialKeys,分别调用FilterTrivialRotationKeys、FilterTrivialPositionKeys、FilterTrivialScaleKeys检查旋转、平移、缩放数据是否有发生过变化,如果有发生过变化,则整个Track保留,如果没有,则只保留第一帧数据。 -
根据面板设置,指定平移、旋转、缩放的编码格式:AnimationFormat_SetInterfaceLinks
ACF_None
ACF_Float96NoW
ACF_Fixed48NoW
ACF_IntervalFixed32NoW
ACF_Fixed32NoW
ACF_Float32NoW
ACF_Identity -
进行按位压缩:BitwiseCompressAnimationTracks
Simple quantization简单量化算法
即对浮点数在某个范围内对其进行归一化,并将其缩放到N位整数的范围,再四舍五入转为N位的整数- ACF_None:不压缩,如果是平移数据,用332位float。如果是旋转数据,用432位float(包含w分量)
- ACF_Float96NoW:对应CompressTranslation_Uncompressed算法,用3*32位float,不包含w分量
- ACF_Fixed48NoW:对应CompressTranslation_16_16_16算法,将332位float数据压缩到3uint16。
- ACF_IntervalFixed32NoW:对应CompressTranslation_10_11_11算法,将332位float数据压缩到1uint32.
将XYZ数据进行映射,先计算Track中数据的最小值MinX、MinY和MinZ,以及Track中数据的范围Range(Max-Min)
通过value = (V - Vmin)/ Range进行映射
再共同存到一个32位int数据,其中21-31位存Z, 10-20位存Y, 0-9位存X
1.3 移除琐碎帧压缩 Remove Trivial Keys
琐碎帧:对没有变化的冗余Track数据,只保留第一帧,实际上并不会删除关键帧信息,只是对相同的数据仅保留一份。基本上其他压缩方案都会先处理琐碎帧。
- SeparateRawDataIntoTracks:将原始数据分离到3个Track
- FilterTrivialKeys:去除琐碎帧
- AnimationFormat_SetInterfaceLinks:设置压缩编码格式,这里都用的不压缩
- BitwiseCompressAnimationTracks:进行按位压缩
1.4 隔帧移除压缩 Remove Every Second Key
在移除琐碎帧的基础上多了一个隔帧移除,可以指定间隔多少帧移除,也可以指定多少帧后才开始执行移除
FilterIntermittentKeys,分别调用
FilterIntermittentRotationKeys(RotationTracks, StartIndex, Interval);
FilterIntermittentPositionKeys(PositionTracks, StartIndex, Interval);
对position的每个track,遍历关键帧,按一定间隔步进,加入到新的Track。Rotation同理
最后调用按用户设置的编码格式进行按位压缩
1.5 移除线性帧压缩 Remove Linear Keys
删除可以通过临帧的线性插值得到的关键帧
原理:
- 选择一个关键帧
- 计算从它的两个邻居线性插值得到的值
- 如果产生的轨迹误差是可以接受的就移除这个键
在上述第一步中选择关键帧的算法可以是多样的,且会产生不同的压缩结果(UE4是怎样的?)
第三步在UE4中是MaxPosDiff、MaxAngleDiff和MaxScaleDiff,UE4还提供了3个参数,MaxEffectorDiff、MinEffectorDiff和EffectorDiffSocket,因为UE4除了检查插值出来的某根骨骼的某个关键帧和原始数据的差距之外,还检查假如应用了这个数据之后,对末端骨骼的影响,即末端骨骼的误差。
相关参数:
详细流程:
-
SeparateRawDataIntoTracks:
分离Track数据 -
FilterBeforeMainKeyRemoval:
调用FilterTrivialKeys删除琐碎帧 -
CompressUsingUnderlyingCompressor
调用BitwiseCompressAnimationTracks在过滤线性帧前先进行一次位压缩 -
ProcessAnimationTracks:去除可以用线性插值近似的关键帧
主要逻辑在FilterLinearKeysTemplate
过滤规则:
① 定义LowKey,从第二帧(下标为1)开始遍历
② 定义GoodHighKey、BadHighKey两个移动下标,在LowKey到帧末划出一个窗口
③ 定义HighKey为Good和Bad的中间下标,从LowKey的下一个帧开始继续向HighKey在位置遍历(二分法)
④ 第三步中遍历到的KeyValue,通过Alpha变量和临帧值(LowKey、HighKeyValue)计算一个插值数据,比较插值数据和原始数据,如果小于配置的允许误差值,则继续检查该骨骼影响的末端骨骼数据误差。
计算末端骨骼的数据误差,转换到世界坐标(全局姿势)做比较(之前是比较局部姿势),记录误差最大的值,检查是否大于配置的末端最大误差。
Alpha的计算:
假如要计算Key(3)位置的线性插值数据,则可以Key(3)的邻居计算Alpha=(3-2)/(4-2)=0.5,插值后的Value=lerp(0.35, 0.85, alpha) = 0.6
⑤ 如果第四步遍历完没有遇到误差过大的情况,则将GoodHighKey的值更新为HighKey,说明从LowKey到HighKey之前的数据都是可以通过线性插值得到的。如果第四步中有遇到误差过大的情况,则将BadHighKey的值更新为HighKey,然后回到第三步,重新计算HighKey。
⑥ 在第三步到第五步逐渐的缩小GoodHighKey和BadHighKey的窗口,直到窗口足够小时停止,然后将GoodHighKey所对应的数据加入到一个新的Track(记录TimeValue和KeyValue),这个Track保存的就是去掉可以通过线性插值得到的帧之后的数据ProcessAnimationTracks函数会对每个骨骼的三个Track(平移、旋转、缩放)都执行FilterLinearKeysTemplate进行过滤
-
在去除线性近似帧之后,对最终剩下的数据再次进行位压缩:CompressUsingUnderlyingCompressor
1.6 独立轨道压缩 Compress each track independently
按照不同的Track单独进行压缩,继承于移除线性帧压缩的类,直接调用UAnimCompress_RemoveLinearKeys::DoReduction,对RemoveLinearKeys的一些关键函数进行重写,即在线性压缩的基础上做额外处理
相关参数:
LinearKeyRemoval和线性压缩的作用一致
1. FilterBeforeMainKeyRemoval
a. ResampleKeys:对原动画数据进行重新采样
如果勾选了Resampling,并且动画关键帧采样数据大于MinKeysForResampling,就会根据设置的采样帧间隔对原数据进行重新采样。
b. CalculateTrackHeights和TallyErrorsFromPerturbation:
勾选AdaptiveError或AdaptiveError2时的处理,暂略。
以上三种配置默认都是不处理的
c. FilterTrivialKeys:删除琐碎帧
2. CompressUsingUnderlyingCompressor
2. 参考资料
http://nfrechette.github.io/2016/10/21/anim_compression_toc/