基于特定旋转序列的两个四元数旋转差值计算方法咨询
解决方法
首先明确:四元数本身是姿态的无顺序表示,你用Quaternion difference = Quaternion.Inverse(rotation2) * rotation1计算出的差值四元数,其物理意义是固定的——它代表了从rotation2姿态变换到rotation1姿态所需的旋转,和任何欧拉角旋转序列无关。
你要的“对应六种旋转序列的差值”,本质是把这个统一的差值四元数,分解为指定旋转顺序的欧拉角分量(即把四元数拆成按某一顺序依次绕X/Y/Z轴旋转的角度)。
下面针对六种旋转序列,给出将差值四元数转换为对应欧拉角的实现(以Unity环境为例,角度单位为度):
1. XYZ顺序(先绕X轴,再绕Y轴,最后绕Z轴)
Vector3 GetEulerXYZ(Quaternion q) { float x = Mathf.Atan2(2 * (q.w * q.x + q.y * q.z), 1 - 2 * (q.x * q.x + q.y * q.y)); float y = Mathf.Asin(Mathf.Clamp(2 * (q.w * q.y - q.z * q.x), -1f, 1f)); float z = Mathf.Atan2(2 * (q.w * q.z + q.x * q.y), 1 - 2 * (q.y * q.y + q.z * q.z)); return new Vector3(Mathf.Rad2Deg * x, Mathf.Rad2Deg * y, Mathf.Rad2Deg * z); }
使用方式:Vector3 diffXYZ = GetEulerXYZ(difference);
2. XZY顺序(先绕X轴,再绕Z轴,最后绕Y轴)
Vector3 GetEulerXZY(Quaternion q) { float x = Mathf.Atan2(2 * (q.w * q.x + q.z * q.y), 1 - 2 * (q.x * q.x + q.z * q.z)); float z = Mathf.Asin(Mathf.Clamp(2 * (q.w * q.z - q.y * q.x), -1f, 1f)); float y = Mathf.Atan2(2 * (q.w * q.y + q.x * q.z), 1 - 2 * (q.y * q.y + q.z * q.z)); return new Vector3(Mathf.Rad2Deg * x, Mathf.Rad2Deg * y, Mathf.Rad2Deg * z); }
使用方式:Vector3 diffXZY = GetEulerXZY(difference);
3. YXZ顺序(先绕Y轴,再绕X轴,最后绕Z轴)
这是Unity内置Quaternion.eulerAngles采用的旋转顺序,直接用即可:
Vector3 diffYXZ = difference.eulerAngles;
手动实现版本:
Vector3 GetEulerYXZ(Quaternion q) { float y = Mathf.Atan2(2 * (q.w * q.y + q.x * q.z), 1 - 2 * (q.y * q.y + q.x * q.x)); float x = Mathf.Asin(Mathf.Clamp(2 * (q.w * q.x - q.z * q.y), -1f, 1f)); float z = Mathf.Atan2(2 * (q.w * q.z + q.y * q.x), 1 - 2 * (q.x * q.x + q.z * q.z)); return new Vector3(Mathf.Rad2Deg * x, Mathf.Rad2Deg * y, Mathf.Rad2Deg * z); }
4. YZX顺序(先绕Y轴,再绕Z轴,最后绕X轴)
Vector3 GetEulerYZX(Quaternion q) { float y = Mathf.Atan2(2 * (q.w * q.y + q.z * q.x), 1 - 2 * (q.y * q.y + q.z * q.z)); float z = Mathf.Asin(Mathf.Clamp(2 * (q.w * q.z - q.x * q.y), -1f, 1f)); float x = Mathf.Atan2(2 * (q.w * q.x + q.y * q.z), 1 - 2 * (q.x * q.x + q.z * q.z)); return new Vector3(Mathf.Rad2Deg * x, Mathf.Rad2Deg * y, Mathf.Rad2Deg * z); }
使用方式:Vector3 diffYZX = GetEulerYZX(difference);
5. ZXY顺序(先绕Z轴,再绕X轴,最后绕Y轴)
Vector3 GetEulerZXY(Quaternion q) { float z = Mathf.Atan2(2 * (q.w * q.z + q.x * q.y), 1 - 2 * (q.z * q.z + q.x * q.x)); float x = Mathf.Asin(Mathf.Clamp(2 * (q.w * q.x - q.y * q.z), -1f, 1f)); float y = Mathf.Atan2(2 * (q.w * q.y + q.z * q.x), 1 - 2 * (q.x * q.x + q.y * q.y)); return new Vector3(Mathf.Rad2Deg * x, Mathf.Rad2Deg * y, Mathf.Rad2Deg * z); }
使用方式:Vector3 diffZXY = GetEulerZXY(difference);
6. ZYX顺序(先绕Z轴,再绕Y轴,最后绕X轴)
Vector3 GetEulerZYX(Quaternion q) { float z = Mathf.Atan2(2 * (q.w * q.z + q.y * q.x), 1 - 2 * (q.z * q.z + q.y * q.y)); float y = Mathf.Asin(Mathf.Clamp(2 * (q.w * q.y - q.x * q.z), -1f, 1f)); float x = Mathf.Atan2(2 * (q.w * q.x + q.z * q.y), 1 - 2 * (q.x * q.x + q.y * q.y)); return new Vector3(Mathf.Rad2Deg * x, Mathf.Rad2Deg * y, Mathf.Rad2Deg * z); }
使用方式:Vector3 diffZYX = GetEulerZYX(difference);
注意事项
- 所有转换函数中的
Mathf.Clamp是为了避免浮点精度问题导致Asin输入超出[-1,1]范围,引发NaN错误。 - 欧拉角存在万向锁问题,若差值旋转接近万向锁姿态(比如绕Y轴旋转±90度时),对应的欧拉角分量会出现不确定性,这是欧拉角的固有缺陷,无法避免。
- 若需要将差值拆成对应旋转序列的四元数组合,只需将每个欧拉角分量转换成四元数后按顺序相乘即可。比如XYZ顺序:
Quaternion diffQ = Quaternion.Euler(x,0,0) * Quaternion.Euler(0,y,0) * Quaternion.Euler(0,0,z);
内容的提问来源于stack exchange,提问作者yogesh sriraman
相关产品推荐
相关产品推荐

