Init
This commit is contained in:
163
03-UnrealEngine/Animation/UE5动画重定向核心逻辑笔记.md
Normal file
163
03-UnrealEngine/Animation/UE5动画重定向核心逻辑笔记.md
Normal file
@@ -0,0 +1,163 @@
|
||||
---
|
||||
title: UE5动画重定向核心逻辑笔记
|
||||
date: 2023-08-18 18:08:12
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
最近研究过了一下UE的重定向逻辑,所以写点笔记作为记录。(IK部分没有去看)
|
||||
|
||||
- FIKRetargetBatchOperation::RunRetarget():编辑器调用的重定向函数。主要是复制对应需要重定向资产并且进行重定向,之后通知UI。
|
||||
- FIKRetargetBatchOperation::RetargetAssets():重定向资产逻辑。
|
||||
- 复制所有曲线轨道(不含数值)。
|
||||
- 设置Skeleton与PreviewMesh资产。
|
||||
- 提前调用重定向资产的PostEditChange(),以避免编辑后钩子函数被调用,产生依赖顺序问题。
|
||||
- ConvertAnimation()
|
||||
- 替换动画蓝图并且编译。
|
||||
# ConvertAnimation()
|
||||
一开始是一些初始化逻辑,主要的是调用UIKRetargetProcessor的Initialize()。里面初始化了
|
||||
- SourceSkeleton & TargetSkeleton:用于重定向计算用的数据载体,存储了骨骼链、骨骼、当前重定向Pose等数据。
|
||||
- RootRetargeter:根骨骼重定向器。
|
||||
- ChainPairsIK、ChainPairsFK:用于FK与IK的骨骼链数据。
|
||||
- UIKRigProcessor:IK重定向处理器。
|
||||
- 所有FIKRigGoal
|
||||
|
||||
从FRetargetSkeleton& SourceSkeleton & FTargetSkeleton& TargetSkeleton获取对应参数来进行之后的动画资产重定向循环,循环逻辑如下:
|
||||
- 获取动画帧数,并且重新构建骨骼动画数据轨道。
|
||||
- 取得速度曲线。
|
||||
- 之后对每一帧Pose进行重定向,主要是的逻辑是:
|
||||
- 取得当前帧Pose,并将其转化WorldSpace。
|
||||
- UIKRetargetProcessor Processor->RunRetargeter()
|
||||
- 将重定向完的结果转化成LocalSpace,并且将每个骨骼的数据放入对应骨骼动画数据轨道。
|
||||
- 调用IAnimationDataController的AddBoneTrack() & SetBoneTrackKeys()给资产设置上关键帧。
|
||||
|
||||
# RunRetargeter()
|
||||
此为核心重定向逻辑,分为RunRootRetarget()、RunFKRetarget()、RunIKRetarget()、RunPoleVectorMatching()。
|
||||
|
||||
重定向数据会直接修改TargetSkeleton的Pose数据。Root与FK重定向执行完之后会调用UpdateGlobalTransformsBelowBone()更新后续骨骼的WorldSpace Transform。
|
||||
|
||||
## RunRootRetarget()
|
||||
FRootRetargeter::EncodePose():
|
||||
取得输入的根骨骼Transform数据并给`FRootSource Source`赋值。
|
||||
|
||||
FRootRetargeter::DecodePose():
|
||||
```c++
|
||||
FVector Position;
|
||||
{
|
||||
// 关键:InitialTransform 为重定向Pose计算出的数值,通过比值计算出Target的Current数值
|
||||
const FVector RetargetedPosition = Source.CurrentPositionNormalized * Target.InitialHeight;
|
||||
// 根据RetargetSetting中设置的BlendToSourceWeights与BlendToSource,与SourcePosition进行混合。
|
||||
Position = FMath::Lerp(RetargetedPosition, Source.CurrentPosition, Settings.BlendToSource*Settings.BlendToSourceWeights);
|
||||
|
||||
// 应用vertical / horizontal的缩放
|
||||
FVector ScaledRetargetedPosition = Position;
|
||||
ScaledRetargetedPosition.Z *= Settings.ScaleVertical;
|
||||
const FVector HorizontalOffset = (ScaledRetargetedPosition - Target.InitialPosition) * FVector(Settings.ScaleHorizontal, Settings.ScaleHorizontal, 1.0f);
|
||||
Position = Target.InitialPosition + HorizontalOffset;
|
||||
// 应用RetargetSetting中Position偏移。
|
||||
Position += Settings.TranslationOffset;
|
||||
|
||||
// blend with alpha
|
||||
Position = FMath::Lerp(Target.InitialPosition, Position, Settings.TranslationAlpha);
|
||||
|
||||
// 记录偏差
|
||||
Target.RootTranslationDelta = Position - RetargetedPosition;
|
||||
}
|
||||
|
||||
FQuat Rotation;
|
||||
{
|
||||
// 计算Source的旋转偏差值
|
||||
const FQuat RotationDelta = Source.CurrentRotation * Source.InitialRotation.Inverse();
|
||||
// 将偏移加到Target上
|
||||
const FQuat RetargetedRotation = RotationDelta * Target.InitialRotation;
|
||||
|
||||
// 将RetargetSetting上的旋转偏差值加上。
|
||||
Rotation = RetargetedRotation * Settings.RotationOffset.Quaternion();
|
||||
|
||||
Rotation = FQuat::FastLerp(Target.InitialRotation, Rotation, Settings.RotationAlpha);
|
||||
Rotation.Normalize();
|
||||
|
||||
// 记录Target的旋转偏差值
|
||||
Target.RootRotationDelta = RetargetedRotation * Target.InitialRotation.Inverse();
|
||||
}
|
||||
|
||||
// 将数据应用到根骨骼上
|
||||
FTransform& TargetRootTransform = OutTargetGlobalPose[Target.BoneIndex];
|
||||
TargetRootTransform.SetTranslation(Position);
|
||||
TargetRootTransform.SetRotation(Rotation);
|
||||
```
|
||||
## RunFKRetarget()
|
||||
遍历所有骨骼链,并执行FChainEncoderFK::EncodePose()与FChainDecoderFK::DecodePose()。
|
||||
|
||||
FChainEncoderFK::EncodePose():
|
||||
1. 遍历所有骨骼,复制SourceGlobalPose到**CurrentGlobalTransforms**
|
||||
2. Resize CurrentLocalTransforms
|
||||
3. FillTransformsWithLocalSpaceOfChain() 转换成LocalSpace数据。
|
||||
1. 遍历所有骨骼链,取得对应骨骼的Index以及父骨骼Index。(跳过根骨骼)
|
||||
2. 计算相对Transform后放入对应ChainIndex的OutLocalTransforms中。
|
||||
4. 根据ChainParentBoneIndex,将父骨骼Transform赋予给ChainParentCurrentGlobalTransform。
|
||||
|
||||
FChainDecoderFK::DecodePose():
|
||||
1. 更新IntermediateParentIndices的所有Index的父骨骼WorldSpace Transform。用来更新重定向后哪些不属于骨骼链的骨骼的WorldSpace Transform。(在InitializeBoneChainPairs() -> InitializeIntermediateParentIndices()中初始化)
|
||||
2. 从根骨骼开始重定向,以保证最后结果不会发生偏差。计算了Source/Target InitialDelta、Target 根骨骼 Transform后,将两者相乘,最后应用给所有Source骨骼链。
|
||||
3. 如果禁用FK重定向,则返回当前WorldSpace Transform并且return。
|
||||
4. 计算Source & Target 骨骼链开始Index。
|
||||
5. 开始遍历所有骨骼。
|
||||
1. 取得Target骨骼Index以及InitialTransform。
|
||||
2. 根据设置的FK的旋转模式执行对应逻辑(默认为插值模式,也就是他实现了骨骼链的不同数量骨骼间的重定向匹配)。计算SourceCurrentTransform与SourceInitialTransform。
|
||||
1. 使用Target的Params(骨骼长度百分比)来插值计算出Source的Transform。参考[[#插值计算]]。
|
||||
2. Params的计算在FChainFK::CalculateBoneParameters(),可以看得出就是根据**当前骨骼长度/总长度**计算百分比,最后加入Params。该函数的调用堆栈为:UIKRetargetProcessor::Initialize() => UIKRetargetProcessor::InitializeBoneChainPairs() => FRetargetChainPairFK::Initialize() => FChainFK::Initialize()
|
||||
3. 计算旋转值SourceCurrentRotation、SourceInitialRotation => RotationDelta TargetInitialRotation => **OutRotation**。
|
||||
4. 计算当前TargetBone的父骨骼 ParentGlobalTransform。
|
||||
5. 根绝设置的FK TranslationMode,计算出 **OutPosition**。
|
||||
6. 计算OutScale = SourceCurrentScale + (TargetInitialScale - SourceInitialScale),计算出**OutScale**。
|
||||
7. 对应index的`CurrentGlobalTransforms[ChainIndex]`/`InOutGlobalPose[BoneIndex]`赋值。
|
||||
6. 进行一些后处理计算,默认状态下跳过。
|
||||
|
||||
## 插值计算
|
||||
```c++
|
||||
FTransform FChainDecoderFK::GetTransformAtParam(
|
||||
const TArray<FTransform>& Transforms,
|
||||
const TArray<float>& InParams,
|
||||
const float& Param) const
|
||||
{
|
||||
if (InParams.Num() == 1)
|
||||
{
|
||||
return Transforms[0];
|
||||
}
|
||||
|
||||
if (Param < KINDA_SMALL_NUMBER)
|
||||
{
|
||||
return Transforms[0];
|
||||
}
|
||||
|
||||
if (Param > 1.0f - KINDA_SMALL_NUMBER)
|
||||
{
|
||||
return Transforms.Last();
|
||||
}
|
||||
|
||||
for (int32 ChainIndex=1; ChainIndex<InParams.Num(); ++ChainIndex)
|
||||
{
|
||||
const float CurrentParam = InParams[ChainIndex];
|
||||
if (CurrentParam <= Param)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//关键在这
|
||||
const float PrevParam = InParams[ChainIndex-1];
|
||||
const float PercentBetweenParams = (Param - PrevParam) / (CurrentParam - PrevParam);
|
||||
const FTransform& Prev = Transforms[ChainIndex-1];
|
||||
const FTransform& Next = Transforms[ChainIndex];
|
||||
const FVector Position = FMath::Lerp(Prev.GetTranslation(), Next.GetTranslation(), PercentBetweenParams);
|
||||
const FQuat Rotation = FQuat::FastLerp(Prev.GetRotation(), Next.GetRotation(), PercentBetweenParams).GetNormalized();
|
||||
const FVector Scale = FMath::Lerp(Prev.GetScale3D(), Next.GetScale3D(), PercentBetweenParams);
|
||||
|
||||
return FTransform(Rotation,Position, Scale);
|
||||
}
|
||||
|
||||
checkNoEntry();
|
||||
return FTransform::Identity;
|
||||
}
|
||||
```
|
Reference in New Issue
Block a user