第12章 · 角色动画二阶:Motion Matching、IK 与 Control Rig

📑 目录

导语:「回响峡谷」竖切评审那天,QA 提了一条让我沉默半分钟的 bug:"主角原地转身 90 度,脚底在地上滑出半个身位,像穿了冰刀。"我打开第 11 章搭的动画蓝图:43 个状态、127 条过渡线,每条线的混合时间都是我一个晚上一个晚上调出来的。问题不在调得勤不勤——状态机这个范式,落笔那天就写好了这个结局。怎么拆这堵墙、拆完要付什么账,是这一章要交代的两件事。

本章地图

mindmap
  root((动画二阶))
    状态机的天花板
    Motion Matching
      Pose Search 姿态搜索
      Trajectory 轨迹预测
      代价函数四个权重
      诚实的成本账
    IK 反向动力学
      Two Bone IK 解析解
      极向量管住膝盖
      FBIK 全身解算
    Control Rig
      引擎内的程序化绑定
      转头看目标实战
    资产复用
      IK Retargeter 重定向
      MetaHuman 生态
    迁移路线
      升级信号清单
      Chooser 中间站

12.1 状态机的天花板:范式到头了

先算一笔结构账。状态机里有 n 个状态,两两之间都可能需要过渡,候选过渡线是 n(n−1) 条。「回响峡谷」的移动状态机做到第 11 章结束时是 43 个状态、实际拉出 127 条过渡线。每条过渡线带着 4 到 6 个参数:混合时长、打断规则、过渡曲线、起始相位。这 127 条线里任何一条调得不到位,玩家手里就是一次"出戏"。

症状你应该也见得多了:

  • 转身滑步。胶囊转了,动画里的脚还踩在原来的方向上,两者的速度差就是滑移量(滑移速度的定义见第 11 章)。
  • 起步对不上。从静止加到 3 m/s,动画师做的起步动画是 2.5 m/s 的节奏,头半秒脚一定在滑。
  • 改一个动画片段,要把所有以它为起点或终点的过渡回归一遍。动画师的时间一半花在做动画,一半花在修接缝。
  • 策划要加蹲移、加持枪变体,状态数直接翻倍,过渡线翻四倍。

根因一句话:状态机是用离散逼近连续。人的移动是一条连续流,状态机逼你把它切成有限个格子,格子之间的缝全靠 transition 手工糊。十个状态糊得住,五十个就糊不住了。

还要单独给 transition 的调参记一笔账。一条过渡线的混合时间没有任何公式可套:0.2 秒太糊、0.05 秒太跳,中间值全靠人肉逐条试,试完还要在走、跑、急停、被打断四种情形下各看一遍。127 条线乘以四种情形,这就是动画师晚上在干的事——创作让位给了填坑,范式的缝隙吃掉了全部精力。

Motion Matching(运动匹配,下称 MM)的思路是把问题反过来:不再画"什么情况下播什么"的图,而是告诉引擎两件事——"我手里有哪些素材"和"我这一刻想干什么",然后每帧让引擎自己去素材库里找最合适的一帧来播。用程序员的话说:状态机是一棵手写的 if-else 决策树,MM 是搜索引擎加最近邻查询。

这条路在 Epic 之前已有深厚的学术根基。动画学界在 2014 年前后就有 Motion Fields 一脉的研究;育碧在 GDC 2016 公开了 Motion Matching 的完整工业方案,并把它装进了《荣耀战魂》;顽皮狗在《最后生还者 第二部》的移动系统上走的也是同一条路。UE 这边,5.4 以 Pose Search 插件的形式把 MM 实验性放出,之后每个版本都在修补,到 5.8,官方文档、示例内容、工具链已经成套,可以当真用了。

但先把第 11 章的遗产保住:MM 与状态机是分层接管的关系——locomotion(站走跑跳这一层)交给 MM,技能、战斗、演出这些"强状态语义"的东西仍然是状态机的地盘。而且 12.6 会把话说透:多数项目走到最后会发现,真正够用的是"第 11 章方案加三个技巧",MM 反而是少数派。

