计算机图形学 E05:目标可达,关节为什么仍然不动
两根长度为 1 的杆,从原点伸向目标 (1,1)。给定关节角度,末端位置很容易计算;反过来给定末端位置,应该把两个关节转到哪里?这就是一个最小的逆运动学问题。
第 30 篇先指定骨骼姿态,再用权重带动网格。本篇向前多走一步:从目标位置求姿态。实验同时保留解析解和迭代解,检查“够不着”“迭代没有求到”和“杆长发生变化”这三种不同情况。一个可达目标,也可能让梯度迭代从第一步就停住。
先把杆长放进参数化
肩关节位于原点,两杆长度为 l₁、l₂,肩角为 θ₁,肘角 θ₂ 相对第一根杆定义。角度使用弧度,位置使用统一的模型长度单位。肘位置 q 和末端 p 是:
每次只更新两个角度,再重新计算位置,理论上就有 |q|=l₁、|p−q|=l₂。无需在每轮末尾拉伸或投影杆件来修补长度。
目标 t 的误差是 eₜ=|p−t|;杆长误差另记为 eₗ=max(||q|−l₁|, ||p−q|−l₂|)。前者衡量任务完成程度,后者检查几何约束。整条迭代轨迹中,eₗ 最大只有 2.220446049250313×10⁻¹⁶,但有些轨迹的 eₜ 始终为 1。长度正确不能代替目标误差检查。
这也区别于第 30 篇的线性混合蒙皮:那里两个刚性骨骼的加权结果可能收缩,这里直接用关节角度构造杆件,保持的是两段中心线长度。
可达环带与两个肘部方向
把两杆及原点到目标的连线看成三角形,三角不等式给出必要条件;反过来,满足该条件的三角形也能通过两个关节角构造出来。因此,没有关节角限制时,可达区域恰为:
注意这里比较的是距离 r,不能把平方距离与杆长直接比较。将正向位置平方展开,得到余弦定理和两组角度:
s 的两个符号对应两个肘部方向。等长杆到 (1,1) 的两组解是 (0,π/2) 与 (π/2,−π/2),末端相同,中间关节不同。在伸直或完全折叠的边界上,两分支会合并。atan2 保留象限信息;角度相差整周时也可以描述相同姿态。这套几何构造可对照 Illinois ECE 470 的逆运动学讲义第 3–5 页。
目标不可达时,程序仍返回一个最近姿态,但明确保留 reachable=false。先把 r 限制到可达环带,再求角度,最小目标残差为 |r−clamp(r,|l₁−l₂|,l₁+l₂)|。
| 杆长 | 目标 | 可达 | 最小残差 |
|---|---|---|---|
| 1、1 | (3,0) | 否 | 1 |
| 1、0.5 | (0.1,0) | 否 | 0.4 |
| 1、0.5 | (0.5,0) 或 (1.5,0) | 是 | 0 |
| 0.5、1 | (0,0) | 否 | 0.5 |
原点还需要单独处理:等长杆完全折叠即可到达,肩角任意;不等长杆则有一整圈同样近的末端位置,仍然够不到原点。程序为这些方向不唯一的情况选一个代表姿态,不能把“存在一族最近解”写成“存在一族精确解”。等长折叠的浮点残差约为 1.22×10⁻¹⁶,来自三角函数求值。
用 Jacobian 把位置误差传回关节
解析解给出了可达性和最优残差,接下来用它检查迭代法。定义目标函数 F(θ)=½|p(θ)−t|²,对两个角度求导:
一轮更新是 θ←θ−ηg。源码在三个非奇异姿态上用中心差分独立检查梯度,差分间距为 10⁻⁶,两分量误差均小于 10⁻⁸。
η 是优化步长。第 31、32 篇的 dt 表示物理时间,这里的一次迭代没有对应多少秒,也没有质量、力或速度状态。把求解过程播放成动画,不会使它自动成为物理运动;橙色轨迹表示历次末端位置。
位置单位也会影响合适的 η。若把杆长和目标坐标同时乘以 a,角度不变,位置残差与 Jacobian 都乘以 a,梯度便乘以 a²。要保持相同角度更新,η 应除以 a²。因此,同一个数值步长不能不经检查就搬到尺度不同的角色上。
步长多大才有下降保证
“较小步长比较稳”可以写成有条件的不等式。令目标极角为 φ,将 F 展开,去掉与角度无关的常数 K:
三个余弦项的 Hessian 范数分别不超过 l₁l₂、l₁r、2l₂r,故全局上界可取 L=l₁l₂+l₁r+2l₂r。Taylor 余项给出:
当 0<η<2/L 且 g≠0 时,一步更新保证下降。这个上界是充分条件,超过它不代表每一步必然变坏;满足它也不保证到达全局最优,更不保证固定预算内误差归零。
常规实验取两杆长度 1,目标 (1,1),初始角 (0.2,0.4)。此时 L=1+3√2,2/L≈0.381487。实际结果如下,最后一列统计 200 步中 F 比上一步增加超过 10⁻¹² 的次数。
| η | 10 步目标误差 | 40 步目标误差 | 200 步目标误差 | F 上升次数 |
|---|---|---|---|---|
| 0.05 | 0.503746 | 0.426158 | 0.0500456 | 0 |
| 0.2 | 0.426680 | 0.0984427 | 3.41392×10⁻⁷ | 0 |
| 2 | 2.17806 | 2.54428 | 3.26107 | 104 |
η=0.05 和 0.2 都满足下降条件,但预算效果不同。η=2 的轨迹反复增大目标函数,200 步后的误差甚至超过两根杆的总长。程序没有把它替换成成功轨迹,也没有因杆长正确而判定求解成功。
目标在身前,梯度却为零
把目标改成 (1,0),初态改成两杆水平伸直,即 θ=(0,0)。目标位于可达区域内部,但此时 p=(2,0),并且:
关节微小转动的一阶位移沿竖直方向,与当前水平误差正交。折叠可以缩短水平距离,但这一变化在当前姿态的一阶导数里没有表现出来。于是三个 η、三个预算组成的九组实验全部保持目标误差 1,梯度为零,杆长误差也为零。减小步长或增加这里的迭代次数都不能改变零更新。
解析解可直接找到折叠姿态。改用非奇异初值也可能让梯度法开始移动,但不能据此承诺任意目标都收敛。本实验没有实现重启、线搜索或其他求解器。
结束状态至少应连同目标误差一起报告。预算耗尽只表示停止计算;更新量很小可能表示接近解,也可能像直杆案例一样停在非零残差处。有解析下界时,还可以比较当前误差与可达到的最小残差,避免对不可达目标提出误差归零的验收要求。
复跑与检查边界
从仓库根目录执行下面两条命令,二进制和生成物都位于仓库 build 目录:
1 | |
头文件与检查程序给出完整实现。summary.csv保留 18 组结果,iterations.csv保留包含初态的 1518 行轨迹,analytic.json保留七种目标的两分支结果。程序另生成 38 张 PPM;本文图片由对应 PPM 转换,蓝、绿分别表示两根杆,红点表示目标。
验收包括解析手算值、梯度差分、全程杆长及奇异初态。零杆长、NaN 目标、零步长、非平面目标和过小杆长这五项输入均被拒绝。--help 返回 0,未知参数返回 1。输入检查允许的步长上限只是数值范围约束,不能当作收敛保证。
这里没有关节角限位、障碍物碰撞或布料。解析可达环带以关节自由旋转为前提;加入角限位后要重新判断可达性,穿过障碍物的姿态也不会被当前代码拒绝。第 32 篇的碰撞处理不能自动由这个角度更新过程获得。完整运行记录见CPU 回执。
自检
- 杆长为 1、0.5,目标为 (0.1,0),能否通过更多迭代消除误差?不能。目标位于内侧不可达区域,最小误差为 0.5−0.1=0.4。
- 两根单位杆伸直时,到 (1,0) 的误差为 1,但梯度为零,是否证明目标不可达?不能。两组解析解为 (−π/3,2π/3) 与 (π/3,−2π/3),零梯度只描述当前姿态的局部一阶信息。
- η=2 时杆长误差接近机器精度,能否认定求解器正确收敛?不能。杆长由参数化保持,而 200 步目标误差约 3.26107,且出现 104 次目标函数上升;两项误差回答不同问题。
系列入口 · 上一篇:GPU任务队列 · 下一篇:逆向渲染。











