AnimSequence压缩与解压

AnimSequence 压缩

动画序列压缩

在UE4中,动画序列在不进行压缩时会记录各骨骼逐帧的变换数据以及曲线数据,

可以通过一些配置以对动画序列的帧信息以进行压缩以优化其所占用的内存。

总的来说,方法主要有以下两种:

  1. 压缩关键帧的数量。
  2. 压缩各关键帧的数据占用量。

压缩关键帧数量

压缩格式

压缩关键帧的格式主要有 VariableKeyConstantKey 两种:

  • Variable Key:除了记录下每个关键帧以外,还会使用一个table来记录每一个关键帧在未经压缩的序列帧中的序号。
  • Constant Key:只记录关键帧的数据,关键帧与关键帧间时间间隔应该相同。

UE中使用 AnimEncoding 类来进行压缩与解压,后文会称此类的及其子类为Codec,下面是其重点函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class AnimEncoding
{
public:
// 从FMemoryReader中读取动画数据
virtual void ByteSwapIn(
FUECompressedAnimData& CompressedData,
FMemoryReader& MemoryReader) PURE_VIRTUAL(AnimEncoding::ByteSwapIn,);

// 从MemoryWriter中写入动画数据
virtual void ByteSwapOut(
FUECompressedAnimData& CompressedData,
FMemoryWriter& MemoryWriter) PURE_VIRTUAL(AnimEncoding::ByteSwapOut, );

// 得到解压后指定骨骼们的旋转数据
virtual void GetPoseRotations(
TArrayView<FTransform>& Atoms,
const BoneTrackArray& DesiredPairs,
FAnimSequenceDecompressionContext& DecompContext) PURE_VIRTUAL(AnimEncoding::GetPoseRotations,);

// 得到解压后指定骨骼们的位移数据
virtual void GetPoseTranslations(
TArrayView<FTransform>& Atoms,
const BoneTrackArray& DesiredPairs,
FAnimSequenceDecompressionContext& DecompContext) PURE_VIRTUAL(AnimEncoding::GetPoseTranslations,);

// 得到解压后指定骨骼们的缩放数据
virtual void GetPoseScales(
TArrayView<FTransform>& Atoms,
const BoneTrackArray& DesiredPairs,
FAnimSequenceDecompressionContext& DecompContext) PURE_VIRTUAL(AnimEncoding::GetPoseScales,);

其类图如下:

AnimEncoding

其中,AEFVariableKeyLerp类对应了VariableKey压缩格式、AEFConstantKeyLerp类对应了ConstantKey的压缩格式。

另外,我们可以看到这两个类是泛型类,这与其帧内数据的格式相关,后文会介绍到。

AEFPerTrackCompressionCodec 则是对每一个Track选取不同的压缩格式。

压缩策略

在对动画序列进行压缩时,可以选择多种压缩策略,不同的压缩策略会根据自己策略的不同,选取不同的压缩格式以对数据进行压缩。

例如,RemoveEverySecondKey(隔帧移除)则是每两帧只记录一帧,这样数据量就会减半,这种压缩方式由于帧与帧间的间隔固定,因此很适合使用ConstantKey的压缩格式,这样可以避免关键帧table的开销。

关于各压缩策略的具体细节,请参考UE4 Animation Sequence 的压缩算法,其类图如下:

AnimCompress

这些压缩策略都继承自 UAnimCompress,其有一个关键函数为 DoReduction ,该函数用于对动画数据进行 剔除 、设置对应的压缩格式、数据格式,并调用 AnimationFormat_SetInterfaceLinks 函数根据之前的设置选取对应的Codec。

以ConstantKeyLerp为数据格式的位移数据为例,设置其Codec的示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void AnimationFormat_SetInterfaceLinks(CompressedDataType& CompressedData)
{
CompressedData.TranslationCodec = NULL;

if (CompressedData.KeyEncodingFormat == AKF_ConstantKeyLerp)
{
// 实例化各种帧内数据压缩格式的Codec
static AEFConstantKeyLerp<ACF_None> AEFConstantKeyLerp_None;
static AEFConstantKeyLerp<ACF_Float96NoW> AEFConstantKeyLerp_Float96NoW;
static AEFConstantKeyLerp<ACF_Fixed48NoW> AEFConstantKeyLerp_Fixed48NoW;
static AEFConstantKeyLerp<ACF_IntervalFixed32NoW> AEFConstantKeyLerp_IntervalFixed32NoW;
static AEFConstantKeyLerp<ACF_Fixed32NoW> AEFConstantKeyLerp_Fixed32NoW;
static AEFConstantKeyLerp<ACF_Float32NoW> AEFConstantKeyLerp_Float32NoW;
static AEFConstantKeyLerp<ACF_Identity> AEFConstantKeyLerp_Identity;

// 根据设置的Format采取实际的Codec
switch(CompressedData.TranslationCompressionFormat)
{
case ACF_None:
CompressedData.TranslationCodec = &AEFConstantKeyLerp_None;
break;
case ACF_Float96NoW:
CompressedData.TranslationCodec = &AEFConstantKeyLerp_Float96NoW;
break;
case ACF_IntervalFixed32NoW:
CompressedData.TranslationCodec = &AEFConstantKeyLerp_IntervalFixed32NoW;
break;
case ACF_Identity:
CompressedData.TranslationCodec = &AEFConstantKeyLerp_Identity;
break;
};
}
}

压缩帧内数据

帧内数据有位移、旋转、缩放、曲线数据,根据数据的特性会有不同的压缩方式可以选择。

概括的说,有以下两种压缩方式:

  1. 不记录可以根据其他数据计算出来的数据。
  2. 降低数据的精度。

例如,对于旋转而言,由于使用四元数来进行存储,归一化后可以通过XYZ来计算出W,因此使用第一种压缩方式即可省下一个数据,剩下的三个数据可以继续通过第二种方式继续压缩。

在UE中,有这些数据压缩格式可以选择:

1
2
3
4
5
6
7
8
9
10
11
enum AnimationCompressionFormat
{
ACF_None,
ACF_Float96NoW,
ACF_Fixed48NoW,
ACF_IntervalFixed32NoW,
ACF_Fixed32NoW,
ACF_Float32NoW,
ACF_Identity,
ACF_MAX UMETA(Hidden),
};

说明

关于本文内容尚有许多不足之处,有两篇博客建议阅读:


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!