机器人程序化动画(二):动画资源解析与 UE 接入

巨型机器人低头时,机体和腿维持支撑姿态,头部俯仰由动作曲线驱动。核心展开则直接采样导入的 UE 动画序列(AnimSequence),以采样结果覆盖头部子树的局部姿态。两条路径共用一套 UE 骨架,输入数据和姿态更新的范围各有区别。

第一篇讲支撑锚点、足端状态与腿部 IK。本篇沿低头、甩身和核心展开三个案例,说明如何把曲线值或骨骼轨道变成 UE 中的逐帧姿态。

文章目录
动作曲线与导入片段进入 UE 骨架的两条路径
左侧是曲线重建与片段采样,中间是参考骨架。右侧三个采样取自 UE 读回,显示 Core_01 相对 Head_01 的局部位移。紫线从 Head_01 的原点连到 Core_01 的骨点,线段的倾角由这段局部位移在 XZ 平面上的投影决定。

低头录像展示头部俯仰与机体的支撑姿态。后文的逐帧对照列出同一采样的曲线查询值、混合权重和头部输出角。

低头独立录像

网格、参考骨架与动画数据的职责

排查一个部件为何移动时,先确定它受哪些骨骼影响,再沿父链追查这些骨骼的姿态来源。下表把蒙皮、参考骨架、碰撞和动画输入分开。

资源 能提供什么 怎样核对接入
Skeletal Mesh 与 LOD 可见几何、蒙皮权重和各级渲染表示 同一参考姿态下检查骨骼是否带动预期的几何,另查 LOD 的骨骼裁减
Reference Skeleton 骨骼名字、父索引和参考局部变换 用实际父链检查身体、腿、头部和核心子树
PhysicsAsset 刚体、碰撞形状及约束的描述 检查实际生成的代理与碰撞配置,和可见骨架分开
动作数据仓库 动作引用、曲线和时间参数 用引用定位到具体数据,按采样时刻读出通道
动画序列(AnimSequence) 沿时间采样的骨骼变换 解码、确认轨道映射,再对照 UE 的骨骼读回

有些名字同时出现在网格、骨架和动画索引中,但索引空间可能不同。一个解码器输出“轨道 12”,不意味着可直接写入 UE 的骨骼 12。对同样一条轨道,写错父节点和写错目标骨骼都可能让头部偏移,错误还会沿父链传播到所有子骨。工程上要保留轨道索引、源骨骼身份和目标索引三者的映射。

碰撞体也要单独核对。动画腿可以有多个关节、装饰骨和实际蒙皮的骨骼,而上一篇的平地控制用机体和足部代理承载力控。它们是当前实验选出来的物理表示。即使碰撞体的名字与某骨一致,也要验证仿真是否真的创建并使用该对象。

本文把 Skeleton 自带的静态骨骼变换称为参考姿态,把当前帧由机体和腿部提供、供动作层继续覆盖的姿态称为支撑基姿态。在核心展开演示中,支撑基姿态取自站定结果,片段只更新头部子树。叠加动画另有各自的增量参考,由资产设置决定。

蒙皮、骨骼姿态与物理接触

以核心展开的一帧为例,先核对轨道采样是否写入核心骨,再检查网格的蒙皮权重是否把这根骨的运动传到对应顶点。若轨道写到了错误的骨骼或父链,换网格也无法修复这一步。

动画和接触也在不同的数据链上。第一篇的足部代理记录的是刚体的世界位置、旋转和地面接触。渲染骨架里的足骨提供蒙皮所需的姿态,原点也不一定落在脚掌接触面上。将包含足骨世界变换的姿态写入骨骼网格组件,画面可以立刻变化。物理代理的位置则要经过控制和步进才变化。在低头和甩身的接地重建中,代码把足底锚点作为运动学求解的目标。这个接地约束可以用来解释腿部姿态,但不能代替第一篇的刚体接触与载荷记录。

以同一只前足为例,至少有参考姿态的骨原点、动画解算后的骨原点、脚掌接触点三个位置。设当前足骨在世界中的变换为 T,骨骼到脚掌的局部偏移为 d,那么脚掌世界位置是 T 作用于 d 的结果。直接检查足骨原点相对地面的高度,误差里会包含这段偏移。若腿转动了,同一个 d 也需要随足骨旋转。本文后面的脚掌误差以世界空间中的足底和支撑锚点计算,头部旋转差则在对应骨骼和空间内计算。

动画轨道与目标骨骼的索引映射

动画绑定涉及三套索引:源片段的轨道索引、轨道所指的源骨骼身份,以及目标组件的骨骼索引。源文件顺序、目标参考骨架顺序和渲染使用的骨骼集合各自独立。调试记录至少保留轨道索引、源骨名、目标骨名和目标父骨。逐帧对照时再记下当前网格和骨架标识。

可用一个教学例子说明错配如何传播。假设源轨道指向核心滑块,父骨是头部。若将同一平移直接写入目标头部,头部的所有子骨都会继承这次移动。若目标骨本身对了,却把局部平移当成组件空间坐标,机体转向以后,核心展开方向就会固定在错误的参考轴上。两种故障在静止正视镜头里都有可能像“某个零件向上移动了”。换成机体转过角度的测试姿态,逐项对照骨名、父骨和坐标空间,才容易区分两种原因。

共享源码中的重建脚本从参考骨架读取父索引,再将父索引转成骨名关系。它按动作设置中的 HeadBoneName 找到头部根,从已有姿态集合沿父链向上查询,收集能回溯到该根的骨骼。它还要求头部根确实在集合中,并检查集合没有被识别为腿和足的骨骼。这一检查保护的是本地覆盖边界。如果源数据换了骨名或层级,边界应重新根据骨架表验证。完整骨骼总数、逐级 LOD 的骨骼名单和参考变换仍需来自资产导出。

三种动作的姿态来源

