万向锁的表现和原理

这次一定要搞明白万向锁是个什么东西。本文会按照以下顺序慢慢展开:

  • 通过一个简单的可以实际操作的例子告诉大家,万向锁在我们生活中的表现
  • 什么是万向锁
  • 分别从矩阵和几何的角度讲一下万向锁存在的原理
  • 万向锁到底有什么问题
  • 为什么四元数可以解决万向锁的问题

万向锁的一个小实验

可以拿出手机放在桌面上,屏幕朝上,手机的最长边垂直与桌子的边缘设置为X轴,这个时候屏幕的短边平行于桌子的边缘设置为Y轴,因此垂直与屏幕的向量为Z轴。

我们先绕手机的最长边X轴顺时针旋转30度,这个时候手机离开桌面,留下一个长边与桌子接触;然后再绕Y轴,也就是手机的短边旋转90度,让屏幕面与桌子的边缘平行;

再绕Z轴旋转10度,也就是绕垂直于屏幕的轴旋转10度,这个时候你会发现,绕Z轴旋转时,屏幕面还是平行桌子的边缘,而此时绕Z轴旋转的角度给手机姿态带来的影响和最开始旋转X轴给手机姿态带来的影响是一样的——都是使手机最终的姿态(已经绕Y轴旋转了90度使得手机屏幕与桌子边缘平行)为绕着垂直于屏幕的轴旋转一定的角度。

也就是说,你完全可以不用绕Z轴旋转,通过调节绕X轴旋转的角度数,使得最终手机的姿态和上述旋转过程达到的姿态一样。

或者说,一旦Y轴转了90度,你的手机平面一定在与桌面垂直的平面内,无论你一开始如何绕X轴转,以及最后如何绕Z轴转

万向锁

什么是欧拉角

在解释万向锁之前简单讲一下欧拉角。

这个概念我们其实都接触过,但是可能没有那么深入,所以在这里再解释一下:

莱昂哈德·欧拉用欧拉角来描述刚体在三维欧几里得空间的取向。对于任何参考系,一个刚体的取向,是依照顺序,从这参考系,做三个欧拉角的旋转而设定的。所以,刚体的取向可以用三个基本旋转矩阵来决定。换句话说,任何关于刚体旋转的旋转矩阵是由三个基本旋转矩阵复合而成的。

它分为静态的欧拉角和动态的欧拉角

1,静态:即绕世界坐标系三个轴的旋转,由于物体旋转过程中坐标轴保持静止,所以称为静态。

2,动态:即绕物体坐标系三个轴的旋转,由于物体旋转过程中坐标轴随着物体做相同的转动,所以称为动态。

静态欧拉角

对于在三维空间里的一个参考系,任何坐标系的取向,都可以用三个欧拉角来表现。参考系(固定系)又称为实验室参考系,是静止不动的。而坐标系(固连系)则固定于刚体,随着刚体的旋转而旋转。

参阅右图。设定xyz-轴为参考系的参考轴。称xy-平面与XY-平面的相交为交点线,用英文字母(N)代表。zxz顺规的欧拉角可以静态地这样定义:

  • α\alpha(进动角)是x-轴与交点线的夹角,
  • β\beta(章动角)是z-轴与Z-轴的夹角,
  • γ\gamma(自旋角)是交点线与X-轴的夹角。

对于夹角的顺序和标记,夹角的两个轴的指定,并没有任何常规。科学家对此从未达成共识。每当用到欧拉角时,我们必须明确的表示出夹角的顺序,指定其参考轴。

角值范围

α\alphaγ\gamma值分别从0至2π2\pi 弧度。
β\beta值从0至π\pi弧度。
对应于每一个取向,设定的一组欧拉角都是独特唯一的;除了某些例外:

  • 两组欧拉角的α\alpha,一个是0,一个是2π2\pi,而β\betaγ\gamma分别相等,则此两组欧拉角都描述同样的取向。
  • 两组欧拉角的γ\gamma,一个是0,一个是2π2\pi,而α\alphaβ\beta分别相等,则此两组欧拉角都描述同样的取向。

前面提到,设定刚体取向的旋转矩阵{\displaystyle [\mathbf {R} ]}[\mathbf{R}]是由三个基本旋转矩阵合成的:

