大家好,这是我的第一篇文章。今后自己的博客文章会不定期进行更新,大部分主题将会是我对近几年
这一篇文章目的是向大家深入介绍四元数,主要的参考文献是2013年
内容大纲
- 背景
- 复数
- 四元数
- 群论观点下的四元数
- 四元数在
Unity 中的应用
目标(将会回答以下几个问题)
- 为什么是四个元?
i,j,k 是什么?- 为什么旋转的公式是
qpq−1 ?- 为什么是
θ/2 ?- 我们应该如何理解
4D (4D 可视化)?
那么话不多说,先来进入第一部分。
背景
- 旋转定义
- 旋转矩阵
- 欧拉角
- 罗德里格旋转公式
旋转定义
通常将物体绕一个点或者一个轴转动一定角度的操作称为旋转。不妨先考虑
旋转示意图
那么自然而言便会引申出旋转角的概念:
旋转角示意图
显然,旋转角串联起了旋转前后的两个图形。而这种“串联”关系,通常采用矩阵的语言进行描述。
旋转矩阵
在线性代数里,我们知道:
- 一个矩阵代表一种变换(并不一定是旋转变换)。
- 一个旋转矩阵乘以一个向量(左乘或者右乘)将使得该向量被旋转。
其中,2维的旋转矩阵形式如下:
通过观察上面的旋转角示意图,这样一个矩阵形式是容易理解的。通过对座标系作逆时针
对于3维的旋转矩阵而言,其矩阵形式是与2维的旋转矩阵类似的:均通过将座标轴上的单位向量经旋转后的像作为其旋转矩阵的某一列。以绕
并且,旋转矩阵有一个非常完美的性质:正交矩阵,即对于旋转矩阵
-
-
-
在算旋转矩阵的逆矩阵的时候,可以直接求其转置矩阵,省下许多计算量。其次,其行列式为1意味着在经过旋转变换以后,图形的大小和形状都不会发生改变(比方说一个圆绕其圆心旋转任意角度,其半径都不会改变)。关于旋转矩阵是正交矩阵的证明可以参见这篇文章:旋转矩阵(Rotate Matrix)的性质分析
在正交矩阵的前提下,我们可以得到旋转矩阵一个有趣的性质:旋转矩阵对应特征值1的特征向量为其旋转轴。因为旋转矩阵为正交矩阵,故其特征值为
欧拉角
欧拉这样一位划时代的数学大师提出的欧拉角,是一种十分简单的表示旋转的有力工具。
来看看
令原始座标系的三个座标轴分别为
x,y,z ,旋转后的座标系的三个座标轴为X,Y,Z ,N 轴是xOy 平面与XOY 平面的交线(也可以利用外积将N 轴定义为N=z×Z )。这样一来,三个欧拉角可定义如下:
-α (或φ ) 是x 轴和N 轴的夹角;
-β (或θ ) 是z 轴和Z 轴的夹角;
-γ (或ψ ) 是N 轴和X 轴的夹角;
欧拉角示意图
也就是说,欧拉角将绕一个过原点的旋转轴旋转
1. 绕
2. 绕
3. 绕
这样的旋转顺序被称为是“
看到这里,也许你已经想骂人了:嘿,说好的“欧拉角很简单呢?”。别急,可能这样将座标系旋转来旋转去确实会令人有些头大(想象不出来如何旋转的同学,可以参看欧拉角-
1. 绕
2. 绕
3. 绕
为了证明上述两种旋转过程等价(即最终的旋转结果相同),可以利用欧拉角对应的旋转矩阵进行证明,证明过程如下:(参考自欧拉角与万向节死锁)
记
- 绕
- 绕
- 绕
- 绕
- 绕
- 绕
那么问题便转化为求证:
证明:
显然,
而对于
同理可得:
综上所述有:
因此,实际上在使用欧拉角表示旋转时,可以直接将其理解为先绕
需要注意的是:
1. 欧拉角的旋转顺序并不一定都是“
2. 细心的同学可能会发现,在旋转矩阵中,最先进行的旋转操作对应的旋转矩阵在最右侧与向量相乘,旋转矩阵按照旋转的次序从右向左排列;而在欧拉角中,最先进行的旋转操作(此处说的是座标系跟随旋转的情形)对应的旋转矩阵在最左侧与向量相乘。看到一篇文章,三维旋转:旋转矩阵,欧拉角,四元数,对于这个问题是解释得不错的:
对于前者(旋转矩阵),我们始终是以绝对参考系为参照来的,对于后者(欧拉角),我们每一次旋转的刻画都是基于刚体的座标系。比如,在欧拉角中的第2步,绕
x 轴旋转β ,这里的x 轴实际上是N 轴了(而不是蓝色的x 轴)。
为什么旋转参考系的不同会导致旋转矩阵次序的差异呢?细想一下便知,旋转矩阵左乘叠加用以描述三维变换效果的叠加,这本身就是基于绝对座标系的,所以旋转矩阵一节没有疑问;而对于欧拉角一节的这种旋转方式,这样考虑:
1. 如果有一个“影子座标系3”与原座标系重合,然后首先进行了第3步(绕z 轴旋转γ );
2. 然后有一个“影子座标系2”也与原座标系重合,然后与“影子座标系3”一起(视作同一个刚体)进行了第二步;
3. 最后一个“影子座标系1”,与前两个座标系一起进行了第一步。
此时,考察“影子座标系”1和2,他们就分别落在了欧拉角旋转的两个“快照”上,而“影子座标系3”就落在旋转后的位置上(红色的)。
而在上述过程中,“影子座标系3”就是相对于绝对座标系依次进行了第三步,第二步,和第一步。所以欧拉角的旋转矩阵写成那样,也是行得通的。
说了这么多,先停下来总结一下旋转矩阵和欧拉角:
旋转矩阵
- 优点:学过线性代数的都能很容易地理解;
- 缺点:和其它表示旋转的方式相比,其空间消耗较大,一个三维的旋转矩阵需要存储9个元素,而且在作矩阵乘法时也会消耗较多的时间。
- 欧拉角
- 优点:表示简单,仅需要存储三个值,
- 缺点:在编程实现上,通常还是将其转化为旋转矩阵进行计算,其空间复杂度和时间复杂度没有太大的变化;且在使用过程当中可能会出现著名的万向节锁现象,直观理解可以参考这个视频,欧拉旋转。
这个视频比较形象地讲解了何谓万向节锁,万向节锁大体说来便是物体绕某个座标轴旋转了90度,使得某两个座标轴平面重合,从而丢失了一个维度。在这种情况下,无论接下来作什么旋转都无法将物体旋转到某个角度,除非打破原先定义的旋转顺序或者同时旋转三个座标轴。这样一个问题的产生也使得欧拉角在球面平滑插值上“力不从心”。
这样看来,也许四元数的出现正是一个
罗德里格旋转公式
提到欧拉角,就不得不提及罗德里格旋转公式。罗德里格旋转公式的出现将欧拉角从较复杂的旋转矩阵计算“解放”了出来。该旋转公式是这样的:
给定旋转轴
r⃗ ,旋转角θ 以及一个点p⃗ ,则p⃗ 绕旋转轴r⃗ 旋转θ 后的点的计算公式为:
R(r⃗ ,θ,p⃗ )=p⃗ cosθ+(r⃗ ×p⃗ )sinθ+r⃗ (r⃗ p⃗ )(1−cosθ).
该公式是由法国数学家
罗德里格旋转公式的核心思想是向量分解:不妨先假设三维空间中待旋转的点(向量)为
罗德里格旋转公式示意图
如此一来,我们直接在一个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⃗ .
证明:将
因此,
罗德里格旋转公式第二种表示形式提出了可以用于创建3维旋转的4个参数。也许对于已经学过四元数的同学,看到这里应该会有似曾相识的感觉吧……别急,咱们继续往下看~(^▽^)
复数
呼呼呼……终于进入第二部分噜,谈及四元数不可避免地需要提及复数。我个人始终认为,两者之间的联系是密不可分的。
复数最初来自于求解一些特定的二次方程,比如
复数便是这样一类数字:
其中,
通常为了便于理解,我们会用二维平面上的一点来表示一个复数,此时两个座标轴分别为实数轴和虚数轴:
四元数的乘法运算法则为:
也可以写成:
因此,一个复数也只是一个带有特殊乘法运算的2维向量罢了。
同时,我们可以得到:
经过旋转矩阵一节的“薰陶”以后,相信对这个形式已经不陌生了。没错,这就是2维旋转的复数形式!!!
四元数
- 四元数的诞生
- 四元数的性质
- 四元数与三维旋转的“千丝万缕”
- 四元数与四维旋转
- 四元数的可视化
- 四元数的插值
第二部分哗地一下就结束了(大雾),主要是因为上过高中的应该都学过复数,因此许多性质也就不再赘述。第二部分仅列出理解四元数需要的一些“前置技能”而已,现终于进入核心章节——四元数了(撒花(^▽^))。
四元数的诞生
爱尔兰数学家汉密尔顿可以说是“四元数之父”了。他在回忆录当中大概是这样描述四元数的诞生的:有一天他在吃早餐时,他的儿子问他:“爸爸,你可以让两个三维向量相乘得到另外一个三维向量吗?”他遗憾地回答:“不,我只能将两个三维向量相加减得到另外一个三维向量。”
那么这里就会出现问题了,
终于在1843年,当汉密尔顿在都柏林的布鲁姆桥下沿着皇家运河散步时,他突然意识到仅仅三个维度是无法定义出满足上述条件的乘法运算,必须增加一个维度才行!四元数就此诞生!他非常兴奋地将他的成果雕刻在桥上,大致内容如下:
任意一个四元数可以表示成
用乘法表即可表示成:
注意,这里的乘法运算不再满足交换律。如果你熟悉叉积,这可能看起来很熟悉。实际上,叉积正是来自四元数。
此外,对于上述定义的乘法运算,每一个非零向量都有其逆元。后面还会提到,这意味着当我们希望撤销某一个旋转时,我们只要取该旋转操作对应的四元数的逆元即可,这是十分方便的。
讲到这里,我们现在已经可以回答前两个问题了(可能有同学已经忘了最初的问题是什么,这里再贴一下):
- 为什么是四个元?
i,j,k 是什么?- 为什么旋转的公式是
qpq−1 ?- 为什么是
θ/2 ?- 我们应该如何理解
4D (4D 可视化)?
对于问题1:为什么是四个元?
因为仅仅三个维度是无法定义出满足条件的乘法运算的:
1. 两个向量作乘法运算的结果仍旧是一个向量;
2. 该乘法运算不满足交换律;
3. 两个非零向量作乘法运算以后不能产生零向量。
对于问题2:
存在于四维空间的三个座标轴。
四元数的性质
某种程度上,四元数是由复数扩展而来的:
它可以表示四维平面上的一点
四元数的加减和普通的向量加减是类似的,因此这里不再赘述。我们讲讲四元数的乘法。
取两个四元数
需要注意的是,因为包含叉积运算的缘故,故四元数之间的乘法运算一般不满足交换律:
这样,我们容易知道,对于四元数之间的乘法运算和任意四元数
四元数与旋转的“千丝万缕”
与复数类似,单位四元数(记住,这类四元数的模是1!!!)可以表示一个旋转,后面会进行证明。
对于3维旋转,令
\omega=cos(\theta/2),\\(x,y,z)=v=sin(\theta/2)r.
看过前面的罗德里格旋转公式的同学,一定对此表示十分熟悉……接下来便会介绍如何利用四元数进行3维向量的旋转。
对於单位四元数q=(\omega,v)=(cos(\theta/2),sin(\theta/2)r) 而言,我们很容易就可以知道它的逆元(自己拿笔在纸上验证一下即可):
q^{-1}=(\omega,v)^{-1}=(cos(-\theta/2),sin(-\theta/2)r)\\=(cos(\theta/2),-sin(\theta/2)r)\\=(\omega,-v)=q^{*}.
易知,单位四元数的逆元是和其共轭相等的。再次强调一下,这个性质仅仅是对单位四元数成立。有了它,四元数的旋转定理(自己命名的,捂脸逃走(/ω\))也就顺理成章地腾空出世了~
四元数旋转定理:
已知三维向量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));
罗德里格旋转公式
a=cos(\theta/2),(b,c,d)=r=sin(\theta/2)\vec r,\\p'=p+2a(r\times p)+2(r\times(r\times p)).
不得不佩服一下,罗德里格真的是一个富有远见的数学家……
再来看看第一种形式:p'=qpq^{-1}=qpq^{*} ,它的证明可不简单,但我觉得还是有必要写一下,因为它的证明确实是蛮精彩的。(当然,没兴趣的同学就跳过吧……)证明过程参考自博客三维旋转:旋转矩阵,欧拉角,四元数,不过我个人感觉博主的证明写得有些乱,因此我整理如下:
p'=qvq^{-1}=qvq^{*}.
证明:如下图所示,u 为旋转轴上的单位向量,旋转角度为\sigma ,向量v 旋转到w 处,旋转到\sigma /2 处为k ,图中并未标出。
(图片来自参考博客)
令四元数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),
为了方便,仍旧用四元数k^* 表示(0,-k) ,四元数v 表示(0,v) ,即
q=\frac{1}{{{|v|^2}}}k^*v.
现在令
w=qvq^*,
如果能证明w 与v 的夹角是\sigma ,且v,k,w 在同一个平面上,则说明w 是v 绕旋转轴u 旋转\sigma 得到的,从而命题得证。
不妨来计算一下wk^* 有:
wk^*=(qvq^*)k^*=qvq^{-1}k^*\\ \ =qv(\frac{1}{{{|v|^2}}}k^*v)^{-1}k^*\\ \ \ =|v|^2qvv^{-1}(k^*)^{-1}k^*\\ \quad \quad \quad \ \ \ \ =|v|^2q=|v|^2\frac{1}{{{|v|^2}}}k^*v=k^*v,
对于上式wk^*=k^*v ,将其拆分为四元数的\omega 部分和v 部分进行分析:\omega 部分相等表明w,k 夹角与k,v 的夹角相同,v 部分相等表明w,-k 与-k,v 所处的平面相同,这就说明w 是v 绕旋转轴u 旋转\sigma 得到的,从而命题得证。
显然,我们已经解决了问题3:为什么旋转的公式是qpq^{-1} ?接下来我们尝试解决问题4:为什么是\theta/2 ?
幸运的是,对于这个问题,我在一篇文章Understanding \ Quaternions 里面找到了答案。限于篇幅,我就不贴出来了,英文比较好的同学可以看一下,英文比较吃力的同学也可以看一下译文Understanding\ Quaternions 中文翻译《理解四元数》。
想要进行一下直观理解的也可以参看下图:
(图片来自知乎问题如何形象地理解四元数?Yang\ Eninala 的回答)
上图非常直观地回答了问题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} 才又回到了三维超平面的世界。
四元数与四维旋转
说完了三维旋转,终于可以讲讲四维旋转这样一种人脑很难想象的操作了。实际上,四元数定义的都是四维旋转,而不是三维旋转。三维旋转仅仅是四维旋转的一种特殊情形,就像二维旋转是三维旋转的一种特殊情形一样。利用四元数进行三维旋转时,其实是在一个四维空间上的某个三维超平面上进行的。
一般来说,单独一个四元数是无法执行四维旋转的,一个四维旋转可以唯一地被拆分为一个左旋转
四元数的可视化
终于到最后一个问题了:我们都是生活在三维空间内的生物,该如何想象“生活在”在四维空间内的四元数呢?
接下来我们仅仅考虑模不超过1的四元数(容易想象一些……),将其构成的4维超球切成三块,它们都是四维空间的半球在3维空间上的投影(在我们看来就是三维球体了),这个“切”的依据便是四元数
模不超过1的四元数构成的4维超球“切割”示意图
经过投影切割后,我们可以得到两个实心球(
当截面上的向量长度等于0时,你就会得到一个单位四元数(此时
四元数的插值
前面已经介绍过欧拉角的万向节锁问题,正是由于万向节锁导致欧拉角无法很好地用于三维旋转的插值(当出现万向节锁现象时无法按照原定旋转顺序得到预期的旋转,即欧拉角用于三维旋转的插值时并不平滑)。那么。这时候就必须要四元数站出来扛大梁了~以下部分参考文章
事实上,球面上的线性插值和一般的线性插值是有一些区别的。一般的线性插值公式为
(图片来自参考文章)
当
不妨设球面线性插值的计算公式为
(图片来自参考文章)
将
再将
联立求解并用三角函数和差公式化简得:
故计算向量的球面线性插值的计算公式如下所示:
(图片来自参考文章)
注意到,在使用这个公式之前还需要计算
需要注意的是,当
四元数的球面插值示意图
可以看到,当
此外,当
群论观点下的四元数
本章建议学习过一些抽象代数的同学观看,没有学过的就跳过吧……(丫咩萝,也是花了一些精力在这里的o(╥﹏╥)o)部分内容参考自文章四元数的运算。
- 四元数除环
- 群旋转
- 四元数的矩阵表示
Q8 群
四元数除环
由除环的定义,四元数所构成的集合是一个除环(如果四元数还满足交换律它就构成一个域)。
四元数除环因为它的不可交换性导致了一个很有趣的结果:四元数的
群旋转
在“四元数和空间转动”的维基百科里提到:
非零四元数的乘法群在R3的实部为零的部分上的共轭作用可以实现转动。单位四元数(绝对值为1的四元数)若实部为
cosθ ,它的共轭作用是一个角度为2θ 的转动,转轴为虚部的方向。四元数的优点是:
- 表达式无奇点(和例如欧拉角之类的表示相比)
- 比矩阵更简炼(也更快速)
- 单位四元数的对可以表示四维空间中的一个转动。
那么在群论观点下,四元数是如何和三维旋转扯上联系的呢?不妨看看四元数除环的四个基
又
同时,
四元数的矩阵表示
因为单位四元数构成的三维单位球面
而
故
此时
同理,我们还可以将单位四元数构成的三维单位球面
再令
则
此时
事实上,上述两种矩阵表示都可以认为是四元数另外的存在形式。
既然可以用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) 的全部不等价的有限不可约复表示。
Q8 群
因为单位四元数构成的三维单位球面
容易验证,它构成一个群,我们称之为
若将
这对应于一个置换(1324)(5768)。这是很符合凯莱定理的:任何一个群都同一个变换群同构,这意味着我们能通过研究常见的置换群去“解剖”抽象的
此外,
证明:对于
当
当
当
综上所述,
因为
四元数在Unity 中的应用
很多时候,我们不仅要仰望星空,还需要脚踏实地。四元数在
static function AngleAxis (angle : float, axis : Vector3) : Quaternion
//绕axis轴旋转angle,创建一个旋转,即返回一个四元数变换。
这是继承自
//将待旋转向量绕y轴旋转30度
Vector3 newVector = Quaternion.AngleAxis(30, Vector3.up) * oldVector;
在
static function Lerp (from : Quaternion, to : Quaternion, t : float) : Quaternion
//通过t值from向to之间插值,并且规范化结果。
//这个比Slerp更快但是如果旋转较远看起来就比较差。
这同样是继承自
using UnityEngine;
using System.Collections;
public class example : MonoBehaviour {
public Transform from;
public Transform to;
public float speed = 0.1F;
void Update() {
transform.rotation = Quaternion.Lerp(from.rotation, to.rotation, Time.time * speed);
}
}
呼呼呼……终于完成这篇“吐血之作”了,希望能对大家有帮助(也不知道有木有人看2333)。这篇写得有点长,开学以后就没那么多时间写了,以后的篇幅当然会短得多,而且更新的频率可能不高……但能肯定的是以后的每一篇博文都会用心写。下一篇博文的主题预计会是今年