演示 动作输入 本地姿态更新 可核对的运行结果
低头 动作仓库曲线、保留的源姿态样本 按动作曲线重建头部局部旋转,与支撑基姿态合成 逐帧头部角度与画面
甩身 动作仓库曲线、保留的源姿态样本 更新机体与头部姿态,按世界空间足底锚点重新求解腿部 IK 逐帧头部角度、画面与单独记录的足底锚点残差
核心展开 导入的 UE 动画序列 按序列时间采样头部子树,保留支撑基姿态 序列采样时间与核心骨骼变换

曲线驱动路径和动画轨道路径的区别在于输出的层级。曲线先给出动作参数,姿态代码再将参数换成旋转、平移或足端目标。片段轨道则已经表达某条骨骼的变换,接入层需要解决的是轨道归属、采样和父链组合。两条路径最后都要通过当前骨架才能形成渲染姿态。

动作引用、曲线采样与运行时参数

低头和甩身沿同一条曲线重建路径。接入顺序是先定位动作和通道,再把运行时钟换成曲线查询时间,最后用淡入淡出权重将采样值接入机体和头部姿态。

动作记录与曲线通道映射

重建脚本按动作标识从 GeneratedData 选出一条记录,再从 Settings 读取头部骨名、参数化模式和头部启用标志。这个入口要求参数化模式为 1,且头部动画已启用。曲线查询通过通道映射定位字段,从 EditorCurveData.Keys 读取关键帧,再调用线性采样函数求值。

采样包装器记录通道、查询时间和调用方传入的缺省值。以头部输出为零为例,曲线本身为零与查询缺失后取零,在最终姿态里可能没有区别。排查时要回到查询记录,对照实际读取的通道和关键帧。现有源码确认了缺省值的传递与查询记录,空通道的具体处理仍需核对采样函数。

这条路径分别求机体平移、机体俯仰偏航横滚和头部俯仰偏航横滚。源曲线返回的浮点数随后才有动作语义。头部旋转会转换为四元数,机体平移则要经过参考高度和场景基准的换算。动作记录、通道、求值脚本和坐标转换是四个可各自失败的环节。提取表里找到一个可读动作名,仅确认第一环节具备入口。确认了动画曲线能采样以后,还要将这组数值和本地程序读取的字段逐项对照。

录制时钟、有效动作时间与归一化进度

录制时钟上的曲线进度与动作权重
曲线进度决定在哪里采样,动作权重决定采样结果参与多少。图中用示意曲线解释两者的关系。

低头与甩身的重建代码按 30 Hz 生成样本。它用源记录的 OriginalAnimationLength 作为动作长度,前后分别增加一秒用于观察起止姿态。设 t 为录像生成时钟,L 为源动作长度,采样时间关系如下。此处的 t 来自生成脚本的帧号,不能从播放器显示的四舍五入时长反推动作参数。

t        = frame / 30
active   = clamp(t − 1 s, 0, L)
progress = active / L

因此前一秒已经在画面里,动作仍采样进度零。动作结束以后,进度停在末端。是否仍显示源动作末端,还要看动作权重。重建代码将动作区间以外的权重置零,保留的曲线末值不会自动作为后续姿态持续写入。这个细节有助于理解录像末尾为什么能回到支撑基姿态。它也说明用于查值的钳位与用于接管骨骼的权重是两个输入。

假设一个教学动作长 2 秒,录像 t=1.5 秒时动作已推进 0.5 秒,归一化进度为 0.25。若某轨道以秒为横轴,应查询 0.5,而归一化曲线查询 0.25。给脚本错误的时间域会让整个动作看起来快了一倍,即使每个关键帧值都正确。相反,若曲线时钟正确但淡入过慢,原始采样点会对齐,输出幅度却偏小。将进度和权重并列记入日志,可以直接区分这两种故障。

下砸攻击使用第一篇已核对的另一条采样规则。主动作和恢复各为 2 秒,归一化分母仍是主动作时间,故恢复区间的进度从 1 继续走到 2。低头与甩身的本地重建使用其源动作长度划定采样区间。即便两个入口都在日志里把某个变量叫 progress,约定仍需从对应入口确定。对工具而言,使用统一的浮点类型是合理的,复用时间换算前则要先核对资产的有效区间。

关键帧插值与姿态权重

设相邻关键帧为 (t0, v0) 和 (t1, v1)。在采用线性插值的区间,先计算区间内进度 u,再按该比例求值。

u = (t − t0) / (t1 − t0)
value = v0 + u × (v1 − v0)

这里的 t 必须先与关键帧的时间轴统一。存归一化相位的仓库要先从动作时钟算出相位,以秒为单位的轨道则直接按时间采样。常量外推会在首尾之外保持端值,但这和动作层的淡入淡出是两件事。前者说明如何从曲线取值,后者决定取出的值以多少权重混进当前姿态。其他曲线格式的插值与外推规则需要按各自的关键帧字段核对。

相同的曲线值也可以参与不同的姿态过渡。以一个标量通道为例,采样得到的动作值为 vAction,支撑基姿态的通道值为 vBase,独立的淡入权重为 w。线性混合的示意结果是 vBase + w × (vAction − vBase)。即使曲线此时已经停在最后一个值,w 仍可继续变化。这个示例只说明采样时间和姿态权重的区别。骨骼旋转应使用适合其旋转表示的混合方式。

淡入淡出权重与机体高度

重建器先将动作权重设为 1,分别计算从动作开始经过的时间和离动作结束的剩余时间。某一端落入配置的 BlendTime 时,代码把该时间除以对应 BlendTime,再调用配置中的 BlendMode 或 CustomCurve 计算权重因子。如果淡入和淡出窗口重叠,两个因子都会乘入总权重。动作区间外再将总权重设为零。这样的顺序允许短动作同时受两端权重影响,直接挑选较小权重会得到另一条包络。

举一个教学算例。假设同一帧淡入权重为 0.8,淡出权重为 0.5,则这个组合得到 0.4。若当前基准机体高度为 900 cm,地面参考与源目标合成后的高度为 1,050 cm,本地机体 Z 的混合结果为 960 cm。例子里的高度只是便于复算的教学数值。实际源码在 Z 方向将源曲线值乘以 RefPoseBodyHeight,加到支撑参考高度后再与支撑基姿态的机体高度混合。支撑参考高度在生成器中由初始六个支撑锚点的 Z 求平均。它对应此处固定足底的离线重建场景。

水平平移使用当前采样减去初始采样,再乘以 RefPoseBodyHeight 和权重,加到初始机体位置。Z 分量随后被绝对高度混合覆盖。由此,机体开始时即使源曲线在三轴都有非零值,水平轴仍以原机体为起点,Z 则按高度契约计算。若统一做差分,原本用于指定高度的 Z 会变成从初始高度开始的相对变化。若统一使用绝对值,XY 则可能在动作起点产生跳变。检查这类问题时,先把三轴的绝对量、相对量和混合权重分别记录,再定位姿态差异。

重建器还将机体欧拉角分量乘总权重,然后转换为四元数。头部是另一条混合路径,先将目标角转换为四元数,再与参考头部局部四元数进行混合。两条分支即使使用同一个动作权重,也不能交换其旋转处理顺序。机体旋转参与整条骨架的父链与脚掌约束,头部旋转只在指定子树根上覆盖。脚本的这一实现可以解释局部适配结果,原游戏头部模块、弹簧和触发流程仍缺运行证据。

骨骼轨道映射与 UE 姿态更新

头部局部轨道、父链与世界变换
骨架变换示意。三个面板分别表示相对父骨的局部轨道、合成后的组件空间姿态,以及组件放入场景后的世界姿态。

用列向量约定表示,骨骼 i 的局部变换是相对父骨的 L_i,组件空间变换为 C_i,骨骼组件的世界变换为 W_mesh:

C_i = C_parent(i) · L_i
W_i = W_mesh · C_i

这个式子表达的是空间关系,不能直接照搬成 UE 的运算符顺序。若头部轨道给的是局部变换,计算头部子树世界姿态时就必须沿用当前身体的父变换。若本地测量在世界空间给出了机体或足端,它们要先进入同一套骨架关系。否则头部在支撑基姿态固定时看起来正确,一移动身体就露出子树脱节。

从已有姿态提取子树局部变换

生成器先求机体和腿的姿态,再为待覆盖的头部子树保存局部变换。保存局部值的目的是让子骨在更新头部根之后仍保留原本的局部关系。若只缓存世界矩阵并在最后写回,头部根虽然转动,其子骨却仍落在旧的世界位置。下文采用和前面公式相同的父子关系。具体组件 API 的变换表示应以调用点为准。

对脚本里的分离平移、四元数与缩放表示,子骨位置相对父骨的位置差先乘父旋转的逆,再除以父缩放。子骨局部旋转由父旋转的逆与子骨旋转相乘,局部缩放是逐轴比值。重新构建时,局部位置先乘父缩放,再经父旋转转到同一空间,最后加父位置。这样父骨换了方向,局部偏移能沿新轴旋转。上述计算描述脚本使用的 TRS 组合,调用方需要确保缩放有效,并避免把这个模型直接当作任意剪切矩阵的分解算法。

p_local = inverseRotate(q_parent, p_child − p_parent) / s_parent
q_local = inverse(q_parent) × q_child
p_child = p_parent + rotate(q_parent, s_parent × p_local)
q_child = q_parent × q_local

用二维算例看这一点。若父骨位置是 (100, 0),子骨在父局部的偏移是 (20, 0),父骨旋转 90° 后,子骨的位置应为 (100, 20)。如果跳过父旋转,仍会得到 (120, 0)。即使局部动画轨道中的 20 完全正确,使用了错误的坐标空间还是会出现位置误差。UE 中调试头部或核心偏移时,可先在父骨旋转明显的帧验证这一步,再检查骨骼写入是否成功。

头部四元数混合与父链重建

四元数的半球修正与单位化
先按点积判断是否翻转目标四元数的符号,再按权重混合,最后把结果归一化。图用单位四元数的二维截面说明三个步骤。

对于头部根,重建器从导出的参考数据取得头部局部四元数,将曲线求得的目标角转换为目标四元数。若两者点积小于零,则翻转目标四元数符号,使两端在同一半球。然后对四个分量按权重线性混合,最后将结果归一化。这一做法是带半球修正的归一化线性插值。它描述当前头部适配代码。原始资产内部对关键帧旋转的插值仍要看具体轨道及解码器。

if dot(qRef, qTarget) < 0:
    qTarget = −qTarget

qHead = normalize((1 − w) × qRef + w × qTarget)

同一旋转可由 q 和 −q 表示。若参考四元数和目标四元数正好互为相反数,直接取两者的中点会得到零向量,无法归一化。即使并非严格相反,忽略符号也可能让动画沿错误方向穿过较大的旋转范围。半球修正解决的是两端表示的选择,归一化保证输出仍是合法旋转。单独记录四元数分量变化容易误判符号翻转,验证时应优先用最短旋转角差。

头部根旋转后,子骨随新父姿态重建
示意图固定机体父骨,叠加头部根的局部旋转。核心子骨保留局部偏移,世界位置随头部改变。

替换头部根的局部旋转后,脚本从待处理集合找出父骨已完成的骨骼,按父先于子的关系重建。每根骨骼都从新的父姿态和保存的局部值计算结果。若待处理集合非空却找不到可处理节点,代码报出层级错误。这个失败条件能揭示父链断裂或层级关系无法解析的情况。它比在组件里看到某个部件静止以后再猜测权重为零更容易定位问题。

按骨骼父链确定子树覆盖范围

实际机体、头部根和核心骨的子树覆盖
以本次核心展开的实际父链说明覆盖范围。Body_01 提供当前机体变换,片段更新 Head_01 及其子骨,腿链沿用支撑基姿态。

