计算机图形学 29:两个姿态之间怎样运动
一根杆从 170° 转到 −170°,终点只相隔 20°。直接混合两个角度,参数中点却是 0°,杆绕了 340°。更换成四元数还不够:同一个旋转有两个反号表示,若选错端点符号,插值仍可能走长路。
本篇固定两秒动画,比较朴素角度插值、最短弧 SLERP 与归一化线性插值。CPU 检查姿态、时间输入和角速度,浏览器真实绘制并提供拖动、播放、暂停。不同采样频率的验收比较同一绝对时刻,避免把“每帧加一点”造成的速度差误认为插值误差。
两个角度数字没有指定中间路线
打开关键帧实验。点击“核对CPU样本与GPU画面”,将时间放在 1000 毫秒,再切换插值方式。最短弧中点指向左,朴素角度中点指向右。
局部杆端点为 p=(1,0,0),绕 +z 轴旋转,沿用第 01 篇右手系。正角将 +x 转向 +y;计算使用弧度,表格和图中使用度。时间以秒输入,屏幕显示毫秒滑块,两者相差 1000 倍。
起止角度 θ₀=170°、θ₁=−170°,时刻 t∈[0,2] 对应 u=t/2。直接线性混合得到:
t=1 时 θ=0°,杆端点为 (1,0,0)。将终点 −170° 展开成等价的 190°,则得到 θshort=170°+20°u,中点为 180°,端点为 (−1,0,0)。两条路线起止姿态相同,路径和角速度方向不同。
因此不能说“欧拉角插值一定走错”。本例只有单轴旋转,显式展开到 190°也能得到所需短路。三维多轴旋转还涉及旋转次序和角度坐标退化,四元数提供另一种统一的旋转表示,但同样需要选择路线。
上图来自 C++ 实际求值:上排朴素角度,下排最短弧,分别在 0、0.5、1、1.5、2 秒生成杆端点,再写成 SVG。圆与杆是计算结果的诊断显示,没有光照、透视和色彩积分;它不替代数值验收。
四个分量怎样表达一个旋转
采用 glTF 的 XYZW 顺序,q=(qx,qy,qz,qw),最后一个分量是标量。给定单位旋转轴 a 和角度 θ,单位四元数为:
旋转使用半角,不是把普通三维方向再增加一个任意坐标。对纯向量四元数 (p,0),旋转可写 q(p,0)q⁻¹。当前实现用等价的叉积形式,复用累计 Vec3:
此式要求 q 为单位四元数。rotate 的入口先归一化;非零有限的非单位输入按其方向归一化,零、NaN 和无穷明确拒绝。归一化先除最大分量,再求长度,避免有限的大分量在平方时直接溢出。它不承诺任意尺度的向量叉积都无溢出,输出仍检查有限值。
把 q 的四个分量全部反号,上式中的两个符号相互抵消,因此 q 与 −q 表示同一个旋转。只翻 z 而不翻 w 会改变旋转,不能称为“统一符号”。测试既覆盖 z 轴,也覆盖 normalize(1,2,3) 的一般轴,防止只对本例恰好成立。
判断两个四元数是否表示同一姿态,应比较旋转后的基向量,或使用含 |q₀·q₁| 的旋转距离。本实验检查三个单位基向量的最大欧氏差;直接比较四个分量会把正常的反号表示当成错误。
插值前先选球面上的端点
单位四元数位于四维单位球面。两个端点的点积 d=q₀·q₁ 是球面夹角的余弦。若 d<0,将 q₁ 整体反号,姿态不变,却选择了距离更近的球面端点。之后将点积钳制到 [0,1],防止舍入使 acos 接收超出定义域的输入。
记球面夹角 α=acos(d),SLERP 为:
物理旋转角是四元数球面角的两倍。本例原始点积为 cos170°<0;翻号后球面夹角为 10°,对应物体转动 20°。在 u=0.5 处得到 180°姿态,而不是通过零度。
当端点接近重合时,sinα 接近零,公式的分子与分母都很小。实现以 d>0.9995 切换为归一化线性混合,避免数值退化。这是选用的实现阈值,与 Khronos 教程示例相同,不能称为 glTF 规范规定的阈值。
q₁=−q₀ 的同姿态情形,经翻号成为完全相同端点,混合不会凭空转一圈。物理相差 180°时,球面角则是 90°,sinα=1,并不发生同一种分母退化;此时两条最短物理路线等长,需要明确确定性的端点选择。测试使用 identity 到 (0,0,1,0),选择绕 +z 的半圈。
本例的符号负对照故意省略翻号。C++ 中混合中点回到零度;浏览器 JavaScript 的长弧球面插值也得到该中点,杆端点与正确结果相差 2 个单位。浏览器按钮核对的是 JavaScript 姿态对 CPU 样本,不是一个错误 WGSL 的 GPU 负对照。
走同一条弧,速度仍可能不同
归一化线性插值 NLERP 先算 (1−u)q₀+uq₁,再归一化。经过相同短弧,但参数推进一般不是恒定角速度。把球面起点放在 (1,0),终点放在 (cosα,sinα),混合方向的角度为:
对 u 求导,可独立得到:
分母随 u 变化;物体角速度为 2β′du/dt。SLERP 在其精确分支中 β=uα,因此线性时间下物理角速度为 2α/T。这里的“恒定”既依赖插值方法,也依赖时间参数,不能略掉后者。
程序分别取 20°与 120°的两秒短弧,按 0.02 秒的相邻端点,用有符号 atan2 求实际角增量,再除以时间差。120°的结果如下,完整 200 行见 animation29-speed.csv。
| 方法 | 相邻样本平均角速度范围,度/秒 |
|---|---|
| SLERP | 60,最大偏差约 1.50×10⁻¹² |
| NLERP | 49.8676861 到 66.1565273 |
表格中的 NLERP 数值是有限区间的平均速度,不是任意时刻导数的精确最大最小值。它足以展示中间更快、端点附近更慢;20°小弧的速度变化更小,也说明相近端点回退的代价有条件。
如果将 u=t/T 改成 ease 函数 e(t/T),即便使用 SLERP,角速度仍要乘 e′/T。多段关键帧在接点处还可能改变旋转轴或速度。第 13 篇的位置连续与速度连续之分,在旋转动画里同样成立;本篇没有实现多段速度平滑或球面样条。
显示频率不能成为动画的时间单位
RotationTrack 保存严格递增的关键帧秒数。求值时先夹到首尾姿态,区间内找到相邻键,再算 u=(t−tk)/(tk+1−tk)。乱序、重复时间、非有限时间和空轨迹拒绝;单个关键帧则恒定返回这个姿态。
这是自有内存求值器,不是 glTF 导入器。glTF 还要求首个输入时间非负;本求值器允许有限的负关键帧时间,方便以任意原点描述运动。它保留严格递增与有限区间约束,不能据此宣称完整 glTF 兼容。
若每帧固定加 1/60 秒,30 Hz 显示下动画会变慢,144 Hz 下会变快。浏览器播放用 performance 的绝对时间减去播放原点;暂停保存当前位置,重新播放调整原点,拖动时间停止播放并重新求值,不依赖先前走过多少帧。
CPU 分别按 30、60、144 Hz 求出两秒内含端点的 61、121、289 个样本。每个都与独立解析角度 170°+10°t 的基向量结果比较。共同时间为 t=m/6 秒,三个频率各 13 个,共 39 次比较。
| 检查 | 实际最大基向量误差 |
|---|---|
| 全部 471 个样本与解析姿态 | 1.04738×10⁻¹⁵ |
| 39 个共同时间求值 | 0 |
| 跳过采样与倒退 seek | 4.99600×10⁻¹⁶ |
共同时间误差为零只描述本次双精度求值,没有宣称任意设备位级相同。显示频率改变的是轨迹的取样密度;低密度仍可能看见跳动,掉帧也不会由正确时间公式自动消失。本实验没有测实际显示器刷新率或性能。
浏览器画面检查到什么程度
浏览器读取 C++ 生成的九个时刻样本,独立 JavaScript 插值后比较杆端点,最大差为 4.44956×10⁻¹⁶,阈值 10⁻¹²。这里检查时间与旋转计算;完整三个基向量和一般轴由 CPU 检查承担,不能把一个端点的对照扩大成所有旋转都通过。
WGSL 顶点着色器接收 16 字节 XYZW uniform,用叉积公式旋转由六个顶点组成的杆。画布为 256×256,局部显示杆长 0.8,绿色是固定诊断码值,不是光照结果。渲染到真实 GPU 附件后读回中点图:左侧内部像素 (36,128) 为 [0,255,0,255],右侧 (220,128) 为 [0,0,0,255]。
这验证了中点方向和一次真实绘制,不是 GPU 全轨迹逐帧精度验证。时间拖动、切换角度模式、播放与暂停另在真实页面操作成功。设备报告 apple、metal-3、Chrome154,device/description 为空,不猜具体硬件型号。
初始化期间允许操作输入会产生竞态:设备已创建,管线和 uniform 却尚未就绪。实现以 ready 状态保护绘制,检查期间忽略时间与模式事件;初始化失败提示重新加载。Node 的延迟管线 mock 检查该保护和暂停、重启时只保留一个帧回调,不把 mock 当成真实 GPU 证据。
练习与复跑
练习一:仍从 170°到 −170°,但时长改为四秒。t=1 秒的短弧姿态是多少?若仍每帧固定推进 1/60 秒,在 30 Hz 下播放四秒会到哪里?
解题要点:u=1/4,姿态为 175°。错误推进只积累两秒动画时间,四秒墙钟后位于 180°而不是终点。这区分了时间映射和旋转路线。
练习二:identity 到 (0,0,1,0) 的球面夹角和物理角分别是多少?它与 q 到 −q 的分母问题一样吗?
解题要点:点积零,球面角 90°、物理角 180°,sinα=1。q 到 −q 表示相同姿态,选短弧后端点相同,走近重合回退;二者不能混为“都是反向”。
练习三:把单位 q 乘以 7,又把结果整体反号,能否仅用四分量差验收姿态?若只核对杆的一个端点,能否确认绕该杆轴的扭转也正确?
解题要点:先归一化后姿态不变,四分量可能相反,应比较旋转结果或符号不变距离。一个轴上的端点不能感知绕轴扭转,需增加不共线向量或比较完整旋转;本篇 CPU 使用三个基向量。
1 | |
程序固定次数结束,没有随机数或性能统计。quaternion.hpp、animation29_check.cpp 与 gpu29.mjs 是累计入口;生成 JSON、SVG、CSV 保留实际输出。帮助、缺参数与不存在输出目录的失败另有记录。
参考资料与导航
- glTF 2.0.1 §3.5.3,XYZW 与单位旋转;§5.8.1,输入时间条件;Appendix C.4,SLERP 与符号选择。
- Khronos 动画教程的 Linear Interpolation,接近重合的归一化线性回退示例。
- PBRT 第三版 §2.9.2,四元数球面插值。数值对照、速度推导与失败策略是本篇有界实验,实际核验见
writing-plans/computer-graphics/evidence/research-29.md和29-review.md。