flowchart LR
  subgraph SM[状态机思维]
    S1["设计师画图
什么状态接什么状态"] --> S2["引擎按条件切状态"] --> S3["格子之间的缝
手工糊混合时间"] end subgraph MMG[MM 思维] M1["动画师供素材
大量真实动作片段"] --> M2["引擎每帧搜索
哪一帧最像当前所需"] --> M3["切换交给代价函数
加一次短混合"] end SM -. 范式升级 .-> MMG

12.2 Motion Matching 的直觉:每帧一次数据库查询

补课:本节补动画资产的两种来源,程序出身必读,动画出身可跳过。

动捕(Motion Capture):演员穿惯性服或在光学棚里表演,系统记录全身骨骼每秒 30 到 120 帧的运动。优点是"量"与"真"——一天能录几千个片段,数据里带着真人运动特有的微小噪声和不对称,这正是"像人"的来源。缺点是要清理(marker 抖动、脚部穿地、手指穿模,业内叫 cleanup,通常吃掉动捕总工时的一半以上),还要按游戏的速度标准二次编辑。

手工关键帧(手 K):动画师在 DCC 里逐帧摆。优点是风格化与精确,缺点是量上不去——游戏品质的成品,熟练动画师一天出 5 到 10 秒。

为什么先补这一课:MM 要的是「同一个动作的几十种变体」——一段好动画远远不够。起步要分左右脚、八个方向、慢中快三档。这种冗余数据只有动捕给得起。记住这一点,12.6 的成本账全靠它。

三件套:Schema、Database、Query

UE 的 MM 由 Pose Search 插件承载,核心三个概念:

  • Schema(特征模式):定义"拿什么来比对"。选哪些骨骼作为特征(通常是骨盆、双脚、双膝,有时加手)、要不要骨骼速度、要不要朝向、轨迹采样窗口多长、每一项权重多少。它相当于数据库的表结构。
  • Database(数据库):一组动画片段,引擎按 Schema 把每帧的特征抽出来建索引。库里的一个"样本"约等于"某片段的某一帧加上它的特征向量"。
  • Query(查询):运行时由动画节点发起——当前角色的姿态特征,加上移动组件外推出的未来轨迹,合成一个查询向量,在库里做最近邻搜索,赢的那一帧成为新的播放目标。
flowchart TD
  A["上一帧姿态 + 各骨骼速度"] --> C["特征采样
位置 / 速度 / 朝向"] B["移动组件 + 玩家输入"] --> D["轨迹预测
未来约 0.3–1.0s 采样点"] C --> E["Pose Search 查询"] D --> E E --> F["代价函数逐样本打分"] F --> G["代价最低样本
叠加惰性偏置"] G --> H{"与当前片段同源?"} H -- "是" --> I["继续播放当前片段"] H -- "否" --> J["0.1–0.2s 短混合切入新片段"] I --> K["输出姿势"] J --> K

一个完整的转身,在 MM 里是这样发生的:

sequenceDiagram
  participant P as 玩家输入
  participant M as 移动组件
  participant T as 轨迹预测
  participant S as Pose Search 数据库
  participant A as 动画实例
  P->>M: 摇杆向左打 90°
  M->>T: 当前速度 + 目标方向
  T->>T: 外推未来轨迹采样点
  A->>S: 查询:当前姿态特征 + 轨迹特征
  S-->>A: 代价最低样本(某左转片段的第 12 帧)
  A->>A: 0.15s 混合切入
  A-->>M: 新姿势驱动骨骼网格
  Note over A,S: 转向动画不是"切状态",是"搜出来"的

UE 5.8 里 MM 自带一组配套节点:Motion Warping 把根运动"拧"到指定目标点(翻越、开门对位用),Distance Matching 负责停步时对停点,Stride Warping 缩放步幅去匹配速度。这几个各有官方文档词条,本章只点到名字,用的时候以文档为准。

模块关系一张图收拢,顺便把 12.6 要讲的 Chooser 先放进来:

classDiagram
  class AnimInstance["UAnimInstance 动画实例"] {
    +每帧更新
  }
  class PoseSearchNode["Pose Search 动画节点"] {
    +求值并输出姿势
  }
  class Database["PoseSearchDatabase 数据库"] {
    +动画片段集
    +按特征建的索引
  }
  class Schema["PoseSearchSchema 特征模式"] {
    +骨骼速度朝向通道
    +轨迹采样窗口
    +各项权重
  }
  class Chooser["ChooserTable 选择表"] {
    +上下文: 速度 模式 武器
    +输出: 用哪个数据库
  }
  AnimInstance --> PoseSearchNode : 驱动
  PoseSearchNode --> Database : 最近邻查询
  Database ..> Schema : 按模式抽取特征
  Chooser --> PoseSearchNode : 指定数据库
  AnimInstance --> Chooser : 提供上层语境

数据库怎么组织,轨迹窗口开多长

一个常见的新手错误是把所有动画塞进一个数据库。实战里按情境分库:常态移动一个库、持枪移动一个库、受创移动一个库。分库有三个好处:查询空间小,搜索更快;特征权重可以按情境分别调,持枪时把上半身通道的权重拉高、常态时拉低;出问题时的排查范围从几千帧缩到几百帧。库与库之间由上层选择器切换——就是图里那个 Chooser,它按速度、模式、武器标签挑出"现在该查哪个库",Pose Search 只负责在库内找帧。

轨迹窗口的长度同样是个旋钮。窗口覆盖未来 0.5 秒上下,大致是一步的时长:太短,引擎只看眼前,选帧频繁横跳;太长,轨迹里掺入太多还不确定的未来,角色对输入的反应变迟钝。起步和停步这类强意图动作,靠的是窗口里过去段与未来段的反差——这个反差窗口配不对,起步永远是糊的。

代价函数:四个权重就是四个手感旋钮

MM 的全部调参艺术浓缩在一个公式里。设查询为 q,库里某个样本为 s:

C(q,s)=wposjJpjp^j2+wveljJvjv^j2+wfaceΔθ2+wtrajk=1Ntkt^k2bcontC(q,s) = w_{pos}\sum_{j\in J}\lVert \mathbf{p}_j - \hat{\mathbf{p}}_j \rVert^2 + w_{vel}\sum_{j\in J}\lVert \mathbf{v}_j - \hat{\mathbf{v}}_j \rVert^2 + w_{face}\,\Delta\theta^2 + w_{traj}\sum_{k=1}^{N}\lVert \mathbf{t}_k - \hat{\mathbf{t}}_k \rVert^2 - b_{cont}

J 是特征骨骼集合(骨盆、双脚、双膝),Δθ 是朝向差角,tₖ 是轨迹的未来采样点。最后一项是惰性偏置:当候选样本恰好来自当前正在播的片段时,从代价里减去 b_cont,奖励"别乱跳"。

公式不复杂,难的是权重。逐个拧给你看,拧大拧小的行为变化都写在这,比文档直白:

  • w_pos(位置权重):管滑步。拧大,脚像钉在地上,滑步肉眼消失;拧过头,位置项压过轨迹项,引擎"只认脚不认路",相邻两帧的最优样本反复横跳,角色开始抽搐。拧小,切换丝般顺滑,但滑步原样回归。
  • w_vel(速度权重):管节奏。拧大,动画步频紧跟移动速度,急停干脆利落;拧过头,慢走时会频繁换步频片段,脚底下像踩了碎步。拧小,角色永远一副散步节奏,跑起来像遛弯。
  • w_face(朝向权重):管响应。拧大,转身指令立刻兑现;拧过头,上身先拧过去、脚步还没换,出现腰椎扭曲的"拧麻花"帧。拧小,转身慢半拍、绕大弯,竞技游戏里等于操作延迟。
  • w_traj(轨迹权重):管"听话"程度。拧大,角色严格服从输入,指哪走哪;拧过头,满足轨迹的候选被筛得只剩几帧,动作重复感、机械感全来了。拧小,动作丰富自然,但角色"有主见"——它觉得此刻换只脚更自然,就不理你的转向指令了。

调参顺序的经验只有一条:一次只拧一个。先把 w_traj 拉到能严格服从输入(哪怕难看),确认控制链路没问题;再加 w_pos 修滑步;然后 w_vel 修节奏、w_face 修响应;最后调 b_cont 防抖。反着来会陷入"拧 A 治 B、拧 B 犯 A"的循环——我见过一个团队在这个循环里泡了三周,最后把数据库整个推翻重来。

两段伪代码把查询侧钉死。第一段是代价函数的概念模型(Pose Search 内部实现做了索引与降维,但语义如此):

# 伪代码:姿态匹配代价函数(Pose Search 内部逻辑的概念模型)
FEATURE_BONES = ["pelvis", "foot_l", "foot_r", "calf_l", "calf_r"]

def sample_cost(query, cand, w, continue_bias):
    cost = 0.0
    for bone in FEATURE_BONES:                      # 位置项
        cost += w.pos * dist2(query.pos[bone], cand.pos[bone])
    for bone in FEATURE_BONES:                      # 速度项
        cost += w.vel * dist2(query.vel[bone], cand.vel[bone])
    dtheta = angle_diff(query.facing, cand.facing)  # 朝向项
    cost += w.face * dtheta * dtheta
    for i, pt in enumerate(query.trajectory):       # 轨迹项,逐采样点比对
        cost += w.traj * dist2(pt, cand.trajectory[i])
    if cand.clip == query.playing_clip:             # 惰性偏置:奖励留在当前片段
        cost -= continue_bias
    return cost

第二段是轨迹预测——MM 与"按姿势相似度乱切"的本质区别,就藏在这段未来里:

# 伪代码:从移动组件外推未来轨迹(采样窗口对应 Schema 里的轨迹通道配置)
def sample_trajectory(move_comp, input_dir, dt=0.1, horizon=0.5, past=0.3):
    traj = []
    sim_pos, sim_vel = move_comp.location, move_comp.velocity
    # 过去段:让数据库知道"我从哪来",起步和停步靠它区分
    for t in frange(-past, 0.0, dt):
        traj.append(move_comp.replay_position_at(t))
    # 未来段:按当前速度与目标方向做简化运动学外推
    for t in frange(0.0, horizon, dt):
        target_vel = input_dir * move_comp.max_speed
        sim_vel = lerp(sim_vel, target_vel, accel_blend(t))
        sim_pos = sim_pos + sim_vel * dt
        traj.append((sim_pos, sim_vel))
    return to_root_space(traj)   # 关键:换到角色本地系,否则跨实例没法比对

注意最后那行:轨迹必须转到角色本地坐标系。世界坐标下的轨迹,角色换个朝向就对不上了——这是新手接 MM 时踩的第一个坑。

第二个坑是盲调。5.8 的 Pose Search 带着可视化调试能力:当前命中了哪个样本、代价由哪几项构成、轨迹预测长什么样,都能在编辑器里画出来(具体开关以官方文档词条为准)。调权重之前先把这套绘制打开——看不见代价构成,拧权重和掷骰子没有区别。

12.3 IK:让脚踩在真实的地面上

动捕棚的地面是平的,你关卡里的地面不是。坡道、台阶、乱石滩——动画数据里的脚底高度是死的,播在斜坡上就是悬空脚和插地脚,截图级的破绽。反向动力学(Inverse Kinematics, IK)才是正解:给定末端骨骼该在哪,反推中间骨骼怎么摆。让动画师给每种坡度各做一套动画,在动捕棚的平地上写不出凹凸世界的答案。

Two Bone IK 是一道有解析解的几何题

腿部结构恰好是最简单的 IK 形态:髋 H、膝 K、踝 A 三个点,大腿长 L₁、小腿长 L₂ 由骨架定死,H 由动画姿势定死,目标踝位 T 由你通过 Line Trace 给定。求 K——这只是解一个三角形:

β=arccosL12+L22d22L1L2,d=TH\beta = \arccos\frac{L_1^2 + L_2^2 - d^2}{2\,L_1 L_2}, \qquad d = \lVert T - H \rVert

β 是膝盖处的内角,余弦定理直接给出解析解,不用迭代。所以 Two Bone IK 便宜到微秒级,「远港 Online」同屏 50 个 NPC 每人都开也毫无压力。两种退化情况要在工程上处理:d ≥ L₁+L₂ 时腿完全伸直(β = 180°),这时该下压骨盆而不是硬拉腿;d < |L₁−L₂| 时无解,目标点太近,同样靠骨盆调整兜底。

那膝盖为什么不乱弯?看下面的图:满足"到 H 距离 L₁、到 T 距离 L₂"的点,在 2D 里是两圆的两个交点(K 和它的镜像 K′),在 3D 里是整整一个圆环上的无穷多个点。求解器必须被告知"膝盖应该朝哪边"——这个信息就是极向量(Pole Vector)。膝盖的解被约束在 H、T、P 三点决定的平面内:P 放在角色前方,膝盖就朝前弯;P 要是不小心绑到了角色后方,你就得到一条反关节的鸟腿。调极向量的经验值:放在角色前方、略微外八。

Two Bone IK 的几何:已知 H、T、L₁、L₂,求膝 K圆(H, L₁)圆(T, L₂)d = ‖T−H‖K′ 镜像解:反关节"鸟腿"βL₁ 大腿L₂ 小腿极向量 P:钉住膝盖朝向H 髋(动画姿势给定)K 膝(待求)T 踝目标(Line Trace 给出)可行解=两圆交点(3D 中是一整个圆环)极向量 P 把膝盖钉在正确一侧,膝盖因此不乱弯P 放错位置(如腿后方)=反关节退化处理:d ≥ L₁+L₂(腿拉直)或 d < |L₁−L₂|(无解)时,下压骨盆兜底

图里的流程在 UE 里的落法:AnimInstance 每帧(或隔帧)从脚踝 socket 向下做 Line Trace 取地面高度,算出 IK 目标偏移,喂给 AnimGraph 里的 Two Bone IK 节点(脚链);同时把左右脚偏移中较小的那个作为骨盆下压量,防止"一条腿站上台阶、另一条腿被拉成橡皮"。坡面行走还可以叠一层 foot roll,让脚底贴住坡面法线。Two Bone IK 节点在 5.8 中的参数面板,以官方文档"Two Bone IK"词条为准。

顺带把 IK 和 MM 的分工说清:MM 管"选哪一帧",从素材层面让脚尽量踩对;IK 管"选出来的帧和真实地面还差的那几厘米",做最后的位置修正。两者互补,互不替代——只有 IK 没有 MM,坡面上姿势对了但动作逻辑还是僵的;只有 MM 没有 IK,数据库里没有的坡度照样穿地。3A 方案里这两层永远是叠着用的。

再往上是 Full Body IK(FBIK):全身多节链的迭代解算,用于手扶梯子、贴掩体、骑乘这类"手也要对位"的场景。迭代解比解析解贵一个量级,主角专用,NPC 慎用。

下面是「回响峡谷」主角的脚部 IK 更新代码,砍到骨架:

// AnimInstance 每帧更新:Line Trace 求双脚 IK 目标偏移(节点名以 5.8 官方文档为准)
void UEchoAnimInstance::UpdateFootIK(float DeltaSeconds)
{
    ACharacter* C = Cast<ACharacter>(TryGetPawnOwner());
    if (!C) return;
    USkeletalMeshComponent* Mesh = C->GetMesh();
    const FVector RootLoc = Mesh->GetSocketLocation(TEXT("root"));

    for (const FName Bone : { TEXT("foot_l"), TEXT("foot_r") })
    {
        const FVector BoneLoc = Mesh->GetSocketLocation(Bone);
        const FVector Start(BoneLoc.X, BoneLoc.Y, RootLoc.Z + 30.f);
        const FVector End = Start - FVector(0.f, 0.f, 80.f);
        FHitResult Hit;
        FCollisionQueryParams Params; Params.AddIgnoredActor(C);
        if (GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Visibility, Params))
        {
            // 偏移 = 命中点抬高量 - 动画脚底高度,负值表示要往下压
            const float Offset = Hit.ImpactPoint.Z + FootSoleOffset - BoneLoc.Z;
            float& Smoothed = FootOffsets.FindOrAdd(Bone);
            Smoothed = FMath::FInterpTo(Smoothed, FMath::Clamp(Offset, -40.f, 40.f),
                                        DeltaSeconds, 12.f);  // 插值防台阶抖动
        }
    }
    // PelvisOffset = 左右脚偏移中的较小值,在 AnimGraph 里喂给骨盆
}

要点三行:trace 起点挂在脚踝正上方而不是骨盆,避免坡面上打歪;FInterpTo 是防台阶抖动的关键,别省;PelvisOffset 永远取双脚偏移的较小值,取反了就等着看劈叉。

一个 MMO 立场的提醒:这套东西纯客户端。专用服务器上没有渲染、默认不做动画更新(卷三会展开),IK 算出的脚位永远不该参与玩法判定——它只负责修正「看起来」——和玩法判定「算不算」没有任何关系。

12.4 Control Rig:引擎内的程序化绑定

传统管线的分工是:绑定(Rig)在 Maya 这类 DCC 里做,烘焙成动画序列,引擎只负责播。想在运行时改骨骼——让 NPC 转头看玩家、让手对齐武器握把——以前只能手写 C++ AnimNode,门槛高、迭代慢。

Control Rig 把绑定搬进了引擎:一张节点图(Rig Graph),正向动力学、IK、约束、数学节点都有,编译成 RigVM 字节码,在动画线程上每帧求值。它与传统 rig 的关系是:DCC 里的 rig 是离线装备,Control Rig 是运行时装备,同一套思路,换了个家。节点与 API 的名字以 5.8 官方文档"Control Rig"词条为准,这里只讲打法。

典型用途清单:程序化转头(Look-At)、手部对齐武器与门把、扶墙扶栏杆、披风挂件的次级运动、按坡度修正站姿。挑最实用的转头展开。

场景:「远港 Online」酒馆里的 NPC 商人,玩家走近 3 米,他的头应该跟上;玩家绕到身后超过 70°,光拧头不够,脊柱得分担。60 分方案是 AnimGraph 自带的 Look At 节点(5.8 官方文档有对应词条):拖一个,填骨骼名、填目标,立刻能用。但它给不了你这些:分轴的角度钳制、脊柱分担比例、弹簧平滑、权重淡入淡出、多目标切换。要这些,上 Control Rig。核心逻辑写成伪代码(Rig 图的概念等价物):

# 伪代码:Control Rig 转头逻辑(节点图的概念等价物,先播动画再叠加修改)
def look_at_solve(head, spine, target_pos, weight, dt):
    if weight <= 0.0:
        return                                   # 权重 0:完全交给底层动画
    to_target = normalize(target_pos - head.position)
    angle = acos(clamp(dot(head.forward, to_target), -1.0, 1.0))
    if angle > radians(70.0):                    # 钳制:±70° 以外让脊柱分担一半
        spine.rotate_toward(to_target, (angle - radians(70.0)) * 0.5)
        to_target = normalize(target_pos - head.position)
    # 弹簧插值:转头带约 0.2s 延迟与轻微过冲,这是"像人"的关键
    smooth_dir = spring_lerp(state.prev_dir, to_target, dt, stiffness=8.0)
    state.prev_dir = smooth_dir
    head.aim(smooth_dir, alpha=weight)           # alpha 与动画姿势按权重混合

三个实战细节,都是踩出来的:目标点取玩家的头部骨骼而不是胶囊中心,否则 NPC 全程盯着你的胸口看;权重在玩家离开时要 0.3 秒内淡出到 0,不然 NPC 的头会恋恋不舍地跟着你划过整个酒馆;弹簧不能省——直接 aim 的转头像监控摄像头,加了 0.2 秒延迟和轻微过冲才像脖子。

Control Rig 叠加在已有动画之上,绝非取代:在 AnimGraph 里它接在状态机或 MM 之后,先播动画、再做程序化修改,思维上是 additive。性能上,一张几十节点的 Rig 图每帧求值在微秒级,但图也能写坏——循环依赖、每帧分配内存,一样能写出毫秒级的怪物。量不出来之前别猜,测量方法见第 18 章。

同样提醒一次:转头是纯客户端表现。如果玩法上需要"NPC 面向玩家"这个状态(比如警戒判定),那是服务器逻辑里的一份朝向数据,和动画蓝图里的转头是两码事,别指望从骨骼矩阵反推(服务器上根本没有动画更新)。

再给一个用例感受一下上限:双手武器握把对齐。主手通过 IK 吸附到武器握把点,副手再以主手前臂为参考做第二次 IK,换武器时只换握把点的 socket,整套持握动画不用重导。以前这套要在 DCC 里给每把武器烘焙一遍,现在运行时几个节点解决。Control Rig 的价值就在这:把"每张新牌都要重印牌盒"的事,变成"换张牌就行"。

12.5 动画重定向与 MetaHuman:一份动画,多套骨架

动画资产有个讨厌的性质:锁死在骨架上。商店里买的动画包是 UE4 Mannequin 骨架的,你的主角是 MetaHuman,NPC 是自定义比例——三套骨架,难道做三份动画?

UE5 的答案是 IK Retargeter。思路前面已经铺垫过:把"骨骼旋转"先翻译成"全身姿态约束",再从约束翻译回目标骨架的骨骼旋转。翻译的词典是两份 IK Rig 资产——为源骨架和目标骨架各定义一套全身链:骨盆、脊柱链、双腿链、双臂链、手指链。比例差异在翻译过程中处理,比如根运动位移按腿长比缩放。

flowchart TD
  subgraph Src[源侧]
    A["MetaHuman / UE4 Mannequin 骨架"] --> B["IK Rig(源)
全身链定义"] C["动画资产:动捕 / 商店包 / 手K"] --> D["IK Retargeter
姿态解码 → 重映射 → 编码"] B --> D end subgraph Dst[目标侧] E["自定义角色骨架
比例不同 / 命名不同"] --> F["IK Rig(目标)
全身链定义"] F --> D end D --> G["重定向动画序列"] G --> H{"验收三问"} H --> I["根运动缩放对了吗
不对 = 原地打滑"] H --> J["手指链鸡爪了吗
不重要就别映射"] H --> K["骨盆穿插了吗"] I --> L["入库:动画蓝图 / MM 数据库"] J --> L K --> L

三个最容易翻车的点都在图右侧。展开说第二个:手指链。源骨架和目标骨架的手指节数常常不一样,硬映射就是"鸡爪"。务实的做法是手指不重要就干脆不映射,让目标骨架保持默认手型——玩家在两米开外根本看不出来。骨骼命名不一致同理:靠链映射解决,不要手贱去改骨骼名,改名会污染整条资产管线,后面每次导动画都要还这笔债。

重定向的验收顺序也有讲究:先只接骨盆和脊柱链,看躯干姿态对不对;再接双腿和双臂,看走跑循环;最后才接手指、脚趾这些末节。每加一条链就重放一遍走跑动画,出问题能立刻定位到刚加的那条链。一次全接上再看,出了问题你分不清是链映射错了、比例没缩放、还是源动画本身就有毛病。

MetaHuman 在这张图里扮演什么角色?它是 Epic 提供的高保真骨架体系(身体加面部一百多根骨骼的量级),官方同时给出了它与 UE5 示例骨架 Manny/Quinn 之间的重定向预设(5.8 的具体入口以 MetaHuman 插件文档为准)。于是资产复用有两个方向:

  • 主角是 MetaHuman:商店里 Manny/UE4 骨架的动画包,重定向一次就能用。
  • NPC 是自定义低模:反向重定向,让整套 MetaHuman 与商店动画生态为你所用。

「远港 Online」的落法:主角 MetaHuman,村民 NPC 用低模自定义骨架,两边共享同一套重定向出来的动画库——动画资产只做一次。注意 MetaHuman 不适合铺满 NPC:骨骼多、材质重,同屏 50 个的网格体更新预算吃不消,预算怎么分见第 18 章。

最后把这条管线和 12.2 接起来:重定向产出的动画序列可以直接进 Pose Search 数据库。MM 的资产荒,靠"商店资产 + 重定向"能解掉一半;另一半——按你的移动参数定制的起步、停步组合——还是逃不掉,要么动捕,要么认命留在状态机。

还有一个隐性收益:重定向把动画资产和角色资产解耦了。外包动画时,验收标准里写清目标骨架与链定义,回来的东西直接进管线,不再需要为每个角色单独返工一轮。

12.6 诚实的成本账:什么时候不该上 Motion Matching

前面把 MM 讲得足够体面,现在把账单拍在桌上。四笔账,一笔都不能少。

资产账。 一个能看的 locomotion 数据库,覆盖"站/走/跑 × 八向 × 起步/停步/转向/急停",经验下限是 100 到 200 个片段,要到"自然"得 300 个以上;官方示例内容的 locomotion 片段也在数百个量级。手 K 的话,按一天 5 到 10 秒的产能,300 个平均 3 秒的片段就是 90 到 180 人日——一个动画师三到六个月,只做一个角色的移动。动捕路线:租棚、演员、数周清理,国内光学棚一天数千到数万元不等,清理工时常常比录制还长。育碧级的数据库以"小时"计的动捕时长打底。这不是小团队的菜单。

内存账。 估一下:60 根骨骼、30 Hz 采样,未压缩每帧 2400 字节上下,十分钟的数据库裸数据 40 MB 出头;UE 默认的 ACL 压缩能压到十分之一以下,落地就是几 MB,加上索引与速度特征,一个数据库几 MB 到十几 MB。PC 和主机无感,移动端要掂量。注意内存按数据库算:同骨架的角色共享一份库,不随角色数翻倍。CPU 才是按角色数翻倍的。

CPU 账。 每角色每帧一次最近邻查询,UE 在数据库上建了索引,量级是几十到几百微秒,随数据库规模涨。主角一个人随便用。「远港 Online」同屏 50 个杂兵全上 MM,就是毫秒级的动画线程开销,帧预算直接爆。答案还是那个:主角 MM,杂兵状态机。

调参账。 12.2 的四个权重,乘以采样窗口、惰性偏置、数据库组织方式,是一个需要技术动画师(TA)这个工种驻场的参数空间,正常要磨一两个版本迭代。团队里没有这个人、也没有招他的预算,这一条本身就是否决项。

量级谁该担心
资产locomotion 库 100–300+ 片段;手 K 需 90–180 人日所有团队
内存单库几 MB 到十几 MB(ACL 压缩后,按骨架共享)移动端
CPU每角色每帧几十到几百微秒同屏角色多的项目
调参一两个版本迭代,需 TA 驻场没有 TA 的团队
quadrantChart
  title "动画方案选型:资源 × 品质目标"
  x-axis "动画资源少" --> "动画资源多"
  y-axis "品质要求低" --> "品质要求高"
  quadrant-1 "全角色 MM(3A 配置)"
  quadrant-2 "主角 MM 混合方案"
  quadrant-3 "状态机 + 混合空间"
  quadrant-4 "状态机 + 技巧强化"
  "模板状态机": [0.2, 0.25]
  "状态机加技巧": [0.38, 0.62]
  "Chooser 表格驱动": [0.55, 0.72]
  "主角MM混合": [0.76, 0.85]
  "全角色MM": [0.92, 0.95]

落到路线图上。从第 11 章的状态机出发,升级实际是分阶段推进的:

stateDiagram-v2
  direction LR
  [*] --> 纯状态机 : 第11章交付
  纯状态机 --> 状态机加技巧 : 补起步/停步/转身状态
  状态机加技巧 --> Chooser驱动 : locomotion 改表格选择
  Chooser驱动 --> 主角混合MM : 移动MM + 技能状态机
  主角混合MM --> 全角色MM : 3A 团队配置
  状态机加技巧 --> 停在这 : 多数项目的理性终点
  Chooser驱动 --> 停在这

每一站的含义:第一站"加技巧"——起步、停步单独做状态,转身用专门的 turn-in-place 逻辑,混合空间管八向,这一套能把状态机做到 80 分;第二站 Chooser(官方 Chooser 插件,5.8 有文档词条)——不画转移图了,改用"上下文表格"选动画:行是候选动画,列是上下文条件,速度区间、朝向扇区、武器标签各是一列,运行时按当前上下文命中一行。它能当中间站有两个原因:选帧逻辑是确定性的,出了错能指着表格说出为什么选它,调试体验比 MM 的黑盒打分好一个量级;它对资产的需求又和状态机一模一样,不需要动捕库,只是换了组织方式;第三站才是 MM,而且只给主角。

什么信号说明项目该动了?以下五条,中三条再考虑:

  1. locomotion 相关状态超过 15 个还在长;
  2. 动画师每周花在 transition 接缝上的时间超过做新动画的时间;
  3. 转向、起步滑步被 QA 反复提单(三次以上);
  4. 手里已有 200 个以上可用片段,或者动捕管线已经跑通;
  5. 团队里有(或招得到)技术动画师。

五条里只中一两条的,回第一站加技巧去。MM 的定位很明确:从 85 分冲向 95 分,它是推进器;从 60 分救到 80 分,它是幻觉——第 11 章的东西没做到 80 分之前,上 MM 只会让你同时维护两套都不及格的系统。

「远港 Online」的答案提前写在这里:主角走"混合 MM"这一站——移动交给 MM,技能与战斗保留状态机,Chooser 在两者之上按情境切库;同屏杂兵全部留在第一站,纯状态机加程序化 IK 与转头。一套游戏里两种技术等级并存,本质是预算分配:把每一帧的动画线程时间,花在玩家盯着看的那个角色身上。

小结

MM 的本质是搜索引擎:每帧一次最近邻查询,换回的是不用手写任何 transition 的自由。代价函数的四个权重就是四个手感旋钮:w_pos 管滑步、w_vel 管节奏、w_face 管响应、w_traj 管听话程度;调参一次只拧一个。MM 的成本在资产与人力,不在内存:没有 200 个以上的片段和专职 TA,状态机加技巧是更诚实的选择。IK 是几何题:Two Bone IK 有解析解所以便宜,极向量决定膝盖往哪边弯。Control Rig 把绑定搬进引擎:转头、扶墙、握持对齐,不用再回 DCC 重导一遍。重定向是资产复利:IK Retargeter 让 MetaHuman 生态和商店动画包为你的自定义角色所用。

上手任务

  1. 脚部 IK 落地:按 12.3 给「回响峡谷」主角加 Two Bone IK 与骨盆补偿。验收:斜坡行走时脚底与地面间隙小于 2 cm,上台阶无肉眼可见的穿插,下坡无悬空帧;滑步相比之前不劣化。
  2. 最小 MM 数据库:用官方动画示例内容(或自凑 50 个以上片段)跑通一个 locomotion 的 MM 原型。验收:八向移动、起步、停步、90° 转向全程无手写 transition;与状态机版本同场景对比,转向滑移肉眼不可见;把数据库片段数与内存占用写进 README。
  3. 程序化转头:给「远港 Online」酒馆 NPC 做 Control Rig 转头。验收:玩家进入 3 米范围头部开始跟随,±70° 内纯头部负责、超出部分脊柱分担;转头带约 0.2 秒自然延迟;玩家离开后权重 0.3 秒内归零。
  4. 一次重定向:把一段 UE4 Mannequin 或商店动画重定向到 Manny。验收:播放无鸡爪、无原地踏步(根运动缩放正确)、骨盆无穿插。

下一章

角色能走、能站、能看人了,但打起来还差一层"看得懂"的东西——特效。第 13 章讲 Niagara:帅?不,特效的第一原则是可读性。

延伸阅读

  • 官方文档:Unreal Engine → Animating Characters → Motion Matching / Pose Search 词条(含 Schema 与 Database 配置)。
  • 官方文档:Unreal Engine → Animating Characters → Control Rig;以及 Animation Retargeting(IK Rig / IK Retargeter)词条。
  • GDC 2016:Simon Clavet(Ubisoft Montreal),"Motion Matching and The Road to Next-Gen Animation"——MM 工业化的开山分享。
  • Epic 官方在 Unreal Fest 上关于 UE 运动匹配与动画示例工程的专题分享(逐年更新,找最近一届)。
  • 源码:Engine/Source/Runtime/AnimGraphRuntime(Two Bone IK 等骨骼控制节点);Engine/Plugins/Animation/PoseSearch(5.8 以实际安装目录为准)。