|
RootMotion
RootMotion的本质上就是先锁住动画位移,然后将动画轨迹提取出来,最后将动画轨迹应用到角色控制器。
背景:以下分析主要基于unreal4.21版本的源码
RootMotion的lockBone原理
将根骨骼锁住不动的核心原理在AnimSequence.cpp中:
void UAnimSequence::ResetRootBoneForRootMotion(FTransform& BoneTransform, const FBoneContainer& RequiredBones, ERootMotionRootLock::Type InRootMotionRootLock) const{ switch (InRootMotionRootLock) { case ERootMotionRootLock::AnimFirstFrame: BoneTransform = ExtractRootTrackTransform(0.f, &RequiredBones); break; case ERootMotionRootLock::Zero: BoneTransform = FTransform::Identity; break; default: case ERootMotionRootLock::RefPose: BoneTransform = RequiredBones.GetRefPoseArray()[0]; break; } if (IsValidAdditive() && InRootMotionRootLock != ERootMotionRootLock::AnimFirstFrame) { //Need to remove default scale here for additives BoneTransform.SetScale3D(BoneTransform.GetScale3D() - FVector(1.f)); }}
ERootMotionRootLock::Zero 将根骨骼的位置和旋转,lock在父空间的原点,x、y、z、pitch、yaw、roll都为0。
ERootMotionRootLock::AnimFirstFrame 将根骨骼的位置和旋转,lock在当前动画的第一帧
ERootMotionRootLock::RefPose 将根骨骼的位置和旋转,lock在骨骼的原始位置。所谓骨骼的原始位置,俗称"TPos", 与动画无关,角色做出来骨骼的时候就有的位置。
RootMotion轨迹的提取
读取轨迹的入口在USkeletalMeshComponent::ConsumeRootMotion
每帧提取的轨迹最终就存储UAnimInstance.ExtractedRootMotion
USkeletalMeshComponent::ConsumeRootMotion中InterpAlpha, 绝大多数的情况为1。
InterpAlpha不是1的情况: 开启一种特殊的优化开关(OptimizeMode == LookAheadMode),此开关默认关闭。
所以从入口函数直接跟进去,直接返回了UAnimInstance.ExtractedRootMotion
FRootMotionMovementParams UAnimInstance::ConsumeExtractedRootMotion(float Alpha){ // 这里的Alpha 就是 InterpAlpha, 只列举为1的情况 if (Alpha > (1.f - ZERO_ANIMWEIGHT_THRESH)) { FRootMotionMovementParams RootMotion = ExtractedRootMotion; //Clear,以保证下一帧的轨迹是全新的 ExtractedRootMotion.Clear(); return RootMotion; }}
UAnimInstace.ExtractedRootMotion 每帧都在UAnimInstance::PostUpdateAnimation函数中赋值
void UAnimInstance::PostUpdateAnimation(){ bNeedsUpdate = false; // acquire the proxy as we need to update FAnimInstanceProxy& Proxy = GetProxyOnGameThread<FAnimInstanceProxy>(); // flip read/write index // Do this first, as we'll be reading cached slot weights, and we want this to be up to date for this frame. Proxy.TickSyncGroupWriteIndex(); Proxy.PostUpdate(this); // 1 先取Proxy里的轨迹 if(Proxy.GetExtractedRootMotion().bHasRootMotion) { FTransform ProxyTransform = Proxy.GetExtractedRootMotion().GetRootMotionTransform(); ProxyTransform.NormalizeRotation(); ExtractedRootMotion.Accumulate(ProxyTransform); Proxy.GetExtractedRootMotion().Clear(); } // 2 再取Montage里面的轨迹 // blend in any montage-blended root motion that we now have correct weights for for(const FQueuedRootMotionBlend& RootMotionBlend : RootMotionBlendQueue) { const float RootMotionSlotWeight = GetSlotNodeGlobalWeight(RootMotionBlend.SlotName); const float RootMotionInstanceWeight = RootMotionBlend.Weight * RootMotionSlotWeight; ExtractedRootMotion.AccumulateWithBlend(RootMotionBlend.Transform, RootMotionInstanceWeight); } // We may have just partially blended root motion, so make it up to 1 by // blending in identity too // 3 如果当前权重小于1, 则用FTransform::Identity补齐到1 if (ExtractedRootMotion.bHasRootMotion) { ExtractedRootMotion.MakeUpToFullWeight(); }}Proxy中轨迹: FAnimInstanceProxy::ExtractedRootMotion
具体提取轨迹的代码在FAnimInstanceProxy::UpdateAnimation => FAnimInstanceProxy::TickAssetPlayerInstances
提取轨迹的具体原理和Montage类似,见下文。
这部分提取的是BlendSpace或者AnimSequence的轨迹, 不包括montage的轨迹。结果保存在FAnimInstanceProxy::ExtractedRootMotion
注意: 只有当RootMotionMode == ERootMotionMode::RootMotionFromEverything, 才会提取这些轨迹
montage的轨迹
montage提取轨迹的入口在: UAnimInstance::UpdateAnimation => UpdateMontage => FAnimMontageInstance::Advance
在Advance函数中,最后通过AnimInstance::QueueRootMotionBlend将轨迹传回到AnimInstance
可以看到提取轨迹用的是 UAnimMontage::ExtractRootMotionFromTrackRange => UAnimCompositeBase::ExtractRootMotionFromTrack => UAnimSequence::ExtractRootMotionFromRange
其实提取轨迹,无论是BlendSpace,还是Montage,最终都会用UAnimSequence::ExtractRootMotionFromRange,核心代码摘抄如下:
FTransform UAnimSequence::ExtractRootMotionFromRange(float StartTrackPosition, float EndTrackPosition) const{ const FVector DefaultScale(1.f); // 第0帧的Transform FTransform InitialTransform = ExtractRootTrackTransform(0.f, NULL); // 上一帧的Transform FTransform StartTransform = ExtractRootTrackTransform(StartTrackPosition, NULL); // 当前帧的Transform FTransform EndTransform = ExtractRootTrackTransform(EndTrackPosition, NULL); // Transform to Component Space Rotation (inverse root transform from first frame) // 第0帧的逆矩阵 const FTransform RootToComponentRot = FTransform(InitialTransform.GetRotation().Inverse()); // 上一帧相对于第0帧的Transform StartTransform = RootToComponentRot * StartTransform; // 当前帧相对于第0帧的Transform EndTransform = RootToComponentRot * EndTransform; // 返回 当前帧的Transform - 上一帧的Transform return EndTransform.GetRelativeTransform(StartTransform);}RootMotion轨迹的应用
RootMotion的应用主要在CharacterMovementComponent.cpp
对于ROLE_Authority类型的Character(例如Player), rootMotion的应用入口在: TickComponent => PerformMovement
对于ROLE_SimulatedProxy类型的Character(例如非Player的Avatar), rootMotion的入口在: TickComponent => SimulatedTick => SimulateRootMotion
获取轨迹的提取结果
首先会调用函数 TickCharacterPose
TickCharacterPose 函数中,会读取USkeletalMeshComponent中提取的RootMotion轨迹,更新到RootMotionParams
void UCharacterMovementComponent::TickCharacterPose(float DeltaTime){ USkeletalMeshComponent* CharacterMesh = CharacterOwner->GetMesh(); // bAutonomousTickPose is set, we control TickPose from the Character's Movement and Networking updates, and bypass the Component's update. // (Or Simulating Root Motion for remote clients) //@zvn6761 bIsAutonomousTickPose 保证了: 即使当前帧已经调用过TickPose, ShouldTickPose可以return True。这个算是UE4自己的黑科技了 CharacterMesh->bIsAutonomousTickPose = true; if (CharacterMesh->ShouldTickPose()) { // Keep track of if we're playing root motion, just in case the root motion montage ends this frame. const bool bWasPlayingRootMotion = CharacterOwner->IsPlayingRootMotion(); CharacterMesh->TickPose(DeltaTime, true); // Grab root motion now that we have ticked the pose if (CharacterOwner->IsPlayingRootMotion() || bWasPlayingRootMotion) { FRootMotionMovementParams RootMotion = CharacterMesh->ConsumeRootMotion(); if (RootMotion.bHasRootMotion) { RootMotion.ScaleRootMotionTranslation(CharacterOwner->GetAnimRootMotionTranslationScale()); RootMotionParams.Accumulate(RootMotion); } } } //和上文呼应,将bIsAutonomousTickPose 改为默认值 CharacterMesh->bIsAutonomousTickPose = false;}RootMotion实现角色移动 和 旋转
RootMotion 最后是通过改变CharacterMovementComponent.Velocity来实现角色移动
AnimRootMotionVelocity = CalcAnimRootMotionVelocity(RootMotionParams.GetRootMotionTransform().GetTranslation(), DeltaSeconds, Velocity);Velocity = ConstrainAnimRootMotionVelocity(AnimRootMotionVelocity, Velocity);
RootMotion 最后是通过MoveUpdateComponent
const FQuat OldActorRotationQuat = UpdatedComponent->GetComponentQuat();const FQuat RootMotionRotationQuat = RootMotionParams.GetRootMotionTransform().GetRotation();if( !RootMotionRotationQuat.IsIdentity() ){ const FQuat NewActorRotationQuat = RootMotionRotationQuat * OldActorRotationQuat; MoveUpdatedComponent(FVector::ZeroVector, NewActorRotationQuat, true);}最后清理当前帧用过的轨迹
每帧, RootMotionParams用完之后都清理一下。
这样,下次执行RootMotionParams.Accumulate时候,才会相当于直接RootMotionParams.Set。
// Root Motion has been used, clearRootMotionParams.Clear() |
|