CSharpGL(32)矩陣與四元數與角度旋轉軸的相互轉換
三維世界里的旋轉(rotate),可以用一個3x3的矩陣描述;可以用(旋轉角度float+旋轉軸vec3)描述。數學家歐拉證明了這兩種形式可以相互轉化,且多次地旋轉可以歸結為一次旋轉。這實際上就是著名的軌跡球(arcball)方式操縱模型的理論基礎。
本文中都設定float angleDegree為旋轉角度,vec3 axis為旋轉軸。

四元數
定義(angleDegree+axis到四元數)
四元數就是一個四維向量(w, x, y, z),其中w描述旋轉的角度(但不是直接的angleDegree值),(x, y, z)描述旋轉軸。從angleDegree和axis得到一個四元數的方式比較簡單。
1 public struct Quaternion 2 { 3 private float w; 4 private float x; 5 private float y; 6 private float z; 7 8 /// <summary> 9 /// Quaternion from a rotation angle and axis. 10 /// </summary> 11 /// <param name="angleDegree">angle in degree.</param> 12 /// <param name="axis">rotation axis.</param> 13 public Quaternion(float angleDegree, vec3 axis) 14 { 15 vec3 normalized = axis.normalize(); 16 double radian = angleDegree * Math.PI / 180.0; 17 double halfRadian = radian / 2.0; 18 this.w = (float)Math.Cos(halfRadian); 19 float sin = (float)Math.Sin(halfRadian); 20 this.x = sin * normalized.x; 21 this.y = sin * normalized.y; 22 this.z = sin * normalized.z; 23 } 24 }
先別管為什么四元數是這么定義的,只要知道這個定義就好。這里引入四元數只是為了方便提取出矩陣中蘊含的angleDegree和aixs。四元數的其他用途本文不涉及。
四元數到angleDegree+axis
從上面的定義可以很容易推算出四元數里蘊含的angleDegree和axis。顯然得到的axis已經失去了原有的長度,但是axis的長度并不重要,保持在單位長度才是最方便的。
1 public void Parse(out float angleDegree, out vec3 axis) 2 { 3 angleDegree = (float)(Math.Acos(w) * 2 * 180.0 / Math.PI); 4 axis = (new vec3(x, y, z)).normalize(); 5 }
四元數到矩陣
從四元數到矩陣的推導有點復雜,有很多相關文章,本文就只貼代碼了。代碼還是很簡練的。
1 /// <summary> 2 /// Transform this quaternion to equivalent matrix. 3 /// </summary> 4 /// <returns></returns> 5 public mat3 ToRotationMatrix() 6 { 7 vec3 col0 = new vec3( 8 2 * (x * x + w * w) - 1, 9 2 * x * y + 2 * w * z, 10 2 * x * z - 2 * w * y); 11 vec3 col1 = new vec3( 12 2 * x * y - 2 * w * z, 13 2 * (y * y + w * w) - 1, 14 2 * y * z + 2 * w * x); 15 vec3 col2 = new vec3( 16 2 * x * z + 2 * w * y, 17 2 * y * z - 2 * w * x, 18 2 * (z * z + w * w) - 1); 19 20 return new mat3(col0, col1, col2); 21 }

矩陣到四元數
矩陣到四元數的推導也有點復雜,借助了一些數學技巧,本文不詳述,直接貼代碼。

1 /// <summary> 2 /// Transform this matrix to a <see cref="Quaternion"/>. 3 /// </summary> 4 /// <returns></returns> 5 struct mat3 6 { 7 public Quaternion ToQuaternion() 8 { 9 // input matrix. 10 float m11 = this.col0.x, m12 = this.col1.x, m13 = this.col2.x; 11 float m21 = this.col0.y, m22 = this.col1.y, m23 = this.col2.y; 12 float m31 = this.col0.z, m32 = this.col1.z, m33 = this.col2.z; 13 // output quaternion 14 float x = 0, y = 0, z = 0, w = 0; 15 // detect biggest in w, x, y, z. 16 float fourWSquaredMinus1 = +m11 + m22 + m33; 17 float fourXSquaredMinus1 = +m11 - m22 - m33; 18 float fourYSquaredMinus1 = -m11 + m22 - m33; 19 float fourZSquaredMinus1 = -m11 - m22 + m33; 20 int biggestIndex = 0; 21 float biggest = fourWSquaredMinus1; 22 if (fourXSquaredMinus1 > biggest) 23 { 24 biggest = fourXSquaredMinus1; 25 biggestIndex = 1; 26 } 27 if (fourYSquaredMinus1 > biggest) 28 { 29 biggest = fourYSquaredMinus1; 30 biggestIndex = 2; 31 } 32 if (fourZSquaredMinus1 > biggest) 33 { 34 biggest = fourZSquaredMinus1; 35 biggestIndex = 3; 36 } 37 // sqrt and division 38 float biggestVal = (float)(Math.Sqrt(biggest + 1) * 0.5); 39 float mult = 0.25f / biggestVal; 40 // get output 41 switch (biggestIndex) 42 { 43 case 0: 44 w = biggestVal; 45 x = (m23 - m32) * mult; 46 y = (m31 - m13) * mult; 47 z = (m12 - m21) * mult; 48 break; 49 50 case 1: 51 x = biggestVal; 52 w = (m23 - m32) * mult; 53 y = (m12 + m21) * mult; 54 z = (m31 + m13) * mult; 55 break; 56 57 case 2: 58 y = biggestVal; 59 w = (m31 - m13) * mult; 60 x = (m12 + m21) * mult; 61 z = (m23 + m32) * mult; 62 break; 63 64 case 3: 65 z = biggestVal; 66 w = (m12 - m21) * mult; 67 x = (m31 + m13) * mult; 68 y = (m23 + m32) * mult; 69 break; 70 71 default: 72 break; 73 } 74 75 return new Quaternion(w, -x, -y, -z); 76 } 77 }
好了,現在矩陣 ⇋ 四元數 ⇋ (angleDegree+axis)之間的轉換就全有了。
BTW,OpenGL里的glRotate{fd}(angle, axis)里的angle是以角度為單位的。為了統一,我將CSharpGL里的所有angle都設定為以角度為單位了。
下載
CSharpGL已在GitHub開源,歡迎對OpenGL有興趣的同學加入(https://github.com/bitzhuwei/CSharpGL)
總結
現在解決了矩陣與(angleDegree+axis)之間的轉換問題,就可以從容地解析軌跡球算出的旋轉矩陣,抽取出里面蘊含的(angleDegree+axis)了。這就可以單獨更新模型的旋轉角度和旋轉軸,避免了對整個模型矩陣的破壞。
文章列表