本地重建用当前机体和腿建立支撑基姿态,再修改头部根。头部子树的覆盖范围由父链决定。只比较骨名的共同前缀,无法可靠表示父子关系。一个核心部件可能用完全不同的命名规则,只要在头部根以下,就需要随着新的父变换重算。反过来,名字接近的装饰骨若在另一条分支,覆盖它会改变本应保留的姿态。当前脚本先用真实父索引建立字典,再向上追溯,正是为了把覆盖范围绑定到骨架关系。

可以用三根骨做受控验证。设头部根有一条局部旋转轨道,核心滑块在其下,机体装饰骨与头部根同级。在本地覆盖头部后,核心滑块应继承头部的新旋转,同时保持自己的局部偏移。机体装饰骨继续沿用支撑基姿态。再给核心滑块一条局部平移轨道,这段偏移应沿头部的新坐标轴施加。一个最小测试同时覆盖了三种情况:子骨继承、同级排除和子骨自身动画。

当轨道子树需要跟随会运动的机体时,保留支撑基姿态也包含时间条件。当前帧头部的局部变换应组合到当前帧的机体父链。若异步输入来自上一帧,角色静止时不一定能看出错位,旋转或加速时才会出现拖影或偏移。调试记录应同时带上动作采样时刻、支撑基姿态的样本标识和姿态写入帧。录制中的保持段适合看端点姿态,运动中的帧更适合确认时钟和父链确实配对。

曲线驱动的单帧姿态求解顺序

重建器先根据本帧的动作进度采样曲线,计算淡入淡出权重。随后更新机体变换,以世界空间支撑锚点求解腿部 IK。头部局部旋转更新后,程序沿父链重建头部子树,再检查腿骨的帧间旋转差和足底位置残差。

progress, weight = sample_time_and_weight(frame, settings)
bodyCurve, bodyRotation, headRotation = evaluate_action(progress)
body = compose_body_target(bodyCurve, bodyRotation, weight, reference)

pose = solve_legs(body, fixedWorldAnchors)
pose = check_clearance_and_refine_continuity(pose, previousPose)
validate_joint_limits_and_self_collision(pose)

locals = extract_head_subtree_locals(pose, referenceParents)
locals[head].rotation = blend_head_rotation(referenceHead, headRotation, weight)
rebuild_head_subtree(pose, locals, referenceParents)

validate_frame_rotation_and_sole_positions(pose, previousPose, fixedWorldAnchors)
record_frame(pose, progress, weight)

头部覆盖前,重建器先检查腿部关节限制与同腿自碰撞。重建子树后,再检查腿骨的帧间角差和足底回算误差。若画面异常,可先用生成器输出复算,确认姿态是否已在生成阶段越界,再对照 UE 组件读回检查写入空间和目标骨骼。

生成器逐帧保存骨骼姿态、机体目标、动作进度、混合权重、缩放后的源高度、曲线查询记录、头部局部四元数,以及间隙处理和连续性搜索结果。遇到失败时,它记录首个失败帧的时间、机体朝向、踝偏置和可用的关节角差。排查时可以先对照失败帧与前一帧。后面的低头案例沿用这个方法,逐项比较相邻两帧的曲线输入、权重和输出。

头部子树覆盖前后的同帧对照

核心展开在同一输入帧的子树覆盖前后
左侧为支撑基姿态,右侧为片段采样约 0.833 秒后的姿态。两侧按同一输入帧、同一相机分别写入 UE 并截图。
头部子树覆盖前后对照

运行时覆盖以头部子树为范围。补录分别将支撑基姿态和应用动画序列后的姿态写入 UE,再并排呈现三个采样点。补录记录表明,87 根参考骨中有 20 根位于覆盖子树内,其余 67 根沿用当前输入姿态。这个数量对应本次骨架和覆盖边界。

低头动作的曲线输入与头部求解

巨型机器人低头的独立实录帧

低头演示保留了源姿态样本,并用恢复出的脚本重新计算头部旋转。运行时按动作时钟查询仓库曲线,将头部结果与机体和足端提供的支撑基姿态合成,再提交给 UE 骨骼组件。录像用来观察俯仰过程,逐帧记录用来核对实际查询值和输出角度。

片长约 5.03 秒、30 fps。这个长度描述成片,不能用来反推原始仓库里某个阶段或曲线的长度。原生游戏触发、原生弹簧,以及模块化头部 Actor 的映射仍没有被这个实验恢复。

低头演示的逐帧头部俯仰曲线

本轮录制的俯仰在约 1.0 秒后开始变化,在 2.0 秒达到 −30°。约 2.33 秒的采样仍保持 −30°,随后回到接近初值。图中横轴使用视频时钟。这条输出曲线可以用来与可见头部动作对齐。源关键帧、播放相位和头部脚本中的各项权重需要单独导出,才能进一步解释每一段弧形来自哪一级。

低头动作中同一采样的 HeadPitch、头部权重与姿态
第 60 帧的 UE 画面与同帧数据叠加。进度 0.33333 为归一化查询时间,查询值和头部俯仰均以度记录。
低头曲线求值与姿态重建

低头相邻帧的曲线值、权重与输出角

低头采样中,视频 2.0 秒到约 2.33 秒的头部俯仰都在 −30° 附近,之后逐渐回到基线。在进入这一平台之前,曲线的斜率也有变化。记录中的第 50 帧约在 1.667 秒,俯仰约 −16.55°。第 51 帧约在 1.700 秒,俯仰约 −25.35°。两帧相差约 8.80°。这是录制输出的离散差,来自头部这一通道。它既不能当作源关键帧的值,也不能拿去和生成器的腿链 5° 连续性阈值直接比较。

补录中的相邻样本将曲线值、头部权重和最终俯仰放在同一行。

帧 归一化进度 HeadPitch 查询值 头部混合权重返回值 输出俯仰
50 0.222222 -24.02277° 0.695089 -16.55299°
51 0.233333 -25.34952° 1.000000 -25.34952°

两帧之间,HeadPitch 查询值变化约 1.33°,头部混合权重由约 0.695 变为 1。导出的头部局部四元数最短旋转差约 8.797°。记录也保留了头部淡入标志从开启到关闭的变化。机体分支的混合权重另有字段记录。这里可以确认曲线值与头部权重在同一相邻采样发生变化,现有记录尚未通过单变量复算分解两者各自的贡献。

甩身动作的机体变换与足底约束

巨型机器人甩身的独立实录帧

甩身独立录像
甩身演示的基准姿态与动作中姿态
同轮 UE 原片的两帧。观察机体朝向与膝关节构型的变化,足底误差需结合逐帧位置核对。

甩身同样使用原仓库曲线,并重新计算头部旋转。机体偏转会通过父链带动腿骨。如果只更新机体根,脚掌会随之偏离支撑锚点。本地重建在新机体姿态下重新求解腿部 IK,让世界空间足底位置继续匹配锚点。录像里看到的机体运动来自曲线,腿部关节的调整来自固定足底求解。

约 5.73 秒的独立实录用来观察机体偏转和恢复。下方过程片叠加同帧骨骼读回,脚掌与世界锚点的残差按两条有记录的腿分别统计。

甩身演示的逐帧头部俯仰、偏航和横滚

同一轮的头部输出同时包含三个角度分量。例如俯仰在约 2.07 秒达到 10.35°,而偏航在约 2.83 秒达到 −9.36°。分量的极值出现在不同采样时刻。它们用来描述这轮头部姿态的变化,不能相加当作旋转总角度。核对甩身期间脚是否发生滑移,应另取同帧的足底位置及支撑状态,再与世界空间支撑锚点比较。

甩身中机体轴、骨骼链和世界空间足底锚点
按同帧骨骼读回和相机参数后期投影。红圈为世界空间锚点,青色十字为 UE 骨骼读回后用 FK 回算的足底,黄线为腿链。
甩身过程中的世界锚点与足底回算

世界空间足底约束下的腿部 IK

相同世界锚点约束两组机体姿态
甩身重建的侧视示意。机体变换后重新求解髋、膝与足底之间的骨链,世界锚点保持不动。图中两条腿代表支撑约束。

重建脚本在动作开始前用初始机体位置与名义足端位置构造世界锚点。每次采样更新机体位置和朝向,并将腿的目标恢复为同一组锚点。由此,脚掌保持在哪里由世界锚点决定,膝和踝如何跟随则交给 IK。脚本随后做间隙和连续性检查,并对关节限制与同腿自碰撞进行断言。第一篇讲过相似的几何约束,区别在于这里主要验证给定机体动作下的姿态能否成立,第一篇的力控则持续读取物理步进后的足部位置。

相邻样本的旋转差包含机体旋转。脚本将上一帧机体四元数与上一帧骨骼姿态组合,将当前机体四元数与当前骨骼姿态组合,再比较两次世界朝向。检查覆盖六条腿的上腿、下腿、踝和足。腿在组件空间几乎不变时,机体转动仍可能带来明显的世界姿态变化。相反,两段旋转也可能在世界空间部分抵消。把两个空间分开记录,可以解释画面上某条腿看似连续但组件数据变化较大的原因。

固定足底的姿态生成器使用 5° 作为相邻样本的旋转差上限,同时用正向运动学(FK)回算脚掌的世界位置,要求它与世界锚点的距离不超过 0.05 cm。前者约束帧间变化,后者约束每帧足底位置,两项独立成立。足底位置能保持一致的多个 IK 解仍可能存在,因而很小的足端位置误差无法替代关节连续性检查。若连续性或足底断言失败,该次重建记录首个失败帧、机体姿态和关节角差,再停止生成。这比只导出成功帧更容易判断究竟是动作幅度还是可达域造成失败。这一阈值用于检查固定锚点下生成的姿态。第一篇下砸试验中的预测试算与步进后的 IK 各有自己的判定条件,录像使用的最终姿态仍以各自的运行记录为准。

甩身的足底锚点残差

甩身期间同时有机体和头部变化。固定足底的本地重建每帧重新求腿部角度,首先要确保世界空间锚点相同。若只对比脚相对机体的位置,机体一转,脚在机体局部空间发生的变化是预期结果。合理的误差是用当前机体和骨骼回算脚掌的世界位置,再减去该脚对应的世界锚点。每条腿保留自己的残差方向,可以判断是某一只脚偏离,还是所有脚受同一坐标轴错误影响。

在录制记录中,头部偏航和俯仰的极值相隔约 0.77 秒。补录另为 L_01、L_02 各保留 172 个样本,对齐世界锚点和从写入 UE 后读回的骨骼变换回算的世界足底位置。两条腿的最大位置差分别约为 5.13 × 10⁻⁶ cm 和 4.23 × 10⁻⁶ cm。这些数值衡量指定两腿在本次固定锚点 IK 重建中回算点与锚点的差。物理接触和载荷仍需另外读取刚体数据。

核心展开的片段采样与子树覆盖

巨型机器人核心展开的独立实录帧

核心展开实录
核心展开前、中、后的同机位对照
上排为同轮 UE 原片的起始、中段和末端,下排按同一范围放大核心区域。核心部件逐步抬升,外部机体沿用支撑基姿态。

核心展开使用导入的 A_Peppermint_01_OpenCore_02,该动画序列长约 1.667 秒。实验沿原片段时间轴采样到头部子树,机体和六腿沿用支撑基姿态。开头和末尾各有约一秒保持,用于对照动作两端。最终视频 111 帧、30 fps,约 3.70 秒。编码总长包含端点保持和离散采样,不能当作原动作时长。

核心展开的片段时钟与两组姿态输入
按核心展开同轮记录对齐。每列上方为录像时刻,下方为对应的片段采样时间。点击可查看原图。

和前两个动作相比,这个实验的重点在片段接入:导入资产的轨道能否随时间驱动正确子树,同时保持外部机体。在这段录制中,导入片段直接驱动头部子树。Focus 攻击与核心展开之间的原版绑定仍待用资源引用和触发记录确认。

核心展开演示中的核心骨骼 Z 平移分量

