四元数转欧拉角可行但需严格约定旋转顺序(如ZYX),且在俯仰角±90°时因万向锁导致roll/yaw耦合、解不唯一;必须使用归一化四元数,用asin/atan2求解时pitch被限于[−π/2, π/2],而欧拉角转四元数则须按顺序(如q=q_zq_yq_x)和半角公式计算,顺序错误或未转弧度将导致结果完全错误。

四元数转欧拉角:注意旋转顺序和奇异点
直接用 atan2 和 asin 从四元数分量解出欧拉角是可行的,但结果依赖于你约定的旋转顺序(如 ZYX、XYZ),且在俯仰角接近 ±90° 时会出现万向锁——此时 roll 和 yaw 耦合,pitch 的 asin 输出虽合法,但其余两角无法唯一确定。
常见做法是按 ZYX(即 yaw→pitch→roll)顺序推导,对应旋转矩阵 R = Rz(ψ)Ry(θ)Rx(φ),再与四元数构造的 R 对比得:
float pitch = asin(-2.0f * (q.x*q.z - q.w*q.y)); float roll = atan2(2.0f * (q.y*q.z + q.w*q.x), q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z); float yaw = atan2(2.0f * (q.x*q.y + q.w*q.z), q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z);
注意:q.w 是实部,q.x/q.y/q.z 是虚部;所有输入四元数必须已归一化,否则 asin 可能越界报错;pitch 范围被限制在 [-π/2, π/2],这是 ZYX 顺序下的自然结果。
欧拉角转四元数:顺序错一位,结果全错
欧拉角本身不携带顺序信息,所以必须显式指定旋转顺序。最常用的是 ZYX(航向-俯仰-滚转),对应四元数乘法顺序为 q = q_z * q_y * q_x(注意:右乘惯例下,先转的写在右边)。
立即学习“C++免费学习笔记(深入)”;
若用 yaw(ψ)、pitch(θ)、roll(φ)表示,则:
float cy = cos(yaw * 0.5f), sy = sin(yaw * 0.5f); float cp = cos(pitch * 0.5f), sp = sin(pitch * 0.5f); float cr = cos(roll * 0.5f), sr = sin(roll * 0.5f); <p>q.w = cr <em> cp </em> cy + sr <em> sp </em> sy; q.x = sr <em> cp </em> cy - cr <em> sp </em> sy; q.y = cr <em> sp </em> cy + sr <em> cp </em> sy; q.z = cr <em> cp </em> sy - sr <em> sp </em> cy;
常见错误包括:sin/cos 参数没除以 2;顺序写成 qx * qy * qz(这对应 XYZ 顺序);角度单位用度数没转弧度;未检查输入角度是否溢出 float 表示范围(一般不会,但批量处理时建议加断言)。
用 Eigen 或 GLM 库时,别自己手算
Eigen 的 Eigen::Quaternionf 不直接提供欧拉角接口,需经 toRotationMatrix() 再调 eulerAngles(i,j,k),例如:q.toRotationMatrix().eulerAngles(2,1,0) 返回 ZYX 顺序的列向量(单位:弧度)。
GLM 更直接:glm::eulerAngles(q) 默认返回 XYZ 顺序的 vec3(单位:弧度),而 glm::quat_cast(mat) 可从矩阵还原四元数。注意:glm::eulerAngles 永远按 XYZ 顺序,不能改——如果原始数据是 ZYX,得自己重排分量或换库。
关键提醒:glm::eulerAngles 在 pitch 接近 ±π/2 时仍会返回值,但此时 roll 和 yaw 已失真,需业务层判断并处理(比如 clamping pitch 或切换控制逻辑)。
精度与性能:别在循环里反复转
一次四元数 ↔ 欧拉角转换涉及约 20 次浮点运算(含多个 sin/cos/atan2),比纯四元数乘法贵一个数量级。若在动画插值或物理更新循环中高频调用,会明显拖慢帧率。
更稳妥的做法是:
- 内部全程用四元数做插值和组合
- 仅在用户编辑、调试输出、或对接老接口时才转一次欧拉角
- 避免“转过去 → 改一个角 → 转回来”这种模式,应直接对四元数做局部旋转(如绕某轴转 Δθ:用
q_new = q * quat(axis, delta))
另外,atan2 和 asin 在某些嵌入式平台或旧编译器上可能有精度差异,若用于飞控等高可靠性场景,建议预生成查找表或用泰勒展开替代(但通常没必要)。


















