Motion Matching 技术介绍和实现
你有没有想过,游戏中的角色动作是怎样实现的?为什么游戏中的你,无论是策马奔腾还是举杯换盏都十分真实流畅自然?目前游戏开发中过程中经常会采用动作捕捉技术,演员们在真实世界中根据角色设计做出相应动作,这些动作都将通过动作捕捉技术记录,而后“转播”在游戏之中。
本文将介绍一种游戏动作相关技术:Motion Matching[1],将动捕数据直接导入游戏引擎,即可驱动角色移动,效果与原始动捕数据相差无几。
背景
雷火开发的许多游戏都使用了动作捕捉技术,角色的动作是在位于杭州网易大厦的雷火动捕中心捕捉制作的,如下图:
首先在动捕演员身体上贴一些跟踪标记,然后利用动捕室内分布在四面八方的相机记录演员的动作信息,经过动捕系统实时运算处理后生成动捕数据存储到计算机中。
例如在制作《逆水寒OL》中的职业男素问时,我们邀请了著名舞蹈家黄豆豆老师来做动作捕捉:
获得动捕数据后,首先美术同学会切分出自己想要的动作,然后经过编辑调整后导入游戏。最后策划和程序同学通过搭建动作状态机来在游戏中播放这些动作。
动作状态机描述了角色在特定状态下应该做什么动作,以及角色如何在不同动作之间转换,比如角色在跑步状态下应该播放跑步动作,当玩家松开按键时,角色应该停下来播放站立的动作,这一过程可以用如下动作状态机表达:
对于大中型游戏而言,角色的动作和状态非常多,动作之间的切换非常复杂,因此动作状态机也特别复杂,例如:
在制作角色的移动相关动作(例如走、跑、停、转弯等)时,很难通过简单修改动捕数据得到理想的动作片段,美术要切分出所需片段,制作动作循环、处理动作之间的融合衔接等,程序和策划还要搭建对应的动作状态机,将每个动作片段配置到对应位置,整个流程会耗费大量时间,导致从动捕到实装动作到游戏的周期变得很长。
本文将介绍一种游戏动作相关技术:Motion Matching[1],将动捕数据直接导入游戏引擎,即可驱动角色移动,效果与原始动捕数据相差无几。与传统的基于动作状态机的系统相比,角色动作表现更加真实自然,并且省去了动作切分编辑以及搭建动作状态机和融合树等步骤,大大缩短了从动捕到实装到游戏中的周期,整个系统清晰简单,如下图所示:
Motion Matching 的基本思想
设想如果能够将一个角色可能做出的所有动作都捕捉并存储下来,在游戏运行时,根据玩家的输入和角色所处的状态等信息,自动化的搜索出最匹配未来角色状态的动作并播放,就可以简化制作流程,并且能够最大程度保留原始动捕数据中角色的动作细节。
Motion Matching 正是采用了上述思路,主要应用在走路、跑步等移动逻辑上。游戏中角色的移动动作一般包含静止站立、起步、直线移动、转弯、停步等动作,每一种动作的路径和速度特征都非常明显且各不相同,例如直线移动时,角色的运动路径是一条直线,并且速度是均匀的;而停步动作虽然路径也是直线,但速度会逐渐减小至零。在游戏中,很容易通过玩家的输入来得到角色的预期运动特征,例如玩家将手柄摇杆推到底,说明希望角色能够立即向推摇杆方向移动,Motion Matching 根据这个特征以及角色当前的动作,从动作数据中搜索出下一帧应该播放的动作,驱动角色运动。
动捕数据的采集
Motion Matching 的最终表现效果取决于动捕数据的质量和完备程度,如果能把一个角色所有可能做出的动作全部记录下来当然是最好的,但受限于当前个人电脑的计算能力和存储能力,仍需要设计一个方案,以最少的动捕数据达到预期的效果。
育碧的研究人员提出了一个概念:Dance Card,它是一个包含了基础移动所需动作数据的最简动捕流程,动捕演员按照 Dance Card 预定的路线完成指定动作,根据需要可以对行走、慢跑、快跑、战斗状态下的行进等不同姿态的动作分别按 Dance Card 录制。
根据 Kristjan[3] 的视频资料,可以按如下 Dance Card 做动捕
- 0°、45°、90°、135°、180°起步、停步、转弯:
2. 直线循环、180°急转弯:
3.连续蛇形转弯:
4.绕圈(可录多种不同半径):
帧数据的预计算
虽然通过 Dance Card 对动捕做了简化,但最终动捕得到的数据量仍然非常多,Motion Matching 需要在这些数据中做搜索,由于动作数据的检索需要从根骨骼开始遍历每根骨骼,如果在运行时对这么多动作数据进行检索,效率会非常低。
为了节省运行时的计算量,首先要对动捕数据做预计算,导出一份 Motion Matching 所需的用于加速搜索的数据集,对于基础移动而言一般需要对每帧动作预计算以下数据:
- RootVelocity:角色根骨骼的运动速度。
- LeftFootPos、LeftFootVelocity:左脚在角色局部坐标空间的位置和运动速度。
- RightFootPos、RightFootVelocity:右脚在角色局部坐标空间的位置和运动速度。
- Trajectory:从此帧开始播放,角色未来的运动轨迹和速度。
将数据 1、2、3 整合在一起称为一个“姿态(Pose)”,每帧动作都对应一个姿态数据,结构如下:
class Pose
{
Vector3 RootVelocity;
Vector3 LeftFootPosition;
Vector3 LeftFootVelocity;
Vector3 RightFootPosition;
Vector3 RightFootVelocity;
}
数据 4 包含未来一段时间角色的运动轨迹和运动速度,例如可以取未来 0.2s、0.5s、1.0s 时角色的位置和速度存下来,结构如下:
class TrajectoryPoint
{
Vector3 Position;
Vector3 Velocity;
}
class Trajectory
{
TrajectoryPoint[] points = new TrajectoryPoint[3];
}
上述预计算结构只是一个基本的实现,根据需要也可以增加除脚骨骼外的其他骨骼的位置速度,也可以在轨迹上增加角色朝向等需要匹配的属性。
运行时算法
输入:根据玩家输入,给出角色预期的运动轨迹和速度
当系统接收到玩家的输入后,需要根据输入模拟一个前进轨迹以及轨迹上各点的速度,例如角色转弯可以模拟一个曲线,计算出角色在未来第 0.2s、0.5s、1.0s 处的位移、速度等属性。《荣耀战魂》[1] 中实现了一个独立于动作的路径模拟器,当角色偏离模拟较远时会拉回角色,这也是为什么其中角色滑步现象非常严重的原因之一,我们在实验中为了更好的动作效果,没有使用这种校正方法。
根据玩家输入模拟出来的预期轨迹称为一个“目标(Goal)”,其结构和 Trajectory 类似:
class Goal
{
Vector3 currentVelocity;
TrajectoryPoint[] points = new TrajectoryPoint[3];
}
输出:下一帧应该播放的动作,需要满足两个条件
- 从下一帧动作开始,未来运动轨迹和输入的预期轨迹一致。
- 即将播放的姿态可以平滑衔接当前姿态,没有跳变。
由于动捕数据有限,几乎不可能从之前动捕的动作中找到完全满足上述条件的帧,因此需要用查找最近邻的方法,找到最满足条件的帧作为近似结果,然后利用动作融合过渡过去。
运行时,将角色当前的姿态以及目标轨迹,与所有预计算的数据比较,找出最近邻。为了减小计算量,可以引入KD-Tree 加速结构,在我们的实验中,C++版本的实现可以将单个角色单次匹配时间缩短至 0.2 - 0.3 ms。
/// <summary>
/// 搜索并匹配下一个最佳动作
/// </summary>
/// <param name="goal">期望的运动方向和速度</param>
/// <param name="currentAnimIndex">当前正在播放的动作编号</param>
/// <param name="currentAnimTime">当前正在播放的动作时间进度</param>
void DoMotionMatching(Goal goal, int currentAnimIndex, float currentAnimTime)
{
// 计算当前帧的姿态数据
// 动作数据是按30帧/秒存储的,在运行时会通过插值计算角色当前动作,所以对于当前帧的姿态,也可以用相邻两帧的数据插值求出。
Pose lerpedPose = EvaluateLerpedPose(currentAnimIndex, currentAnimTime)
Pose bestPose = lerpedPose;
float bestCost = 1000000;
// 在动捕数据中搜索最佳匹配帧,综合比较姿态和目标轨迹求出最近邻
// 可以使用 KD-Tree 等数据结构加速计算,此处略去
for (int i = 0; i < poses.Count; i++)
{
Pose candidatePose = poses[i];
float thisCost = ComputeCost(lerpedPose, candidatePose, goal);
if (thisCost < bestCost)
{
bestCost = thisCost;
bestPose = candidatePose;
}
}
// 如果搜索结果很靠近当前正在播放的帧,则继续播放当前动画,避免反复卡在同一帧或同一局部循环上
bool theWinnerIsAtTheSameLocation =
(currentAnimIndex == bestPose.animIndex) &&
Abs(currentAnimTime - bestPose.animTime) < 0.2f;
if (!theWinnerIsAtTheSameLocation)
{
currentAnimIndex = bestPose.animIndex;
currentAnimTime = bestPose.animTime;
// 从搜索到的动作以及时间处开始逐渐过渡动作
PlayAnimStartingAtTime(currentAnimIndex, currentAnimTime);
}
}
在求最近邻时,可以使用欧式距离,《荣耀战魂》[1] 中引入了参数Responsiveness,代表未来轨迹占的权重,权重越高,则轨迹配越精确,响应速度越高,但姿态匹配精确度会下降,可能导致动作不连贯,这个参数需要根据实际效果调整。此外,也可以针对匹配项里的不同属性分别设置权重,希望匹配度高的属性可以分配更高的权重。
float ComputeCost(Pose currentPose, Pose candidatePose, Goal goal)
{
// 计算当前姿态和候选姿态的差异,取姿态中各属性的欧式距离之和
float currentCost = currentPose.ComputePoseCost(candidatePose);
// 计算候选姿态的未来运动轨迹和预期的运动轨迹之间的差异,取轨迹点各属性的欧式距离之和
float futureCost = ComputeFutureCost(candidatePose, goal);
// 合并两个差异
return currentCost + responsiveness * futureCost;
}
结果展示
本文按照上述流程和算法对 Motion Matching 技术做了复现,我们按照 Dance Card 捕获了动作数据,未经任何修改或切分直接导入引擎,得到了以下视频所示效果:
地面上的红色标记代表玩家手柄输入对应的预期轨迹,蓝色标记代表算法匹配得到动作帧的未来轨迹,可以看到Motion Matching通过不断切换动作,会尝试让两个轨迹尽量保持重合,使得角色朝着玩家输入方向前进。
上面视频里的动捕动作是笔者做实验时自己亲自上阵录制的,虽然动作形态不好看导致表现上有折扣,但是从实际效果来看,最终生成的动作很好的还原了原始动捕动作的效果,角色在起步、转身、停步时都具有丰富的细节,这是用传统的动作状态机难以做到的。请演员来做动捕,并且后期美术对动捕数据中的瑕疵做适当修正,可以得到更加理想的效果。
总结
Motion Matching 的优势显而易见,与传统的基于状态机的方案相比,动作更加真实自然,并且从动捕到导入引擎驱动角色,只需极少的工作量,迭代周期短。
在测试实验结果时,也发现此技术的一些问题:
- 需要的动作数据量较大,导致打包体积和运行时数据量过大,难以应用到手机等设备上。
- 由于真实的动捕动作在转弯、起步、停步时本身存在一定延迟,不是瞬时响应,所以从获取玩家输入到角色转向或速度发生改变的延迟略大于传统的状态机实现,一定程度上会影响手感,对于一些强调操控性的动作游戏可能不适用。
- 有一些动捕数据不能很好覆盖到的情况,会导致角色播放一些意料之外的动作帧,Simon[1]在GDC 2016演讲中也提到了这个问题,除了增加动捕数据,没有很好的解决方法。
展望
近年来在智能生成动作方面有较多研究成果,不过目前还未被任何产品应用,可以持续关注学习:
- 基于深度学习的动作生成,PFNN、MANN等。url:https://github.com/sebastianstarke/AI4Animation
- Unity目前在研的Kinematica。url: https://blogs.unity3d.com/cn/2018/06/20/announcing-kinematica-animation-meets-machine-learning/
参考资料
[1] Clavet, Simon. Motion matching and the road to next-gen animation[J]. Proc. of GDC 2016, 2016. url:https://archive.org/details/GDC2016Clavet
[2] Holden, Daniel. "Character Control with Neural Networks and Machine Learning" url: https://www.gdcvault.com/play/1025389/Character-Control-with-Neural-Networks
[3] Zadziuk, Kristjan. "Motion Matching - Dance Card Breakdown". url:https://www.youtube.com/watch?v=_Bd2T7uP9VA
[4] Zadziuk, Kristjan (2016). GDC 2016 - Motion Matching, The Future of Games Animation... Today. Ubisoft. url: https://www.youtube.com/watch?v=KSTn3ePDt50