CSharpGL(32)矩阵与四元数与角度旋转轴的相互转换

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)了。这就可以单独更新模型的旋转角度和旋转轴,避免了对整个模型矩阵的破坏。

原文地址:https://www.cnblogs.com/bitzhuwei/p/CSharpGL-32-transform-between-matrix-and-angle-axis.html