录制记录同时保存了帧号、片段采样时间和核心骨骼的平移字段。开头的保持段使用片段时间 0,约 1.83 秒的录制帧采样到片段约 0.833 秒。末段则持续采样片段末端。核心骨骼在记录中的 Z 分量从约 100.03 cm 变为约 237.52 cm,相差约 137.49 cm。这两个读数可以和片段端点对应,尚不能据此推断整个机器人的世界位移。记录另报出最大平移变化量约 137.53 cm。该统计和这里的 Z 分量端点差使用不同口径。

本次录制中,常规头部输出的俯仰、偏航和横滚接近支撑基姿态,核心骨仍有明显的局部平移。检查子树覆盖时,应直接查看片段驱动的目标骨。仅记录头部根的角度,会漏掉 Core_01 这类独立运动的子骨。

UE 动画编辑器中的 Core_01 骨骼、片段时间轴和局部位置
实际 UE 动画资产窗口。片段采样约 0.833 秒,骨架树选中 Core_01,右侧当前骨骼位置以父骨 Head_01 为坐标系。
核心展开从 UE 编辑器采样到运行时

过程片先呈现 CLI 逐点取样的实际编辑器窗口,再切到运行时原速动作及三点暂停对照。逐帧取样、暂停和剪接均按过程片标注。编辑器播放完整资产姿态,运行时则把片段姿态应用于头部子树。两条路径在同一片段时间比较核心骨的父骨局部位置。

片段采样时间(s) 编辑器局部 Z(cm) 运行时局部 Z(cm) 两侧 XYZ 位置差(cm)
0.000 100.030243 100.033748 0.003505
0.833 201.230011 201.225571 0.004440
1.667 237.521347 237.520615 0.000732

骨架中的父链为 Body_01 → Head_01 → Core_01。表里的 Z 是 Core_01 相对 Head_01 的局部平移,两侧 XYZ 位置差是这个局部空间里的距离。三个采样点的差值均小于 0.005 cm,最大约 0.00444 cm,差异来源尚待核对。编辑器和运行时的子树外姿态分别来自各自的基姿态。

导入片段的轨道绑定与采样边界

核心展开直接从已导入的 UE 动画序列采样头部子树。要从源文件复现这条路径,还需核对解码后的轨道映射、目标骨骼和采样时间。现有记录可以复查 UE 中的采样结果,完整压缩轨道、缺轨默认规则和导入记录仍待核实。

对每根骨骼应先确认轨道是绝对局部姿态,还是相对资产所指定参考的增量。前者可以沿父链组合,后者需要按资产的增量类型及其叠加参考计算实际局部变换。类似地,缺失整条轨道与只有旋转通道也要分开核对。前者可能沿用该骨的支撑基姿态,后者还需要确定平移和缩放各从哪里取得。本文没有足够的源轨道记录给核心片段指定逐骨回退规则。工程验收宜至少选择一根完整轨道骨、一根部分通道骨和一根缺轨骨,固定同一时刻比对输入与 UE 输出。

从片段时钟看,录像在起始和结束各留一秒保持。以实测记录的 30 fps 为例,第 30 帧已播放一秒,但片段仍采样 0 秒。第 80 帧到达约 1.667 秒的片段末端,之后维持该采样时刻。第 110 帧的录像时刻为 110 ÷ 30 ≈ 3.667 秒,此时片段仍采样约 1.667 秒的末端。末帧还显示一个帧间隔,因此整段视频的时长为 111 ÷ 30 = 3.70 秒。

编辑器只读查询返回的 87 个模型骨名,均可按 FName 与参考骨架匹配。导出表中的序号来自模型骨名的枚举顺序,查询接口返回的轨道索引和骨树索引均为 −1。原始压缩轨道索引需要从压缩数据单独导出。核对缺轨规则则需要使用刻意去掉轨道的样本。

下砸曲线与第一篇的力控接口

低头和甩身用曲线重建骨骼姿态。第一篇「下砸动画曲线采样与执行器目标生成」介绍了同一套下砸适配的运行链。曲线先生成机体和攻击足目标,控制器在物理步进中跟踪这些目标,IK 再根据实测足底求解骨骼姿态。本节展开说明曲线字段、坐标基准和前馈目标如何对应。

第一篇的下砸攻击提供了一个已核对的时间域实例。主动作与源恢复段各为 2 秒,本地适配以 clamp(t, 0, 4) / 2 取样。动作开始后第 3 秒对应曲线进度 1.5。若在输入端统一把相位截到 1,恢复区间将读不到后半段。这个范围属于该下砸适配器,低头、甩身与导入片段仍需以各自的时间输入为准。

下砸适配按当前时刻的曲线值计算高度。机体 Z 目标 = 攻击足初始锚点高度 + BodyTZ × RefPoseBodyHeight。 把曲线值逐帧累加会改变轨迹,再加一次初始机体高度则会抬高整个目标,可能让支撑腿超出可达范围。

足部的 Towardness、Up、Outness 则提示另一个问题:某个值表达相对于哪条轴,以及在动作的什么时刻读取。纵向、向外、抬高可以解释运动意图,真正的世界坐标还依赖机体参考系和足部基点。身体高度、腿端坐标和头部角度应分别看待;即使都存成浮点曲线,也不能共用一个单位规则。

固定足底重建与下砸力控的参考高度

RefPoseBodyHeight 连接了资源中归一化的机体平移与本地角色尺度。固定足底的重建器把它乘到机体平移通道上。最后 Z 的目标仍按绝对高度解释,支撑锚点平均高度充当场景基准。下砸适配也用同一设置缩放平移,但取的 Z 基准是攻击足初始锚点,并将结果提交给后续执行器。这个差别在平地上可能不明显,因为锚点高度接近。一旦前足与其他支撑足处于不同高度,选用哪个锚点基准会改变机体目标。因此应明确记录动作所用的参考位置、尺度及使用这些参数的模块。

