图形学知识基础:三维变换,旋转(欧拉角旋转与万向锁,绕任意轴旋转,四元数)

三维变换

与上一篇文章中的二维变换类似,我们可以使用一个 3*3 的矩阵来表示一个三维的线性变换:

\begin{bmatrix}x_{out}\\ y_{out}\\ z_{out}\end{bmatrix} = \begin{bmatrix}x_{a}& x_{b}& x_{c}\\ y_{a}& y_{b}& y_{c}\\ z_{a}& z_{b}& z_{c}\end{bmatrix} * \begin{bmatrix}x_{in}\\ y_{in}\\ z_{in}\end{bmatrix}

并且矩阵 \begin{bmatrix}x_{a}& x_{b}& x_{c}\\ y_{a}& y_{b}& y_{c}\\ z_{a}& z_{b}& z_{c}\end{bmatrix} 的 x_{a} , y_{a} 和 z_{a} 即为 \vec{x}=(1,0,0) 变化后的值, x_{b} , y_{b} 和 z_{b} 即为 \vec{y}=(0,1,0) 变化后的值, x_{c} , y_{c} 和 z_{c} 即为 \vec{z}=(0,0,1) 变化后的值

仿射变换的公式为:

\begin{bmatrix} x_{out} \\ y_{out} \\ z_{out} \end{bmatrix} = \begin{bmatrix}a& b &c\\ d& e&f\\g&h&i\end{bmatrix}\begin{bmatrix} x_{in} \\ y_{in}\\z_{in} \end{bmatrix}+\begin{bmatrix} x_{offset} \\ y_{offset} \\z_{offset} \end{bmatrix}

同样的,我们可以通过引入齐次坐标来表示仿射变换,公式为:

\begin{bmatrix} x_{out} \\ y_{out} \\ z_{out} \\1\end{bmatrix} = \begin{bmatrix}a& b &c & x_{offset}\\ d& e&f&y_{offset}\\g&h&i&z_{offset}\\0&0&0&1\end{bmatrix}\begin{bmatrix} x_{in} \\ y_{in}\\z_{in}\\1 \end{bmatrix}

例如缩放s倍的矩阵为:\begin{bmatrix}s& 0 &0 & 0\\ 0& s&0&0\\0&0&s&0\\0&0&0&1\end{bmatrix}

 

旋转变换

相比其他变换,旋转变换相对的要复杂一些。首先我们要确定我们的坐标系是左手坐标系还是右手坐标系,这会影响到x,y,z三个轴的相对关系,本文我们使用右手坐标系来进行相关的运算。右手坐标系如下图,右手四指弯曲的方向代表x轴到y轴的方向,大拇指方向代表z轴方向。

我们先来看看在三维空间中绕x,y,z三个轴中的某个轴逆时针旋转 \theta 角度的矩阵是如何的

  • 绕x轴旋转,则可以看作是在yz平面的二维旋转,x轴的(1,0,0)不变,y轴(0,1,0)变为(0,cos\theta,sin\theta),z轴的(0,0,1)变为(0,-sin\theta,cos\theta),因此对应的矩阵为: A_{x}=\begin{bmatrix} 1 & 0 & 0\\ 0 & \cos\theta &-\sin\theta \\ 0 & \sin\theta &\cos\theta \end{bmatrix} 
  • 绕y轴旋转,则可以看作是在xz平面的二维旋转,y轴的(0,1,0)不变,x轴(1,0,0)变为(cos\theta,0,-sin\theta),z轴的(0,0,1)变为(sin\theta,0,cos\theta),因此对应的矩阵为: A_{y}=\begin{bmatrix} \cos\theta & 0 & \sin\theta\\ 0 & 1 & 0\\ -\sin\theta & 0 &\cos\theta \end{bmatrix} 
  • 绕z轴旋转,则可以看作是在xy平面的二维旋转,z轴的(0,0,1)不变,x轴(1,0,0)变为(cos\theta,sin\theta,0),y轴的(0,1,0)变为(-sin\theta,cos\theta,0),因此对应的矩阵为: A_{z}=\begin{bmatrix} \cos\theta & -\sin\theta & 0\\ \sin\theta & \cos\theta &0 \\ 0 & 0 &1 \end{bmatrix} 

 

A_{x}A_{y}A_{z} 的结合使用

两者结合使用

对于如下表达式我们应该如何理解

\begin{bmatrix} x_{out}\\ y_{out} \\z_{out} \end{bmatrix}=A_{y}A_{x}\begin{bmatrix} x_{in}\\ y_{in} \\z_{in} \end{bmatrix}

通过上一篇提到的复合变换,上面式子表达的应该是先绕x轴旋转,然后再绕y轴旋转。假设原先物体的x轴方向为(1,0,0),y轴方向为(0,1,0),z轴方向为(0,0,1),我们先将其绕x轴逆时针旋转 \alpha 度,即 A_{x} 操作。此时物体自身的y轴(0,1,0)会跟着x轴的旋转变为(0,cos\alpha,sin\alpha),记作 {y}'。那么我们再做 A{y} 操作时,旋转的y轴是原先的 y=(0,1,0) 还是 {y}'=(0,cos\alpha,sin\alpha) ?这是两种完全不一样的情况,如下图

图形学知识基础:三维变换,旋转(欧拉角旋转与万向锁,绕任意轴旋转,四元数)_第1张图片

垂直向上较长的那根绿线为 y ,而较短的绿线即为 {y}',我们来看看绕它们旋转的情况分别是怎样的(左图为绕y旋转,右图为绕{y}'旋转)

                  图形学知识基础:三维变换,旋转(欧拉角旋转与万向锁,绕任意轴旋转,四元数)_第2张图片

答案是:再做 A{y} 操作时,旋转的y轴就是原先的 y=(0,1,0),即上左图的情况。因为 A{y} 的旋转矩阵是根据 y=(0,1,0)的情况计算出来了,因此使用该矩阵计算时,无论物体属于一个什么角度,计算出来的结果都是根据 y=(0,1,0)这个轴进行逆时针旋转的结果(注:(0,0,0)为物体的中心点)。