[R]=[cosγsinγ0sinγcosγ0001][1000cosβsinβ0sinβcosβ][cosαsinα0sinαcosα0001][R] = \begin{bmatrix} cos\gamma & sin\gamma & 0 \\ -sin\gamma & cos\gamma & 0 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 1 & 0 & 0 \\ 0 & cos\beta & sin\beta \\ 0 & -sin\beta & cos\beta \end{bmatrix} \begin{bmatrix} cos\alpha & sin\alpha & 0 \\ -sin\alpha & cos\alpha & 0 \\ 0 & 0 & 1 \end{bmatrix}

经过运算之后的结果为

[R]=[cosαcosγcosβsinαsinγsinαcosγ+cosβcosαsinγsinβsinγcosαsinγcosβsinαcosγsinαsinγ+cosβcosαcosγsinβcosγsinβsinαsinβcosαcosβ][R] = \begin{bmatrix} cos\alpha cos\gamma - cos\beta sin\alpha sin\gamma & sin\alpha cos\gamma + cos\beta cos\alpha sin\gamma & sin\beta sin\gamma \\ -cos\alpha sin\gamma - cos\beta sin\alpha cos\gamma & -sin\alpha sin\gamma + cos\beta cos\alpha cos\gamma & sin\beta cos\gamma \\ sin\beta sin\alpha & -sin\beta cos\alpha & cos\beta \end{bmatrix}

在经典力学里,时常用zxz顺规来设定欧拉角;照着第二个转动轴的轴名,简称为x顺规。另外,还有别种欧拉角组。合法的欧拉角组中,唯一的限制是,任何两个连续的旋转,必须绕着不同的转动轴旋转。因此,一共有12种顺规。例如,y顺规,第二个转动轴是y-轴,时常用在量子力学、核子物理学、粒子物理学。另外,还有一种顺规,xyz顺规,是用在航空航天工程学;参阅泰特-布莱恩角

动态欧拉角

我们也可以给予欧拉角两种不同的动态定义。一种是绕着固定于刚体的坐标轴的三个旋转的复合;另外一种是绕着实验室参考轴的三个旋转的复合。用动态的定义,我们能更了解,欧拉角在物理上的含义与应用。特别注意,以下的描述, XYZ坐标轴是旋转的刚体坐标轴;而xyz坐标轴是静止不动的实验室参考轴。

  • 绕着XYZ坐标轴旋转:最初,两个坐标系统xyz与XYZ的坐标轴都是重叠着的。开始先绕着Z-轴旋转α\alpha,角值。然后,绕着X-轴旋转β\beta,角值。最后,绕着Z-轴作角值γ\gamma,的旋转。

  • 绕着xyz坐标轴旋转:最初,两个坐标系统xyz与XYZ的坐标轴都是重叠着的。开始先绕着z-轴旋转γ\gamma,角值。然后,绕着x-轴旋转β\beta,角值。最后,绕着z-轴作角值α\alpha,的旋转。

什么是万向锁

我们了解了欧拉角分为静态欧拉角和动态欧拉角,我们在继续看看什么是万向锁。

首先我们先直观地理解一点,万向锁只会产生在动态欧拉角,也就是我们是绕物体自身的坐标系旋转的,物体自身的坐标系会随着物体的旋转一起旋转

通过上面那个小实验,我们可以大致这样理解,所以万向锁之所以叫做锁,是因为有两个维度的旋转效果重叠了,即旋转X轴的效果和完全可以通过旋转Z轴来实现,也就是丢失了一个维度.

但是从另一个角度讲,这个“锁”的意思并不是说欧拉角有什么不能够表示出来的角度,而是如果你的章动角是90度的话你最终的结果一定是在某个平面上的

万向锁的原理

从几何角度讲

我们首先看一下下面这个图,这个图的坐标系既是物体自身坐标轴,和世界坐标轴完全一样

我们首先绕x轴旋转一个角度,没有什么问题

然后我们再绕y轴旋转90度

这个时候可以看到z轴变成了原来x轴的方向,这个时候我们绕物体自身的z轴转,从世界坐标上来看,就是绕初始状态的x轴在转,也就是说从世界坐标系上看来,一开始绕物体自身的x轴,和最后绕物体自身的z轴转,都是一样的,都是绕世界坐标系的x轴转