从下砸适配代码可以逐轴追踪这个差别。它先取当前机体平移曲线与初始值的差,乘参考高度,在动作朝向下旋转后加到初始机体位置。接着单独覆盖 Z,把攻击足初始锚点的高度与缩放后的曲线绝对 Z 相加。旋转组合使用初始姿态与动作局部旋转。攻击足的平面通道使用本地的 150 尺度,竖向通道先截到非负,再把足端偏移转到动作朝向并加到脚的锚点。150 是这个本地适配的比例参数,当前资料没有支持把它当作仓库全局单位。

换一个教学坐标便于检查。设曲线初始值 X=0.10,当前 X=0.15,参考高度为 1,000 cm,水平位移的参考值为 50 cm。如果动作朝向在水平面上旋转 90°,这 50 cm 也应转到对应的世界方向。设同帧的曲线 Z=0.9,攻击足锚点高度为 40 cm,绝对 Z 目标是 940 cm。若误用 Z 的相邻差分,机体只会得到一个相对移动量。若额外再加初始机体高度,目标会整体上移。三种公式输入的数字看起来相近,运行结果却会落在不同可达域。

足端速度与加速度前馈

第一篇的下砸适配还向足端执行器提交位置目标、速度前馈和加速度前馈。位置可以直接从同一套曲线求出,但速度和加速度涉及采样间隔。适配器用伺服间隔 h,在当前时刻前后各取一次足端目标位置。一般区间采用中心速度和二阶差分。靠近动作起点时,速度按实际覆盖的时间跨度计算,加速度改用前向样本。这样不会让负时间的样本参与导数估计。

v(t) ≈ (p(t + h) − p(t − h)) / (2h)
a(t) ≈ (p(t + h) − 2p(t) + p(t − h)) / h²

第一篇的伺服积分对照对照了差分采样与输入保持如何影响每步的速度增量。这里的 h 取伺服间隔,与执行器保持前馈输入的时间对应。对分段线性位置曲线,线段内速度稳定,跨越拐点时速度变化。如果用远短于实际伺服步的差分跨度估计加速度,再把这个值保持一整个伺服步,实际积分进去的速度变化会受采样相位影响。两个动作在关键帧位置完全一致也可能产生不同的物理跟踪结果。因此资源验收除了比较 p,还要检查导数使用的步长以及何时把目标写给执行器。

这条下砸路径将速度前馈的向量长度限制到 3,900 cm/s,将加速度前馈限制到 49,000 cm/s²。执行器接到它们之后仍会结合位置、速度反馈和输出限幅。动画片段或仓库数据里出现一个陡峭变化,不能直接推导物理刚体获得同等加速度。第二篇关注数据如何成为目标,第一篇的连续性试验记录目标、实际读回和制动分别在哪一层发生。

资源版本兼容与运行时验证

资源解析先核对输入版本、字段映射和对象边界。遇到未知类型或对象尾部存在未读字节时,记录输入资产、字段、偏移和报错。随后分别验证动画载荷解码、轨道映射、坐标转换和 UE 姿态输出。网格 LOD 与动画轨道各自验收。

版本 2.01 的样本记录还缺两个失败对象的原始报错和逐轨道解码记录。复核时按容器读取、字段解析、动画解码和 UE 姿态写入分别记录。若已读出对象元数据,但压缩载荷无法展开,就把问题定位在动画解码阶段。

检查阶段 通过时应保留的证据 常见失效位置
容器读取 资源身份、输入版本、对象边界 输入截断、偏移或长度错误
字段解析 资源引用、类型与原始字段值 字段映射与当前版本不匹配
动画解码 每条轨道的时间、骨骼身份、有效通道与采样值 压缩载荷未支持、轨道错配、缺轨规则不完整
UE 姿态写入 同帧预期变换、组件读回及画面 父骨局部空间与组件空间换算错误、写入被后续层覆盖

容器、字段与动画轨道的兼容性诊断

排查版本差异时,第一步应保留输入文件、对象标识和失败偏移。若更换映射后只报告“可以读取”,读者无法判断是字段位置修正,还是解析器跳过了此前不支持的载荷。对动画,建议用同一对象、同一轨道、同一时间点做版本对照。先检查对象边界与字段值,再核对轨道数量、骨骼身份和样本值,最后确认 UE 骨骼组件读回的姿态。如果某版本只能读出片段名和时长,则兼容证据停留在元数据层。

两个具体故障可以通过诊断对照区分。若多条轨道的变化同时从某帧开始突然错位,先看每个分段的时间索引和压缩块边界。若同一时刻只有某根骨运动到了错误部位,优先核对轨道到骨骼映射。两者的表面结果都是“动画有问题”,但前者需要重新验证输入解码,后者需要核对身份映射。对缺轨骨,还应固定一种不同于参考姿态的支撑基姿态重复采样。只有在这种输入下,才能区分使用参考姿态和沿用当前支撑基姿态这两种规则。

原有三段实录与四段过程片分别展示动作结果和接入过程。低头和甩身可以核对头部曲线重建及本地姿态输出,核心展开可以核对导入片段时钟与指定子树运动。动画事件、攻击能力绑定及实际游戏触发属于第三篇的协调问题。版本兼容和完整原生动画图的结论仍需对应的源数据与运行记录。

轨道映射、姿态合成与 UE 读回

用世界位移核对头部变换的写入空间
教学算例。保持头部局部姿态,给组件施加已知的世界位移,再用实际读回定位空间转换问题。

固定一个演示样本,对照下表中的四层数据。视频帧号和资产采样时间分别保留。源解码器和 UE 预览的采样率可能不同,对齐时以同一资产时间为准。检查动作仓库曲线时,可以在第一、二层将轨道换成动作标识、曲线通道和采样值,再从生成的局部姿态继续检查。

资源与时钟 轨道映射 局部姿态与父链 UE 写入和读回
片段或动作标识、目标骨架、资产采样时间、录像帧号 轨道索引、源骨名及父骨、有效通道 目标骨名和父骨、缺轨处理后的局部 TRS、机体姿态与支撑锚点 预期组件变换、待写入值、目标组件及骨骼索引、刷新后的实际变换

