四元数和欧拉角

来源:http://blog.csdn.net/candycat1992/article/details/41254799html

四元数介绍

 

旋转,应该是三种坐标变换——缩放、旋转和平移,中最复杂的一种了。你们应该都听过,有一种旋转的表示方法叫四元数。按照咱们的习惯,咱们更加熟悉的是另外两种旋转的表示方法——矩阵旋转和欧拉旋转。矩阵旋转使用了一个4*4大小的矩阵来表示绕任意轴旋转的变换矩阵,而欧拉选择则是按照必定的坐标轴顺序(例如先x、再y、最后z)、每一个轴旋转必定角度来变换坐标或向量,它其实是一系列坐标轴旋转的组合。ide

 

那么,四元数又是什么呢?简单来讲,四元数本质上是一种高阶复数(听不懂了吧。。。),是一个四维空间,相对于复数的二维空间。咱们高中的时候应该都学过复数,一个复数由实部和虚部组成,即x = a + bi,i是虚数单位,若是你还记得的话应该知道i^2 = -1。而四元数其实和咱们学到的这种是相似的,不一样的是,它的虚部包含了三个虚数单位,i、j、k,即一个四元数能够表示为x = a + bi + cj + dk。那么,它和旋转为何会有关系呢?函数

 

在Unity里,tranform组件有一个变量名为rotation,它的类型就是四元数。不少初学者会直接取rotation的x、y、z,认为它们分别对应了Transform面板里R的各个份量。固然很快咱们就会发现这是彻底不对的。实际上,四元数的x、y、z和R的那三个值从直观上来说没什么关系,固然会存在一个表达式能够转换,在后面会讲。atom

 

你们应该和我同样都有不少疑问,既然已经存在了这两种旋转表示方式,为何还要使用四元数这种听起来很难懂的东西呢?咱们先要了解这三种旋转方式的优缺点:idea

 

  • 矩阵旋转
    • 优势:
      • 旋转轴能够是任意向量;
    • 缺点:
      • 旋转其实只须要知道一个向量+一个角度,一共4个值的信息,但矩阵法却使用了16个元素;
      • 并且在作乘法操做时也会增长计算量,形成了空间和时间上的一些浪费;

  • 欧拉旋转
    • 优势:
      • 很容易理解,形象直观;
      • 表示更方便,只须要3个值(分别对应x、y、z轴的旋转角度);但按个人理解,它仍是转换到了3个3*3的矩阵作变换,效率不如四元数;
    • 缺点:
      • 以前提到过这种方法是要按照一个固定的坐标轴的顺序旋转的,所以不一样的顺序会形成不一样的结果;
      • 会形成万向节锁(Gimbal Lock)的现象。这种现象的发生就是因为上述固定坐标轴旋转顺序形成的。理论上,欧拉旋转能够靠这种顺序让一个物体指到任何一个想要的方向,但若是在旋转中不幸让某些坐标轴重合了就会发生万向节锁,这时就会丢失一个方向上的旋转能力,也就是说在这种状态下咱们不管怎么旋转(固然仍是要原先的顺序)都不可能获得某些想要的旋转效果,除非咱们打破原先的旋转顺序或者同时旋转3个坐标轴。这里有个视频能够直观的理解下;
      • 因为万向节锁的存在,欧拉旋转没法实现球面平滑插值;

  • 四元数旋转
    • 优势:
      • 能够避免万向节锁现象;
      • 只须要一个4维的四元数就能够执行绕任意过原点的向量的旋转,方便快捷,在某些实现下比旋转矩阵效率更高;
      • 能够提供平滑插值;
    • 缺点:
      • 比欧拉旋转稍微复杂了一点点,由于多了一个维度;
      • 理解更困难,不直观;
 
 

四元数和欧拉角

 
 

基础知识

 
 
前面说过,一个四元数能够表示为q = w + xi + yj + zk,如今就来回答这样一个简单的式子是怎么和三维旋转结合在一块儿的。为了方便,咱们下面使用q = ((x, y, z),w) = (v, w),其中v是向量,w是实数,这样的式子来表示一个四元数。
 
咱们先来看问题的答案。咱们可使用一个四元数 q=((x,y,z)sinθ2cosθ2) 来执行一个旋转。具体来讲,若是咱们想要把空间的一个点P绕着单位向量轴u = (x, y, z)表示的旋转轴旋转θ角度,咱们首先把点P扩展到四元数空间,即四元数p = (P, 0)。那么,旋转后新的点对应的四元数(固然这个计算而得的四元数的实部为0,虚部系数就是新的坐标)为:

p=qpq1
 
其中, q=(cosθ2, (x,y,z)sinθ2) ,q1=qN(q),因为u是单位向量,所以
N(q)=1,即q1=q∗。右边表达式包含了四元数乘法。相关的定义以下:
  • 四元数乘法:q1q2=(v1×v2+w1v2+w2v1,w1w2v1v2)
     
  • 共轭四元数:q=(v⃗ ,w)

  • 四元数的模:N(q) = √(x^2 + y^2 + z^2 +w^2),即四元数到原点的距离

  • 四元数的逆:q1=qN(q)

 
它的证实这里再也不赘述,有兴趣的能够参见 这篇文章。主要思想是构建了一个辅助向量k,它是将p绕旋转轴旋转θ/2获得的。证实过程尝试证实 wk=kv∗,以此证实w与v、k在同一平面内,且与v夹角为θ。
 
咱们举个最简单的例子:把点P(1, 0, 1)绕旋转轴u = (0, 1, 0)旋转90°,求旋转后的顶点坐标。首先将P扩充到四元数,即p = (P, 0)。而q = (u*sin45°, cos45°)。求 p=qpq1的值。建议你们必定要在纸上计算一边,这样才能加深印象,连笔都懒得动的人仍是不要往下看了。最后的结果p` = ((1, 0, -1), 0),即旋转后的顶点位置是(1, 0, -1)。
 
若是想要获得复合旋转,只需相似复合矩阵那样左乘新的四元数,再进行运算便可。
 
咱们来总结下四元数旋转的 几个须要注意的地方
 
  • 用于旋转的四元数,每一个份量的范围都在(-1,1);

  • 每一次旋转实际上须要两个四元数的参与,即q和q*;

  • 全部用于旋转的四元数都是单位四元数,即它们的模是1;
 
 
下面是几点建议:
 
  • 实际上,在Unity里即使你不知道上述公式和变换也丝绝不妨碍咱们使用四元数,可是有一点要提醒你,除非你对四元数很是了解,那么不要直接对它们进行赋值

  • 若是你不想知道原理,只想在Unity里找到对应的函数来进行四元数变换,那么你可使用这两个函数:Quaternion.EulerQuaternion.eulerAngles。它们基本能够知足绝大多数的四元数旋转变换。
 
 

和其余类型的转换

 
首先是 轴角到四元数
 
给定一个单位长度的旋转轴(x, y, z)和一个角度θ。对应的四元数为:
q=((x,y,z)sinθ2cosθ2
 
 
这个公式的推导过程上面已经给出。
 
欧拉角到四元数
 
给定一个欧拉旋转(X, Y, Z)(即分别绕x轴、y轴和z轴旋转X、Y、Z度),则对应的四元数为:
 
x = sin(Y/2)sin(Z/2)cos(X/2)+cos(Y/2)cos(Z/2)sin(X/2)
y = sin(Y/2)cos(Z/2)cos(X/2)+cos(Y/2)sin(Z/2)sin(X/2)
z = cos(Y/2)sin(Z/2)cos(X/2)-sin(Y/2)cos(Z/2)sin(X/2)
w = cos(Y/2)cos(Z/2)cos(X/2)-sin(Y/2)sin(Z/2)sin(X/2)
q = ((x, y, z), w)
 
它的证实过程能够依靠轴角到四元数的公式进行推导。
 
其余 参考连接
 
 
 

四元数的插值

 
这里的插值指的是球面线性插值。
 
设t是一个在0到1之间的变量。咱们想要基于t求Q1到Q2之间插值后四元数Q。它的公式是:

Q3  = (sin((1-t)A)/sin(A))*Q1 + (sin((tA)/sin(A))*Q2)
Q = Q3/|Q3|,即单位化
 
 
 

四元数的建立

 
在了解了上述知识后,咱们就不须要那么害怕四元数了,实际上它和矩阵相似,不一样的只是它的表示方式以及运算方式。那么在Unity里如何利用四元数进行旋转呢?Unity里提供了很是多的方式来建立一个四元数。例如Quaternion.AngleAxis(float angle, Vector3 axis),它能够返回一个绕轴线axis旋转angle角度的四元数变换。咱们能够一个Vector3和它进行左乘,就将获得旋转后的Vector3。在Unity里只须要用一个“ * ”操做符就能够进行四元数对向量的变换操做,至关于咱们上述讲到的 p=qpq1操做。若是咱们想要进行多个旋转变换,只须要左乘其余四元数变换便可。例以下面这样:
[csharp]  view plain  copy
 
 print?在CODE上查看代码片派生到个人代码片
  1. Vector3 newVector = Quaternion.AngleAxis(90, Vector3.up) * Quaternion.LookRotation(someDirection) * someVector;  
尽管欧拉角更容易咱们理解,但四元数比欧拉角要强大不少。Unity提供了这两种方式供咱们选择,咱们能够选择最合适的变换。
例如,若是咱们须要对旋转进行插值,咱们能够首先使用Quaternion.eulerAngles来获得欧拉角度,而后使用Mathf.Clamp对其进行插值运算。
最后更新Quaternion.eulerAngles或者使用Quaternion.Euler(yourAngles)来建立一个新的四元数。
 
又例如,若是你想要组合旋转,好比让人物的脑壳向下看或者旋转身体,两种方法其实均可以,但一旦这些旋转不是以世界坐标轴为旋转轴,好比人物扭动脖子向下看等,那么四元数是一个更合适的选择。Unity还提供了transform.forward, transform.right and transform.up 这些很是有用的轴,这些轴能够和Quaternion.AngleAxis组合起来,来建立很是有用的旋转组合。例如,下面的代码让物体执行低头的动做:
[csharp]  view plain  copy
 
 print?在CODE上查看代码片派生到个人代码片
  1. transform.rotation = Quaternion.AngleAxis(degrees, transform.right) * transform.rotation;  


关于Quaternion的其余函数,后面再补充吧,原理相似~
 
 
 

补充:欧拉旋转

 
在文章开头关于欧拉旋转的细节没有解释的太清楚,而又有很多人询问相关问题,我尽可能把本身的理解写到这里,若有不对还望指出。
 
 

欧拉旋转是怎么运做的

 
 
欧拉旋转是咱们最容易理解的一种旋转方式。以咱们生活中为例,一个舞蹈老师告诉咱们,完成某个舞蹈动做须要先向你的左边转30°,再向左侧弯腰60°,再起身向后弯腰90°(若是你能办到的话)。上面这样一个旋转的过程其实和咱们在三维中进行欧拉旋转很相似,即咱们是经过指明绕三个轴旋转的角度来进行旋转的,不一样的是,平常生活中咱们更愿意叫这些轴为先后左右上下。而这也意味着咱们须要指明一个旋转顺序。这是由于,先绕X轴旋转90°、再绕Y轴30°和先绕Y轴旋转90°、再绕X轴30°获得的是不一样的结果。
 
在Unity里,欧拉旋转的旋转顺序是Z、X、Y,这在相关的API文档中都有说明,例如 Transform.Rotate。其实文档中说得不是很是详细,还有一个细节咱们须要明白。若是你仔细想一想,就会发现有一个很是重要的东西咱们没有说明白,那就是旋转时使用的坐标系。给定一个旋转顺序(例如这里的Z、X、Y),以及它们对应的旋转角度(α,β,r),有两种坐标系能够选择:
  1. 绕坐标系E下的Z轴旋转α,绕坐标系E下的Y轴旋转β,绕坐标系E下的X轴旋转r,即进行一次旋转时不一块儿旋转当前坐标系;
  2. 绕坐标系E下的Z轴旋转α,绕坐标系E在绕Z轴旋转α后的新坐标系E'下的Y轴旋转β,绕坐标系E'在绕Y轴旋转β后的新坐标系E''下的X轴旋转r, 即在旋转时,把坐标系一块儿转动;
 
很容易知道,这两种选择的结果是不同的。但若是把它们的旋转顺序颠倒一下,其实结果就会同样。说得明白点,在第一种状况下、按ZXY顺序旋转和在第二种状况下、按YXZ顺序旋转是同样的。证实方法能够看下 这篇文章。而Unity文档中说明的旋转顺序指的是在第一种状况下的顺序。
 
若是你仍是不懂这意味着什么,能够试着调用下这个函数。例如,你认为下面代码的结果是什么:
[csharp]  view plain  copy
 
 print?在CODE上查看代码片派生到个人代码片
  1. transform.Rotate(new Vector3(0, 30, 90));  

原模型的方向和执行结果以下:
 
 
 
 
而咱们能够再分别执行下面的代码:
[csharp]  view plain  copy
 
 print?在CODE上查看代码片派生到个人代码片
  1.         // First case  
  2.         transform.Rotate(new Vector3(0, 30, 0));  
  3.         transform.Rotate(new Vector3(0, 0, 90));  
  4.   
  5.         // Second case  
  6. //      transform.Rotate(new Vector3(0, 0, 90));  
  7. //      transform.Rotate(new Vector3(0, 30, 0));  

两种状况的结果分别是:
 
 
能够发现,调用transform.Rotate(new Vector3(0, 30, 90));是和第一种状况中的代码是同样的结果,即先旋转Y、再旋转Z。进一步实验,咱们会发现transform.Rotate(new Vector3(30, 90, -40));的结果是和transform.Rotate(new Vector3(0, 90, 0));transform.Rotate(new Vector3(30, 0, 0));transform.Rotate(new Vector3(0, 0, -40));的结果同样的。你会问了,文档中不是明明说了旋转顺序是Z、X、Y吗?怎么如今彻底反过来了呢?缘由就是咱们以前说的两种坐标系的选择。在一次调用transform.Rotate的过程当中,坐标轴是不随每次单个坐标轴的旋转而旋转的。而在调用transform.Rotate后,这个旋转坐标系才会变化。也就是说,transform.Rotate(new Vector3(30, 90, -40));执行时使用的是第一种状况,而transform.Rotate(new Vector3(0, 90, 0));transform.Rotate(new Vector3(30, 0, 0));transform.Rotate(new Vector3(0, 0, -40));每一句则是分别使用了上一句执行后的坐标系,即第二种坐标系状况。所以,咱们看起来顺序好像是彻底是反了,但结果是同样的。
 
上面只是说了一些容易混淆的地方,更多的内容你们能够搜搜wiki之类的。
 
 

数学模型

欧拉旋转的数学实现就是使用矩阵。而最多见的表示方法就是3*3的矩阵。在 Wiki里咱们能够找到这种矩阵的表示形式,如下以按XYZ的旋转顺序为例,三个矩阵分别表示了:
 
 
 
在计算时,咱们将原来的旋转矩阵右乘(这里使用的是列向量)上面的矩阵。从这里咱们也能够证实上面所说的两种坐标系选择是同样的结果,它们之间的不一样从这里来看其实就是矩阵相乘时的顺序不一样。第一种坐标系状况,指的是在计算时,先从左到右直接计算R中3个矩阵的结果矩阵,最后再和原旋转矩阵相乘,所以顺序是XYZ;而第二种坐标系状况,指的是在计算时,从右往左依次相乘,所以顺序是反过来的,ZYX。你能够验证R左乘和右乘的结果表达式,就能够相信这个结论了!
 
 

万向节锁

 
 
虽然欧拉旋转很是容易理解,但它会形成臭名昭著的万向节锁问题。我以前给出了连接你们可能都看了,但仍是不明白这是怎么回事。这里 有一篇文章是我目前找到说得最容易懂的中文文章,你们能够看看。
 
若是你仍是不明白,咱们来作个试验。仍是使用以前的模型,此次咱们直接在面板中把它的欧拉角中的X值设为90°,其余先保持不变:
 
此时模型是脸朝下(下图你看到的只是一个头顶):
 
如今,若是我让你不动X轴,只设置Y和Z的值,把这个模型的脸转上来,让它向侧面看,你能够办到吗?你能够发现,这时候不管你怎么设置Y和Z的值,模型始终是脸朝下、在同一平面旋转,看起来就是Y和Z控制的是同一个轴的旋转,下面是我截取的任意两种状况:
 
 
这就是一种万向节锁的状况。这里咱们先设置X轴为90°也是有缘由的,这是由于Unity中欧拉角的旋转顺序是ZXY,即X轴是第二个旋转轴。当咱们在面板中设置任意旋转值时,Unity实际是按照固定的ZXY顺序依次旋转特定角度的。
 
在代码里,咱们一样能够重现万向节锁现象。
[csharp]  view plain  copy
 
 print?在CODE上查看代码片派生到个人代码片
  1. transform.Rotate(new Vector3(0, 0, 40));  
  2. transform.Rotate(new Vector3(0, 90, 0));  
  3. transform.Rotate(new Vector3(80, 0, 0));  

咱们只须要固定中间一句代码,即便Y轴的旋转角度始终为90°,那么你会发现不管你怎么调整第一句和最后一句中的X或Z值,它会像一个钟表的表针同样老是在同一个平面上运动。
 
万向节锁中的“锁”,实际上是给人一种误导,这可能也是让不少人以为难以理解的一个缘由。实际上,实际上它并无锁住任何一个旋转轴,只是说咱们会在这种旋转状况下会感受丧失了一个维度。以上面的例子来讲,尽管固定了第二个旋转轴的角度为90°,但咱们原觉得依靠改变其余两个轴的旋转角度是能够获得任意旋转位置的(由于按咱们理解,两个轴应该控制的是两个空间维度),而事实是它被“锁”在了一个平面,即只有一个维度了,缺失了一个维度。而只要第二个旋转轴不是±90°,咱们就能够依靠改变其余两个轴的旋转角度来获得任意旋转位置。
 
 

数学解释

 
咱们从最简单的矩阵来理解。仍是使用XYZ的旋转顺序。当Y轴的旋转角度为90°时,咱们会获得下面的旋转矩阵:
 
 
咱们对上述矩阵进行左乘能够获得下面的结果:
 
能够发现,此时当咱们改变第一次和第三次的旋转角度时,是一样的效果,而不会改变第一行和第三列的任何数值,从而缺失了一个维度。
 
咱们再尝试着理解下它的本质。 Wiki上写,万向节锁出现的本质缘由,是由于从欧拉角到旋转的映射并非一个覆盖映射,即它并非在每一个点处都是局部同胚的。不懂吧。。。恩,咱们再来通俗一下解释,这意味着,从欧拉角到旋转是一个多对一的映射(即不一样的欧拉角能够表示同一个旋转方向),并且并非每个旋转变化均可以用欧拉角来表示。其余更多的你们去参考wiki吧。
 
 
建议仍是多看看视频,尤为是后面的部分。固然,若是仍是以为懵懵懂懂的话,在《3D数学基础:图形与游戏开发》一书中有一话说的颇有道理,“若是您历来没有遇到过万向锁状况,你可能会对此感到困惑,并且不幸的是,很难在本书中讲清楚这个问题,你须要亲身经历才能明白。”所以,你们也不要纠结啦,等到遇到的时候能够想到是由于万向节锁的缘由就好。
相关文章
相关标签/搜索