基于MotionMatching的交互动动画技术工作总结
基于MotionMatching的交互动动画技术工作总结
背景
Motion Matching首先由Büttner和Clavet在2015年提出,该算法基于运动捕捉获得的动作数据库,用程序自动搜索最合适的下一帧,由此生成完整的动画。相比于以往主流采用的状态机动画,具有动画真实度更高、更节约人力和时间成本、能实现更高产能等特点。该算法在2016年在GDC2016由育碧正式提出,同年被应用于开发育碧的多人动作游戏荣耀战魂。之后又被应用于最后生还者,黑神话悟空等众多3A游戏中。在2021年,EA在GDC上分享了将Motion Matching与环境交互结合的技术并展示其在游戏Madden中的表现效果。2024年3月,UE5.4版本正式推出Motion Matching功能并于6月发布示例工程和相关动画数据。可以预见未来Motion Matching会被更加广泛地应用在游戏动作系统的开发中。
问题
一直以来,Motion Matching常被应用于角色Locomotion中,并取得了很好的效果。其实在交互动画领域,Motion Matching也能发挥奇效,EA的多人运动类游戏麦登橄榄球和FIFA中的角色间、角色与场景间的交互动画都有Motion Matching的身影。本课题聚焦的问题就是将Motion Matching应用到交互动动画,探索该技术在角色Locomotion之外的潜力。
Motion Matching算法
基础算法
Motion Matching的思路可以总结为一句话:在动画数据库中检索出下一帧应该播放的那个姿势。
顺着这个思路要回答两个问题:
- 下一帧应该播放哪个姿势(Pose)
- 如何从数据库中得到这一帧Pose
首先,下一帧该播放的pose显然是和当前帧强相关的,不能与当前帧差距过大,因为动画的本质就是pose的连续播放,下一帧的pose如果与当前帧pose差距过大,就会破坏动画的连续性。其次,游戏中的角色动画都是可交互的(指玩家可以控制角色播放什么样的动画),那么下一帧pose也与玩家的输入有关。总结起来就是当前姿势和玩家输入。
接下来回答第二个问题,如何得到这个pose;我们需要在数据库中根据判断下一帧姿势的两个条件评估每个姿势是否是我们理想的姿势,当前姿势很好得到,从数据库中获得与当前姿势相似的姿势也很容易。因此重点在于如何使用玩家输入进行检索,容易想到根据玩家输入可以得到未来的运动趋势,如玩家当前时刻向左推摇杆那么之后一段时间角色就应该向左运动,那么就需要动画数据库中有特征用来表示每一个pose的运动趋势。MM算法就是这种思路,使用轨迹(Trajectory)来匹配玩家输入,这里的轨迹指的是一段连续姿势的特征,如未来几帧姿势的位置、方向等信息。在Offline阶段得到每个pose的一段时间内的轨迹,在Runtime时根据玩家的输入预测出相同时间段内的轨迹,在数据库中匹配哪个姿势的轨迹与当前预测出的轨迹最相似。将当前姿势和玩家输入两部分影响因素综合考虑,就可以得到最适合在下一帧播放的pose;
上面我们重点分析了两个因素:当前姿势和运动轨迹,其实在实际使用时还有很多因素会产生影响,对于角色Locomotion常见的因素还有角色当前运动速度和朝向。抽象出数学公式如下,为数据库中的每一个姿势计算Cost(这里的cost理解为与理想姿势间的差距),Cost最低的就是match到的下一帧播放的姿势。
由上的算法思路分析可知,MM的关键点是要确定下一帧要播放哪个姿势再去想办法在数据库中得到这个姿势,抽象来说就是一个特征匹配的过程。具体到实现上,就需要提供方法分别在数据库中和runtime时提取姿势的特征。因此MM的流程如下:
- 确定特征:判断哪些因素会决定下一帧姿势
- 提取特征:在Offline阶段为数据库中的每个姿势提取这些特征
- 计算特征:在Runtime阶段计算下一帧期望播放的姿势的特征
- 特征匹配:搜索数据库,进行特征匹配得到姿势
- 姿势过渡:从当前姿势过渡到匹配结果
交互算法
思路
从MM的本质出发交互动MM的实现思路也很简单,可以从上面的两个问题开始思考,下一帧应该播放哪个姿势以及如何得到这个姿势。交互动动画中,决定下一帧播放哪个动画的因素显然除了角色Locomotion中用到的特征,还应该有交互特征,也就是描述环境信息的特征,比如希望让角色扶墙行走,就需要墙的特征。因此交互动MM的算法流程与基础的MM完全相同,只是需要对交互特征进行特殊处理。
难点
交互动MM的思路很简单,只是在原有框架下加入新的特征即可,但是实现难点主要在于交互特征难以提取,也就是难以将动画数据中的交互特征提取出来并Bake到特征数据库。大部分的骨骼动画数据,都是以单个角色的骨架为单位存储动画数据的,对于交互式动画例如开门,受击等,也都只存储了角色骨骼的信息,对于门或者攻击来源的信息是完全没有的;因此如何从动画数据中得到这些交互物的信息是实现交互动MM的关键。
参考
明确了需要交互特征和实现难点,我们可以先参考一下目前的一些工作是如何处理将MM与交互动动画相结合的。EA在GDC2021分享的工作Environmental and Motion Matched Interactions,是在EA2017年提出的N-teract技术的基础上用MM取代状态机实现的,N-teract是一种用于实现多人交互动画效果的技术,被应用在多人体育运动类游戏中,这类游戏往往有大量多人间的接触和交互,如篮球和橄榄球游戏。N-teract本质是匹配开始播放多人动画的入口,然后开始播放对应的多人动画,具体原理可以参考之前专门总结的文章。整个过程如下:
左图检测到了1号和3号,但是数据库中没有1号和3号同时存在的多人动画,故没有播放tackle动画,继续移动到了右图所示,检测到了1号和2号,数据库中有1号和2号同时存在的动画就播放。
而GDC2021上提出的EMMI是在N-teract过程结束后用MM代替直接播放对应动画:
由上图我们也可以看出,交互动MotionMatching最适用的场景是:“入口”相同但是期望播放的动画不同,否则完全可以根据“入口”的不同直接播放对应动画即可。因此在挑选测试场景时最好选择这种场景,我们最终选取了开门场景作为交互动MM的测试场景,开门时一般都需要走到门前,即交互“入口”相同,但是不同的门(如双开门,内开门,外开门)期望播放的动画不同,很适合使用交互动MM。
方案
本课题在Motion Sympathy插件的基础上进行开发,具体分析了交互物与角色有接触(如开门,受击等)的这一类交互动动画的特征提取方式。对于这类交互动动画,由于交互物与角色有接触,将角色与交互物接触过程中的接触点的骨骼当作交互物,那么就可以通过接触的骨骼间接得到接触过程中交互物的特征信息。除此之外,对于交互动MM需要调整轨迹预测算法,使得预测轨迹可以准确得停在对对应的交互点。
对于交互动动画的触发应该由被交互物决定,例如对于开门demo,可以准备不同种类的门(双开门,单开门,内/外开门),在交互过程中通过不同的门的动画来决定当前角色应该播放哪个动画。
具体gameplay应为,玩家按下交互按键检测周围是否有门,如果有,门actor将此时的角色由locomotion状态切换为交互状态,并告知角色交互入口(即角色应该走到哪里停下开始开门)的位置和交互点相对角色根的相对位置(门把手)位置,角色开始向交互入口移动,由交互组件判断是否到达,如果到达就通知被交互物:门,开始播放打开动画。当门的开打动画播放完毕后,门actor将角色由交互状态切换为locomotion状态,整个交互过程结束。
可以看出,整个交互流程是由被交互物驱动的,这符合MotionMatching的算法思想:自动寻找合适的动画帧,而不是像动画状态机一样完全由角色决定当前应该播放的动画。
下面详细介绍为开门交互动MotionMatching测试demo开发的功能:
功能1:开门框架
需求
实现用MotionMatching驱动的开门效果,即角色走到门前播放开门动画,门被打开;
实现
实现依靠CBP_InteractComponent蓝图类和BP_Door蓝图类,这两个类都继承接口 BPI_Interact 并实现该接口中定义的三个函数:
交互接口:BPI_Interact
- InteractPrepare:被交互物(BP_Door)实现,实现交互前的准备工作,缓存发起交互的角色Investigator,并调用Investigator(能发起交互说明有交互组件且继承了BPI_Interact)实现的接口函数 MoveTo;
- MoveTo:交互组件实现,实现驱动带有交互组件的角色移动到对应的交互点;Location表示交互点位置,ActprTarget表示被交互物,LinkedActor可以是另一个对此交互做出反应的Actor,UseAnimation表示被交互物播放的动画(但是这里使用Timeline实现开门效果)
- Interact:被交互物(BP_Door)实现,实现由Timeline驱动的开关门效果;
交互组件:CBP_InteractComponent
- 按下交互键进行SphereTraceForObjects,检测视角方向一定距离内有无可交互物(由是否是自定义的碰撞对象决定)
如果有可交互物,调用其实现的InteractPrepare接口函数进行交互前的准备; - 实现MoveTo接口函数,使用bool变量 ShouldMove 判断是否已经走到交互点,如果还没走到交互点,在Tick中计算走到交互点的需要的方向(该方向为TrajectoryGenerator的输入)
角色蓝图:BP_MotionMatchingCharacter
如果是传统方法实现开门,不需要角色蓝图有任何多余的修改,只需要添加交互组件即可;
如果使用MotionMatching,需要根据交互组件中的bool变量ShouldMove判断是否应该向交互点移动,此时不使用用户输入生成轨迹,而使用交互组件中生成的移动方向生成轨迹:
同时,还需要根据交互组件中的bool变量Interacting判断是否开始了交互,需要切换MotionMatching输入特征构建时的MotionConfig:
动画蓝图
动画蓝图中需要根据是否处于Interact状态切换到交互状态机,交互状态机中有一个状态,其中为一个使用交互动画数据集的MotionMatching节点:
功能2:交互特征(MatchFeature_Interaction)
要使用MotionMatching驱动交互动画,目前的思路是添加一个交互点位置特征,在Bake阶段得到交互点相对于角色根骨骼的相对位置,在Runtime阶段检测交互点的实时相对角色根骨骼的位置,进行Cost计算;
Runtime阶段
在被交互物的Tick中判断是否开始了交互,即调用接口函数InteractPrepare直到接口函数Interact执行完成,在此期间每帧计算交互点相对于角色根骨骼的相对位置,并将该位置通过交互组件的函数 SetCurrentInteractPointLocation 保存起来,同时在构建输入特征函数中由函数 GetCurrentInteractPointLocation 提取;
Bake阶段
骨骼动画数据一般都只有当前角色的骨骼信息,没有被交互对象的任何信息,如开门动画,在动画数据中没有门的任何信息;我们要实现MM中的交互点位置特征匹配,就必须动画数据库存在该信息。
对于大部分交互动画,在交互过程中交互点的位置往往和角色的某一块骨骼的位置重合,因此使用这个骨骼位置代替交互点位置(如开门过程中,手部会一直握住门把手,就可以用手部骨骼位置代表交互点门把手的位置);因此交互特征MatchFeature_Interaction得到关注的骨骼的位置;
但是这样存在问题,就是只能表示手接触门把手之后的交互点的位置,我们的动画数据是从不同角度走向门并开门的动画,在走向门的过程中不能表示门把手的位置;因此使用Tag_Interaction来解决这个问题:在一段动画中,使用Tag_Interaction标记角色手部接触门把手直到松开的过程,使用Tag标记的第一帧的手部位置的世界坐标表示门把手(交互点)的世界坐标,再从动画序列的开头计算出该世界坐标每帧相对于角色根骨骼的位置,这样就把交互点的位置Bake到了数据库中,效果如下:
交互特征的权重不宜过大
功能3:Bake界面显示信息
显示根速度
在MotionSymphonyEditor\Private\Toolkits\MotionPreProcessToolkitViewportClient.cpp 中的 DrawCanvas 函数中添加要关注的信息即可,根速度使用函数 FMMPreProcessUtils::ExtractRootVelocity 得到;
显示角度点位置
添加信息和上面显示根速度的位置一样,关键在于如何得到交互点的位置信息,这里借助姿势特征数组(PoseArray),先遍历一遍MotionConfig中的特征数组(Feature)得到 Interaction特征 的FeatureOffset,之后凭借该偏移从PoseArray中得到交互点相对位置;
功能4:统计单个特征的Cost值
借助每个特征的Size函数得到该特征的大小,在特征数组中计算;计算过程在 GetLowestCostPoseId_Standard() 函数中,计算完成之后,定义一个TMap,Key为每个特征的名称(在每个特征类中添加函数GetFeatureName得到),Value为该特征的Cost;
功能5:走到目标点的轨迹生成
原本的Trajectory Generator生成轨迹的思路是,输入的向量代表希望移动的方向,乘上移动速度得到当前期望的速度向量 DesiredLinearVelocity(有方向有大小),之后根据Trajectory Generator的Sample Rate计算出期望的移动位移 DesiredLinearDisplacement,这个位移就表示了轨迹预测最后一帧的位移,再选择不同的插值方式插值出中间的轨迹点;
走到目标点的轨迹生成要求在到达目标点时速度为零,也就是在接近目标点时要减速,那么就很简单了,只需要根据当前位置到目标点的距离设置移动速度即可;这一步在原本框架中即使计算 DesiredLinearVelocity 的函数,那么只需要自定义新的函数,设定一定的减速距离(表示距离目标点多远开始减速),进入这个减速距离之后,速度为原速度乘上 距目标点距离/减速距离,实现减速:
交互动MotionMaatching工作流
整体工作流与Motion Symphony相同,只需要额外处理交互特征:
- 动画数据获取:准备交互动动画数据,保证有正确的根运动
- 动画数据库构建:对动画数据进行预处理,在Bake阶段使用Tag_Interaction来解决这个问题:在一段动画中,使用Tag_Interaction标记角色手部接触门把手直到松开的过程;
- 设置轨迹生成算法:将轨迹生成算法设置为Interaction
- 添加交互组件:为角色添加交互组件
- 自定义被交互物行为:设置被交互物动画
实现效果
特征提取效果
其中红色小球代表该开门动画中的交互点(门把手)的位置信息,图中其会左右抖动为显示问题,不影响交互特征信息提取的准确性。
交互动MM效果
虽然还有抖动,不够准确等问题,但是在没有刻意优化特征参数的情况下可以达到基本的开门效果,可以证明当前方案具备可行性。
未来工作
- 交互特征提取: 当前只解决了接触式交互动画中的特征提取问题,对于非接触式还没有特别好的思路;同时当前只提取交互点位置特征还不够导致效果不稳定,还可以添加速度,加速度等特征,对于开门动画可以考虑添加角色与门的角度特征;
- 权重参数优化:目前角色向被交互物移动的过程也考虑了交互点的信息,但是向交互点移动的过程中需要考虑双脚骨骼,如果交互点特征权重过大可能导致双脚骨骼的信息被稀释,导致行走动画不流畅;如果交互点特征权重过小可能导致交互点信息被稀释,导致不能正确开门,或者无法匹配正确角度的开门;
- 组件化:目前的功能耦合度较高,尤其是被交互物,后期可以将被交互物功能组件化,实现对任意物体添加被交互组件即可驱动交互动MM;