1. 程式人生 > >萬向節死鎖產生的原因

萬向節死鎖產生的原因

萬向節死鎖產生的根本原因是,旋轉矩陣是依次進行的,假設先圍繞x軸旋轉,再圍繞y軸旋轉,最後圍繞z軸旋轉,這就導致物體其實是圍繞自己的X軸旋轉,而不是世界座標的X軸旋轉。

表現就是,在一個尤拉角(x,y,z)下,改變x的值,物體會圍繞物體自己的x軸進行旋轉,而不是世界座標系的x軸進行旋轉。


下圖所示,改變x值,都是在繞自己的紅色的軸(x軸)轉。RGB三個顏色分別對應XYZ正半軸:



首先,旋轉3D模型時,實際是對原始模型的頂點進行旋轉。比如旋轉(x1,y1,z1),就是把原始模型旋轉到這個角度,然後渲染顯示。當旋轉變成(x2,y2,z2),會把原始模型旋轉到這個角度,然後渲染顯示。


然後,進行一次旋轉時,物體是先圍繞x軸進行旋轉,這時候物體的區域性座標系和世界座標系是重合的,雖然是圍繞世界座標的x軸旋轉,但也是圍繞自己的x軸進行旋轉。最後得到的旋轉結果,也變成了物體是繞自己的x軸轉的結果了。


最後,當把物體的x軸旋轉到與世界的z軸重合時,歐垃角的x和z旋轉結果就都一樣了,也就丟失了一個維度。另一方面,比如在(30, 30, 30)的歐垃角下,把y從30調到60會發現並不是繞自己的y軸在轉,也不是繞世界座標的y旋轉。


可以自己通過尤拉角計算出頂點旋轉後的座標來驗證這個。


參考資料:

萬向節死鎖(Gimbal Lock)

尤拉角與萬向節死鎖(圖文版)

旋轉矩陣公式推導


附上自己的Unity測試程式碼:

using UnityEngine;

[ExecuteInEditMode]
public class TestEuler : MonoBehaviour
{
	public Vector3 Euler;

	public Vector3 OrgPosOfA;
	public Vector3 OrgPosOfB;
	public Vector3 OrgPosOfC;

	public Transform TransA;
	public Transform TransB;
	public Transform TransC;

	// Use this for initialization
	void Start()
	{
	}

	// Update is called once per frame
	void Update ()
	{
		TransA.position = Rotate(OrgPosOfA, Euler) + transform.position;
		TransB.position = Rotate(OrgPosOfB, Euler) + transform.position;
		TransC.position = Rotate(OrgPosOfC, Euler) + transform.position;
	}

	Vector3 Rotate(Vector3 pos, Vector3 euler)
	{
		pos = RotateX(pos, euler.x * Mathf.Deg2Rad);
		pos = RotateY(pos, euler.y * Mathf.Deg2Rad);
		pos = RotateZ(pos, euler.z * Mathf.Deg2Rad);
		return pos;
	}

	Vector3 RotateX(Vector3 pos, float r)
	{
		Vector3 newPos = pos;

		float cos = Mathf.Cos(r);
		float sin = Mathf.Sin(r);
		newPos.y = pos.y * cos + pos.z * sin;
		newPos.z = -pos.y * sin + pos.z * cos;

		return newPos;
	}

	Vector3 RotateY(Vector3 pos, float r)
	{
		Vector3 newPos = pos;

		float cos = Mathf.Cos(r);
		float sin = Mathf.Sin(r);
		newPos.x = pos.x * cos - pos.z * sin;
		newPos.z = pos.x * sin + pos.z * cos;

		return newPos;
	}

	Vector3 RotateZ(Vector3 pos, float r)
	{
		Vector3 newPos = pos;

		float cos = Mathf.Cos(r);
		float sin = Mathf.Sin(r);
		newPos.x = pos.x * cos + pos.y * sin;
		newPos.y = -pos.x * sin + pos.y * cos;

		return newPos;
	}

	void OnDrawGizmos()
	{
		//座標軸
		Gizmos.color = Color.red;
		Gizmos.DrawLine(transform.position, transform.position + Vector3.right);
		Gizmos.color = Color.green;
		Gizmos.DrawLine(transform.position, transform.position + Vector3.up);
		Gizmos.color = Color.blue;
		Gizmos.DrawLine(transform.position, transform.position + Vector3.forward);

		//旋轉後的點
		Gizmos.color = Color.red;
		Gizmos.DrawLine(transform.position, TransA.position);
		Gizmos.color = Color.green;
		Gizmos.DrawLine(transform.position, TransB.position);
		Gizmos.color = Color.blue;
		Gizmos.DrawLine(transform.position, TransC.position);
	}
}




下圖所示,改變x值,都是在繞紅色的軸(x軸)轉。RGB三個顏色分別對應XYZ正半軸: