0. 写在前面
本文为个人学习的笔记整理,如有错误,望不吝指出。
本篇粗略的描述UE4中每帧动画计算的主流程,该流程涉及的相关代码,因为动画设置的多样性,还有相当多的分支代码。
本篇只描述角色跑起一个最基础的动画,从SkeletalMeshComp到动画蓝图中动画播放节点的姿势计算所经历的流程。
1. SkeletalMeshComp流程
【AnimInstance】
动画实例,该类是控制动画逻辑的核心,可以理解为一个AnimInstance对象就对应着一个动画蓝图。
SkeletalMeshComp中持有三种AnimInstance对象:
- AnimScriptInstance:当前激活的动画蓝图对象
- PostProcessAnimInstance
- LinkedInstances
【入口】
1)SkinnedMeshComp->TickComponent
SkinnedMeshComp中的TickComponent是会被每帧调用的接口,该接口也是动画更新的入口
其中两个重要的函数:
- TickPose:处理动画计算中非并行的部分
- RefreshBoneTransforms:处理动画计算中多线程并行的部分,且是由AnimInstanceProxy处理
注:UE4动画蓝图默认设置为多线程更新,动画计算中大部分的数据计算都由AnimInstanceProxy负责在多线程中并行处理。
2)SkeletalMeshComp->TickPose:
- 计算tick的时间间隔 DeltaTimeForTick
- 调用TickAnimation(DeltaTimeForTick, bNeedsValidRootMotion)更新三种动画实例
- 调用AnimInstance的UpdateAnimation,这里处理动画在游戏线程计算的部分
- 更新顺序为:先更新LinkedInstances,再更新AnimScriptInstance,最后更新PostProcessAnimInstance
3)SkeletalMeshComp->RefreshBoneTransforms接口:
- DispatchParallelEvaluationTasks,负责发起多线程计算:
- SwapEvaluationContextBuffers:
计算前交换缓冲区中的上下文数据,把SkeletalMeshComp中缓存的一些动画数据交换到AnimEvaluationContext。
AnimEvaluationContext(FAnimationEvaluationContext):该变量在SkeletalMeshComp中负责缓存在计算该角色当前动画姿势时的全部数据。 - 发起两个Task:
- FParallelAnimationEvaluationTask:该Task调用ParallelAnimationEvaluation
- PerformAnimationProcessing:该函数主要负责调动画实例的Update和Evaluate接口,这两个接口会逐节点更新动画数据,然后更新动画姿势。最后应用动画姿势数据。
- ParallelDuplicateAndInterpolate:
- FParallelAnimationCompletionTask:该Task调用CompleteParallelAnimationEvaluation
- SwapEvaluationContextBuffers:将AnimEvaluationContext的数据交换回SkeletalMeshComp中缓存,等渲染线程调用
- PostAnimEvaluation(AnimEvaluationContext):
- FParallelAnimationEvaluationTask:该Task调用ParallelAnimationEvaluation
- SwapEvaluationContextBuffers:
2. AnimInstance&&Proxy流程
【游戏线程】
前面说到SkeletalMeshComp会调AnimInstance的UpdateAnimation接口处理该动画蓝图在游戏线程计算的逻辑。
- UpdateAnimation
- 在动画节点逻辑更新前先更新Montage
UpdateMontage(DeltaSeconds)
UpdateMontageSyncGroup
UpdateMontageEvaluationData - BlueprintUpdateAnimation,更新蓝图各种动画逻辑的入口,具体实现就是蓝图的动画树
- 确定是否需要立即更新动画,如果不用立即更新动画的话,会由多线程调用Proxy进行并行计算
const bool bWantsImmediateUpdate = bNeedsValidRootMotion || NeedsImmediateUpdate(DeltaSeconds);
如果需要立即更新,如RootMotion无法并行计算,则在该函数下调用更新接口
ParallelUpdateAnimation();
PostUpdateAnimation();
- 在动画节点逻辑更新前先更新Montage
【多线程】
SkeletalMeshComp的PerformAnimationProcessing负责调用AnimInstance的Update和Evaluate,进行蓝图数据更新,和姿势数据更新。
几个关键的函数:
- 关于Update:
AnimInstance->ParallelUpdateAnimation - 关于Evaluate:
EvaluateAnimation
EvaluatePostProcessMeshInstance
FinalizePoseEvaluationResult
FillComponentSpaceTransforms
1)ParallelUpdateAnimation
-
调用堆栈,由其他线程调用
-
该函数调的是Proxy的UpdateAnimation接口,直接看Proxy:
FAnimationUpdateSharedContext SharedContext; FAnimationUpdateContext Context(this, CurrentDeltaSeconds, &SharedContext); UpdateAnimation_WithRoot(Context, RootNode, NAME_AnimGraph);
Context:创建一个上下文对象
UpdateAnimation_WithRoot:从动画蓝图的根节点(最后一个节点,也就是动画输出姿势节点)往前回溯,遍历所有的动画节点对每个节点进行Update。
2)Proxy->UpdateAnimation_WithRoot
主要做的几件事:
-
CacheBones
-
Proxy->UpdateAnimationNode(InContext)
- 通过调用根节点的Update_AnyThread接口,从根节点开始,并不断往前回溯,更新蓝图数据
- FAnimNode_Root->Update_AnyThread(Context)
通过连接线找到自己的上一个节点N,然后再调用N节点的Update_AnyThread
不停的往前回溯,直到到达尽头节点,不再有连接线。
FPoseLink:连接线对象。
FPoseLink->LinkedNode:连接线指向的节点
根节点:
具体的更新逻辑需深入到每个不同的动画蓝图节点逻辑,暂略。
3)SkeletalMeshComp->EvaluateAnimation
- AnimInstance->ParallelEvaluateAnimation
调用堆栈:
创建一个临时的EvaluationContext上下文对象
计算结束后,将Curve和Pose结果赋值到Comp的AnimEvaluationContextFPoseContext EvaluationContext(&Proxy); EvaluationContext.ResetToRefPose(); Proxy.EvaluateAnimation(EvaluationContext); OutCurve.CopyFrom(EvaluationContext.Curve); OutPose.CopyBonesFrom(EvaluationContext.Pose);
- Proxy.EvaluateAnimation(EvaluationContext)
- Proxy.EvaluateAnimation_WithRoot(Output, RootNode)
- CacheBones
- EvaluateAnimationNode_WithRoot(Output, InRootNode)
- 和Update类似的流程,也是从根节点开始,通过连接线回溯,直到节点尽头。
- 不过具体节点内部的Evaluate逻辑应该决定了和Update调用到的节点数不一样
4)SkeletalMeshComp->EvaluatePostProcessMeshInstance
EvaluatePostProcessMeshInstance也是调用的EvaluateAnimation,一样的更新流程,只是对象不一样:PostProcessAnimInstance
5)SkeletalMeshComp->FinalizePoseEvaluationResult
- 计算TArray& OutBoneSpaceTransforms
将压缩骨骼索引(CompactBoneIndex)转为网格骨骼索引(MeshBoneIndex)
注:三种骨骼索引:CompactBoneIndex、MeshBoneIndex、SkeletalBoneIndex - 输出FVector& OutRootBoneTranslation:RootBone相对位移
6)SkeletalMeshComp->FillComponentSpaceTransforms
获取平移、旋转、缩放向量,以更新每个骨骼的姿势矩阵
每个骨骼矩阵会从根骨骼开始逐个应用父骨的局部矩阵,即该函数输出的是一个全局姿势
- OutComponentSpaceTransforms :AnimEvaluationContext.CachedComponentSpaceTransforms,最终输出的全局姿势矩阵
InBoneSpaceTransforms:由Evaluate计算的输入的关节姿势矩阵 - 计算全局姿势
FTransform::Multiply(SpaceBase, LocalTransformsData + BoneIndex, ParentSpaceBase);
其中SpaceBase是OutTrans,LocalTransformData+BoneIndex是根据index索引的InTrans
Multiply(FTransform* OutTransform, const FTransform* A, const FTransform* B);
3. AnimNode流程
动画播放节点:FAnimNode_SequencePlayer
其中动画播放的具体逻辑在UAnimSequence对象
1)UAnimSequence->GetBonePose (FCompactPose& OutPose, FBlendedCurve& OutCurve, const FAnimExtractContext& ExtractionContext, bool bForceUseRawData=false) const;
- 初始化OutPose
根据设置,使用Retargeting Source的T-Pose,或者是自身的T-Pose数据,先初始化OutPose - EvaluateCurveData:提取曲线数据
- DecompressPose:解压压缩的动画数据,完成OutPose的计算
4. 参考资料
https://docs.unrealengine.com/4.27/zh-CN/AnimatingObjects/SkeletalMeshAnimation/Optimization