BlueRoseNote/03-UnrealEngine/Animation/UE5动画重定向核心逻辑笔记.md

7.9 KiB
Raw Blame History

title, date, excerpt, tags, rating
title date excerpt tags rating
UE5动画重定向核心逻辑笔记 2023-08-18 18:08:12

前言

最近研究过了一下UE的重定向逻辑所以写点笔记作为记录。IK部分没有去看

  • FIKRetargetBatchOperation::RunRetarget()编辑器调用的重定向函数。主要是复制对应需要重定向资产并且进行重定向之后通知UI。
  • FIKRetargetBatchOperation::RetargetAssets():重定向资产逻辑。
    • 复制所有曲线轨道(不含数值)。
    • 设置Skeleton与PreviewMesh资产。
    • 提前调用重定向资产的PostEditChange(),以避免编辑后钩子函数被调用,产生依赖顺序问题。
    • ConvertAnimation()
    • 替换动画蓝图并且编译。

ConvertAnimation()

一开始是一些初始化逻辑主要的是调用UIKRetargetProcessor的Initialize()。里面初始化了

  • SourceSkeleton & TargetSkeleton用于重定向计算用的数据载体存储了骨骼链、骨骼、当前重定向Pose等数据。
  • RootRetargeter根骨骼重定向器。
  • ChainPairsIK、ChainPairsFK用于FK与IK的骨骼链数据。
  • UIKRigProcessorIK重定向处理器。
  • 所有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()

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. 进行一些后处理计算,默认状态下跳过。

插值计算

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;
}