这个系列是GAMES001-图形学中的数学(GAMES 001: Mathematics in Computer Graphics)的同步课程笔记。课程旨在总结归纳图形学学习过程中重要的数学概念、理论和方法,并将以索引的形式在每一章节中穿插讲解该数学专题在图形学中的应用。本课程既可以作为GAMES系列其它课程学习的基础或”手册”,也可以作为站在不一样的视角复习图形学知识的平台。本节主要介绍三维旋转变换的相关内容。
旋转矩阵
旋转是图形学中最为基础的变换之一。在三维空间中,旋转通常表示为一个旋转矩阵。旋转矩阵可以描述绕不同坐标轴的旋转,其具体形式取决于旋转的轴和角度。

正交矩阵
对于更一般情况的旋转我们可能无法给出较为简单的表达式,不过根据旋转的性质可以证明旋转矩阵一定是一个正交矩阵。



特殊正交群
需要注意的是并不是所有的正交矩阵都是旋转矩阵。换句话说,旋转矩阵是正交矩阵的子集,只有特征值都是+1的正交矩阵才是我们所需要的旋转矩阵。我们定义满足特征值为+1的n维正交矩阵构成的集合称为特殊正交群(special orthogonal group),记为SO(n)。二维和三维空间中的旋转矩阵构成的集合分别是SO(2)和SO(3)。


旋转的性质
旋转矩阵具有以下重要性质:
- 结合律:\(R_2 (R_1 R_0) = (R_2 R_1) R_0\)
- 可逆性:\(R^{-1} = R^T\)
- 不满足交换律:\(R_1 R_0 \neq R_0 R_1\)




旋转的表示
尽管旋转矩阵可以用来描述一个旋转,在很多场景中旋转矩阵并不是一个最好的表达方式。因此,在不同的应用中往往会使用一些其它的旋转表达方式。




旋转自由度
在进一步介绍旋转的不同表示之前,我们需要明确旋转的自由度是多少,即我们需要多少个参数来描述一个旋转。对于二维的旋转,显然它的自由度是1;而在三维空间中,我们需要确定旋转轴以及转过的角度,因此其自由度为3。


流形
更加严格的说法是旋转矩阵是一个嵌入在高维空间的一个流形(manifold)。尽管旋转矩阵本身位于一个相对高维的空间,但是要确定旋转矩阵只需要使用少量的参数(自由度)。在流形概念的基础上,结合群的性质可以证明旋转构成李群(Lie group)。


引入流形视角的好处之一在于通过流形可以更好地对旋转进行计算。以旋转插值为例,直接对旋转矩阵进行插值往往会脱离旋转所在的流形,得到无效的旋转;而在流形上进行进行插值则可以保证每一步都能得到正确的旋转。

同时,基于流形的概念可以帮助我们更高层次地理解和定义一些物理量。比如说角速度就可以理解为流形切空间上的矢量。

除此之外,从流形的视角来看不同的旋转表示方法实际上就是对流形进行不同的参数化的过程。

欧拉角
欧拉角(Euler angle)是描述旋转最为直观的一种方式,它可以理解为将一个三维旋转分解成三次绕自身坐标轴的旋转。