从矩阵角度讲

上述过程用矩阵的乘法形式写出来就是

Rz(β)Ry(π/2)Rx(α)=[cosβsinβ0sinβcosβ0001][001010100][1000cosαsinα0sinαcosα]=[0sin(αβ)cos(αβ)0con(αβ)sin(αβ)100]=[001010100][1000cos(αβ)sin(αβ)0sin(αβ)cos(αβ)]=Ry(π/2)Rx(αβ)R_z(\beta)R_y(\pi / 2)R_x(\alpha) \\ =\begin{bmatrix} cos\beta & -sin\beta & 0 \\ sin\beta & cos\beta & 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(\alpha - \beta) & cos(\alpha - \beta) \\ 0 & con(\alpha - \beta) & -sin(\alpha - \beta) \\ -1 & 0 & 0 \end{bmatrix} \\ = \begin{bmatrix} 0 & 0 & 1 \\ 0 & 1 & 0 \\ -1 & 0 & 0 \end{bmatrix} \begin{bmatrix} 1 & 0 & 0 \\ 0 & cos(\alpha - \beta) & -sin(\alpha - \beta) \\ 0 & sin(\alpha - \beta) & cos(\alpha - \beta) \end{bmatrix} \\ = R_y(\pi /2)R_x(\alpha - \beta)

万向锁到底有什么问题

所以万向锁并不是不能表示空间中的某个角位移,而是因为万向锁,角位移之间的插值变得不如意,那么万向锁是怎么出现的呢?第二个旋转的角度为90度的时候,使用第一个轴与第三个轴相重合,这是从感性的角度去认识这个现象,如果从数学的角度去解释,那就是cos90=0

在游戏中,当角色旋转的动画触发时,角色就会做一系列连续的旋转变换,每一个变换都要用一组欧拉角来表示,但是不可能把每一个方位的欧拉角都存储起来,因此动画师定义了一系列关键帧,指定关键帧处角色的方位(用一组欧拉角描述),然后计算机根据时间t对这几组欧拉角进行插值,得到一系列欧拉角。

如果pitch不是±90°,就不会出现万向锁现象,插值后的一系列欧拉角完全可以刻画出我们所期望的角色旋转路径。

如果某个关键帧的pitch即绕第二个轴的旋转为90°,就会遇到万向锁,这时手机只能在水平面内旋转,如果动画师指定下一个关键帧手机的方位不是立起来的,没有任何问题,但如果指定的下一个关键帧的方位是立起来的,会出现什么情况呢?

四元数如何解决万向锁问题

换种方式表示旋转

要解决这个问题,首先要问一下,万向锁问题存在的前提条件

我们前面知道了,万向锁只存在于动态欧拉角,即物体绕自身坐标系旋转,且自身坐标系会随着自身的旋转一起旋转,它表现为Y轴旋转90度的话,X轴和Z轴的旋转效果是相通的。

换个角度想,其实就是在欧拉角下,同样的效果有多种不同的旋转方式,导致插值的时侯结果不确定。

那要解决这个问题,其实思路也比较明确,就是换个表示方法,在这个新的表示方法中,一个旋转结果的表示方法是唯一的。

我们给出另一种表述方向的方法:轴角表示(Axis-Angle-Representation)。跟欧拉角不同的是,我们这次不再采取多次旋转的方式来找到目标方向,而是找到一根旋转轴,只通过绕这根轴旋转一次就可以得到目标方向。这样就不会产生问题了吗?是的,证明方法很简单,首先以目标矩阵原点为一角,三轴为三边建立一个立方体,这个立方体中通过原点的对角线就是我们要找的旋转轴,显然,这个旋转轴是唯一的,而我们知道,绕一个旋转轴旋转不同角度,对应的方向也是不同的(角度范围 (−π,π]),由此可见,空间中任一位置的轴角表示是唯一的。

一般来说,轴角表示方向时,会出现4个参数,其中3个用于表示旋转轴,1个用于表示旋转角大小。而如果我们规定了表示旋转轴的向量为单位向量 e ,考虑到x2+y2+z2=1\sqrt{x^2+y^2+z^2}=1 ,我们就可以用两个参数表示出旋转轴,总共只需要三个参数,跟欧拉角一样多。旋转向量我们可以用θ=θe\bm{\theta} = \theta e 表示,其中 e 是单位向量。顺便提一句,这里的旋转轴我们称为欧拉轴(Euler axis),这里的旋转向量我们称为欧拉向量(Euler vector)。