父链。 头部轨道提供子树内的局部变换,边界外沿用当前机体与腿部输入。若局部四元数吻合但组件空间位置偏移,先查父骨关系和骨骼网格组件的坐标空间。可以先用参考姿态核对根、头部根与核心骨的相对轴,再选一帧机体明显旋转的样本检查符号、轴交换、父骨和覆盖顺序。核心展开保留机体的世界变换,头部和核心仍须组合到当前父链上。核心骨骼记录中的 Z 分量可按同一字段比较端点。若要把这项差值解释为沿部件轴的伸出量,还需先确认字段的空间。

姿态写入接口。 第一篇的宿主将姿态包序列化,交给 QueenLivePoseLibrary.apply_world_pose,并检查返回结果。复位时也用同一入口提交初始姿态。现有记录列出了 RefreshBoneTransforms。核心展开录制器内部的完整刷新顺序仍需源码确认。排查时可用一次受控位移区分空间转换问题。让机体沿世界 X 平移 100 cm,头部局部姿态应保持。若头部世界读回保持原位,检查是否漏乘组件变换。若移动了 200 cm,检查外层和写入入口是否各应用了一次。

读回。 若预期组件空间变换正确,刷新后读回却偏离,先核对目标组件、骨名和后续姿态覆盖。父骨已经更新而子部件滞后时,可选一对父子骨,在刷新前后分别由组件变换还原子骨相对父骨的局部位置,检查轨道关系是否保持。旋转对照使用同骨、同帧、同空间的归一化四元数 qExpected 和 qRead。最短旋转差为 2 × acos(clamp(abs(dot(qExpected, qRead)), 0, 1)),单位为弧度。它和演示里的头部俯仰角回答不同的问题。若读回一致而画面仍偏移,再查蒙皮权重、LOD 或机位。录制统计中的最大误差需要带上比较对象和统计区间,才能与这一帧的读回对应。

骨架迁移中的单位、换基与旋转验证

要把这一数据链迁到另一种模型,先确认长度单位、参考坐标轴和旋转表示,再讨论按比例缩放。若源局部坐标到目标局部坐标的换基矩阵为 S,平移使用 scale × S × p。对同一组坐标基之间的转换,旋转矩阵应写成 S × R × inverse(S)。如果 S 含镜像,单纯交换四元数分量的顺序没有普适含义。迁移时应根据已验证的轴和左右手约定推导变换,再用已知旋转和偏移的骨骼测试。这个换基关系是诊断方法,当前资料没有给核心展开片段提供可独立复核的完整源到 UE 换基矩阵。

平移的尺度也要按字段处理。例如同一个导入器可能用单位换算处理骨骼轨道,而动作仓库里的机体高度还需要参考高度作为尺度。二者若分别作用于不同量,可以同时存在。把资产单位换算和动作参数缩放相乘之前,应核对输入是否已经在厘米制、是否已经通过参考高度换算。最直接的检查是固定一个有明确局部位移的轨道,同时比较输入位移、变换后的预期位移和最终读回。对骨长不同的目标,还要另查姿态比例和关节限制。相同的位移尺度不会自动得到相同的接地动作。

旋转诊断可用单轴样本与复合样本两步完成。先让参考骨架只绕一条已知局部轴转动,检查目标的轴向和符号。随后同时转动父骨与子骨,检查组合顺序。若只做单轴测试,某些顺序错误会被同轴旋转掩盖。对复合姿态,记录归一化四元数和比较空间,再计算最短角差。欧拉角分量可以用于解释头部向哪一侧转动,却不适合直接相减来判定任意两组骨骼旋转是否等价。

骨骼旋转可直接以四元数传递。欧拉角在某些轴接近奇异姿态时,一个很小的旋转差会引起分解参数的大变化,量化后转回也可能产生额外误差。诊断应固定同一个输入和目标骨,把预期和读回都转换为归一化四元数,再取最短旋转差。只有同骨、同帧、同一误差定义的结果可以直接比较。欧拉角往返转换的具体误差仍需与同骨同帧的四元数读回样本核对。

逐帧采样与误差统计口径

录制摘要里的最大误差只有结合比较对象、空间、单位和采样范围才有意义。例如生成姿态与本地骨骼的差值,衡量的是姿态重建。解码输出与 UE 采样的差值,衡量的是资源采样。本篇图表直接使用能对应到时钟的头部旋转和核心骨平移样本。其余摘要字段仍需核对计算过程。

低头可取最低俯仰核对幅度,再取起止附近的帧核对淡入淡出和端点保持。甩身分别取俯仰与偏航极值,检查机体朝向变化后映射是否一致。核心展开对照同一片段的起点、中段和终点,检查轨道在整个采样区间内是否持续写入目标骨骼。

动作曲线与动画序列的回归检查

对于仓库曲线,可以先选一个正常区间、一处关键帧边界和一处区间外时间。固定输入时钟后,分别保存查询通道、相邻关键帧、采用的插值或外推方式以及最终标量值。如果曲线缺少有效样本,再独立记录调用者提供的缺省值。这样一次回归能同时覆盖资产数据、时间域和默认路径。若工具只能导出最终动画姿态,下一次遇到偏差时仍要回到查询过程才能确定原因。

验证导入的动画序列时,应保存轨道映射、局部变换、目标父骨和刷新后读回的组件空间变换。起点和末点用来验证时长与边界,中段用来验证解码和父链。还应选择一根位于覆盖范围外的机体骨,验证播放核心展开序列时,该骨骼依然沿用支撑基姿态。如果测试只断言“核心移动了”,头部子树外的意外覆盖仍可能被遗漏。若希望证明与战斗动作绑定,则要另加触发条件、片段选择与状态切换记录。播放时机由动作调度决定。

从片段采样到运行时动作调度

核心展开的实录按片段时钟采样,录像编排另设起止保持。游戏里的攻击请求还受移动状态、站稳条件和动作所有权约束。第三篇将说明运行时如何等待站稳、把动作交给执行器,以及执行结束后何时释放占用。

发表评论