万向锁(Gimbal Lock)是使用欧拉角(特别是三个旋转轴依次旋转的方式)表示三维旋转时出现的一种现象。简单来说,就是在某种特定姿态下,原本应该独立的两个旋转轴变得共线(重合),导致系统丢失了一个旋转自由度,无法区分这两个旋转。
直观理解
想象一个放在桌上的手机:
- 绕 Z 轴旋转(偏航):手机在桌面上水平转动。
- 绕 Y 轴旋转(俯仰):手机的前后仰合。
- 绕 X 轴旋转(横滚):手机的左右侧翻。
当俯仰角(绕 Y 轴)旋转到 ±90° 时,手机完全竖立起来(屏幕朝前或朝后)。此时,绕 Z 轴(偏航)和绕 X 轴(横滚)的旋转方向在空间中变得完全一致。
- 现象:你无法区分当前是“偏航”在转动还是“横滚”在转动。这两个轴“锁”在一起了,你失去了其中一个方向的调节能力。
数学解释
以固定轴 X-Y-Z 顺序(RPY角)为例,总旋转矩阵为:
[
R = R_z(\gamma) \cdot R_y(\beta) \cdot R_x(\alpha)
]当 (\cos\beta = 0) 时(即 (\beta = \pm 90^\circ)),旋转矩阵简化为:
[
R =
\begin{bmatrix}
0 & -\sin(\alpha \mp \gamma) & \cos(\alpha \mp \gamma) \
0 & \cos(\alpha \mp \gamma) & \sin(\alpha \mp \gamma) \
\pm 1 & 0 & 0
\end{bmatrix}
]
(具体符号取决于 (\beta) 的正负)
- 丢失自由度:矩阵中只出现了 (\alpha – \gamma) 或 (\alpha + \gamma) 的组合。这意味着无论你如何单独改变 (\alpha)(横滚)或 (\gamma)(偏航),只要它们的和或差不变,旋转效果就完全相同。
- 奇异性:原本需要两个独立角度((\alpha) 和 (\gamma))描述的自由度,现在只需要一个数值(它们的组合)就能描述。系统降维了,这就是万向锁的数学表现。
为什么叫“锁”?
这个术语源于物理陀螺仪中的万向节结构。当三个嵌套的环(对应三个旋转轴)中的中间环转到特定角度时,最内环和最外环的旋转轴会重叠,导致内环无法在外环的垂直方向上进行调节,仿佛被“锁住”一样。
重要性与解决方案
万向锁是欧拉角表示法固有的数学模型缺陷,而非物理世界的限制。物体本身依然可以自由旋转到任意方向,只是用欧拉角这种参数化方式无法平滑地描述某些路径。
因此,在需要平滑插值(如动画、飞行控制)的领域,通常会使用四元数或旋转矩阵来替代欧拉角,因为它们没有万向锁问题。
是的,之前抱怨CLI矩阵乘法水平不行,昨天今天拿了它最近搞不定的case决定让它死磕。发现它还是搞不定,不断查代码、重构、跟踪变量。最好笑还是纠结着Mat4::New,到底是行优先还是列优先参数,行优先还是列优先存储……明明对了又改错了。
最后我让deepseek也看了相关case,deepseek让我留意万向锁这玩意,这边调试也做到最最精简的一步,发现欧拉角的转换在万向锁附近变得不可预测了。
于是CLI告知我这就是万向锁。我想,我自己也差不多了解了,问题是,早几天你干嘛去了,本来我还想把这些case直接忽视了。CLI之前面对出来的角和矩阵还死不认错,反复在文本中断章取义出增强它判断的字眼(比如,用户刚才说xxx就是yyy)
一不小心让自己复习了三维转换矩阵的玩法。
也许换个强一点的AI就没这个问题了。
都把vibe coding玩成古法编程了。