漫画软件源码(Unreal 动画系统原理机制源码剖析)

wufei123 发布于 2023-11-28 阅读(511)

因平台限制,部分图片可能没法放大查看,可给公众号发送私信获取,也可访问知乎查看大图,作者知乎链接:https://www.zhihu.com/people/zhouwensi/posts前言本文基于 Unreal 源码和执行堆栈,对 Unreal 动画系统的组织结构、执行流程进行分析和总结,并输出相关流程图、时序图和类图,以便更好地理解 Unreal 动画系统相关源码和原理机制,最终达到更好地使用、优化和修改相关功能的目的。

本文主要关注于 Unreal 动画系统的执行流程机制和组织结构,不涉及各种压缩算法原理、IK细节,也不会细说动画的基础知识、动画系统的基础使用个人理解,仅供参考如有错误,敬请指正概要Unreal 骨骼动画系统实质:把各个输入的动画姿势(Pose)资源( AnimSequence ),通过动画蓝图等进行混合和更新,得到最终姿势。

再对该 Pose 进行评估和计算,得到各骨骼顶点的目标 Transform 等数据之后交由渲染线程绘制Unreal 的动画系统主要基于 SkeletalMeshComponent 实现在 USkeletalMeshComponent::InitAnim 中做初始化,在 USkeletalMeshComponent::TickComponent 中执行 USkeletalMeshComponent::TickPose 以便混合和更新动画 Pose。

在 TickComponent 或 FParallelAnimationEvaluationTask 中执行 USkeletalMeshComponent::ParallelAnimationEvaluation 以更新计算骨骼 transform 。

最后在帧尾 UWorld::SendAllEndOfFrameUpdates 时通过 USkinnedMeshComponent::SendRenderDynamicData_Concurrent 把数据提交渲染线程绘制。

流程动画系统说明此段先说概要,再对每个大的步骤做必要的说明。概要动画系统的关键流程主要分为这几个部分:初始化、更新、运算、渲染。流程图(简化版,忽略了执行条件等)

时序图(简化版,部分复杂函数在对应章节单独列出。主要看类之间的关联和函数的调用,有注明执行条件)

初始化(Initialize)关键方法对应的关键方法为 USkeletalMeshComponent::InitAnim触发由 USkeletalMeshComponent::OnRegister 或 USkeletalMeshComponent::InitializeComponent 在 AActor::FinishSpawning 后触发。

主要工作主要是各种数据的清理(如 ClearAnimScriptInstance、ResetLinkedAnimInstances、EndNotifyStates)、初始化(如 RecalcRequiredBones)、创建和刷新(如 RefreshMorphTargets)等。

也会通过 FAnimInstanceProxy 执行各 FAnimNode_Base 的 Initialize_AnyThread (节点的详细机制见后文)流程时序图USkeletalMeshComponent::InitAnim。

动画初始化关键函数,包括初始化方方面面,如数据的准备、清理和交换等及事件的派发。

UAnimInstance::InitializeAnimation动画初始化主要函数,数据清理、准备等,也会触发蓝图相关事件和各节点的 Initialize_AnyThread 。

备注需要说明的是,可能在 InitAnim 中执行一次 TickAnimation ,因为:“在编辑器中,动画不会被勾选所以更新一次以获得准确的表示而不是 T-Posepre-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计算所需曲线数据、更新动画、派发事件。

UAnimInstance::UpdateAnimation动画更新的关键函数,包括 PreUpdate、Update和PostUpdate三部分,主要是montage和动画蓝图等的更新。

UAnimInstance::UpdateMontagemontage的计算、播放和时间派发。

UAnimInstance::ParallelUpdateAnimation在工作线程上运行更新动画,也会执行各节点的 CacheBones_AnyThread 和 Update_AnyThread

计算(Evaluate)关键方法USkeletalMeshComponent::PerformAnimationProcessing 和 USkeletalMeshComponent::EvaluateAnimation 等

触发通过 USkeletalMeshComponent::DoParallelEvaluationTasks_OnGameThread 或 FParallelAnimationEvaluationTask::DoTask 调用。

由 USkeletalMeshComponent::InitAnim 、USkinnedMeshComponent::TickComponent 或异步子任务 FParallelAnimationEvaluationTask::DoTask 触发。

主要工作根据前面更新步骤得到的曲线数据、权重等数据,计算新的目标 Pose,更新骨骼的 Transform 等,为渲染准备数据流程时序图USkeletalMeshComponent::ParallelAnimationEvaluation。

异步更新、数据计算、数据交换、事件派发等。

UAnimInstance::ParallelEvaluateAnimation动画计算的关键函数,也会反向递归调用动画节点的 CacheBones_AnyThread 和 Evaluate_AnyThread 。

USkeletalMeshComponent::EvaluateAnimation其实就是对 UAnimInstance::ParallelEvaluateAnimation 的调用。