在进一步介绍欧拉角前我们再从坐标变换的角度来理解一下旋转矩阵。假设空间中存在一个点\(p\),记它在世界坐标系和局部坐标系下的坐标分别为\(p_w\)和\(p_l\)。在一开始我们令两个坐标系重合,即
\[p_w = p_l = p\]经过旋转后,在世界坐标系下\(p\)点的坐标为
\[p_w' = R p\]而在局部坐标系中,\(p\)点的位置没有发生变化,即
\[p_l' = p\]此时,世界坐标系和局部坐标系下\(p\)点的坐标关系为
\[p_w' = R p_l'\]因此可以认为旋转矩阵\(R\)描述了局部坐标系到世界坐标系的坐标变换。

有了坐标变换的相关知识后我们可以开始推导欧拉角的表达。首先,我们定义三次旋转分别是绕着局部坐标系的z轴、x轴以及z轴进行,旋转角度分别为\(\alpha\)、\(\beta\)以及\(\gamma\)。这样,世界坐标系与第一次旋转后坐标系之间只相差一个旋转\(R_1\):
\[x_w = R_1 x_l^1\]类似地,第一次旋转与第二次旋转之间同样只相差一个旋转\(R_2\):
\[x_l^1 = R_2 x_l^2\]第二次旋转与第三次旋转只相差旋转\(R_3\):
\[x_l^2 = R_3 x_l^3\]这样,整个旋转矩阵\(R\)可以表示为三个旋转矩阵的乘积:
\[R = R_1 R_2 R_3 = Z(\alpha) X(\beta) Z(\gamma)\]
需要说明的是欧拉角对于三次旋转的组合是相当丰富的,使用时一定要注意是哪三个旋转以及它们的顺序。

除此之外,在使用欧拉角表示旋转时还存在内旋和外旋两种形式。所谓内旋(intrinsic rotations)是指每次进行旋转时都是绕着当前局部坐标系的某个轴进行旋转,我们目前定义的欧拉角就是内旋的;而外旋(extrinsic rotations)则是指每次的旋转都是绕着外部的某个固定轴,这个轴不会随着局部坐标系的转动而发生变化。

不难发现欧拉角可以比较直观地描述三维的旋转,因此在游戏表示相机运动以及各种三维建模软件中都会使用这种旋转表达方式。


欧拉角的一个缺陷在于在某些情况下会出现自由度被锁住的情况,这种现象称为万向锁(gimbal lock)。由于万向锁的存在,使用欧拉角来对旋转进行计算时可能会出现旋转的不稳定,因此在涉及对旋转的计算时往往不会使用欧拉角进行表示。

轴角
轴角(axis angle)是使用一个旋转轴\(\mathbf{u}\)以及绕着它进行旋转的角度\(\theta\)来表示旋转的方法。

当我们给定了旋转轴和旋转角度后,轴角表示可以通过Rodrigues公式(Rodrigues’ formula)来转换成旋转矩阵。类似地,任意旋转矩阵\(R\)同样可以转换成对应的轴角表示。


实际上,Rodrigues公式对应着SO(3)上李代数到李群的指数映射(exponential map)。

基于轴角表示我们可以计算两个旋转的插值。具体来说,设初始旋转为\(R_0\)、目标旋转为\(R_1\),我们可以定义这两个旋转之间的差异为
\[\Delta R = R_2 R_0^T\]接下来我们把\(\Delta R\)转换为轴角表示:
\[(\Delta \theta, \Delta \mathbf{u}) \leftarrow \Delta R\]这样我们可以固定旋转轴\(\Delta \mathbf{u}\),通过对旋转角度\(\Delta \theta\)进行插值来实现旋转的插值。

需要注意的是使用轴角表示进行插值时不要直接对两个旋转向量进行插值。尽管这种做法确实可以得到有效的旋转,但它往往不会产生我们期望的平滑稳定的旋转过渡效果。

四元数
四元数(quaternion)是图形学中最为常用的旋转表示方法。四元数可以看作是对复数的推广,我们知道二维平面上的旋转可以使用单位复数来表达,实际上每个单位复数都对应着一个二维旋转矩阵。

四元数在复数的基础上引入了两个额外的虚部来对应三维的旋转。三个虚部之间的乘法满足运算规则:
\[i^2 = j^2 = k^2 = ijk = -1\]
这样我们可以定义两个四元数的乘法:
\[\begin{aligned} q_1 q_2 &= (a + bi + cj + dk) (e + fi + gj + hk) \\ &= (ae - bf - cg + dh) + (be + af - dg + ch) i \\ &+ (ce + df + ag - bh)j + (de - cg + bg + ah) k \end{aligned}\]其矩阵形式为
\[q_1 q_2 = \begin{pmatrix} a & -b & -c & -d \\ b & a & -d & c \\ c & d & a & -b \\ d & -c & b & a \end{pmatrix} \begin{pmatrix} e \\ f \\ g\\ h \end{pmatrix}\]
四元数的乘法还可以使用Graßmann积来进行表示。此时,四元数的三个虚部可以使用一个三维向量来表示:
\[q = (u, \mathbf{v})\]这样两个四元数的乘法就可以使用向量乘法来表达:
\[q_1 q_2 = (ae - \mathbf{v} \cdot \mathbf{u}, a \mathbf{u} + e \mathbf{v} + \mathbf{v} \times \mathbf{u})\]从上式不难发现,四元数的乘法不满足交换律。
当四元数的实部为0时称其为纯四元数,或虚四元数。两个纯四元数的乘法有相对简单的形式:
\[q_1 q_2 = (- \mathbf{v} \cdot \mathbf{u}, \mathbf{v} \times \mathbf{u})\]
四元数的共轭(conjugate)是指保持其实部不变,三个虚部同时取相反数:
\[q^* = a - bi - cj - dk\]通过共轭我们可以计算四元数的模长:
\[\Vert q \Vert^2 = q q^* = a^2 + b^2 + c^2 + d^2\]这使得我们可以定义四元数的逆:
\[q^{-1} = \frac{1}{\Vert q \Vert^2} q^*\]模长为1的四元数称为单位四元数,它的逆与共轭相同:
\[q^{-1} = q^*\]
使用四元数来进行旋转时需要先把三维空间中的点\(\mathbf{v} \in \mathbb{R}^3\)映射为一个纯四元数:
\[\mathbf{v} \rightarrow (0, \mathbf{v})\]然后在\((0, \mathbf{v})\)左右两边分别用四元数乘法乘以\(q = (\cos{\theta}, \sin{\theta} \ \mathbf{u})\)和\(q^{-1} = (\cos{\theta}, -\sin{\theta} \ \mathbf{u})\):
\[\mathbf{v}_{rot} = q \mathbf{v} q^{-1}\]得到四元数的虚部即为旋转后点的坐标。


通过对四元数乘法进行展开不难发现旋转后四元数的虚部实际上就是Rodrigues公式,不过旋转的角度是\(2 \theta\)。因此可以把四元数\(q\)重新定义为:
\[q = (\cos{\frac{1}{2} \theta}, \sin{\frac{1}{2} \theta} \ \mathbf{u})\]这样就得到了四元数与轴角表示之间的转换公式。

利用四元数乘法的性质可以推导出如下性质:
- 多次旋转等价于四元数之间的左乘
- 四元数\(q\)与\(-q\)表示的是相同的旋转
- 四元数同样具有指数形式:\(q = (\cos{\frac{1}{2} \theta}, \sin{\frac{1}{2} \theta} \ \mathbf{u}) = \exp \bigg( \frac{\theta}{2} \ \mathbf{u} \bigg)\)

从流形的角度来看,四元数本身具有4个参数,而用来表示旋转的单位四元数只有三个自由度。因此单位四元数构成了四元数所在空间的一个三维流形,相当于四维空间中的一个三维球。

流形的视角可以方便我们推导四元数的插值。Nlerp是四元数插值中最简单的一种方法,对于两个四元数\(q_0\)和\(q_1\),Nlerp会直接对它们按照向量进行插值然后归一化到单位四元数上。显然这种做法无法得到均匀的插值结果。

更好的插值方法是基于四元数转过的角度进行插值。基于这样的思想可以得到更加常用的Slerp插值。不过Slerp的计算代价比较大,当四元数夹角的角度比较小时Nlerp可以看作是对Slerp的一种近似。


除此之外,在计算四元数之间夹角时实际上存在短弧和长弧两种可行的路径。

其它旋转表示方法
总结一下,欧拉角、轴角以及四元数是目前在图形学中最为常用的旋转表示方法。

随着AI技术的发展,越来越多的研究和应用开始关注如何使用神经网络来处理旋转。和传统的学习任务相比,使用神经网络来表示旋转的难点在于旋转具有多义性,即同样一个旋转可能有多个不同的表示方法。此外,旋转的表示方式可能是不连续的,这又给神经网络的计算带来了一些困难。

目前,如何构造适合神经网络处理的旋转表达方式是一个仍待探索的问题。不同的应用场景可能需要根据具体情况选择不同的旋转表示方法,以便神经网络能够更有效地学习和处理旋转任务。


角速度
旋转在物理仿真中的一个重要应用在于计算物体的角速度。首先我们来推导角速度的表达式,根据旋转矩阵的约束有:
\[R^T R = \mathbf{I}\]两边同时对时间求导得到:
\[R^T \dot{R} + \dot{R} R^T = (\dot{R} R^T)^T + \dot{R} R^T = \mathbf{0}\]因此\(\dot{R} R^T\)是一个反对称矩阵,我们定义
\[[\omega] = \dot{R} R^T\]这样就得到了旋转矩阵\(R\)的导数表达式:
\[\dot{R} = [\omega] R\]
上面推导的角速度实际是在世界坐标系下的角速度,有些场景中我们还需要获得局部坐标系下的角速度。这两个角速度的表达式是非常接近的,唯一的区别在于旋转矩阵\(R\)位于角速度\([\omega]\)的左边还是右边。

除了旋转矩阵外,角速度还可以使用四元数来表示。