我们可以称x轴方向为(1,0,0),y轴方向为(0,1,0),z轴方向为(0,0,1)的三个轴为世界坐标轴,它们不根据物体的旋转而改变。而会根据物体自身的坐标轴会根据物体的旋转而改变,例如上诉中的{y}',我们可以称之为模型坐标轴。(自己按照unity常用的说法瞎定义了,懂原理就好。)

因此上诉的两个旋转操作都是按照世界坐标轴来进行的,这种我们称之为外旋,表达式 A_{y}A_{x} ,我们可以称之为 x-y外旋 操作。那么内旋就是使用模型坐标轴来做旋转操作咯,那么如果我想要实现先绕x轴旋转\alpha 度,在绕旋转后的模型坐标轴的y轴({y}')旋转 \beta (即 x-y内旋 操作),那么应该怎么计算呢?

首先,第一步是绕x轴旋转,此时物体还没发生旋转,因此模型坐标的x轴等于世界坐标的x轴,因此我们依旧可以使用 A_{x} 矩阵。接着我们要绕 {y}'=(0,cos\alpha,sin\alpha) 旋转,这里我们需要将其进行拆解(类似于上一篇中二维空间绕空间中任意一点旋转那样,拆解成先把任意点移到原点,然后旋转,然后再把改点移回去),先把 {y}' 绕世界坐标的x轴旋转 -\alpha度,使其与世界坐标的y轴重叠,然后使用 A{y} 矩阵进行旋转 \beta 角度,最后再绕世界坐标的x轴旋转\alpha,是旋转轴{y}'回到(0,cos\alpha,sin\alpha)方向。

因此 x-y内旋操作即为:

  1. 绕世界坐标x轴旋转\alpha度,对应矩阵A_{x}
  2. 绕世界坐标x轴旋转-\alpha度,对应矩阵A_{x}^{-1}
  3. 绕世界坐标y轴旋转\beta度,对应矩阵A_{y}
  4. 绕世界坐标x轴旋转\alpha度,对应矩阵A_{x}

可以发现第一第二步可以抵消,因此x-y内旋操作等价于先绕世界坐标y轴旋转\beta度再绕世界坐标x轴旋转\alpha度,即 x-y内旋 等于 y-x外旋

上诉过程用矩阵来表达的话,我们知道绕世界坐标x轴旋转-\alpha度即为绕世界坐标x轴旋转\alpha度的逆变换,因此其矩阵即为A_{x}的逆矩阵,为A_{x}^{-1},因此矩阵如下

x-y内旋 = A_{x}A_{y}A_{x}A_{x}^{-1} = A_{x}A_{y} (因为A_{x}A_{x}^{-1} 等于单位矩阵) = y-z外旋

同理也可得出 x-z内旋等于z-x外旋,z-y内旋等于y-z内旋等等。

 

三者结合使用

知道上面这些原理后,我们再进一步,来理解下面表达式

\begin{bmatrix} x_{out}\\ y_{out} \\z_{out} \end{bmatrix}=A_{z}A_{y}A_{x}\begin{bmatrix} x_{in}\\ y_{in} \\z_{in} \end{bmatrix}

同样的,上面式子可以称之为 x-y-z外旋 操作,即先绕世界坐标的x轴旋转\alpha度,然后在绕世界坐标的y轴旋转\beta度,最后绕世界坐标的z轴旋转\gamma度。带入可得:

\begin{bmatrix}x_{out}\\y_{out}\\z_{out}\end{bmatrix} = \begin{bmatrix} \cos\gamma& -\sin\gamma& 0\\ \sin\gamma& \cos\gamma&0 \\ 0 & 0 &1 \end{bmatrix} \begin{bmatrix} \cos\beta& 0 & \sin\beta\\ 0 & 1 & 0\\ -\sin\beta& 0 &\cos\beta\end{bmatrix}\begin{bmatrix} 1 & 0 & 0\\ 0 & \cos\alpha&-\sin\alpha\\ 0 & \sin\alpha&\cos\alpha \end{bmatrix}\begin{bmatrix}x_{in}\\y_{in}\\z_{in}\end{bmatrix}

那么它是否和前面一样 x-y-z外旋 等于 z-y-x内旋 呢?答案是肯定的,我们来看下推导。

 z-y-x内旋,重点在于如何把z-y操作后模型坐标x轴移动到世界坐标的x轴上,通过前面我们知道 z-y 内旋等于 y-z 外旋,因此旋转后的模型坐标x轴即执行了A_{z}A_{y} 操作,此时的模型坐标x轴,我们标记为{x}'。因此若我们要使得{x}'变回与世界坐标的x重叠,只需要执行一下相反的操作即可,即把{x}'先绕z轴旋转-\gamma度,然后在绕y轴旋转-\beta度,对应矩阵为A_{y}^{-1}A_{z}^{-1}

因此z-y-x内旋可以分解为如下几步骤:

  1. 先将其分解为 y-z外旋 + x内旋
  2. 做y-z外旋,对应矩阵A_{z}A_{y}
  3. 做y-z外旋的逆操作,是{x}'到世界坐标x轴上,对应矩阵A_{y}^{-1}A_{z}^{-1}
  4. 绕世界坐标x轴旋转,对应矩阵A_{x}
  5. {x}'归位,即再做一次y-z外旋操作,对应矩阵A_{z}A_{y}

连起来可得公式为:

z-y-x内旋 = A_{z}A_{y}A_{x}A_{y}^{-1}A_{z}^{-1}A_{z}A_{y} = A_{z}A_{y}A_{x} (A_{y}^{-1}A_{z}^{-1}A_{z}A_{y} = A_{y}^{-1}(A_{z}^{-1}A_{z})A_{y}=A_{y}^{-1}A_{y}=I) = x-y-z外旋

可得结论:所有外旋操作等于与其操作顺序相反的内旋操作。

 

排列组合

我们一共可以得到以下六种排列组合。不同的组合得到值是不一样的,因为 A_{z}A_{y}A_{x}\neq A_{y}A_{z}A_{x} 等。

  • x-y-z外旋(等价于z-y-x内旋)
  • x-z-y外旋(等价于y-z-x内旋)
  • y-x-z外旋(等价于z-x-y内旋)
  • y-z-x外旋(等价于x-z-y内旋)
  • z-x-y外旋(等价于y-x-z内旋)
  • z-y-x外旋(等价于x-y-z内旋)

上面的这些旋转方式也就是我们常用的欧拉角旋转

对于上面这些排列组合,很多会解释为每个轴对应的层级关系,例如x-y-z外旋,转动x后,y和z在之后的计算用的还是世界坐标轴,就好像x的转动和yz没有关系。y和z之间也是一样,转动y后,z还是用世界坐标轴计算,不会因为y的改变而改变。这个就很像所谓的层级关系,即z为最父层,x为最子层,y在中间层,当子层转动的时候,不会影响到父层。反过来也一样,x-y-z外旋等于z-y-x内旋,父层的z转动会影响y和x(内旋用的模型坐标轴)。

欧拉角的维基百科:https://en.wikipedia.org/wiki/Euler_angles

 

万向锁(Gimbal Lock)

关于万向锁是什么,看这个视频应该就可以明白了:https://v.youku.com/v_show/id_XNzkyOTIyMTI=.html

简单来说上诉六个组合的操作,只要把中间那个操作的旋转角度设为90度的整数倍,就会触发万向锁。

我们可以从内旋的角度很好理解,以z-y-x的内旋为例子,当我们做第一步操作时,即绕模型坐标的z轴旋转\alpha度,无论怎么旋转,z轴都不会改变且等于世界坐标的z轴,但是模型坐标的x,y轴会跟着旋转变换。此时我们再做第二步操作,即绕模型坐标的y轴逆时针旋转 \beta 度,当 \beta=90 时,我们会发现此时的x轴会和做第一步操作时的z轴(同时也是世界坐标的z轴)处于同一条直线,方向相反。那么当我们再做第三步操作时,即绕x轴旋转,那就等于在绕世界坐标的z轴旋转,就等同于第一步操作,只不过方向相反。这就是万向锁了,即欧拉角有两个角的旋转都在绕同一个轴在旋转

 

接着我们来从数学的角度分析一下:假设我们使用x-y-z外旋,对应的矩阵即为 A_{z}A_{y}A_{x} :

\begin{bmatrix}x_{out}\\y_{out}\\z_{out}\end{bmatrix} = \begin{bmatrix} \cos\gamma& -\sin\gamma& 0\\ \sin\gamma& \cos\gamma&0 \\ 0 & 0 &1 \end{bmatrix} \begin{bmatrix} \cos\beta& 0 & \sin\beta\\ 0 & 1 & 0\\ -\sin\beta& 0 &\cos\beta\end{bmatrix}\begin{bmatrix} 1 & 0 & 0\\ 0 & \cos\alpha&-\sin\alpha\\ 0 & \sin\alpha&\cos\alpha \end{bmatrix}\begin{bmatrix}x_{in}\\y_{in}\\z_{in}\end{bmatrix}

此时我们把\beta的值设为90,可得下面矩阵:

\begin{bmatrix} \cos\gamma& -\sin\gamma& 0\\ \sin\gamma& \cos\gamma&0 \\ 0 & 0 &1 \end{bmatrix} \begin{bmatrix} 0& 0 & 1\\ 0 & 1 & 0\\ -1& 0 &0\end{bmatrix}\begin{bmatrix} 1 & 0 & 0\\ 0 & \cos\alpha&-\sin\alpha\\ 0 & \sin\alpha&\cos\alpha \end{bmatrix}= \begin{bmatrix} 0& -\sin\gamma& \cos\gamma\\ 0& \cos\gamma&\sin\gamma \\ 1 & 0 &0 \end{bmatrix} \begin{bmatrix} 1 & 0 & 0\\ 0 & \cos\alpha&-\sin\alpha\\ 0 & \sin\alpha&\cos\alpha \end{bmatrix}

即:

\begin{bmatrix}x_{out}\\y_{out}\\z_{out}\end{bmatrix} = \begin{bmatrix} 0 & \cos\gamma\sin\alpha-\sin\gamma\cos\alpha & \cos\gamma\cos\alpha-\sin\gamma\sin\alpha\\ 0 & \cos\gamma\cos\alpha+\sin\gamma\sin\alpha&\sin\gamma\cos\alpha-\cos\gamma\sin\alpha\\ 1 & 0&0 \end{bmatrix}\begin{bmatrix}x_{in}\\y_{in}\\z_{in}\end{bmatrix}

最终得到:

\begin{bmatrix}x_{out}\\y_{out}\\z_{out}\end{bmatrix} = \begin{bmatrix}(\cos\gamma\sin\alpha-\sin\gamma\cos\alpha)y_{in} + (\cos\gamma\cos\alpha-\sin\gamma\sin\alpha)z_{in}\\ (\cos\gamma\cos\alpha+\sin\gamma\sin\alpha)y_{in}+(\sin\gamma\cos\alpha-\cos\gamma\sin\alpha)z_{in}\\ x_{in} \end{bmatrix}

即无论我们的\alpha\gamma取何值,z_{out} 永远等于 x_{in}

怎么理解z_{out} 永远等于 x_{in}呢?当我们做一次绕世界坐标y轴逆时针旋转90度时,此时(1,0,0)即会变为(0,0,1),即z_{out} 等于 x_{in} (\beta=90的效果)。而永远等于就说明之后的z轴不会发生改变,什么情况下不会改变呢?那就是绕着世界坐标轴的z轴做旋转,即我们修改\alpha\gamma的值会发现,只绕着世界坐标轴的z轴在旋转。

 

总结:每个内旋组合,将第二个轴的旋转角度设为90度或其整数倍时,就会触发万向锁,此时无论第一个轴或第三个轴的旋转角度为多少,都是在绕第一个轴所对应的世界坐标轴做旋转,从而失去了一个方向(第三个轴)的旋转能力。

 

绕任意轴旋转

前面我们说提到的是欧拉角旋转,若我们想要计算绕某个轴旋转,应该如何计算呢?

 

绕xy平面上过原点的轴旋转

我们先从简单的来,假如有个轴,它在xy平面上,并且经过原点,那么绕该轴做逆时针旋转应该如何来解?首先因为这个轴在xy平面且经过原点,因此通过绕z轴旋转某个角度(设 \alpha 度)即可将该轴转到原y轴的方向上,然后绕该轴的逆时针旋转即是原先绕y轴逆时针旋转,旋转完成后再绕z轴旋转 -\alpha 度即可。这点和我们前面讲欧拉角旋转时是一样的,使用公式表达即为:

\begin{bmatrix}x_{out}\\y_{out}\\z_{out}\end{bmatrix} = \begin{bmatrix} \cos-\alpha& -\sin-\alpha& 0\\ \sin-\alpha& \cos-\alpha&0 \\ 0 & 0 &1 \end{bmatrix} \begin{bmatrix} \cos\theta & 0 & \sin\theta\\ 0 & 1 & 0\\ -\sin\theta & 0 &\cos\theta \end{bmatrix}\begin{bmatrix} \cos\alpha& -\sin\alpha& 0\\ \sin\alpha& \cos\alpha&0 \\ 0 & 0 &1 \end{bmatrix} \begin{bmatrix}x_{in}\\y_{in}\\z_{in}\end{bmatrix}

合并得

\begin{bmatrix}x_{out}\\y_{out}\\z_{out}\end{bmatrix} = \begin{bmatrix} \cos^{2}\alpha\cos\theta+\sin^{2}\alpha& -\cos\alpha\cos\theta\sin\alpha+\sin\alpha\cos\alpha& \cos\alpha\sin\theta\\ -\sin\alpha\cos\theta\cos\alpha+\cos\alpha\sin\alpha& \sin^{2}\alpha\cos\theta+\cos^{2}\alpha&-\sin\alpha\sin\theta \\ -\sin\theta\cos\alpha & \sin\theta\sin\alpha &\cos\theta \end{bmatrix} \begin{bmatrix}x_{in}\\y_{in}\\z_{in}\end{bmatrix}

上诉矩阵即为三维空间中,绕xy平面上过原点的任意轴逆时针旋转 \theta 度的矩阵,其中 \alpha 为该轴与y轴的夹角。

当然了,我们一般会给定的是一个轴(x,y,z)这样,而不是一个轴与某个向量的夹角,那么假设在xy平面上的某个轴为(x,y,0),那么绕这个轴旋转\theta度的矩阵应该是多少呢?

同样的,我们设(x,y,0)与y轴的夹角为\alpha度,那么可得: \sin\alpha = \frac{x}{\sqrt{x^{2}+y^{2}}}  与 \cos\alpha = \frac{y}{\sqrt{x^{2}+y^{2}}},看着很麻烦是不是,那么假设我们的(x,y,0)是单位向量呢?那么\sqrt{x^{2}+y^{2}} = 1,即 \sin\alpha = x\cos\alpha = y, 所以我们第一步先把给定的轴的向量转换为单位向量

接着我们将它们带入到上面的矩阵中可得:

\begin{bmatrix} x^2+y^2\cos\theta& xy-xy\cos\theta& y\sin\theta\\ xy-xy\cos\theta& x^2\cos\theta+y^2& -x\sin\theta \\ -y\sin\theta & x\sin\theta&\cos\theta \end{bmatrix}

再根据矩阵的加法定则,把 sin\theta , cos\theta,和常量 来个质壁分离,可得:

\begin{bmatrix} x^2& xy& 0\\ xy& y^2& 0 \\ 0 & 0&0 \end{bmatrix} + \begin{bmatrix} y^2\cos\theta& -xy\cos\theta& 0\\ -xy\cos\theta& x^2\cos\theta& 0 \\ 0 & 0&\cos\theta \end{bmatrix} + \begin{bmatrix} 0& 0& y\sin\theta\\ 0& 0& -x\sin\theta \\ -y\sin\theta & x\sin\theta&0 \end{bmatrix}

再根据矩阵和常数的乘法,我们可以把sin\theta 和 cos\theta 提取出来,可得到:

\begin{bmatrix} x^2& xy& 0\\ xy& y^2& 0 \\ 0 & 0&0 \end{bmatrix} + \cos\theta \begin{bmatrix} y^2& -xy& 0\\ -xy& x^2& 0 \\ 0 & 0&1 \end{bmatrix} + \sin\theta \begin{bmatrix} 0& 0& y\\ 0& 0& -x \\ -y & x&0 \end{bmatrix}

因为前面说过,转为了单位向量,所以: y^2=1-x^2 和 x^2=1-y^2 ,进一步的简化为:

\begin{bmatrix} x^2& xy& 0\\ xy& y^2& 0 \\ 0 & 0&0 \end{bmatrix} + \cos\theta \begin{bmatrix} 1-x^2& -xy& 0\\ -xy& 1-y^2& 0 \\ 0 & 0&1 \end{bmatrix} + \sin\theta \begin{bmatrix} 0& 0& y\\ 0& 0& -x \\ -y & x&0 \end{bmatrix}

可以发现,上面中间的那个矩阵,和最左边的那个矩阵很相似,其实就是单位矩阵减去左边的那个矩阵:

\begin{bmatrix} x^2& xy& 0\\ xy& y^2& 0 \\ 0 & 0&0 \end{bmatrix} + \cos\theta( \begin{bmatrix} 1& 0& 0\\ 0& 1& 0 \\ 0 & 0&1 \end{bmatrix}-\begin{bmatrix} x^2& xy& 0\\ xy& y^2& 0 \\ 0 & 0&0 \end{bmatrix}) + \sin\theta \begin{bmatrix} 0& 0& y\\ 0& 0& -x \\ -y & x&0 \end{bmatrix}

换下位置可以得到:

\cos\theta\begin{bmatrix} 1& 0& 0\\ 0& 1& 0 \\ 0 & 0&1 \end{bmatrix}+(1-\cos\theta)\begin{bmatrix} x^2& xy& 0\\ xy& y^2& 0 \\ 0 & 0&0 \end{bmatrix} + \sin\theta \begin{bmatrix} 0& 0& y\\ 0& 0& -x \\ -y & x&0 \end{bmatrix}

单位矩阵我们标记为 I ,而 \begin{bmatrix} x^2& xy& 0\\ xy& y^2& 0 \\ 0 & 0&0 \end{bmatrix} 其实就是 向量 \begin{bmatrix} x\\y \\z \end{bmatrix} 和其转置 \begin{bmatrix} x &y &z \end{bmatrix} 的积(该例子中,z=0):\begin{bmatrix} x\\y \\z \end{bmatrix}\begin{bmatrix} x&y &z \end{bmatrix}=\begin{bmatrix} x^2&xy&xz\\xy&y^2&yz \\xz&yz&z^2 \end{bmatrix}

 

结论:若我们要绕xy平面上的某个轴 A=(x,y,0) 旋转\theta度(其中A为单位向量),其公式为:

\begin{bmatrix}x_{out}\\y_{out}\\z_{out}\end{bmatrix} =(\cos\theta I+(1-\cos\theta) AA^T + \sin\theta \begin{bmatrix} 0& 0& y\\ 0& 0& -x \\ -y & x&0 \end{bmatrix})\begin{bmatrix}x_{in}\\y_{in}\\z_{in}\end{bmatrix}

前面我们是把xy平面的轴转到y上,当然我们也可以转到x上,然后使用x的旋转,得到最终结果是一样的。举一反三也可以计算绕xz平面或yz平面的轴旋转,不过也不用举了,下面我们之间来看绕xyz空间中的轴旋转的公式。

 

绕空间中过原点的轴旋转

前面我们是绕 (x,y,0)旋转,最终我们想要的肯定是绕(x,y,z)的旋转公式,那么绕过原点的一个轴 A=(x,y,z) 旋转 \theta 度(其中A为单位向量),其公式应该是什么?

其实原理都是一样的,我们要先把这个轴搞到世界坐标y轴上,然后执行旋转操作,最后把这个轴归位。分如下几步操作:

  1. 通过绕世界坐标y轴旋转将该轴转到xy平面上,其中夹角 \beta 为该轴在xz平面上的投影(x,0,z)与x轴(1,0,0)的夹角。
  2. 通过绕世界坐标z轴旋转将该轴转到y轴上,其中夹角 \alpha 为该轴(x,y,z)与y轴(0,1,0)的夹角。(因为第一步操作时,我们的旋转轴A与y轴的夹角不会发生变换。对边为\sqrt{x^2+z^2}
  3. 绕世界坐标y轴旋转 \theta 度
  4. 2操作的逆操作
  5. 1操作的逆操作

可以得到如下公式:

\begin{bmatrix}x_{out}\\y_{out}\\z_{out}\end{bmatrix} = \begin{bmatrix} \cos-\beta& 0 & \sin-\beta\\ 0 & 1 & 0\\ -\sin-\beta& 0 &\cos-\beta\end{bmatrix}\begin{bmatrix} \cos-\alpha& -\sin-\alpha& 0\\ \sin-\alpha& \cos-\alpha&0 \\ 0 & 0 &1 \end{bmatrix} \begin{bmatrix} \cos\theta & 0 & \sin\theta\\ 0 & 1 & 0\\ -\sin\theta & 0 &\cos\theta \end{bmatrix}\begin{bmatrix} \cos\alpha& -\sin\alpha& 0\\ \sin\alpha& \cos\alpha&0 \\ 0 & 0 &1 \end{bmatrix} \begin{bmatrix} \cos\beta& 0 & \sin\beta\\ 0 & 1 & 0\\ -\sin\beta& 0 &\cos\beta\end{bmatrix}\begin{bmatrix}x_{in}\\y_{in}\\z_{in}\end{bmatrix}

根据三角函数可以得到  \sin\alpha = \frac{\sqrt{x^2+z^2}}{\sqrt{x^2+y^2+z^2}}=\sqrt{x^2+z^2}\cos\alpha = \frac{y}{\sqrt{x^2+y^2+z^2}}=y\sin\beta = \frac{z}{\sqrt{x^2+z^2}}\cos\beta = \frac{x}{\sqrt{x^2+z^2}},我们设m = \sqrt{x^2+z^2}n = \frac{1}{\sqrt{x^2+z^2}} (m*n =1),将它们带入上面公式,得到:

\begin{bmatrix} nx& 0 & -nz\\ 0 & 1 & 0\\ nz& 0 &nx\end{bmatrix}\begin{bmatrix} y& m& 0\\ -m& y&0 \\ 0 & 0 &1 \end{bmatrix} \begin{bmatrix} \cos\theta & 0 & \sin\theta\\ 0 & 1 & 0\\ -\sin\theta & 0 &\cos\theta \end{bmatrix}\begin{bmatrix} y& -m& 0\\ m& y&0 \\ 0 & 0 &1 \end{bmatrix} \begin{bmatrix} nx& 0 & nz\\ 0 & 1 & 0\\ -nz& 0 &nx\end{bmatrix}

进一步得到:

\begin{bmatrix} nxy& mnx & -nz\\ -m & y & 0\\ nyz& mnz &nx\end{bmatrix}\begin{bmatrix} \cos\theta & 0 & \sin\theta\\ 0 & 1 & 0\\ -\sin\theta & 0 &\cos\theta \end{bmatrix}\begin{bmatrix} nxy& -m& nyz\\ mnx& y&mnz \\ -nz & 0 &nx \end{bmatrix}

因为m*n =1所有再简化为:

\begin{bmatrix} nxy& x & -nz\\ -m & y & 0\\ nyz& z &nx\end{bmatrix}\begin{bmatrix} \cos\theta & 0 & \sin\theta\\ 0 & 1 & 0\\ -\sin\theta & 0 &\cos\theta \end{bmatrix}\begin{bmatrix} nxy& -m& nyz\\ x& y&z \\ -nz & 0 &nx \end{bmatrix}

全部相乘可得:

\begin{bmatrix} (nxy)^2\cos\theta+n^2xyz\sin\theta+x^2-n^2xyz\sin\theta+n^2z^2\cos\theta & -xy\cos\theta-z\sin\theta+xy & n^2xy^2z\cos\theta+n^2yz^2\sin\theta+xz+n^2x^2y\sin\theta-n^2xz\cos\theta \\ -xy\cos\theta+xy+z\sin\theta & m^2\cos\theta+y^2 & -yz\cos\theta+yz-x\sin\theta \\ n^2xy^2z\cos\theta-n^2x^2y\sin\theta+xz-n^2yz^2\sin\theta-n^2xz\cos\theta & -yz\cos\theta+x\sin\theta+yz &(nyz)^2\cos\theta-n^2xyz\sin\theta+z^2+n^2xyz\sin\theta+n^2x^2\cos\theta \end{bmatrix}

把 sin\theta , cos\theta,和常量拆分出来分别可得:


常量为:\begin{bmatrix} x^2 & xy & xz \\ xy & y^2 &yz \\ xz & yz &z^2 \end{bmatrix} 就是我们 AA^T 的值


cos\theta 为:\begin{bmatrix} (nxy)^2\cos\theta+(nz)^2\cos\theta & -xy\cos\theta & n^2xy^2z\cos\theta-n^2xz\cos\theta \\ -xy\cos\theta & m^2\cos\theta & -yz\cos\theta \\ n^2xy^2z\cos\theta-n^2xz\cos\theta & -yz\cos\theta &(nyz)^2\cos\theta+n^2x^2\cos\theta \end{bmatrix}

第一项带入n值得到:\frac{x^2y^2+z^2}{x^2+z^2}\cos\theta,我们来对\frac{x^2y^2+z^2}{x^2+z^2}进行简化,如下:

\frac{x^2y^2+z^2}{x^2+z^2}=\frac{x^2y^2+(x^2+y^2+z^2)z^2}{x^2+z^2}= \frac{x^2y^2+x^2z^2+y^2z^2+z^4}{x^2+z^2}=\frac{(x^2+z^2)(y^2+z^2)}{x^2+z^2}

所以 (nxy)^2\cos\theta+(nz)^2\cos\theta = (y^2+z^2)\cos\theta=(1-x^2)\cos\theta

第三项带入n值得到:\frac{xy^2z-xz}{x^2+z^2}\cos\theta=\frac{xz(y^2-1)}{x^2+z^2}\cos\theta=\frac{-xz(x^2+z^2)}{x^2+z^2}\cos\theta=-xz\cos\theta

第五项带入m值得到:(x^2+z^2)\cos\theta=(1-y^2)\cos\theta

第七项等价于第三项。

最后一项带入n值得到:\frac{y^2z^2+x^2}{x^2+z^2}\cos\theta,和第一项的解法类似,最终得到:=(x^2+y^2)\cos\theta=(1-z^2)\cos\theta

所以简化后的矩阵为:

\begin{bmatrix} (1-x^2)\cos\theta & -xy\cos\theta & -xz\cos\theta \\ -xy\cos\theta & (1-y^2)\cos\theta & -yz\cos\theta \\ -xz\cos\theta & -yz\cos\theta &(1-z^2)\cos\theta \end{bmatrix}=\cos\theta(\begin{bmatrix} 1 & 0 & 0 \\ 0 &1 & 0 \\ 0 & 0 &1 \end{bmatrix}-\begin{bmatrix} x^2 & xy & xz \\ xy & y^2 & yz \\ xz & yz &z^2 \end{bmatrix})


sin\theta 为:\begin{bmatrix} 0 & -z\sin\theta & n^2yz^2\sin\theta+n^2x^2y\sin\theta \\ z\sin\theta & 0 & -x\sin\theta \\ -n^2x^2y\sin\theta-n^2yz^2\sin\theta & x\sin\theta &0 \end{bmatrix}

其中由于n^2z^2+n^2x^2 = \frac{z^2+x^2}{(\sqrt{x^2+z^2})^2}=1,提取sin\theta,可得:

\sin\theta\begin{bmatrix} 0 & -z & y \\ z & 0 & -x \\ -y & x &0 \end{bmatrix}


结论:若我们要绕xy平面上的某个轴 A=(x,y,z) 旋转\theta度(其中A为单位向量),其公式为:

\begin{bmatrix}x_{out}\\y_{out}\\z_{out}\end{bmatrix} =(\cos\theta I+(1-\cos\theta) AA^T + \sin\theta \begin{bmatrix} 0& -z& y\\ z& 0& -x \\ -y & x&0 \end{bmatrix})\begin{bmatrix}x_{in}\\y_{in}\\z_{in}\end{bmatrix}

若z=0,就是我们上面在xy平面上的轴旋转,同理y=0就是在xz平面上的轴旋转,x=0就是yz平面上的轴旋转。

 

绕空间中不过原点的轴旋转

前面我们的轴都是规定是过原点的,若我们想要绕不过原点的某个轴旋转,其实一样的思路,先把这个轴使用平移变换移动到原点处,然后做上面的操作,最后把这个轴移回去即可。因为是平移操作,使用矩阵时要使用齐次坐标的矩阵,推导就不多描述了,懂原理就肯定能算出来!

 

旋转矩阵都为正交矩阵

关于正交矩阵的定义可以查看前面矩阵相关的文章。

我们知道旋转矩阵的值即为(1,0,0),(0,1,0),(0,0,1)三个向量旋转后的值,我们假设旋转后的值分别为 \vec{a}=(a_x,a_y,a_z)\vec{b}=(b_x,b_y,b_z)\vec{c}=(c_x,c_y,c_z),可得旋转矩阵:

R=\begin{bmatrix} a_x &b_x &c_x \\ a_y &b_y &c_y \\ a_z &b_z &c_z \end{bmatrix}

根据正交矩阵的定义可得 RR^T=R^TR=I,根据 R 我们可得 R^T 的值如下:

R^T=\begin{bmatrix} a_x &a_y &a_z \\ b_x &b_y &b_z \\ c_x &c_y &c_z \end{bmatrix}

让我们来验证下,如下:

R^TR=\begin{bmatrix} a_x &a_y &a_z \\ b_x &b_y &b_z \\ c_x &c_y &c_z \end{bmatrix}\begin{bmatrix} a_x &b_x &c_x \\ a_y &b_y &c_y \\ a_z &b_z &c_z \end{bmatrix}=\begin{bmatrix} a_x^2+a_y^2+a_z^2 &a_xb_x+a_yb_y+a_zb_z &... \\ ... &... &... \\ ... &... &... \end{bmatrix}

因为单位向量旋转后依旧是单位向量,因此 a_x^2+a_y^2+a_z^2=1。同时原本互相垂直的三个轴旋转后依旧互相垂直,因此a_xb_x+a_yb_y+a_zb_z=0(向量点乘的定义),其他几个位置的值也同理,因此可得结果为单位矩阵,旋转矩阵为正交矩阵没有问题。

这个性质非常重要,因为旋转矩阵是正交矩阵,所以旋转变换的逆变换对应的矩阵即为旋转矩阵的转置,即 R^{-1}=R^T

 

四元数旋转

我们先来说说上诉两种旋转的缺点:

欧拉角旋转:

  • 有很多种顺序,不同的顺序结果还不一样
  • 万向锁的问题
  • 无法实现平滑的插值,例如旋转15度的矩阵和旋转25度的矩阵相加除以2得到的矩阵并不是旋转20度的矩阵。或者说当我们的旋转角度均匀/平滑的增长时对应的旋转矩阵并不是均匀的增长。这些都是因为三角函数是曲线的。
  • 计算起来需要三个矩阵,在空间和时间上花费都较大

绕任意轴旋转:

  • 计算复杂
  • 无法实现平滑的插值

因此又衍生出了一种旋转,即四元数旋转,它最大的优势就是可以实现平滑的插值,以及不会产生万向锁的问题,缺点就是理解起来比较困难。

 

四元数

接下来我们先来了解一下四元数是什么,以及他的一些运算法则。

一个四元数可以表示为 q = w + xi + yj + zk,其中w,x,y,z为实数,i,j,k三者为复数,即 i^2=j^2=k^2=-1,因此四元数的实部为w,虚部为xi,yj和zk。

此外三个虚数还有如下关系:ij=k,ji=-k,jk=i,kj=-1,ki=j,ik=-j。这是不是和我们的坐标系叉乘一样(x轴叉乘y轴等于z轴等等)

除了上面的表达式外,四元数还有一种我们更熟悉的写法,为 q = ((x,y,z),w),其中(x,y,z)代表一个向量,即 q=(\vec{v},w) 或者写成 q=w+\vec{v}

若我们空间中的一个向量(1,2,3),使用四元数表示即为 0+1i+2j+3k 或者是 ((1,2,3),0)。

因此一个向量看作为一个实部为0的四元数,而一个虚部为0的四元数即为一个实数

 

四元数的模

类似于向量的模,四元数q的模即为: \left | q \right | = \sqrt{w^2+x^2+y^2+z^2}

 

四元数的乘法

设我们有两个四元数,分别为 q_1=w_1+x_1i+y_1j+z_1k=((\vec{v_1}),w_1) 和 q_2=w_2+x_2i+y_2j+z_2k=((\vec{v_2}),w_2),那么他们相乘的结果为:

 

q_1q_2=((\vec{v_1}\times \vec{v_2}+w_1\vec{v_2}+w_2\vec{v_1}),w_1w_2-\vec{v_1}\cdot \vec{v_2})

根据该式子我们也可发现 q_1q_2\neq q_2q_1 (不满足交换律)因为 \vec{v_1}\times \vec{v_2}\neq \vec{v_2}\times \vec{v_1} 

根据向量点乘,可得 \vec{v_1}\cdot \vec{v_2} = (x_1x_2+y_1y_2+z_1z_2)

根据向量叉乘,可得 \vec{v_1}\times \vec{v_2} = (y_1z_2-z_1y_2,z_1x_2-x_1z_2,x_1y_2-y_1x_2)

根据向量的数量积,可得 w_1\vec{v_2} = (w_1x_2,w_1y_2,w_1z_2)     w_2\vec{v_1} = (w_2x_1,w_2y_1,w_2z_1)

所有全部结合起来可得:

q_1q_2=(w_1w_2-x_1x_2-y_1y_2-z_1z_2)+(w_1x_2+w_2x_1+y_1z_2-z_1y_2)i+(w_1y_2+w_2y_1+z_1x_2-x_1z_2)j+(w_1z_2+w_2z_1+x_1y_2-y_1x_2)k

 

四元数的加法

四元数的各部位相加即可,满足交换律和结合律:

q_1+q_2=((\vec{v_1}+\vec{v_2}),w_1+w_2)=(w_1+w_2)+(x_1+x_2)i+(y_1+y_2)j+(z_1+z_2)k

 

四元数的点积

类似于向量的点积,如下:

q_1\cdot q_2=w_1w_2+\vec{v_1}\cdot \vec{v_2}=w_1w_2+x_1x_2+y_zy_2+z_1z_2

因此也可知: q\cdot q=\left | q \right |^2

 

共轭四元数

四元数q的共轭四元数,常标记为 q^* ,其值为 q^*=(-\vec{v},w) 。共轭四元数即为矩阵的共轭转置。根据四元数的乘法我们可得:

qq^*=(\vec{v}\times(-\vec{v})+w\vec{v}+w(-\vec{v}),ww-\vec{v}\cdot(-\vec{v}))

由于 \vec{v}\times(-\vec{v}) =0 所以 qq^*=ww-\vec{v}\cdot(-\vec{v})=ww-(-xx-yy-zz)=w^2+x^2+y^2+z^2=\left | q \right |^2

最终可得:

qq^*=q^*q=q\cdot q=\left | q \right |^2

 

四元数的逆

一个非零四元数q的逆,常标记为 q^{-1} ,其值为:

 q^{-1}=\frac{q^*}{\left | q \right |^2}

很明显可得: qq^{-1}=\frac{qq^*}{\left | q \right |^2} = 1

 

旋转

了解了四元数以及四元数的运算后,我们来看看怎么用四元数来表示旋转。前面我们说过变换的本质是函数,使用四元数表达也是一样的,即从一个四元数通过一个函数变成一个新的四元数。而这个用于表达旋转的函数即为:

{p}'=qpq^{-1}

其中p为我们的输入向量对应的四元数,{p}' 为旋转后的向量对应的四元数,而 q 的值为 ((x,y,z)\sin\frac{\theta}{2},\cos\frac{\theta}{2}) ,xyz为我们旋转轴的值(需要是单位向量,下面有解释),\theta 为我们的旋转角度。

根据前面所知 q^{-1}=\frac{q^*}{\left | q \right |^2},我们可以求得 {\left | q \right |^2} = \cos^2\frac{\theta}{2}+\sin^2\frac{\theta}{2}x^2+\sin^2\frac{\theta}{2}y^2+\sin^2\frac{\theta}{2}z^2= \cos^2\frac{\theta}{2}+\sin^2\frac{\theta}{2}(x^2+y^2+z^2),emmmm,很复杂,但是假如我们的旋转轴是单位向量呢?那么x^2+y^2+z^2 = 1,可得 {\left | q \right |^2} = \cos^2\frac{\theta}{2}+\sin^2\frac{\theta}{2}=1,因此 q^{-1}=q^*=(-(x,y,z)\sin\frac{\theta}{2},\cos\frac{\theta}{2}) 。

 

例如我们的输入向量为(1,0,0),那么它对应的四元数即为((1,0,0),0),此时我们想将它绕(0,1,0)旋转90度,就可得到如下公式

{p}'=((0,1,0)\sin45,\cos45)((1,0,0),0)(-(0,1,0)\sin45,\cos45)

通过四元数的乘法,我们来计算一下:

首先因为 \sin45 = \cos45 = \frac{\sqrt{2}}{2}  带入可得 {p}'=((0,\frac{\sqrt{2}}{2},0),\frac{\sqrt{2}}{2})((1,0,0),0)((0,-\frac{\sqrt{2}}{2},0),\frac{\sqrt{2}}{2})

然后先求前两项相乘的结果  ((0,\frac{\sqrt{2}}{2},0),\frac{\sqrt{2}}{2})((1,0,0),0)=\frac{\sqrt{2}}{2}i-\frac{\sqrt{2}}{2}k=((\frac{\sqrt{2}}{2},0,-\frac{\sqrt{2}}{2}),0) 

将前两项的结果乘以后一项得 ((\frac{\sqrt{2}}{2},0,-\frac{\sqrt{2}}{2}),0)((0,-\frac{\sqrt{2}}{2},0),\frac{\sqrt{2}}{2})=-1k=((0,0,-1),0)

可得旋转后的向量为 (0,0,-1) ,结果正确!

 

备注:相关其他知识待后续补充

 

Unity与旋转

注:虽然是说图形学基础,但是个人学习的目的主要还是更深入理解Unity,因此顺便也记录一些其中与Unity相关的知识点。

 

欧拉角旋转

在Unity中,我们在Inspector界面中设置Transform的Rotation属性,或者在代码里设置 transform.localEulerAngles ,这两种方法都是使用的欧拉角旋转。

此外我们还要注意,Unity使用的是左手坐标系而非前面我们一直所举例用的右手坐标系。

左手坐标系:

前面我们提到欧拉角旋转有6中排列组合,那么Unity使用的是哪种呢?在Transform的Rotate方法中,有如下一段注释:

The implementation of this method applies a rotation of zAngle degrees around the z axis, xAngle degrees around the x axis, and yAngle degrees around the y axis (in that order).

因此我们可以知道,Unity使用的是z-x-y外旋,也就是y-x-z内旋

如何确定是正确的呢?我们可以创建一个Cube,然后通过按照y-x-z的顺序来调整值,这个是内旋的顺序,即每次旋转都是按照模型坐标来旋转的,因此每次旋转对应的模型旋转轴不会发生改变。如下:

图形学知识基础:三维变换,旋转(欧拉角旋转与万向锁,绕任意轴旋转,四元数)_第3张图片

既然是使用欧拉角旋转,那么同样会存着万向锁的问题,因为是z-x-y的顺序,那么我们只需要把x轴旋转90度,就可触发万向锁,如图无论怎么修改y和z,都是在绕模型坐标的x轴或者说是世界坐标的y轴在旋转。

图形学知识基础:三维变换,旋转(欧拉角旋转与万向锁,绕任意轴旋转,四元数)_第4张图片

同时我们还要注意,像图中,我们是先设置好x轴的旋转,然后再设置y或者z的旋转,这并不代表unity先旋转了x轴,再旋转其他轴。实际上无论我们怎么先后设置xyz三个轴的旋转值时,Unity始终都是要按照z-x-y的顺序来从初始位置开始旋转。这点也很好理解,因为顺序不一样的话,结果也是不一样的。

 

绕任意轴旋转

Transform中提供了RotateRotateAround两种方法用于绕任意轴旋转。

Rotate

有如下三个参数,第一个参数为旋转轴的向量,第二个参数为旋转角度,第三个参数决定旋转轴的起点(默认为Space.Self即起点为物体的中心点,否则为世界坐标的原点)。

public void Rotate(Vector3 axis, float angle, [DefaultValue("Space.Self")] Space relativeTo)
{
  if (relativeTo == Space.Self)
    this.RotateAroundInternal(this.transform.TransformDirection(axis), angle * ((float) Math.PI / 180f));
  else
    this.RotateAroundInternal(axis, angle * ((float) Math.PI / 180f));
}

例如我们想绕过物体中心点的方向为(1,1,1)的轴旋转,使用下面方法即可

transform.Rotate(new Vector3(1, 1, 1), Time.deltaTime * 30);

效果如下:

 

RotateAround

若想自定义旋转轴的起点,则使用RotateAround方法即可

public void RotateAround(Vector3 point, Vector3 axis, float angle)

第一个参数即为旋转轴的起点,需要注意的是该起点的值为世界坐标的值,而不是相对于物体中心的偏移量。

若想绕起点在世界坐标(1,0,0)的向量(1,1,1)旋转,则代码为:

transform.RotateAround(new Vector3(1,0,0), new Vector3(1, 1, 1), Time.deltaTime * 30);

效果图如下(物体中心在世界坐标(0,0,0)上):

图形学知识基础:三维变换,旋转(欧拉角旋转与万向锁,绕任意轴旋转,四元数)_第5张图片

你可能感兴趣的:(图形学,欧拉角,万向锁,矩阵和旋转,四元数)