四元数左乘右乘_四元数的创建

在了解了上述知识后,我们就不需要那么惧怕四元数了,实际上它和矩阵类似,不同的只是它的表示方式以及运算方式。那么在Unity里如何利用四元数进行旋转呢?Unity里提供了非常多的方式来创建一个四元数。例如Quaternion.AngleAxis(float

angle, Vector3

axis),它可以返回一个绕轴线axis旋转angle角度的四元数变换。我们可以一个Vector3和它进行左乘,就将得到旋转后的Vector3。在Unity里只需要用一个“

*

”操作符就可以进行四元数对向量的变换操作,相当于我们上述讲到的p′=qpq−1操作。如果我们想要进行多个旋转变换,只需要左乘其他四元数变换即可。例如下面这样:

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组合起来,来创建非常有用的旋转组合。例如,下面的代码让物体执行低头的动作:

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),有两种坐标系可以选择:

绕坐标系E下的Z轴旋转α,绕坐标系E下的Y轴旋转β,绕坐标系E下的X轴旋转r,即进行一次旋转时不一起旋转当前坐标系;

绕坐标系E下的Z轴旋转α,绕坐标系E在绕Z轴旋转α后的新坐标系E'下的Y轴旋转β,绕坐标系E'在绕Y轴旋转β后的新坐标系E''下的X轴旋转r,

即在旋转时,把坐标系一起转动;

很容易知道,这两种选择的结果是不一样的。但如果把它们的旋转顺序颠倒一下,其实结果就会一样。说得明白点,在第一种情况下、按ZXY顺序旋转和在第二种情况下、按YXZ顺序旋转是一样的。证明方法可以看下这篇文章。而Unity文档中说明的旋转顺序指的是在第一种情况下的顺序。

如果你还是不懂这意味着什么,可以试着调用下这个函数。例如,你认为下面代码的结果是什么:

1. transform.Rotate(new Vector3(0, 30, 90));

原模型的方向和执行结果如下:

而我们可以再分别执行下面的代码:

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顺序依次旋转特定角度的。

在代码里,我们同样可以重现万向节锁现象。

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数学基础:图形与游戏开发》一书中有一话说的很有道理,“如果您从来没有遇到过万向锁情况,你可能会对此感到困惑,而且不幸的是,很难在本书中讲清楚这个问题,你需要亲身经历才能明白。”因此,大家也不要纠结啦,等到遇到的时候可以想到是因为万向节锁的原因就好。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值