动画里常见一个需求:脚要踩在任何地形上、手要扶住随机摆放的栏杆、头要转向说话的角色。这些用正向动画(FK)做不现实——目标位置是运行时才知道的。逆运动学(IK)解决这个问题:给定末端骨骼的目标位置(有的还带朝向约束),反解出中间每根骨骼的 pose,让链在保持骨骼长度不变的前提下够到目标。Godot 内置了 IK 求解器,这篇讲它背后的两个算法。
问题的数学形式
一条骨骼链有 $n$ 根骨骼,第 $i$ 根长度 $l_i$(骨骼间距离是刚性的——关节只转不滑)。末端位置 $\mathbf{p}_e$ 是各关节角的复合函数,没有闭式解($n > 2$ 时还是欠定/超定的混合体)。所以实用 IK 全是迭代数值解:从一个初始姿势出发,每轮调整让误差 $\|\mathbf{p}_e - \mathbf{p}_t\|$ 下降,收敛到够近就停。两个经典算法的区别在于"每轮怎么调整"。
CCD:从末端往根逐个关节转
CCD(Cyclic Coordinate Descent,循环坐标下降)每轮迭代从末端关节往根走,一次只转一个关节。对当前关节 $i$,要让末端朝目标靠近,旋转轴取关节位置到末端、关节位置到目标的两个向量的叉积,旋转角取它们的夹角:
$$\mathbf{a} = \mathbf{p}_e - \mathbf{p}_i, \qquad \mathbf{b} = \mathbf{p}_t - \mathbf{p}_i$$
$$\Delta\theta = \operatorname{atan2}\big( \|\mathbf{a} \times \mathbf{b}\|,\ \mathbf{a} \cdot \mathbf{b} \big), \qquad \text{axis} = \frac{\mathbf{a} \times \mathbf{b}}{\|\mathbf{a} \times \mathbf{b}\|}$$
转完一个关节立刻更新末端位置(下一轮关节看到的是新位置),一路转回根,算一轮。直觉上:每个关节都"自私地"把末端往目标拽一把,多轮之后误差单调下降。优点是好实现、天然支持关节角度限制(每步 clamp 一下);缺点是收敛有偏好——靠近末端的关节转得多、靠近根的几乎不动,长链上容易产生"蛇形"的扭曲姿势,而且离目标还有段距离时就提前僵住(每个关节都被自己的角度限位卡死)。
FABRIK:前向够、后向锚
FABRIK(Forward And Backward Reaching IK)完全不碰角度,纯在位置空间操作。一轮分两步:
- 前向:把末端点直接放到目标 $\mathbf{p}_t$ 上,然后从末端往回走:第 $i$ 个关节的新位置 = 第 $i+1$ 个关节的位置 + 指向旧第 $i$ 个关节方向的单位向量 × $l_i$——即保持段长,把每段"拉回"链上;
- 后向:此时根关节已经偏离了原位置(前向传播挪动了它),从根出发反向再做一次同样的段长保持传播,把根锚回原位置。
前向保证"末端够到目标",后向保证"根没动",一轮之后两端约束同时满足,中间段被拉伸分摊。重复几轮误差快速收敛。收敛速度通常比 CCD 快一个量级,且没有角度突变,姿势自然。它的代价是:输出的是关节位置,要转回关节旋转得再做一步(用相邻段方向差求旋转增量),且处理角度限位比 CCD 麻烦(限位要在位置更新后投影回去,容易破坏段长约束)。Godot 内置的 SkeletonIK3D(3.x 就有,4.4 起归入 SkeletonModifier 栈)实现的就是 FABRIK。
Godot 里的接入点:SkeletonModifier 栈
4.4 的架构变化值得单独说:3.x 的 SkeletonIK3D 是 Skeleton 的附属节点,和物理骨骼(布娃娃)各改各的 pose,互相覆盖。4.4 引入 SkeletonModifier3D 基类,IK、物理骨骼模拟、弹簧骨全都变成挂在 Skeleton 下的 modifier,按节点顺序排队执行,每级读上一级输出的 pose、写自己的修改。这解决了执行顺序这一整类 bug——以前的"IK 和布娃娃打架"从此变成显式的栈排序问题。
脚本层做自定义 IK(比如 2 根骨骼的解析解手臂)也走这条路:继承 SkeletonModifier3D,在 _process_modification() 里调 set_bone_global_pose() 写 pose(九-3 的双变换语义)。注意它和 AnimationTree 的关系:mixer 求值 → modifier 栈逐层覆盖,所以modifier 永远赢——想让动画和 IK 混合(比如走路动画只驱动大腿、脚由 IK 贴地),正确姿势是在动画层留出不驱动小腿的轨道,而不是指望 modifier 和动画加权。
2 根骨骼的闭式解
$n = 2$ 的平面链(大腿+小腿是最常见 case)不用迭代,余弦定理直接解:设大腿长 $l_1$、小腿长 $l_2$、根到目标距离 $d = \|\mathbf{p}_t - \mathbf{p}_{\text{root}}\|$(clamp 到 $|l_1 - l_2| \le d \le l_1 + l_2$ 防不可达),膝关节角度
$$\cos\theta_2 = \frac{d^2 - l_1^2 - l_2^2}{2 l_1 l_2}$$
膝角定了之后,根关节的朝向由根-目标连线与大腿方向的夹角唯一决定,肘/膝的弯向(pole target,膝盖朝前不朝后)再选一个平面。双腿 + 脊柱的三链系统就是这么拼出来的。解析解零迭代、零抖振,游戏里凡是"够得着"的 case 都应该优先于通用迭代器——引擎自带的 FABRIK 留给长链和软约束。
本文基于 Godot 4.2–4.4;SkeletonModifier3D 栈与 SkeletonIK3D 的重定位为 4.4 内容,4.3 及以前用旧的 SkeletonIK3D 节点。