IK:想让手抓住那个点,关节该转多少
全课骨架:动画 = 每一帧先把骨架的姿态算出来,再把顶点按权重「挂」到骨头上。 上一课做的是前半句里的「读姿态」(姿态给定 → 顶点跟着走);这一课做的是后半句里的「写姿态」第一步: 给定末端要到达的位置,反解出各节的角。这件事在游戏里到处都是(脚踩地面、手扶栏杆、瞄准), 而它和我们习惯的「拧一个角、看它转到哪」是反着的 —— 反着的问题,解起来完全是另一回事。 保真度:真机可证(画面与读数都由本页自己的求解器给出;对照物是双骨的余弦定理闭式解、以及可达性判断,读者能拿笔复核)。 交互:在画面上直接拖动那个目标环。
本页的原理与数值表照常可读(画面只是把同一个数画出来)。
(脚本启动中…)
定点观察:在画面上拖动那个目标环,把它拖到虚线圆(= 臂长总和)外面 → 看画面里那条链的末端与目标环之间的空隙,以及表④ 的「末端误差」 → 够不到时误差不再是 0,而恰好等于「目标距离 − 臂长总和」:这不是求解器坏了,是"这个问题本身无解", 好的实现会把链朝着目标拉直并如实地告诉你差多少。
本帧的读数
(脚本启动中…)
2.1 段 · 两个角,一个目标:余弦定理直接算
本段只增加一样东西:目标点(画面上出现可拖动的目标环;求解用解析解;其余控件与本课各段一致)。 保真度:真机可证。两节骨头、给定末端目标,这件事有闭式解:末端到根的距离 d 与两节骨长 围成一个三角形,用余弦定理先求出两节之间的夹角,再求出根节的绝对方向。全程四次三角函数,没有迭代。
| 目标 (tx, ty) | |t| = d | 相对角 dA (°) | 根节角 a₀ (°) | 末端误差 (m) | 闭式核对 dA |
|---|
算式:把末端到根的距离记作 d = |t|。两节之间要张开多大?由余弦定理 cos(dA) = (d² − L₁² − L₂²) / (2L₁L₂) 给出相对角 dA;再让根节先指向目标方向、往回让一点: a₀ = atan2(t_y, t_x) − atan2(L₂·sin dA, L₁ + L₂·cos dA)。拿 t = (1.4, 0) 验算: d = 1.4,cos(dA) = (1.96 − 0.64 − 0.36)/(2×0.8×0.6) = 0.96/0.96 = 1 ⇒ dA = 0°(手臂完全伸直), a₀ = 0 − atan2(0, 1.4) = 0° —— 表① 那一行应当逐位对上(差 0)。 注意这条解只在"末端是两节"时唯一:换成三节,三角形就变成四边形,解从"一个"变成"一族"。
2.2 段 · 同一个目标,换成迭代:CCD
本段只增加一样东西:求解方法(把解析解换成迭代法 CCD;目标、骨长、控件都不动)。 保真度:真机可证。CCD 的想法朴素到一句话:从最靠近末端的那一节开始,把这一节转到"末端正好对准目标", 再往根走一节、重复。一遍扫完算一次迭代,多扫几遍误差就下去了 —— 它和解析解应当收敛到同一个答案。
| 迭代次数 | 末端误差 (m) | 相对上一档 | 根节角 a₀ (°) | 与解析解的角差 (°) |
|---|
算式:CCD 的一遍扫描是"末节先转、再往前一节",所以信息在一遍里是自梢向根传的;
要让整条链同时满足末端位置,得扫若干遍。表② 的看点有两个:误差一路下降(相对上一档 < 1),
以及收敛之后根节角与解析解逐位一致(差 → 0)—— 两节的解是唯一的,所以两条路不可能到不同地方。
阻尼那一档要单独看:它不改变"最终到哪儿",只改变"每步走多远" —— 阻尼越小,同样的遍数走得越少,
所以误差更大。真实引擎要给阻尼/限位,是为了让动作看起来自然(不要一帧扭到位),不是为了精度。
★ CCD 有一个真实的脾气,本页实测到并如实写出来:当目标恰好落在当前各节的同一条射线上时,
每一节"要转的角"算出来都是 0 —— 它会原地不动(本页把目标放在 (1.0, 0)、链从全零姿态起步时,
误差就卡在 0.4000 一动不动)。所以真实实现从不给它全零/共线的初值,而是用上一帧的姿态当初值 ——
游戏里角色每帧都在动,天然避开了这个退化点。本页的初始姿态也因此设成"微弯"而不是全零。
| 目标 (tx, ty) | d (m) | d / ΣL | 到 1e−9 需几遍 | 48 遍时的误差 (m) |
|---|
这张表是 2.2 段最值钱的一条,也是实测才看出来的:CCD 的收敛速度不是常数,它强烈依赖目标在哪儿。 目标落在"手臂中段"(d/ΣL ≈ 0.73~0.77)时 32 遍就够了;越靠近"手臂全伸",需要的遍数越大 —— d/ΣL = 0.958 要 256 遍、0.988 要 512 遍、0.995 要 2048 遍(最后 3% 里翻了 8 倍)。 原因是几何上的:接近全伸时这条链接近奇异位形,各节的转动对末端位置的影响几乎互相抵消, 于是"每一遍能纠正多少"急剧变小。这就是后来 FABRIK 一类算法被广泛采用的原因 —— 它们换了一种更新方式(先把末端拉到目标、再往回推保骨长),在接近全伸时不会这么慢。 本页选 CCD 是因为它最好解释(每节只需转到"对准目标"),它的这个缺点如实写在这里。
2.3 段 · 三节的时候,答案不止一个
本段只增加一样东西:骨节数(2 → 3~4 节;方法仍是上一段的 CCD,其余控件一致)。 保真度:真机可证。2.1 的解之所以是"一个",是因为两节加一个目标刚好构成一个三角形。 加第三节,位置方程变成 2 个方程、3 个未知角 —— 于是解从"一个"变成"一族": 末端都能落在目标上,但肘、腕摆在哪儿,取决于你从哪儿出发。
| 初值(各节角 °) | 末端位置 | 末端误差 (m) | 第 1 个关节位置 | 与 A 的关节距离 (m) |
|---|
算式(欠定的由来):平面里"末端落在目标上"是 2 个方程;n 节骨头有 n 个未知角。
于是 n = 2 时"2 方程 2 未知"⇒ 解是离散的(本例唯一);n ≥ 3 时"2 方程 n 未知"⇒ 解是一条(n−2)维的族。
表③ 就是把这个族摊开给读者看:三行末端逐位相同,而第 1 个关节的位置彼此分开(0.30 m 与 0.75 m)。
迭代法会停在离初值最近的那个解上 —— 所以同一个目标、同一个求解器,换个初值就换一个姿势,
这在动画上要么是"可控性",要么是"每帧乱跳"(取决于你有没有做帧间连贯)。
★ 顺带记一笔实测:把表③ 的迭代次数从 200 降到 32 时,第三行那个反折的初值还差
2.5e−4 没收敛(另外两行已经到 1e−16 量级)—— CCD 的收敛速度与初值有关,
这是它和解析解最不一样的脾气:解析解一次到位,迭代法得看"从哪儿出发"。所以本表统一用 200 遍,
让"末端逐位相同"这句话站得住。
2.4 段 · 够不到、或者懒得迭代:可达性与阻尼
本段只增加一样东西:目标距离(把目标拖到臂长总和之外;其余控件一致)。 保真度:真机可证。2.1~2.3 都默认"这个问题有解"。可目标一旦落在虚线圆外面, 方程就无解了:余弦定理里的 acos 入参会被夹到 ±1(解退化),CCD 则会让整条链慢慢朝目标方向伸直、 但永远差一截。好的实现不假装能解,而是把链拉直、并如实报出差多少。
| 目标距离 d (m) | 可达? | 末端误差 (m) | 闭式 d − ΣL(负取 0) | 根节角 a₀ (°) |
|---|
算式:可达的上界就是臂长总和 ΣL(链完全伸直)。于是末端误差 = max(0, d − ΣL) ——
与目标方向、与节数、与迭代次数、与阻尼都无关。拿 d = 2.00、ΣL = 1.40 验算:误差 = 0.60 m,
表④ 那一行应当逐位对上。而根节角在圆外恒为 0°(朝 +x 拉直),圆内则跟着目标方向走。
这就是它和前三段最大的不同:前三段在问"怎么解得更快更准",这一段在问"这个问题有没有解"。
真实项目里,可达性判断和"目标预平滑"是同一个模块的两半(脚要踩地、手要扶墙,目标几乎总在边缘上跳)。
课末:写姿态有两种,这是第一种
这一课把"写姿态"里的第一种做完了:给定末端目标,反解关节角。
它的形状很固定 —— 解析解(只有两节才有)× 迭代解(多节唯一可行)× 可达性(无解时怎么办),
而三段其实在回答同一个问题的三个面:解存不存在、唯一不唯一、算得快不快。
如果只记一句话:IK 不是在"求一个角",而是在"从一个解族里挑一个" ——
数学只能告诉你族的形状,挑哪一个得靠限位、偏好姿态与帧间连贯(那些是动画师的活)。
下一课是"写姿态"的第二种:弹簧骨(头发、尾巴、挂坠)—— 那一半不求解,而是让骨头自己变成一根弹簧,
跟着主体滞后地动。