动画混合:两个动作怎么叠成一个
全课骨架:动画 = 每一帧先把骨架的姿态算出来,再把顶点按权重「挂」到骨头上。 到这里,"姿态从哪来"已经有三条路(第 2 课解出来的、第 3 课演出来的、第 4 课补出来的), 可它们讲的都是一条动作。真实角色同时有走路、跑步、转身、挥手 —— 引擎每帧要问的是 这几条动作各出多少力、怎么叠。这一课就是那个"叠"字。 保真度:真机可证(混合算式、蒙皮与四张表都由本页自己的代码算出;对照物是四条闭式: 混合中点 p = (1−w)·A + w·B、相位包络 S·|cos(Φ/2)|、遮罩"被遮通道差恰好 0"、加层 k = 0 ⇒ 逐位还原)。 画面用的是网上的真实人体模型:Mixamo 的 Vanguard(49 关节 / 7 325 顶点 / 11 186 三角, 自带 512² 漫反射贴图;Mixamo 的角色可免费使用),蒙皮按 glTF 2.0 的 jointMatrix = world · inverseBind 逐顶点算 —— 也就是第 1 课那条式子,只不过这次挂的是真人网格。光栅化走 CPU:逐三角形 z-buffer + 贴图 + 三点光照 + 地面 + 平面投影软阴影 (示意 · 等价重现;该资产有些顶点的权重和不为 1,本页按 glTF 惯例先归一化再算)。
本页的原理与数值表照常可读(画面只是把同一个数画出来)。
(脚本启动中…)
定点观察:先点「5.2 同一条动作错开 φ」,然后把「相位差 φ」从 0 慢慢拉到 180° → 看画面里这个人(真人网格,49 个关节)的迈步幅度一路缩下去、以及表② 的「混合后的极值」一列 → φ = 180° 时两条动作各出一半,恰好把彼此抵消干净(极值 0.0000)—— 人当场立定。 所以"混一半"从来不是"各取一半",它取决于两条动作当时对不对得上。
本帧的读数
(脚本启动中…)
5.1 段 · 两个动作之间:按权重混合
本段只增加一样东西:混合权重 w(画面里是一个真实的蒙皮人体 + 右上腿摆曲线;其余控件与各段一致)。 保真度:真机可证。第 4 课能把一条动作播出来了,可游戏里角色随时要换动作 —— 站在"走"突然变成"跑",两条动作的姿态不接,硬切会在那一帧跳一下。最省事的补法就是 让两条动作在同一时刻各出多少力:w = 0 全听 A,w = 1 全听 B,中间按权重取。
| w | 闭式 (1−w)·A + w·B (°) | 实测腿·左 (°) | 差 (°) |
|---|
算式:混合是逐通道做的 —— 四条通道各算一次 p = (1−w)·A + w·B,共用同一个 w。
把 w = 0.5 代进去就得到 (A + B)/2,也就是"两点中点"。表① 的五档逐位对上(差 0,是浮点意义的 0)。
注意这四条通道是什么意思:它们是摆幅(度,0 = 静止位,正 = 向前)——
腿·左 / 腿·右是两条腿的髋摆,臂·左 / 臂·右是两条臂的肩摆。它们不是"关节角"本身:
膝、肘、踝由它们经一条固定的示意曲线导出来(本页如实标注)。
混合发生在这四条上;这也是引擎里的真实做法(混合的是每条通道的曲线,不是"整个姿态")。
但"中点"与"幅度"要分开看:中点这条与相位无关、永远成立;
可混合出来的动作幅度完全由两条动作"对不对得上"决定 —— 那正是下一段的事。
真实引擎里还会把 w 先归一化(`wᵢ / Σwⱼ`),否则多条动作一起混时姿态会被整体缩放 ——
本页只有两条,`(1−w) + w ≡ 1` 天然成立,所以看不出这件事。
5.2 段 · 相位:两条循环错开了,就会互相抵消
本段只增加一样东西:两条动作的相位差 φ(本段把 B 换成 A 的同款动作、只整体错开 φ, 这样"抵消"这条式子才量得干净;其余控件一致)。 保真度:真机可证。5.1 悄悄假设了一件事:两条动作在同一时刻是"可比"的。 可真实的两条动作各自从自己的第一帧开始播 —— 过渡那一刻它们往往根本不对齐。 于是"各出一半"出来的不是中点姿态,而是两个错开的波动相加。
| 相位差 Φ (°) | 闭式 30·|cos(Φ/2)| (°) | 实测混合极值 (°) | 差 (°) |
|---|
算式:两条同振幅同周期的通道错开 Φ、各出一半,用和差化积:
S·sin(a) 与 S·sin(a+Φ) 各一半 → S·sin(a + Φ/2)·cos(Φ/2)。
括号里那个 cos(Φ/2) 就是包络 —— 它乘在整个正弦上,所以"幅度"恰好被削成 S·|cos(Φ/2)|。
拿 Φ = 90° 验算:cos45° = 0.70711,作用在 ±30° 上就是 21.213°;Φ = 180° 时 cos90° = 0,
极值 0.0000 —— 表② 逐行对上。
注意这条式子为什么要"同振幅同周期":振幅或周期一旦不同,包络就不再是一个常数,
而变成随时间的"拍"(5.1 那种走 + 跑就是拍)。本段的表用同款动作的两个相位副本,正是为了让这个包络可逐位验证 ——
页面上把 B 换成 A 的同款,就是这件事。
★ "差"这一列不是 0,而是 ~1e−7,这是扫描分辨率、不是模型误差:峰值在 α 网格上只能落在最近的一档,
取整误差 ≈ ½·S|cos(Φ/2)|·(π/N)²,N = 20000 时约 3e−7 —— 实测 1.4e−7,同量级。
把 N 从 2000 提到 20000,这一列从 ~6e−5 掉到 ~1e−7(差十倍),正是"它随步长同比例缩、所以是精度问题"的实证。
(两条我特意没做的"作弊":照着闭式直接返回、或者只在峰值附近加密采样 —— 那样这一列就变成 0,
可它也就不是在"扫"了。)
这条式子还解释了一件常见的事:为什么"随便交叉淡化两条动作"有时看起来像没动?
因为那两条动作在过渡时相位差接近 180°,恰好把彼此抵消掉了 —— 不是引擎坏了,是相位没对齐。
5.3 段 · 遮罩:只混一部分
本段只增加一样东西:遮罩(下半身不参与混合)(其余控件一致)。 保真度:真机可证。5.1/5.2 的混合是"全身一起混",可很多时候你只想换一部分: 上半身在挥手,下半身还得照常走路。做法就是在混合那一步加一张遮罩: 被遮住的通道直接取基础动作,其余照常混合。 本段的遮罩按"通道"分(腿两条 / 臂两条);真实引擎会把它做成关节级的遮罩 —— 这一页的 49 个关节里,髋以上的关节正好由臂通道驱动、髋及以下由腿通道驱动,所以两者是同一件事。
| 档位 | 腿通道 与 A 的最大差 (°) | 臂通道 与 A 的最大差 (°) | 臂通道 与"纯混合"的最大差 (°) |
|---|
算式:遮罩那一步是一个分支,不是一个系数 —— 被遮的通道完全不走那行加权。
所以"被遮通道与 A 的差"在数学上恒等于 0,实测 0.000000(是浮点意义的 0,不是"很小")。
这条"恰好为 0"比"大致不变"值钱得多:如果那里写成 `o[c] = (1−w)·A[c] + w·B[c]` 再乘一个 α=0 的遮罩权重,
差就会变成 `w·|B−A|` 量级的残渣 —— 肉眼同样看不出,但它在长时间播放里会累积成漂移。
另外注意表③ 的第三列:两行完全相同。这正是"遮罩只改了它该改的那几条通道"的证据 ——
如果那一列也变了,说明遮罩影响到了不该影响的通道。
5.4 段 · 加层:在基础动作之上叠一个偏移
本段只增加一样东西:层强度 k(其余控件一致)。 保真度:真机可证。遮罩解决的是"哪几条参与混合",可它做不到另一件事: 在现成的动作之上再叠一个偏移(走路的同时挥手、受伤的同时瘸腿)。 做法是加层:把一条动作减去它的参考姿态,得到"纯偏移",再按 k 加到基础动作上。
| 层强度 k | 与 base 的最大差 (°) | 闭式 k × 35 (°) | 臂·左 与 base 的最大差 (°) |
|---|
算式:加层做的是 p = base + k·(add − ref),其中 add − ref 是"纯偏移"。
于是与 base 的差恰好是 k·|add − ref| —— 一条过原点的直线:k = 0 ⇒ 0.000,
k = 1 时等于那条曲线自己的极值 35.000°(表④ 逐行对上)。
"k = 0 必须逐位还原"是加层最重要的性质,它一条就锁死了三处最常见的写错:
① 层里直接存了 `add` 而不是 `add − ref`(k = 0 时动作会整体偏掉一截);
② 参考姿态取成了另一个东西(比如绑定位),于是"层关掉"时动作仍然被挪走;
③ 层越过了遮罩作用到全身(k = 0 也看不出,可 k ≠ 0 时另一条臂也跟着摆)。
还有一条配套的:层的效果必须与 base 无关 —— 把基础动作从"走"换成"跑"、或换成 5.1 的混合结果,
表④ 那一列数应当一位不变(因为它是加法)。这正是"加层可以叠在任何基础上"的可验形式。
课末:把"叠"拆成四件事
这一课把"叠"字拆成了四件互不替代的事,它们恰好对应真实引擎里动画图上的四类节点:
① 权重混合(两条动作各出多少)/② 相位对齐(它们当时对不对得上)/
③ 遮罩(哪几条通道参与)/④ 加层(在基础上叠偏移)。
把它们串起来就是一次最普通的"走 → 跑"过渡:先把两条动作的脚步接触时刻对齐(②),
再让 w 在一小段时间里从 0 走到 1(①),下半身用遮罩留在走上面(③),
同时叠一个"喘气"的加层(④)。少了②,过渡会变成抵消;少了④,你只能整条整条地换,做不出叠加效果。
如果只记一句话:混合不是"选一条动作",而是"每一条动作各出多少力" —— 而"出多少力"必须和"对不对得上"分开算。
本课题到此五课收口(蒙皮 → IK → 弹簧骨 → 关键帧 → 混合);附录里留了三样下一层的:
状态机与过渡条件(什么时候切、切多久)、根运动与脚步锁定(怎么让脚不滑)、以及混合树与分层遮罩。
本页那一格"如实说明":腿/臂的驱动信号是按通道用正弦生成的示意步态(膝、肘、踝、趾由通道经一条固定曲线导出;
其中膝的时机还额外用了驱动层算出的一个相位提示 —— 通道值本身区分不了"摆动中段"与"支撑中段"),
不是动作捕捉数据;模型本身是真实人体网格,蒙皮、贴图、地面阴影与混合都是逐顶点真算的。
模型 Vanguard 取自 three.js 的示例资产(原作者 Mixamo / Adobe;Mixamo 的角色与动画可免费使用,含商用)——
此处仍列出出处:threejs.org/examples/#webgl_animation_skinning_blending。