如何实现Unity中关节四元数到球面角的逆转换函数
逆函数实现思路与代码
核心原理拆解
原函数ComputeSphericalTarget的本质是将仰角/方位角对应的世界空间旋转增量,转换为关节局部空间的目标旋转。逆函数需要反向推导:从关节局部旋转还原出世界空间旋转增量,再从增量中拆解出两个轴的旋转角度。
步骤1:还原世界空间旋转增量
原函数的输出targetLocal(关节局部旋转)满足:
targetLocal = Quaternion.Inverse(P) * deltaW * P * bindRelative
其中P是父物体世界旋转,deltaW是仰角+方位角对应的世界空间旋转增量。变形可得:
deltaW = P * targetLocal * Quaternion.Inverse(bindRelative) * Quaternion.Inverse(P)
这里的targetLocal就是需要反推的关节当前局部旋转。
步骤2:从旋转增量中提取角度
deltaW是先绕liftW转仰角,再绕hangW转方位角的复合旋转。利用旋转轴的特性拆解:
- 绕liftW的仰角旋转只会改变hangW的方向(且保持与liftW正交),绕hangW的方位角旋转不会改变hangW与其他向量的夹角,因此可通过
deltaW * hangW与原始hangW的夹角得到仰角。 - 绕liftW的仰角旋转不会改变liftW的方向,绕hangW的方位角旋转会改变liftW的方向,因此可通过
deltaW * liftW与原始liftW的夹角得到方位角。
完整逆函数代码
public static (float elevationDeg, float azimuthDeg) DecomposeSphericalTarget( ConfigurableJoint joint, Quaternion bindRelative, Quaternion currentJointLocalRotation, Vector3 liftAxisParentLocal, Vector3 hangAxisParentLocal ) { // 边界情况:无关节或父物体,直接从欧拉角反推 if (joint == null || joint.connectedBody == null) { Quaternion deltaLocal = currentJointLocalRotation * Quaternion.Inverse(bindRelative); Vector3 euler = deltaLocal.eulerAngles; // 处理欧拉角0-360的循环问题,转换为-180到180范围 float elevation = euler.x > 180f ? euler.x - 360f : euler.x; float azimuth = euler.z > 180f ? euler.z - 360f : euler.z; return (elevation, azimuth); } Quaternion P = joint.connectedBody.rotation; Vector3 liftW = (P * liftAxisParentLocal).normalized; Vector3 hangW = (P * hangAxisParentLocal).normalized; // 确保hangW与liftW正交,避免轴不正交导致的角度误差 hangW = Vector3.ProjectOnPlane(hangW, liftW).normalized; // 计算世界空间的旋转增量deltaW Quaternion deltaW = P * currentJointLocalRotation * Quaternion.Inverse(bindRelative) * Quaternion.Inverse(P); // 提取仰角elevationDeg Vector3 rotatedHang = deltaW * hangW; float elevationAbs = Vector3.Angle(hangW, rotatedHang); Vector3 crossHang = Vector3.Cross(hangW, rotatedHang); float elevationDir = Mathf.Sign(Vector3.Dot(crossHang, liftW)); float elevationDeg = elevationAbs * elevationDir; // 提取方位角azimuthDeg Vector3 rotatedLift = deltaW * liftW; float azimuthAbs = Vector3.Angle(liftW, rotatedLift); Vector3 crossLift = Vector3.Cross(liftW, rotatedLift); float azimuthDir = Mathf.Sign(Vector3.Dot(crossLift, hangW)); float azimuthDeg = azimuthAbs * azimuthDir; return (elevationDeg, azimuthDeg); }
关键细节说明
- 轴正交化处理:强制将hangW投影到liftW的垂直平面,避免因原始轴不正交导致的角度计算偏差,确保球面旋转的逻辑一致性。
- 欧拉角范围修正:边界情况中,将欧拉角从0-360转换为-180到180的范围,与原函数输入的角度范围保持一致。
- 旋转方向判断:通过叉乘与轴的点积符号判断旋转方向(顺时针/逆时针),保证角度的正负正确性。
内容的提问来源于stack exchange,提问作者guru_laghima
相关产品推荐
相关产品推荐

