在三维空间中描述旋转是一件挺麻烦的事。常用的欧拉角虽然直观简单,但是存在顺序依赖和万向节死锁的问题,在通用的旋转运算上并不可行。进而引入的四维数虽然在运算上很有效,但是对于第一次见到的人来说一点也不算直观、易懂。
Krasjet的文章从二维复数出发,进而理解三维四元数的表示意义,循序渐进,通俗易懂,因此记录一下学习笔记。
复数与2D旋转
复数的定义和运算性质不再赘述。对于$ z = a + bi $,相当于对复数基底 $ (1,i) $ 的线性组合,因此可以表示为一个列向量 \(z=[a,b]^T\),但更进一步,其实我们能把复数表示为矩阵。
对于两个复数\(z_1 = a + bi , z_2=c + di\),可以得到观察一下其乘法结果:
\[ \begin{aligned} z_1z_2&=a \boldsymbol{c} -b \boldsymbol{d} \\ &+(b \boldsymbol{c}+a \boldsymbol{d})i \\ &=\left[\begin{array}{cc} a & -b \\ b & a \end{array}\right]\left[\begin{array}{l} c \\ d \end{array}\right] \end{aligned} \]
我们知道右边的\([c,d]^T\)是\(z_2\)的列向量表示形式,而左边的矩阵,也只和\(z_1\)的系数有关,因此也可以看做是\(z_1=a+bi\)的矩阵表示。我们不妨尝试将两边都写成矩阵形式:
\[ z_1z_2=\left[\begin{array}{cc} a & -b \\ b & a \end{array}\right] \left[\begin{array}{l} c & -d\\ d & c \end{array}\right] =\left[\begin{array}{cc} ac-bd & -(bc+ad) \\ bc+ad & ac-bd \end{array}\right] =ac-bd + (bc+ad)i \]
上式可以看出\(z_1,z_2,z_1z_2\)在矩阵表示下也同样满足复数基本运算。这时我们可以发现一个神奇的现象:复数可以表示为矩阵,复数乘法可以表示为矩阵乘法,而矩阵乘法进而可以让我们联想到矩阵表示的仿射变换:
\[ z=\left[\begin{array}{cc} a & -b \\ b & a \end{array}\right] =\sqrt{a^2+b^2} \left[\begin{array}{cc} \frac{a}{\sqrt{a^2+b^2}} & \frac{-b}{\sqrt{a^2+b^2}} \\ \frac{b}{\sqrt{a^2+b^2}} & \frac{a}{\sqrt{a^2+b^2}} \end{array}\right] \]
这种式子是不是很眼熟——即以a,b为边的三角变换式:
\[ z=\sqrt{a^2+b^2}\left[\begin{array}{cc} cos\theta & -sin\theta \\ sin\theta & cos\theta \end{array}\right] =\left[\begin{array}{cc} ||z|| & 0 \\ 0 & ||z|| \end{array}\right] \left[\begin{array}{cc} cos\theta & -sin\theta \\ sin\theta & cos\theta \end{array}\right] \]
此时复数\(z=a+bi=||z||(cos\theta+sin\theta i)\)表示的二维变换水落石出,左边的矩阵是一个 等比放缩矩阵,右边的矩阵是一个 旋转矩阵。复数乘法,即是两个变换矩阵的作用。
并且再将复数的三角形式代回\(z_1z_2\)可知,\(z_1z_2=cos(\theta+\alpha)+sin(\theta+\alpha) i\) ,即复数的累乘,在变换上体现为旋转角度的累加\(\theta+\alpha\),以及缩放上的累乘。
三维旋转
三维旋转描述起来比二维旋转更弯弯绕绕一些,但其实理解起来也不难。
要旋转\(\theta\)角,三维向量\(v\)首先得确定一个三维的旋转轴心\(u\),并且通过投影分解成垂直平面的二维旋转:
- \(v\)和\(u\)构成的平面\(\alpha\):在这个平面上不进行任何旋转,因为这个平面的旋转是靠近或远离\(u\)的旋转,而这不符合我们以\(u\)为旋转轴的初心。
- 垂直于\(\alpha\)的平面\(\beta\):在这个平面上以\(u\)为圆心旋转\(\theta\)角。
因此简单计算可得任意轴旋转公式为:
\[ \begin{aligned} v^`&=\mathbf{v}_{\|}+\cos (\theta) \mathbf{v}_{\perp}+\sin (\theta)\left(\mathbf{u} \times \mathbf{v}_{\perp}\right) \\ &=cos\theta v + (1-cos\theta)(uv)u + sin\theta (u \times v) \end{aligned} \]
\(v_{\|}=cos\theta v\)即\(v\)在\(u\)上的投影向量,其不参与旋转。后面一大串即垂直平面上\(v_{\perp}\)的二维旋转,具体推导细节可参考Krasjet的文章第2节
四元数与三维旋转
四元数基本性质:
\[ \begin{aligned} q&=a+bi+cj+dk \\ i^2&=j^2=k^2=ijk=-1 \\ ij&=k, jk=i, ki=j \\ qq^{-1}&=1 \end{aligned} \]
同样我们可以写成向量形式:\(q=[a,b,c,d]^T\)。将实部和虚部分离,可以写为:
\[ q=\left[\begin{array}{cc} a \\ \boldsymbol{u} \end{array}\right] , (\boldsymbol{u}=[x,y,z]^T) \]
那么我们可以找到四元数的矩阵形式吗?同样通过\(q_1q_2\)来尝试一下:
\[ \begin{aligned} q_{1} q_{2}=& a e+a f i+a g j+a h k+\\ & b e i-b f+b g k-b h j+\\ & c e j-c f k-c g+c h i+\\ & d e k+d f j-d g i-d h \\ =&(a e-b f-c g-d h)+\\ &(b e+a f-d g+c h) i+\\ &(c e+d f+a g-b h) j+\\ &(d e-c f+b g+a h) k \\ =&\left[\begin{array}{cccc} a & -b & -c & -d \\ b & a & -d & c \\ c & d & a & -b \\ d & -c & b & a \end{array}\right]\left[\begin{array}{l} e \\ f \\ g \\ h \end{array}\right] \end{aligned} \]
可以发现\(q_1\)表现为这样的矩阵对我们帮助不大。一方面由于四元数乘法不满足交换律,\(q_1\)用来左乘和右乘时的矩阵并不一样。另一方面,这样的矩阵晦涩抽象,难以整理出相关三维联系。
因此借鉴之前实部与虚部分离的整理方式,可以写出\(q_1q_2=[a,\boldsymbol{u}] \cdot [e,\boldsymbol{v}]\)简化版本:
\[ q_1q_2=[ae-\boldsymbol u \boldsymbol v, a\boldsymbol v+e\boldsymbol u+\boldsymbol u \times \boldsymbol v] \\ \]
从中可以推导出一个有用的共轭性质:\(qq^*=[a,\boldsymbol{u}][a,\boldsymbol{-u}]=[a^2+\|v\|^2,0]\)
再回想\(\boldsymbol{v}\)的旋转公式,可以发现形式有点相似了:
\[ v^`=\mathbf{v}_{\|}+\cos (\theta) \mathbf{v}_{\perp}+\sin (\theta)\left(\mathbf{u} \times \mathbf{v}_{\perp}\right) \]
此时不如尝试用四元数替换掉旋转公式里的三维向量,令\(\dot v=[0,\boldsymbol v],\dot u=[0,\boldsymbol u],\dot v_\|,\dot v_\perp\)等同理。这样一来,旋转公式中\(v_\|,v_\perp\)可以直接替换成四元数\(\dot v_\|,\dot v_\perp\),但是\(\mathbf{u} \times \mathbf{v}_{\perp}\)还不行。
\([0,v],[0,u]\)中的向量即三维空间中的旋转向量\(v\)和旋转轴向量\(u\)。
再看四元数乘积公式\(q_1q_2=[ae-\boldsymbol v \boldsymbol u, a\boldsymbol u+e\boldsymbol v+\boldsymbol u \times \boldsymbol v]\),\(a,e\)是两个四元数的实部,而在我们的替换中,所有的实部都为0,此时四元数乘积为\(q_1q_2=[0,\boldsymbol u \times \boldsymbol v]\)。因此我们可以用四元数\(\dot u \dot v_\perp=[0,\boldsymbol u \times \boldsymbol v_\perp]\) 来替换 \(\mathbf{u} \times \mathbf{v}_{\perp}\) 。
至此,可以得到四元数版本的旋转公式:
\[ \begin{aligned} \dot v^`&=\dot{v_{\|}}+\cos (\theta) \dot{v_{\perp}}+\sin (\theta)\left(\dot u \dot v_\perp\right) \\ &= \dot{v_{\|}}+(\cos (\theta) +\sin (\theta)\dot u) \dot v_\perp \\ &= \dot{v_{\|}} + \dot q \dot v_\perp, \ \ \ \ \ where \ \ \dot q=[cos\theta,0]+[0,sin\theta u]=\cos (\theta) +\sin (\theta)\dot u \end{aligned} \]
最终,我们实现通过一个额外的四元数\(\dot q=[\cos (\theta),\sin (\theta) \boldsymbol{u}]\),即可表示\(v\)绕任意轴\(u\)的旋转\(\theta\)角。反过来,给定一个四元数,我们也可以很轻易地从实部得到其代表的旋转角度\(\theta\),从虚部得到其代表的旋转轴\(\boldsymbol{u}\)。
代码小提示:可以发现,四元数的x,y,z,w属性并不直接代表旋转角度,因此不能在旋转时直接修改四元数的属性。当然,也不能先转成欧拉角——修改——再存回四元数,这样就失去了四元数的意义。通常,我们构造一个表示旋转变换的四元数,然后与原来的角度相乘,即可完成旋转。
根据四元数运算可以将旋转 \(\theta\) 角进一步化简为:
\[ \begin{aligned} \dot v^`&=\dot p \dot v \dot p^* , \ \ \ where \ \ \ \ \dot p=[cos\frac{\theta}{2},sin\frac{\theta}{2} \boldsymbol{u}] \\ &= [0, \cos (\theta) \mathbf{v}+(1-\cos (\theta))(\mathbf{u} \cdot \mathbf{v}) \mathbf{u}+\sin (\theta)(\mathbf{u} \times \mathbf{v})] \end{aligned} \]
而这种表示形式下也可以很方便地进行旋转的复合:
\[ v^`=\dot p_2 ( \dot p_1 v \dot p_1^* ) \dot p_2^* \]
当然,在已知最终变换\(p=p_2p_1\)和初始变换\(p_1\)的情况下,我们可能要求解中间变换\(p_2\),此时:
\[ \begin{aligned} p_2p_1&=p \\ p_2p_1p_1^{-1} &=p p_1^{-1} \\ p_2 &= pp_1^{-1} \\ p_2 &=pp_1^* \end{aligned} \]
默认所有变换向量都是单位向量,因此变换四元数都是单位四元数,有\(p^{-1}=p^*\)
更进一步,多次求解中间变换即可实现高阶变换插值。