从矩阵角度考虑这个东西的存在也是可以的,矩阵中有个相似矩阵和特征向量的概念,大家可以理解这个旋转轴就是欧拉角旋转矩阵的特征向量,这个旋转轴的旋转角度就是该特征向量的特征值。

那么四元数是什么呢?

四元数是复数,更具体地说四元数是存在三个虚部的复数。q=w+ix+jy+kz,其中i,j,k是虚数单位,满足 i2=j2=k^2 = -1,且 i⋅j=k,j⋅i=k 。

q1+q2=(w1+w2)+i(x1+x2)+j(y1+y2)+k(z1+z2)=[(w1+22),(v1+v2)]q1q2=(w1w2x1x2y1y2z1z2)+i(w1x2+x1w2+y1z2z1y2)+j(w1y2x1z2+y1w2+z1x2)+k(w1z2+x1y2yzx2+z1w2)q_1 + q_2 = (w_1 + w_2) + i(x_1 + x_2) + j(y_1 + y_2) + k(z_1 +z_2) = [(w_1 + 2_2), (v_1 +v_2)] \\ q_1 \cdot q_2 = (w_1w_2 -x_1x_2 - y_1y_2 - z_1z_2) + \\ i(w_1x_2 + x_1w_2 + y_1z_2 - z1y_2) + \\ j(w_1y_2 - x_1z_2 + y_1w_2 + z_1x_2) + \\ k(w_1z_2 + x_1y_2 -y_zx_2 + z_1w_2)

将四元数用于计算轴角表示运算时,我们通常写成向量形式(vector representation) ,q=[w,v]=[w,(xyz)]q=\begin{bmatrix} w, \vec v \end{bmatrix}=\begin{bmatrix} w, \begin{pmatrix} x \\ y \\ z \end{pmatrix} \end{bmatrix},为了表达清晰和计算方便,我们将w,x,y,z的取值定位w2+x2+y2+z2=1w^2+x^2+y^2+z^2 = 1 ,并称之为单位四元数,在方向计算时单位四元数中w,x,y,z分饰的角色我们后面会解释。此时,复数乘法可表示为向量形式

q1q2=[w1,v1][w2,v2]=[w1w2v1v2,v1×v2+w1v2+w2v1]q_1 \cdot q_2 = [w_1, \vec v_1] \cdot [w_2, \vec v_2] = [w_1w_2 - v_1v_2, v1 \times v_2 + w_1 \cdot v_2 + w_2 \cdot v_1]

或者我们也可以写成矩阵的形式

q1q2=[w1x1y1z1x1w1z1y1y1z1w1x1z1y1x1w1][w2x2y2z2]q_1q_2 = \begin{bmatrix} w_1 & -x_1 & -y_1 & -z_1 \\ x_1 & w_1 & -z_1 & y_1 \\ y_1 & z_1 & w_1 & -x_1 \\ z_1 & -y_1 & x_1 & w_1 \end{bmatrix} \begin{bmatrix} w_2 \\ x_2 \\ y_2 \\ z_2 \end{bmatrix}

观察此式,我们发现两个四元数相乘,需要存储8个单位的数据,也就是说,每个参与运算的四元数只要存储4个单位数据即可。此外还需要几个特殊的性质:

  • 四元数模q=w2+x2+y2+z2||q|| = \sqrt{w^2 + x^2 + y^2 + z^2}
  • 四元数共轭q=(w+ix+jy+kz)=wixjykzq^{*} = (w + ix + jy + kz)^{*}= w -ix -jy - kz
  • 共轭的向量形式q=([wv])=[wv]q^{*} = (\begin{bmatrix} w & \vec v \end{bmatrix})^{*} = \begin{bmatrix} w & -\vec v \end{bmatrix}
  • 四元数的倒数:qq1=q1q=1q \cdot q^{-1} = q^{-1} \cdot q = 1
  • 共轭与倒数的关系:q1=qw2+x2+y2+z2q^{-1}=\frac{q^*}{w^2+x^2+y^2+z^2},对于单位四元数q=q1q^{*} = q{-1}
  • 四元数运算同时满足结合律和分配律,不符合交换律

了解了上面的计算法则,我们就可以利用四元数来计算方向变换过程了。还记得我们之前说过计算时四元数我们用向量形式表示,且保证它是单位四元数吗?其实四元数的向量形式我们还可以进一步改写为极形式(polar representation),q=q[cosθ,nsinθ]q = \|q\|[cos\theta, \vec{n}\cdot sin\theta]

其中 ‖q‖ 代表了四元数的模,单位四元数模为1,而 θ 是四元数表示的旋转过程的半角大小,也就是说(2θ) 就是旋转角大小, n 则是表示旋转轴方向的单位向量。用这种表示方法,四元数即可表示任意轴角表达的方向变换。

先将原向量坐标表示为四元数p=[0,v]p=[0,\vec{v}] ,将旋转角度及旋转轴表示为单位四元数 q ,旋转后的向量坐标可通过r=qpqr = q\cdot p\cdot q^*r=qpq1r = q \cdot p\cdot q^{-1} 计算得出

为什么引入四元数

其实四元数就是为了更好地表示旋转轴和旋转角度而已,旋转轴是个三维向量,加上旋转角度就变成四维了。而选择四元数来表示是因为它的一些特性。

先说结论,四元数的引用是为了减少计算量和计算时存储占用的空间。

但是,如果你足够细心,一定可以发现两个四元数相乘的过程其实是一个4×4矩阵与一个4×1矩阵相乘的过程,而四元数计算一次变换需要两次这个过程,其中包括24次加法运算和32次乘法运算,反观欧拉角的矩阵变换只要进行一次3×3矩阵和3×1矩阵的乘法运算,其中包括6次加法运算和9次乘法运算,运算量明显是四元数更大一些。如果你再细心一些可以发现,四元数运算时虽然有个4×4矩阵参与运算,但是矩阵中的每一项都是已经存储过的单位数据,而参与欧拉角运算的3×3矩阵则要通过另外已存储的单位数据进行的16次乘法运算,4次加法运算以及4次符号改变运算来求出,不过即使加上这些运算过程,矩阵运算也只要25次乘法运算,10次加法运算以及4次符号改变运算,运算量上来说,欧拉角的矩阵运算依然比四元数运算要有优势。但事实上,我们一般遇到的运动学问题很少会有只做一次方向转换的情况出现,对于复杂的系统和机器人来说,我们往往会面对数量庞大的转变方向过程。这种情况下四元数的优势就体现出来了,我们考虑多次变换的四元数运算(利用上面极坐标形式):

R=R1R2...Rn1Rn=(qn(qn1...(q2(q1pq1)q2)...qn1)qn)R = R_1R_2...R_{n-1}R_n = (q_n(q_{n-1}...(q_2(q_1pq^*_1)q^*_2)...q^*_{n-1})q^*_n)

利用结合律:

R=R1R2...Rn1Rn=(qnqn1...q2q1)p(q1q2...qn1qnR = R_1R_2...R_{n-1}R_n = (q_nq_{n-1}...q_2q_1)p(q^*_1q^*_2...q^*_{n-1}q^*_n)

考虑四元数的共轭(qaqb)=qbqa(q_aq_b)^* = q^*_bq^*_a

R=R1R2...Rn1Rn=(qnqn1...q2q1)p(qnqn1...q2q1)R = R_1R_2...R_{n-1}R_n = (q_nq_{n-1}...q_2q_1)p(q_nq_{n-1}...q_2q_1)^*

可以发现,原向量 ppp 左右两侧括号里的运算结果是一对共轭四元数,也就是说可以利用3次易号运算代替n次四元数相乘运算,大大减少了计算量。

最后稍微提一嘴,很多接触这个问题的人都是游戏开发者,可能会有疑问,为什么在Unity的Inspector中没有这个问题,这个大家可以去试试,Unity的Inspector中Y轴不是物体自身坐标轴旋转,所以万向锁就被解了,具体Unity内部的旋转是怎么回事可以看下一篇博客,这里挖个坑

参考文章:

欧拉角 - 维基百科

如何通俗地解释欧拉角?之后为何要引入四元数?