大家好,这是我的第一篇文章。今后自己的博客文章会不定期进行更新,大部分主题将会是我对近几年 GDC (游戏开发者大会)上的数学专题与编程专题的学习心得(总结)~
这一篇文章目的是向大家深入介绍四元数,主要的参考文献是2013年 GDC 的 《UnderstandingQuaternions》 。和很多博客不同的是,自己最后会尝试利用一些群论的知识去理解四元数,学习过相关知识的同学可以看看,相互探讨一下。文章篇幅可能有些长,可以直接在目录里面选择自己需要了解的进行阅读。
- 背景
- 复数
- 四元数
- 群论观点下的四元数
- 四元数在 Unity 中的应用
- 为什么是四个元?
- i,j,k 是什么?
- 为什么旋转的公式是 qpq−1 ?
- 为什么是 θ/2 ?
- 我们应该如何理解 4D ( 4D 可视化)?
那么话不多说,先来进入第一部分。
- 旋转定义
- 旋转矩阵
- 欧拉角
- 罗德里格旋转公式
通常将物体绕一个点或者一个轴转动一定角度的操作称为旋转。不妨先考虑 2D 的情形:
那么自然而言便会引申出旋转角的概念:
显然,旋转角串联起了旋转前后的两个图形。而这种“串联”关系,通常采用矩阵的语言进行描述。
在线性代数里,我们知道:
- 一个矩阵代表一种变换(并不一定是旋转变换)。
- 一个旋转矩阵乘以一个向量(左乘或者右乘)将使得该向量被旋转。
其中,2维的旋转矩阵形式如下:
欧拉这样一位划时代的数学大师提出的欧拉角,是一种十分简单的表示旋转的有力工具。
来看看 wikipedia 对欧拉角的定义:
令原始坐标系的三个坐标轴分别为 x,y,z ,旋转后的坐标系的三个坐标轴为 X,Y,Z , N 轴是 xOy 平面与 XOY 平面的交线(也可以利用外积将 N 轴定义为 N=z×Z )。这样一来,三个欧拉角可定义如下:
- α (或 φ ) 是 x 轴和 N 轴的夹角;
- β (或 θ ) 是 z 轴和 Z 轴的夹角;
- γ (或 ψ ) 是 N 轴和 X 轴的夹角;
也就是说,欧拉角将绕一个过原点的旋转轴旋转 θ 分解成了“三步曲”(此时 xyz 坐标系跟随旋转):
1. 绕 z 轴旋转 α ,使得 x 轴与 N 轴重合;
2. 绕 x 轴旋转 β ,使得 z 轴与 Z 轴重合;
3. 绕 z 轴旋转 γ ,此时 xyz 坐标系将与 XYZ 坐标系重合。
这样的旋转顺序被称为是“ zxz ”顺序的,实际上并不一定非要按照这个顺序进行旋转,这个我们等等会再提及一下,此处先以“ zxz ”顺序为标准继续进行介绍。
看到这里,也许你已经想骂人了:嘿,说好的“欧拉角很简单呢?”。别急,可能这样将坐标系旋转来旋转去确实会令人有些头大(想象不出来如何旋转的同学,可以参看欧拉角- wikipedia 百科里的动图演示)。接下来,我们会证明上述的旋转过程是与下述旋转过程(此时 xyz 坐标系不跟随旋转,注意与上述旋转过程的区别)等价的:
1. 绕 z 轴旋转 γ ;
2. 绕 x 轴旋转 β ;
3. 绕 z 轴旋转 α 。
为了证明上述两种旋转过程等价(即最终的旋转结果相同),可以利用欧拉角对应的旋转矩阵进行证明,证明过程如下:(参考自欧拉角与万向节死锁)
记
- 绕 z 轴旋转 α ,使得 x 轴与 N 轴重合对应的旋转矩阵为 Mz1N ;
- 绕 x 轴旋转 β ,使得 z 轴与 Z 轴重合对应的旋转矩阵为 MxN ;
- 绕 z 轴旋转 γ ,此时 xyz 坐标系将与 XYZ 坐标系重合对应的旋转矩阵为 Mz2N ;
- 绕 z 轴旋转 γ 对应的旋转矩阵为 Mz2 ;
- 绕 x 轴旋转 β 对应的旋转矩阵为 Mx ;
- 绕 z 轴旋转 α 对应的旋转矩阵为 Mz1 。
那么问题便转化为求证: Mz1NMxNMz2N=Mz2MxMz1 。
证明:
显然, Mz1N=Mz1 ,因为此时的旋转还不受坐标系改变的影响。
而对于 MxN ,要得到绕 x 轴旋转 β ,使得 z 轴与 Z 轴重合的效果,可以先绕 z 轴旋转 α (此时坐标系跟随旋转),然后绕 x 轴旋转 β ,最后再绕 z 轴旋转 α (脑海里面想象不出来的话可以拿些物体比划比划,我就是这么过来的=。=)。用旋转矩阵的语言描述出来即为:
因此,实际上在使用欧拉角表示旋转时,可以直接将其理解为先绕 z 轴旋转 γ ,再绕 x 轴旋转 β ,最后绕 z 轴旋转 α 。这样不涉及坐标系的旋转,在理解和运用上便会简单得多。
需要注意的是:
1. 欧拉角的旋转顺序并不一定都是“ zxz ”顺序,你也可以根据自己需要定义为“ xyz ”,“ zyx ”等等,上述性质是不会改变的。只要保证前后定义的旋转顺序一样即可,否则可能会出现无法预料的错误,毕竟不同旋转顺序下旋转效果还是有可能大不相同的。
2. 细心的同学可能会发现,在旋转矩阵中,最先进行的旋转操作对应的旋转矩阵在最右侧与向量相乘,旋转矩阵按照旋转的次序从右向左排列;而在欧拉角中,最先进行的旋转操作(此处说的是坐标系跟随旋转的情形)对应的旋转矩阵在最左側与向量相乘。看到一篇文章,三维旋转:旋转矩阵,欧拉角,四元数,对于这个问题是解释得不错的:
对于前者(旋转矩阵),我们始终是以绝对参考系为参照来的,对于后者(欧拉角),我们每一次旋转的刻画都是基于刚体的坐标系。比如,在欧拉角中的第2步,绕 x 轴旋转 β ,这里的 x 轴实际上是 N 轴了(而不是蓝色的 x 轴)。
为什么旋转参考系的不同会导致旋转矩阵次序的差异呢?细想一下便知,旋转矩阵左乘叠加用以描述三维变换效果的叠加,这本身就是基于绝对坐标系的,所以旋转矩阵一节没有疑问;而对于欧拉角一节的这种旋转方式,这样考虑:
1. 如果有一个“影子坐标系3”与原坐标系重合,然后首先进行了第3步(绕 z 轴旋转 γ );
2. 然后有一个“影子坐标系2”也与原坐标系重合,然后与“影子坐标系3”一起(视作同一个刚体)进行了第二步;
3. 最后一个“影子坐标系1”,与前两个坐标系一起进行了第一步。
此时,考察“影子坐标系”1和2,他们就分别落在了欧拉角旋转的两个“快照”上,而“影子坐标系3”就落在旋转后的位置上(红色的)。
而在上述过程中,“影子坐标系3”就是相对于绝对坐标系依次进行了第三步,第二步,和第一步。所以欧拉角的旋转矩阵写成那样,也是行得通的。
说了这么多,先停下来总结一下旋转矩阵和欧拉角:
旋转矩阵
- 优点:学过线性代数的都能很容易地理解;
- 缺点:和其它表示旋转的方式相比,其空间消耗较大,一个三维的旋转矩阵需要存储9个元素,而且在作矩阵乘法时也会消耗较多的时间。
- 欧拉角
- 优点:表示简单,仅需要存储三个值, (α,β,γ) ;
- 缺点:在编程实现上,通常还是将其转化为旋转矩阵进行计算,其空间复杂度和时间复杂度没有太大的变化;且在使用过程当中可能会出现著名的万向节锁现象,直观理解可以参考这个视频,欧拉旋转。
这个视频比较形象地讲解了何谓万向节锁,万向节锁大体说来便是物体绕某个坐标轴旋转了90度,使得某两个坐标轴平面重合,从而丢失了一个维度。在这种情况下,无论接下来作什么旋转都无法将物体旋转到某个角度,除非打破原先定义的旋转顺序或者同时旋转三个坐标轴。这样一个问题的产生也使得欧拉角在球面平滑插值上“力不从心”。
这样看来,也许四元数的出现正是一个 timing 吧……
提到欧拉角,就不得不提及罗德里格旋转公式。罗德里格旋转公式的出现将欧拉角从较复杂的旋转矩阵计算“解放”了出来。该旋转公式是这样的:
给定旋转轴 r⃗ ,旋转角 θ 以及一个点 p⃗ ,则 p⃗ 绕旋转轴 r⃗ 旋转 θ 后的点的计算公式为:
R(r⃗ ,θ,p⃗ )=p⃗ cosθ+(r⃗ ×p⃗ )sinθ+r⃗ (r⃗ p⃗ )(1−cosθ).
该公式是由法国数学家 Benjamin Olinde Rodrigues 提出的,当然这位数学家最著名的工作并非这个旋转公式,而是勒让德多项式……好吧,有点扯远了……关于罗德里格旋转公式的证明在此先简略,有兴趣的同学直接参看罗德里格旋转公式的维基百科即可。( Rodrigues′rotation formula )
罗德里格旋转公式的核心思想是向量分解:不妨先假设三维空间中待旋转的点(向量)为 p⃗ ,将其分解为两个分向量的和,其中一个向量平行于旋转轴,另外一个向量与旋转轴正交。这样,我们便可以使用二维平面上的旋转方法操作 p⃗ 正交于旋转轴的分向量(如下图所示):
如此一来,我们直接在一个2维平面上旋转 p⃗ 正交于旋转轴的分向量之后,再把它与 p⃗ 平行于旋转轴的分向量相加即得最终的结果。某种程度上,我们将3维的旋转操作转化为了2维的旋转操作,达到“降维”的效果。
有趣的是,罗德里格旋转公式通常还有另外一种形式(先剧透一下,这与后面要介绍的四元数息息相关~):
令 a2+b2+c2+d2=1 ,其中 a=cos(θ/2) ,再设 r=(b,c,d)=sin(θ/2)r⃗ ,那么罗德里格旋转公式还可以写成:
R(a,r,p⃗ )=2a(r×p⃗ )+2(r×(r×p⃗ ))+p⃗ .
证明:将 a=cos(θ/2),r=(b,c,d)=sin(θ/2)r⃗ 代入即可,
因此, R(r⃗ ,θ,p⃗ )=R(a,r,p⃗ ) ,说明罗德里格旋转公式的两种表示形式是等价的。
罗德里格旋转公式第二种表示形式提出了可以用于创建3维旋转的4个参数。也许对于已经学过四元数的同学,看到这里应该会有似曾相识的感觉吧……别急,咱们继续往下看~(^▽^)
呼呼呼……终于进入第二部分噜,谈及四元数不可避免地需要提及复数。我个人始终认为,两者之间的联系是密不可分的。
复数最初来自于求解一些特定的二次方程,比如 x2+1=0 ,其解为 x=−1−−−√ ,不妨给它一个特定的名字,就让 x=i 吧。
复数便是这样一类数字:
四元数的乘法运算法则为:
- 四元数的诞生
- 四元数的性质
- 四元数与三维旋转的“千丝万缕”
- 四元数与四维旋转
- 四元数的可视化
- 四元数的插值
第二部分哗地一下就结束了(大雾),主要是因为上过高中的应该都学过复数,因此许多性质也就不再赘述。第二部分仅列出理解四元数需要的一些“前置技能”而已,现终于进入核心章节——四元数了(撒花(^▽^))。
爱尔兰数学家汉密尔顿可以说是“四元数之父”了。他在回忆录当中大概是这样描述四元数的诞生的:有一天他在吃早餐时,他的儿子问他:“爸爸,你可以让两个三维向量相乘得到另外一个三维向量吗?”他遗憾地回答:“不,我只能将两个三维向量相加减得到另外一个三维向量。”
注意,这里的乘法运算不再满足交换律。如果你熟悉叉积,这可能看起来很熟悉。实际上,叉积正是来自四元数。
此外,对于上述定义的乘法运算,每一个非零向量都有其逆元。后面还会提到,这意味着当我们希望撤销某一个旋转时,我们只要取该旋转操作对应的四元数的逆元即可,这是十分方便的。
讲到这里,我们现在已经可以回答前两个问题了(可能有同学已经忘了最初的问题是什么,这里再贴一下):
- 为什么是四个元?
- i,j,k 是什么?
- 为什么旋转的公式是 qpq−1 ?
- 为什么是 θ/2 ?
- 我们应该如何理解 4D ( 4D 可视化)?
对于问题1:为什么是四个元?
因为仅仅三个维度是无法定义出满足条件的乘法运算的:
1. 两个向量作乘法运算的结果仍旧是一个向量;
2. 该乘法运算不满足交换律;
3. 两个非零向量作乘法运算以后不能产生零向量。
对于问题2: i,j,k 是什么?
存在于四维空间的三个坐标轴。
某种程度上,四元数是由复数扩展而来的:
与复数类似,单位四元数(记住,这类四元数的模是1!!!)可以表示一个旋转,后面会进行证明。
对于3维旋转,令
\omega=cos(\theta/2),\\(x,y,z)=v=sin(\theta/2)r.
四元数旋转定理:
已知三维向量p 和单位四元数q,将p 看成一个四元数(为了方便,我依旧命名为p),即p=(0,p),则向量p 经旋转q 操作后的结果为:
p'=qpq^{-1}=qpq^{*}.
还可以写成如下形式:
p'=p+2\omega(v\times p)+2(v\times(v\times p)).
(喂,这不就是罗德里格旋转公式吗……)
先来看第二种形式,你们的猜测是没错的,这其实就是罗德里格旋转公式的“变种”,证明方法也与罗德里格旋转公式的证明方法类似,只要先把单位四元数q 拆成\omega 和v 即可,即:
四元数
\omega=cos(\theta/2),(x,y,z)=v=sin(\theta/2)\vec r,\\p'=p+2\omega(v\times p)+2(v\times(v\times p));
令四元数q=(cos(\sigma/2),sin(\sigma/2)u),则
q=(cos(\sigma/2),sin(\sigma/2)u)=(\frac{kv}{{{|v|^2}}},v\times k)=\frac{1}{{{|v|^2}}}(0,-k)(0,v),
上图非常直观地回答了问题4:为什么在p'=qpq^{-1}里的q=(cos(\sigma/2),sin(\sigma/2)r) 用的是\theta/2 而非\theta?
直观原因就是q,q^{-1} 做的均是一个\theta/2 的旋转,且qp,pq^{-1} 不一定是一个纯四元数(即第一个分量为0的四元数,这是三维向量的特殊表示形式)。因此,为了保证最终结果为一个纯四元数,我们须作两次旋转:第一次旋转结果qp 跳出了原先的三维超平面,第二次旋转结果pq^{-1} 才又回到了三维超平面的世界。
说完了三维旋转,终于可以讲讲四维旋转这样一种人脑很难想象的操作了。实际上,四元数定义的都是四维旋转,而不是三维旋转。三维旋转仅仅是四维旋转的一种特殊情形,就像二维旋转是三维旋转的一种特殊情形一样。利用四元数进行三维旋转时,其实是在一个四维空间上的某个三维超平面上进行的。
一般来说,单独一个四元数是无法执行四维旋转的,一个四维旋转可以唯一地被拆分为一个左旋转 qL 和一个右旋转 qR ,表达出来就是 p′=qLpqR ,此处的 p,p′ 已经不仅仅是一个纯四元数了。
终于到最后一个问题了:我们都是生活在三维空间内的生物,该如何想象“生活在”在四维空间内的四元数呢?
接下来我们仅仅考虑模不超过1的四元数(容易想象一些……),将其构成的4维超球切成三块,它们都是四维空间的半球在3维空间上的投影(在我们看来就是三维球体了),这个“切”的依据便是四元数 q=(w,v),w∈R,v∈R3 中 w 的大小了。如下图所示:
经过投影切割后,我们可以得到两个实心球( w>0 或 w<0 )和一个空心球( w=0 )。在4维空间上,左右两个实心球通过中间的空心球连接在一起。对于任意球体的某个截面而言,四元数“退化”成了一个三维向量,其长度为 |sin(θ/2)| ( q=(w,v)=(cos(θ/2),sin(θ/2)r,|r|=1 )。
当截面上的向量长度等于0时,你就会得到一个单位四元数(此时 sin(θ/2)=0,cos(θ/2)=1,−π≤θ≤π )。 w>0 所属实心超球里的四元数表示 −π 到 π 的旋转,而 w<0 所属实心超球里的四元数同样表示 −π 到 π 的旋转,只不过此时的旋转轴需要被翻转; w=0 所属空心超球里的四元数则代表大小为 π 的旋转。
前面已经介绍过欧拉角的万向节锁问题,正是由于万向节锁导致欧拉角无法很好地用于三维旋转的插值(当出现万向节锁现象时无法按照原定旋转顺序得到预期的旋转,即欧拉角用于三维旋转的插值时并不平滑)。那么。这时候就必须要四元数站出来扛大梁了~以下部分参考文章 Understanding Quaternions 中文翻译《理解四元数》以及四元数插值与均值(姿态平滑),限于篇幅,这里只介绍计算机图形学里表示三维旋转插值常用的 SLERP 方案的球面线性插值。
SLERP 方案可以在两个朝向之间平滑地进行插值,不妨设第一个朝向为 q1=(ω1,x1,y1,z1) ,第二个朝向为 q2=(ω2,x2,y2,z2) ,它们都是单位四元数,它们的夹角是 θ ;被插值前的点为 p ,插值后的点为 p′ ,插值参数为 t∈[0,1] ,当 t=0 时 p′=q1 ,当 t=1 时 p′=q2 。
事实上,球面上的线性插值和一般的线性插值是有一些区别的。一般的线性插值公式为 vt=v1+(v2−v1)t,t∈[0,1] ,我们可以将 vt 理解为起点为原点,终点在 v2−v1 上的插值向量。如下图所示:
当 t=14,24,34 时, vt 将 v2−v1 等分为四部分,但对应的弦长却并不相同!说明,当 t 均匀变化时,对应的旋转角速度并不是均匀变化的,因此有必要采用更合理的插值公式。
不妨设球面线性插值的计算公式为 vt=p(t)v1+q(t)v2,|v1|=|vt|=|v2|=1 ,现在我们要计算出 p(t),q(t) 。注意到 v1,v2 之间的夹角为 θ , v1,vt 之间的夹角为 tθ , vt,v2 之间的夹角为 (1−t)θ 。如下图所示:
将 vt=p(t)v1+q(t)v2 与 v1 作内积得:
SLERP 方案将上述公式原封不动地套用到了四元数身上,得到:
可以看到,当 q1,q2 内积为负数时,从 q1 “走”到 qt 需要绕过半个多球面(你能怎么办?相信你也很绝望吧QAQ)。这时候,我们需要对其中一个取反(即四元数四个分量都取其相反数),这里我们对 q1 取反,可以看到,从 −q1 “走”到 qt 要比从 q1 “走”到 qt 少绕了半个球面!!!
此外,当 sinθ=0 时上述计算公式是无法使用的,因为分母为0整个分式是个未定式。此时,我们只能退而求其次采用平面上的线性插值公式了: qt=q1+(q2−q1)t,t∈[0,1].
本章建议学习过一些抽象代数的同学观看,没有学过的就跳过吧……(丫咩萝,也是花了一些精力在这里的o(╥﹏╥)o)部分内容参考自文章四元数的运算。
- 四元数除环
- 群旋转
- 四元数的矩阵表示
- Q8 群
由除环的定义,四元数所构成的集合是一个除环(如果四元数还满足交换律它就构成一个域)。
四元数除环因为它的不可交换性导致了一个很有趣的结果:四元数的 n 阶多项式可以拥有数目多于 n 的根(即在四元数除环代数基本定理无法成立)。例如方程 x2+1=0 就有无数多个解。事实上,只要 b2+c2+d2=1,b,c,d∈R ,那么 x=0+bi+cj+dk 便是一个解。
在“四元数和空间转动”的维基百科里提到:
非零四元数的乘法群在R3的实部为零的部分上的共轭作用可以实现转动。单位四元数(绝对值为1的四元数)若实部为 cosθ ,它的共轭作用是一个角度为 2θ 的转动,转轴为虚部的方向。四元数的优点是:
- 表达式无奇点(和例如欧拉角之类的表示相比)
- 比矩阵更简炼(也更快速)
- 单位四元数的对可以表示四维空间中的一个转动。
那么在群论观点下,四元数是如何和三维旋转扯上联系的呢?不妨看看四元数除环的四个基 1,i,j,k ,它们生成群 U(2) ,而 U(2)/U(1)=SU(2) 。
又 SU(2)/Z2≅SO(3),SU(2)≅S3 ,其中, U(n) 是 n 级酉群(即 n×n 阶酉矩阵组成的群), SU(2) 是2级特殊酉群, SO(3) 是3级特殊正交群(又称为三维旋转群), S3 是三维单位球面。这说明单位四元数通过群同构能够与三维旋转产生极强的联系,且单位四元数从抽象意义上看是分布在一个三维单位球面上的。
同时, S3 与 SO(3) 之间存在着一个2对1的同态满射,这说明 S3 是 SO(3) 的双重覆盖,说人话就是每两个四元数才能对应一个三维旋转,这样大家也许能对四元数旋转公式 p′=qpq∗ 有一个更深的理解吧……
因为单位四元数构成的三维单位球面 S3 群同构于2级特殊酉群 SU(2) ,因此我们也可以通过研究 SU(2) 去刻画 S3 的性质。给出 S3 到 SU(2) 的同构映射:
设 Vn 是两个不定元 x 和 y 的 n 次齐次多项式组成的复线性空间。考虑拓扑群 SL(2,C) 到拓扑群 GL(Vn) 的一个映射 ϕn :
ϕn:SL(2,C)→GL(Vn),A↦ϕn(A),
其中
(ϕn(A)f)(x,y)=f((x,y)A)=f(a11x+a21y,a12x+a22y),
这里 f∈Vn,A=(aij) ,则 SU(2) 的上述不可约复表示 ϕn(n=0,1,2,……) 是 SU(2) 的全部不等价的有限不可约复表示。从而 SO(3) 的上述不可约复表示 ϕ2m(m=0,1,2,……) 是 SO(3) 的全部不等价的有限不可约复表示。
因为单位四元数构成的三维单位球面 S3 群同构于2级特殊酉群 SU(2) (熟悉的开场白……),我们不妨只取 SU(2) 的部分元素来研究,从而能够解释 S3 群的部分结构。只取以下八个元素: