概要
- Unreal 骨骼动画系统实质:把各个输入的动画姿势(Pose)资源( AnimSequence ),通过动画蓝图等进行混合和更新,得到最终姿势。再对该 Pose 进行评估和计算,得到各骨骼顶点的目标 Transform 等数据。之后交由渲染线程绘制。
- Unreal 的动画系统主要基于 SkeletalMeshComponent 实现。在 USkeletalMeshComponent::InitAnim 中做初始化,在 USkeletalMeshComponent::TickComponent 中执行 USkeletalMeshComponent::TickPose 以便混合和更新动画 Pose。在 TickComponent 或 FParallelAnimationEvaluationTask 中执行 USkeletalMeshComponent::ParallelAnimationEvaluation 以更新计算骨骼 transform 。最后在帧尾 UWorld::SendAllEndOfFrameUpdates 时通过 USkinnedMeshComponent::SendRenderDynamicData_Concurrent 把数据提交渲染线程绘制。
流程
动画系统
说明
- 此段先说概要,再对每个大的步骤做必要的说明。
概要
- 动画系统的关键流程主要分为这几个部分:初始化、更新、运算、渲染。
- 流程图(简化版,忽略了执行条件等)
536cbro2fb0vqdu6e8tnhi67qa.png
- 时序图(简化版,部分复杂函数在对应章节单独列出。主要看类之间的关联和函数的调用,有注明执行条件)
5up8ai2hm5ji87djbttucb8908.png
初始化(Initialize)
关键方法
- 对应的关键方法为 USkeletalMeshComponent::InitAnim
触发
- 由 USkeletalMeshComponent::OnRegister 或 USkeletalMeshComponent::InitializeComponent 在 AActor::FinishSpawning 后触发。
主要工作
- 主要是各种数据的清理(如 ClearAnimScriptInstance、ResetLinkedAnimInstances、EndNotifyStates)、初始化(如 RecalcRequiredBones)、创建和刷新(如 RefreshMorphTargets)等。
- 也会通过 FAnimInstanceProxy 执行各 FAnimNode_Base 的 Initialize_AnyThread (节点的详细机制见后文)。
流程时序图
- USkeletalMeshComponent::InitAnim
- 动画初始化关键函数,包括初始化方方面面,如数据的准备、清理和交换等及事件的派发。
5lla6cnn06h7u91d4gd43tltoj.png
- UAnimInstance::InitializeAnimation
- 动画初始化主要函数,数据清理、准备等,也会触发蓝图相关事件和各节点的 Initialize_AnyThread 。
1jcepl0tc4h6je0ck2g57me3n0.png
备注
- 需要说明的是,可能在 InitAnim 中执行一次 TickAnimation ,因为:“在编辑器中,动画不会被勾选。 所以更新一次以获得准确的表示而不是 T-Pose。 pre-4.19 的引擎也可能需要这个设置。”
更新(Update)
关键方法
- 动画的更新对应的关键函数为 USkeletalMeshComponent::TickPose (及其父类 USkinnedMeshComponent 的同名方法)、UAnimInstance::UpdateAnimation 等
触发
- 由 USkeletalMeshComponent::TickComponent (或者说 USkinnedMeshComponent::TickComponent )在引擎 Tick 循环中触发
主要工作
- 主要工作为更新动作Pose,基于 DeltaTime 更新动画(含montage)的进度,算权重(Weight),以及调用更新动画蓝图及各Node的相关方法。
- 该流程从 USkeletalMeshComponent::TickAnimation 进入,主要函数为 UAnimInstance::UpdateAnimation ,其涉及 PreUpdateAnimation 、Update 和 PostUpdate,在 PreUpdateAnimation 中初始化(FAnimInstanceProxy::InitializeObjects)基本数据、复制我们可能使用的任何 UObject、调用 FAnimInstanceProxy::PreUpdate 以及执行动画 Node 的 PreUpdate 。
流程时序图
- USkeletalMeshComponent::TickAnimation
- 计算所需曲线数据、更新动画、派发事件。
7039lvthro9tgl2b7tj47hbfm9.png
- UAnimInstance::UpdateAnimation
- 动画更新的关键函数,包括 PreUpdate、Update和PostUpdate三部分,主要是montage和动画蓝图等的更新。
41gfvv1qp2vss8d7vui1sh53nd.png
- UAnimInstance::UpdateMontage
- montage的计算、播放和时间派发。
3cnpnp9gb6pa3udfn85mq6b303.png
- UAnimInstance::ParallelUpdateAnimation
- 在工作线程上运行更新动画,也会执行各节点的 CacheBones_AnyThread 和 Update_AnyThread 。
7sdque5ab349df4e26u7un8pu5.png
计算(Evaluate)
关键方法
- USkeletalMeshComponent::PerformAnimationProcessing 和 USkeletalMeshComponent::EvaluateAnimation 等
触发
- 通过 USkeletalMeshComponent::DoParallelEvaluationTasks_OnGameThread 或 FParallelAnimationEvaluationTask::DoTask 调用。由 USkeletalMeshComponent::InitAnim 、USkinnedMeshComponent::TickComponent 或异步子任务 FParallelAnimationEvaluationTask::DoTask 触发。
主要工作
- 根据前面更新步骤得到的曲线数据、权重等数据,计算新的目标 Pose,更新骨骼的 Transform 等,为渲染准备数据。
流程时序图
- USkeletalMeshComponent::ParallelAnimationEvaluation
- 异步更新、数据计算、数据交换、事件派发等。
3bnrri9obpqjsqjluhuc9sbcec.png
- UAnimInstance::ParallelEvaluateAnimation
- 动画计算的关键函数,也会反向递归调用动画节点的 CacheBones_AnyThread 和 Evaluate_AnyThread 。
1b7t56qfrrodcssb3gd7lk2ana.png
- USkeletalMeshComponent::EvaluateAnimation
- 其实就是对 UAnimInstance::ParallelEvaluateAnimation 的调用。
58j282159kljupvpr389l51i7t.png
- USkeletalMeshComponent::FinalizeAnimationUpdate
- 动画更新完毕,标记一下,可以发数据给渲染了。注意这里的更新完毕包括计算。一般发生在 PostAnimEvaluation 中。
669u32uo9j8e5391gh7lu43hj7.png
- USkeletalMeshComponent::PostAnimEvaluation
- 计算完毕后的处理,如数据的存储和交换(为渲染做准备),也会触发动画事件。
26ga8ubcjk72vv2o075hm8k1lo.png
- USkeletalMeshComponent::FinalizeBoneTransform
- 骨骼数据更新完毕后的处理,比如拷贝和赋值。发生在计算处理之后。
2gvhlf562u62a6hb1oo6jejbpb.png
备注
- 这个操作即可能在主线程也可能在异步子任务中执行,因为很多操作是数据的计算,可以异步。
渲染(Render)
说明
- 基本步骤可分为3部分:创建、更新和销毁。
- 本文不对渲染框架和底层机制做过多分析,相关部分请查看对应文章。
主要作用
- 基于前面准备的各种数据(如骨骼 transform 等),利用 ENQUEUE_RENDER_COMMAND 等宏,调用渲染线程,请求渲染线程对动画进行渲染绘制。
触发
- 创建发生在组件注册时。更新发生在引擎循环 tick 的帧尾 UWorld::SendAllEndOfFrameUpdates 。销毁发生在组件销毁时。
- 更新的触发流程相对较为重要和复杂一点点,流程图如下
2d2222qeei2uocbnts36nav06q.png
关键类和方法
- USkinnedMeshComponent
- USkinnedMeshComponent::CreateRenderState_Concurrent
- USkinnedMeshComponent::SendRenderDynamicData_Concurrent
- USkinnedMeshComponent::DestroyRenderState_Concurrent
- USkinnedMeshComponent::UpdateMorphMaterialUsageOnProxy
- FSkeletalMeshObject
- FSkeletalMeshObjectGPUSkin
- FSkeletalMeshObjectCPUSkin
- FSkeletalMeshObjectStatic
- FGPUBaseSkinVertexFactory::FShaderDataType::UpdateBoneData
- GpuSkinVertexFactory.ush
- GetMaterialVertexParameters
- FSkeletalMeshObjectGPUSkin
- FSkeletalMeshObjectGPUSkin::Update
- FSkeletalMeshObjectGPUSkin::UpdateDynamicData_RenderThread
时序图
- 创建、更新和销毁
- 主要还是各种数据的准备和组装及对渲染线程的调用。
7j3aogoobh61m0tson5cq19ivk.png
- FSkeletalMeshObjectGPUSkin::Update
- 有利用 ENQUEUE_RENDER_COMMAND 宏触发渲染线程执行对应命令。
22lchhtsarc4ei6vfcirc5qdob.png
其它重要操作
数据操作
- USkeletalMeshComponent::RefreshBoneTransforms
- 很重要的操作,更新和计算的桥梁。发生在初始化完毕或动画更新完毕后,此时需要对骨骼进行刷新和计算,以便为渲染提供数据。在初始化、tick 等时均可能触发。
4c11ha0mc0vg9vpu2fk0iq7qcs.png
- USkeletalMeshComponent::RecalcRequiredBones
- 根据当前的 SkeletalMesh、LOD 和 PhysicsAsset 重新计算此 SkeletalMeshComponent 中的 RequiredBones 数组。一般发生在初始化时。
2qd98p6c51039knpuvqsc7b7in.png
- USkeletalMeshComponent::RecalcRequiredCurves
- 根据当前所需的骨骼集重新计算此 SkeletalMeshComponent 的 RequiredBone 中的 AnimCurveUids 数组。一般发生在 animation tick 时。
68s1tug3d3fv2ksd9m25cplhpk.png
- USkeletalMeshComponent::RefreshMorphTargets
- 重置和刷新montage数据,一般发生在初始化时。
6vbtv94eimlsdmablc9rdar0h4.png
- USkeletalMeshComponent::UpdateLODStatus
- 更新网格的 LOD 信息以及使用对应 LOD 的网格,在初始化和tick时均可能触发。
32j00lsujn6ha3hbjvc7ecub8t.png
事件
- USkeletalMeshComponent::ConditionallyDispatchQueuedAnimEvents
- 在动画执行到一定阶段后派发对应的事件
2ns3al9otabulbhji3rddudq5o.png
- USkeletalMeshComponent::DispatchParallelEvaluationTasks
- 派发异步计算的子任务,可使 Evaluation 在主线程和子线程都能执行。
1nev9kt5t55mggjsctfoce9v21.png
- USkeletalMeshComponent::HandleExistingParallelEvaluationTask
- 接受处理异步计算子任务,让异步计算在指定位置执行。
0askchjms9siai1ahslk7f85os.png
重要判断
- USkeletalMeshComponent::ShouldOnlyTickMontages
- 如果设置为 OnlyTickMontagesWhenNotRendered 并且最近没有被渲染,那么只更新蒙太奇并跳过其他所有内容。
- USkeletalMeshComponent::ShouldTickAnimation
- 是否应该更新动画(可能因为 URO 而跳过)
- USkeletalMeshComponent::ShouldTickPose
- 自主Tick允许每帧执行多次,因为可以接收和处理同一帧的多个网络更新。当播放联网的 Root Motion Montages 时,我们希望这些在DS和远程客户端上播放以便联网和位置校正。所以我们在这种情况下强制更新姿势以保持根运动和位置同步。
- USkinnedMeshComponent::ShouldUpdateTransform
- 如果最近被渲染过,并且 bForceRefPose 已打开至少一帧,或者 LOD 已更改,更新骨骼矩阵。
动画节点
概述
- Unreal 动画蓝图利用其图表中的各种节点对输入姿势执行操作,例如混合、直接骨骼操作等。
- 引擎中提供了几种不同类型的动画节点,包括事件、混合节点、骨骼控制器、空间节点和转换节点。
- 动画节点的基类为 FAnimNode_Base ,根节点为 FAnimNode_Root。
关键函数
- Initialize_AnyThread * 节点首次运行时调用。 如果节点在状态机或缓存的姿势分支内,则可以多次调用。
- CacheBones_AnyThread * 刷新节点引用的骨骼索引
- Update_AnyThread * 更新当前播放时间、混合权重等信息。此函数采用一个 FAnimationUpdateContext,它知道更新的 DeltaTime 和当前节点的混合权重 * 节点间通过 FAnimationUpdateContext 在动画树 Evaluate 期间传递的相关上下文,其包含 CurrentWeight 、 DeltaTime 等信息。
- Evaluate_AnyThread * 调用以根据 Update 中设置的权重等数据计算生成 Pose(骨骼变换列表)。 * 节点间通过 FPoseContext 在动画树 Evaluate 期间传递的相关上下文,其包含 Pose 、Curve 、 CustomAttributes等信息。
执行顺序
- 不论是 Initialize 、CacheBones 还是 Update、Evaluate,节点的执行都是反序遍历的。即从根节点 RootNode (FAnimNode_Root) 开始,通过其
XXX _AnyThread
,调用 Result (FPoseLink) 的 XXX 函数,进而调用 LinkedNode (FAnimNode_Base) 的XXX_AnyThread
,依次反向递归。内部通过 FPoseContext 进行数据的传递。
类图
关键类和关键方法类图
7shavb7vqvfq5d15i58b9hsm6t.png
说明
- USkeletalMeshComponent
- 可以看做是对动画系统的封装和接口,外部对动画系统的驱动等均通过本组件进行,其又通过 UAnimInstance 更新动画表现,通过 FAnimInstanceProxy 传递数据,以及通知渲染进行绘制。
- FAnimInstanceProxy
- 如其名字所示,一个代理,用于在动画更新期间进行数据的传递(有利于多线程操作)。
- UAnimInstance
- 负责操作动画表现。
- FAnimNode_Base
- 各动画节点的基类,通过 FAnimInstanceProxy 对节点进行反向递归操作。
相关链接
- 解析UE动画系统--核心实现 https://zhuanlan.zhihu.com/p/560801479
- UE4 动画系统 源码及原理剖析 https://blog.csdn.net/qq_23030843/article/details/109103433
- UE4/UE5 动画的原理和性能优化 https://zhuanlan.zhihu.com/p/545596818
- UE动画蓝图和动画节点拆解 http://www.lpq.design/2022/03/UE_AnimBP/
- 骨架网格体动画系统 https://docs.unrealengine.com/4.27/zh-CN/AnimatingObjects/SkeletalMeshAnimation
- $UE$ 浅析UE动画系统 https://zhuanlan.zhihu.com/p/393884450
- 【UE4】图解动画系统源码 https://zhuanlan.zhihu.com/p/446851284上原文链接(https://mp.weixin.qq.com/s/fjhVb1gJJRmjBTZIvyrOLg)及本声明。
**声明:**本文来自公众号:GameDevLearning),转载请附上原文链接(https://mp.weixin.qq.com/s/fjhVb1gJJRmjBTZIvyrOLg)及本声明。