USkeletalMeshComponent::FinalizeAnimationUpdate动画更新完毕,标记一下,可以发数据给渲染了注意这里的更新完毕包括计算一般发生在 PostAnimEvaluation 中。

USkeletalMeshComponent::PostAnimEvaluation计算完毕后的处理,如数据的存储和交换(为渲染做准备),也会触发动画事件。

USkeletalMeshComponent::FinalizeBoneTransform骨骼数据更新完毕后的处理,比如拷贝和赋值。发生在计算处理之后。

备注这个操作即可能在主线程也可能在异步子任务中执行,因为很多操作是数据的计算,可以异步渲染(Render)说明基本步骤可分为3部分:创建、更新和销毁本文不对渲染框架和底层机制做过多分析,相关部分请查看对应文章。

主要作用基于前面准备的各种数据(如骨骼 transform 等),利用 ENQUEUE_RENDER_COMMAND 等宏,调用渲染线程,请求渲染线程对动画进行渲染绘制触发创建发生在组件注册时更新发生在引擎循环 tick 的帧尾 UWorld::SendAllEndOfFrameUpdates 。

销毁发生在组件销毁时更新的触发流程相对较为重要和复杂一点点,流程图如下

关键类和方法USkinnedMeshComponentUSkinnedMeshComponent::CreateRenderState_ConcurrentUSkinnedMeshComponent::SendRenderDynamicData_Concurrent

USkinnedMeshComponent::DestroyRenderState_ConcurrentUSkinnedMeshComponent::UpdateMorphMaterialUsageOnProxy

FSkeletalMeshObjectFSkeletalMeshObjectGPUSkinFSkeletalMeshObjectCPUSkinFSkeletalMeshObjectStaticFGPUBaseSkinVertexFactory::FShaderDataType::UpdateBoneData

GpuSkinVertexFactory.ushGetMaterialVertexParametersFSkeletalMeshObjectGPUSkinFSkeletalMeshObjectGPUSkin::Update

FSkeletalMeshObjectGPUSkin::UpdateDynamicData_RenderThread时序图创建、更新和销毁主要还是各种数据的准备和组装及对渲染线程的调用。

FSkeletalMeshObjectGPUSkin::Update有利用 ENQUEUE_RENDER_COMMAND 宏触发渲染线程执行对应命令。

其它重要操作数据操作USkeletalMeshComponent::RefreshBoneTransforms很重要的操作,更新和计算的桥梁发生在初始化完毕或动画更新完毕后,此时需要对骨骼进行刷新和计算,以便为渲染提供数据。

在初始化、tick 等时均可能触发

USkeletalMeshComponent::RecalcRequiredBones根据当前的 SkeletalMesh、LOD 和 PhysicsAsset 重新计算此 SkeletalMeshComponent 中的 RequiredBones 数组。

一般发生在初始化时

USkeletalMeshComponent::RecalcRequiredCurves根据当前所需的骨骼集重新计算此 SkeletalMeshComponent 的 RequiredBone 中的 AnimCurveUids 数组。

一般发生在 animation tick 时

USkeletalMeshComponent::RefreshMorphTargets重置和刷新montage数据,一般发生在初始化时。

USkeletalMeshComponent::UpdateLODStatus更新网格的 LOD 信息以及使用对应 LOD 的网格,在初始化和tick时均可能触发。

事件USkeletalMeshComponent::ConditionallyDispatchQueuedAnimEvents在动画执行到一定阶段后派发对应的事件

USkeletalMeshComponent::DispatchParallelEvaluationTasks派发异步计算的子任务,可使 Evaluation 在主线程和子线程都能执行。

USkeletalMeshComponent::HandleExistingParallelEvaluationTask接受处理异步计算子任务,让异步计算在指定位置执行。

重要判断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 进行数据的传递类图关键类和关键方法类图

说明USkeletalMeshComponent可以看做是对动画系统的封装和接口,外部对动画系统的驱动等均通过本组件进行,其又通过 UAnimInstance 更新动画表现,通过 FAnimInstanceProxy 传递数据,以及通知渲染进行绘制。

FAnimInstanceProxy如其名字所示,一个代理,用于在动画更新期间进行数据的传递(有利于多线程操作)UAnimInstance负责操作动画表现FAnimNode_Base各动画节点的基类,通过 FAnimInstanceProxy 对节点进行反向递归操作。

相关链接解析UE动画系统--核心实现 https://zhuanlan.zhihu.com/p/560801479UE4 动画系统 源码及原理剖析 https://blog.csdn.net/qq_23030843/article/details/109103433

UE4/UE5 动画的原理和性能优化 https://zhuanlan.zhihu.com/p/545596818UE动画蓝图和动画节点拆解 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声明:本文来自公众号:Unity 与Unreal 游戏开发(GameDevLearning),转载请附上原文链接及本声明。

亲爱的读者们,感谢您花时间阅读本文。如果您对本文有任何疑问或建议,请随时联系我。我非常乐意与您交流。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

宝骏汽车 新闻52810