基本原理
每個(gè)動畫(AnimSequence)要指定一個(gè)重定向源(RetargetSource)鸠蚪,然后實(shí)時(shí)的時(shí)候根據(jù)重定向源的TPose和當(dāng)前角色的TPose進(jìn)行重定向的計(jì)算。
實(shí)時(shí)重定向的計(jì)算方法又分為五種: Animation, Skeleton, AnimationScale, AnimationRelative, OrientAndScale
骨骼重定向方法的設(shè)置粒度是骨骼級別萨螺,具體使用方法參見官方文檔
官方文檔:
https://docs.unrealengine.com/en-US/Engine/Animation/AnimationRetargeting/index.html
我印象中的常用操作饲鄙,通常有問題都是用以下操作解決:
某個(gè)動畫重定向結(jié)果不對:在動畫編輯器里指定下重定向源
某根骨骼的效果結(jié)果不對:在骨骼樹窗口里更改此骨骼的重定向設(shè)置
新角色的動畫勺届,需要在最大化骨架編輯器里新增一下新角色的重定向源
雙手武器重定向后廊散,掛接效果不對歹垫。這時(shí)候可能就需要ik了耸别,ik和重定向是一個(gè)搭檔。
實(shí)時(shí)重定向源碼
UE中獲取任何的骨骼動畫數(shù)據(jù)县钥,都會調(diào)用 UAnimSequence::GetBonePose
GetBonePose就像古代的關(guān)隘,連接動畫邏輯(動畫樹慈迈、Montage等)與動畫數(shù)據(jù)
壓縮動畫的重定向
壓縮動畫的重定向核心代碼就直接在GetBonePose函數(shù)的末尾
由于壓縮動畫重定向和非壓縮動畫的重定向的原理是一樣的若贮,只是讀取動畫數(shù)據(jù)的方式不同,所以重定向源碼分析放到下節(jié)
非壓縮動畫的重定向
GetBonePose函數(shù)并沒有直接處理非壓縮動畫的重定向痒留,而是通過如下調(diào)用棧:
BuildPoseFromRawData -> RetargetBoneTransform -> FAnimationRuntime::RetargetBoneTransform
所以非壓縮動畫的重定向核心邏輯如下
void FAnimationRuntime::RetargetBoneTransform(const USkeleton* MySkeleton, const FName& RetargetSource, FTransform& BoneTransform, const int32 SkeletonBoneIndex, const FCompactPoseBoneIndex& BoneIndex, const FBoneContainer& RequiredBones, const bool bIsBakedAdditive)
{
if (MySkeleton)
{
switch (MySkeleton->GetBoneTranslationRetargetingMode(SkeletonBoneIndex))
{
case EBoneTranslationRetargetingMode::AnimationScaled:
{
// @todo - precache that in FBoneContainer when we have SkeletonIndex->TrackIndex mapping. So we can just apply scale right away.
const TArray<FTransform>& SkeletonRefPoseArray = MySkeleton->GetRefLocalPoses(RetargetSource);
const float SourceTranslationLength = SkeletonRefPoseArray[SkeletonBoneIndex].GetTranslation().Size();
if (SourceTranslationLength > KINDA_SMALL_NUMBER)
{
const float TargetTranslationLength = RequiredBones.GetRefPoseTransform(BoneIndex).GetTranslation().Size();
BoneTransform.ScaleTranslation(TargetTranslationLength / SourceTranslationLength);
}
break;
}
case EBoneTranslationRetargetingMode::Skeleton:
{
BoneTransform.SetTranslation(bIsBakedAdditive ? FVector::ZeroVector : RequiredBones.GetRefPoseTransform(BoneIndex).GetTranslation());
break;
}
case EBoneTranslationRetargetingMode::AnimationRelative:
{
// With baked additive animations, Animation Relative delta gets canceled out, so we can skip it.
// (A1 + Rel) - (A2 + Rel) = A1 - A2.
if (!bIsBakedAdditive)
{
const TArray<FTransform>& AuthoredOnRefSkeleton = MySkeleton->GetRefLocalPoses(RetargetSource);
const TArray<FTransform>& PlayingOnRefSkeleton = RequiredBones.GetRefPoseCompactArray();
const FTransform& RefPoseTransform = RequiredBones.GetRefPoseTransform(BoneIndex);
// Apply the retargeting as if it were an additive difference between the current skeleton and the retarget skeleton.
BoneTransform.SetRotation(BoneTransform.GetRotation() * AuthoredOnRefSkeleton[SkeletonBoneIndex].GetRotation().Inverse() * RefPoseTransform.GetRotation());
BoneTransform.SetTranslation(BoneTransform.GetTranslation() + (RefPoseTransform.GetTranslation() - AuthoredOnRefSkeleton[SkeletonBoneIndex].GetTranslation()));
BoneTransform.SetScale3D(BoneTransform.GetScale3D() * (RefPoseTransform.GetScale3D() * AuthoredOnRefSkeleton[SkeletonBoneIndex].GetSafeScaleReciprocal(AuthoredOnRefSkeleton[SkeletonBoneIndex].GetScale3D())));
BoneTransform.NormalizeRotation();
}
break;
}
case EBoneTranslationRetargetingMode::OrientAndScale:
{
if (!bIsBakedAdditive)
{
const FRetargetSourceCachedData& RetargetSourceCachedData = RequiredBones.GetRetargetSourceCachedData(RetargetSource);
const TArray<FOrientAndScaleRetargetingCachedData>& OrientAndScaleDataArray = RetargetSourceCachedData.OrientAndScaleData;
const TArray<int32>& CompactPoseIndexToOrientAndScaleIndex = RetargetSourceCachedData.CompactPoseIndexToOrientAndScaleIndex;
// If we have any cached retargeting data.
if ((OrientAndScaleDataArray.Num() > 0) && (CompactPoseIndexToOrientAndScaleIndex.Num() == RequiredBones.GetCompactPoseNumBones()))
{
const int32 OrientAndScaleIndex = CompactPoseIndexToOrientAndScaleIndex[BoneIndex.GetInt()];
if (OrientAndScaleIndex != INDEX_NONE)
{
const FOrientAndScaleRetargetingCachedData& OrientAndScaleData = OrientAndScaleDataArray[OrientAndScaleIndex];
const FVector AnimatedTranslation = BoneTransform.GetTranslation();
// If Translation is not animated, we can just copy the TargetTranslation. No retargeting needs to be done.
const FVector NewTranslation = (AnimatedTranslation - OrientAndScaleData.SourceTranslation).IsNearlyZero(BONE_TRANS_RT_ORIENT_AND_SCALE_PRECISION) ?
OrientAndScaleData.TargetTranslation :
OrientAndScaleData.TranslationDeltaOrient.RotateVector(AnimatedTranslation) * OrientAndScaleData.TranslationScale;
BoneTransform.SetTranslation(NewTranslation);
}
}
}
break;
}
}
}
}
這個(gè)函數(shù)只是一根骨骼的重定向計(jì)算谴麦。
當(dāng)前角色的TPose就存在RequiredBones里。
重定向源的TPose都是存在最大化骨架MySkeleton中的伸头,根據(jù)AnimSequence.RetargetSource來獲取當(dāng)前的重定向源數(shù)據(jù)
大部分的實(shí)時(shí)重定向的方法主要改的就是骨骼的translation匾效,scale和rotation不會動,AnimationRelative模式例外
- Animation: 純動畫恤磷,不做任何重定向
- Skeleton: 直接用角色TPose的translation
- AnimationScale: 用動畫的Translation面哼,但是會根據(jù)當(dāng)前角色的TPose和RetargetSource的TPose的比例對Translation進(jìn)行縮放。 和AnimationRelative相比扫步,delta是用除法算出來的魔策,且只改Translation
- AnimationRelative: 用動畫的Translation,scale河胎,rotation闯袒,但是會根據(jù)當(dāng)前角色的TPose和RetargetSource的TPose的差值對動畫的prs進(jìn)行增減。和AnimationScale相比游岳,delta是用減法算出來的政敢。
- OrientAndScale: 用動畫的Translation,實(shí)時(shí)會根據(jù)OrientAndScaleData的信息對Translation進(jìn)行變換
OrientAndScale模式下的OrientAndScaleData的計(jì)算
- OrientAndScaleData.TranslationDeltaOrient是一個(gè)四元數(shù)旋轉(zhuǎn): 是從重定向源的骨骼translation(SourceSkelTransDir)到當(dāng)前角色TPose的骨骼Translation(TargetSkelTransDir)的一個(gè)旋轉(zhuǎn)
- OrientAndScaleData.TranslationScale是一個(gè)scale的除法: 當(dāng)前角色TPose的骨骼Translation的長度(TargetSkelTransLength) / 重定向源的骨骼translation的長度(SourceSkelTransLength)
const FRetargetSourceCachedData& FBoneContainer::GetRetargetSourceCachedData(const FName& InRetargetSourceName) const
{
FRetargetSourceCachedData* RetargetSourceCachedData = RetargetSourceCachedDataLUT.Find(InRetargetSourceName);
if (!RetargetSourceCachedData)
{
RetargetSourceCachedData = &RetargetSourceCachedDataLUT.Add(InRetargetSourceName);
// Build Cached Data for OrientAndScale retargeting.
const TArray<FTransform>& AuthoredOnRefSkeleton = AssetSkeleton->GetRefLocalPoses(InRetargetSourceName);
const TArray<FTransform>& PlayingOnRefSkeleton = GetRefPoseCompactArray();
const int32 CompactPoseNumBones = GetCompactPoseNumBones();
RetargetSourceCachedData->CompactPoseIndexToOrientAndScaleIndex.Reset();
for (int32 CompactBoneIndex = 0; CompactBoneIndex < CompactPoseNumBones; CompactBoneIndex++)
{
const int32& SkeletonBoneIndex = CompactPoseToSkeletonIndex[CompactBoneIndex];
if (AssetSkeleton->GetBoneTranslationRetargetingMode(SkeletonBoneIndex) == EBoneTranslationRetargetingMode::OrientAndScale)
{
const FVector SourceSkelTrans = AuthoredOnRefSkeleton[SkeletonBoneIndex].GetTranslation();
const FVector TargetSkelTrans = PlayingOnRefSkeleton[CompactBoneIndex].GetTranslation();
// If translations are identical, we don't need to do any retargeting
if (!SourceSkelTrans.Equals(TargetSkelTrans, BONE_TRANS_RT_ORIENT_AND_SCALE_PRECISION))
{
const float SourceSkelTransLength = SourceSkelTrans.Size();
const float TargetSkelTransLength = TargetSkelTrans.Size();
// this only works on non zero vectors.
if (!FMath::IsNearlyZero(SourceSkelTransLength * TargetSkelTransLength))
{
const FVector SourceSkelTransDir = SourceSkelTrans / SourceSkelTransLength;
const FVector TargetSkelTransDir = TargetSkelTrans / TargetSkelTransLength;
const FQuat DeltaRotation = FQuat::FindBetweenNormals(SourceSkelTransDir, TargetSkelTransDir);
const float Scale = TargetSkelTransLength / SourceSkelTransLength;
const int32 OrientAndScaleIndex = RetargetSourceCachedData->OrientAndScaleData.Add(FOrientAndScaleRetargetingCachedData(DeltaRotation, Scale, SourceSkelTrans, TargetSkelTrans));
// initialize CompactPoseBoneIndex to OrientAndScale Index LUT on demand
if (RetargetSourceCachedData->CompactPoseIndexToOrientAndScaleIndex.Num() == 0)
{
RetargetSourceCachedData->CompactPoseIndexToOrientAndScaleIndex.Init(INDEX_NONE, CompactPoseNumBones);
}
RetargetSourceCachedData->CompactPoseIndexToOrientAndScaleIndex[CompactBoneIndex] = OrientAndScaleIndex;
}
}
}
}
}
return *RetargetSourceCachedData;
}