欧拉角会死锁是因为俯仰角±90°时旋转轴重合导致自由度丢失,而四元数用单位球面上唯一点表示旋转,无坐标系依赖,slerp插值天然平滑,从根本上避免奇点。

为什么欧拉角会死锁,而四元数不会
万向节死锁不是硬件问题,是欧拉角表示法的数学缺陷:当第二个旋转轴(通常是俯仰 pitch)达到 ±90° 时,第一次和第三次旋转轴会重合,自由度丢失。此时 glm::eulerAngles() 或手动组合 rotateX * rotateY * rotateZ 会突然跳变、插值失真、导数不连续。
四元数用 4 个实数 (w, x, y, z) 表示三维空间中的单一旋转,没有中间坐标系依赖,球面线性插值(slerp)天然平滑,且所有合法旋转都对应单位球面上唯一点(除反向四元数外),从根本上绕开了轴顺序和奇点问题。
用 glm::quat 正确构造和组合旋转
别从欧拉角“转换”过来再修——那是把病根带进来。直接用四元数原语构建:
-
glm::quat rotX = glm::angleAxis(glm::radians(45.0f), glm::vec3(1,0,0))—— 绕 X 轴转 45° -
glm::quat rotY = glm::angleAxis(glm::radians(-30.0f), glm::vec3(0,1,0))—— 绕 Y 轴转 -30° -
glm::quat total = rotY * rotX—— 注意顺序:右乘表示“先 X 后 Y”,和矩阵左乘相反 - 避免
glm::quat(euler)构造函数,它隐式做了欧拉→四元数转换,仍可能继承输入角度的奇点敏感性
插值必须用 slerp,别用 lerp
对两个四元数做线性插值(glm::lerp(a, b, t))会导致速度不均、旋转轴漂移,尤其当 a 和 b 夹角大时,路径会“抄近道”穿过单位球内部,违反旋转约束。
立即学习“C++免费学习笔记(深入)”;
正确做法是球面线性插值:
-
glm::quat interpolated = glm::slerp(q_start, q_end, t),其中t ∈ [0,1] - 确保输入是单位四元数:
q_start = glm::normalize(q_start),q_end = glm::normalize(q_end),否则slerp行为未定义 - 若插值跨度大(如 >120°),可先检测点积符号:
if (glm::dot(q_start, q_end) ,强制走短弧
转回矩阵或欧拉角时的陷阱
你几乎不需要转回去——渲染管线(如 OpenGL/Vulkan)接受 glm::mat4_cast(q),动画系统可直接存 glm::quat。但若真要转:
-
glm::mat4_cast(q)是安全的,无奇点;但glm::eulerAngles(q)又会掉回欧拉角坑里,仅适合调试输出 - 不要对插值后的四元数反复做
eulerAngles → 修改某分量 → 再转回 quat,这等于在四元数壳子里继续用欧拉角思维 - 如果必须调整单个旋转分量(比如只改偏航 yaw),应分解为:提取当前 forward/up 向量 → 绕 world Y 轴旋转 → 用
glm::quatLookAt或正交化重建四元数
真正难的不是写对四元数,而是戒掉“我想调一下 pitch 角度”的直觉——那说明你还在用欧拉角的脑回路想问题。


















