文章出處

CSharpGL(32)矩陣與四元數與角度旋轉軸的相互轉換

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

本文中都設定float angleDegree為旋轉角度,vec3 axis為旋轉軸。

+BIT祝威+悄悄在此留下版了個權的信息說:

四元數

+BIT祝威+悄悄在此留下版了個權的信息說:

定義(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。四元數的其他用途本文不涉及。

+BIT祝威+悄悄在此留下版了個權的信息說:

四元數到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         }
+BIT祝威+悄悄在此留下版了個權的信息說:

四元數到矩陣

從四元數到矩陣的推導有點復雜,有很多相關文章,本文就只貼代碼了。代碼還是很簡練的。

 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         }
實際上得到的矩陣就是這樣的:
+BIT祝威+悄悄在此留下版了個權的信息說:

矩陣到四元數

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

 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     }
matrix to quaternion

好了,現在矩陣 ⇋ 四元數 ⇋ (angleDegree+axis)之間的轉換就全有了。

BTW,OpenGL里的glRotate{fd}(angle, axis)里的angle是以角度為單位的。為了統一,我將CSharpGL里的所有angle都設定為以角度為單位了。

+BIT祝威+悄悄在此留下版了個權的信息說:

下載

CSharpGL已在GitHub開源,歡迎對OpenGL有興趣的同學加入(https://github.com/bitzhuwei/CSharpGL

 

+BIT祝威+悄悄在此留下版了個權的信息說:

總結

現在解決了矩陣與(angleDegree+axis)之間的轉換問題,就可以從容地解析軌跡球算出的旋轉矩陣,抽取出里面蘊含的(angleDegree+axis)了。這就可以單獨更新模型的旋轉角度和旋轉軸,避免了對整個模型矩陣的破壞。

 


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()