参考1、关于万向节死锁 2、【Unity技巧】四元数(Quaternion)和旋转 一、Unity中的Rotation在unity中,旋转的表示的常用方法之一,是一个三维向量(x、y、z): 图1、Unity中的旋转 实际上这是欧拉角。这三个分量分别是绕x轴、y轴、z轴旋转的角度。 要对一个object进行旋转,还可以通过代码: [csharp]view plain copy
这里,如果看过《坐标系》一文,就会产生以下两个疑问: 1)x轴、y轴、z轴指的是那组基?是世界坐标系下的xyz轴,还是本地坐标系下的xyz轴? 2)旋转的正方向是如何确定的? 下面分别讨论。 二、旋转轴:静态欧拉角和动态欧拉角首先回答第一个问题:到底哪个是旋转轴。这又要分为 3种情况。 1、旋转轴:Editor 中 Transform的旋转数值对这个情况来说,其显示的旋转轴既不是世界坐标系的坐标轴,也不失本地坐标系的坐标轴。Editor中transform的旋转轴是父节点的坐标轴。这点在editor中看的非常明显,因此不再赘述。 2、旋转轴:在script中使用 rotate 函数,在 Space.Self 中旋转Rotate 函数有两个入参: [csharp]view plain copy
第二个入参的取值有两种:Space.Self 或者 Space.World。我们先看默认的 Self 的情况。使用下面的一段简单的代码来进行测试: [csharp]view plain copy
图2、在Space.Self中旋转 可以看到,圆柱体是绕着本地坐标系的Y轴旋转的。使用Space.Self进行旋转,旋转轴就是本地坐标系的坐标轴。 3、在script中使用 rotate 函数,在 Space.World 中旋转下面测试 Space.World 图3、在Space.World中旋转 注意到这里 Parent 的 Y轴并不是 world 的 Y 轴,而这里的圆柱体明显是绕着世界坐标系下的 Y 轴旋转的,所以如代码所述,使用Space.World旋转,旋转是绕着世界坐标系的坐标轴旋转的。4、静态欧拉角和动态欧拉角前面说到的旋转轴的问题,在数学上有对应的概念。这就是所谓的静态欧拉角和动态欧拉角。 所谓静态欧拉角,就是其旋转轴使用的是静止不动的参考系。动态欧拉角,使用的是刚体本身作为参考系,因而会随着刚体的旋转而旋转。 因此,再看看前面的三种情况,使用Space.World旋转,以及 Editor 中的旋转,是静态欧拉角;使用Space.self,是动态欧拉角。 三、旋转的正方向由于上面的代码是每次使得圆柱绕Y轴旋转10度,因此从上面的动图就可以看到,是符合左手规则的,即以左手大拇指指向旋转轴,则四指指向为正方向。 这其实是当然的:unity的本地坐标系和世界坐标系都是左手坐标系,当然应该使用左手法则。 图4、旋转的正方向 四、旋转的顺序我们之前使用的旋转是一个vector3,包含x、y、z三个向量,分别对应着对 X旋转轴、Y旋转轴、Z旋转轴进行旋转。这里就又产生了一个问题:他是如何绕着这三个轴旋转的呢? 我们也分为静态欧拉角和动态欧拉角的情况讨论。 1、静态欧拉角这种情况对应着上面的editor中显示的旋转,以及使用Space.World进行的旋转。即使旋转轴保持不变,旋转的顺序也决定了最后的旋转效果,我们来看下面的例子: 现在有一个物体摆放在世界中,现在我们要让他旋转角度(90,90,0)。现在有两种方法。 1)首先绕世界坐标系的x轴旋转90度,再绕世界坐标系的y轴旋转90度
2)首先绕世界坐标系的y轴旋转90度,再绕世界坐标系的x轴旋转90度
3)旋转顺序的影响可以看到,结果完全不同! 其实从数学上也是可以理解的。在坐标系一文中我们说到,坐标系变换就相当于乘以变换矩阵,现在的旋转,实际上就是坐标系变换。而矩阵乘法是不满足交换律的,因此旋转的顺序不能交换,否则会得到不同的结果。 对于旋转的顺序,一般没有定式,需要明确指出其顺序。对此有一个专门的术语,称为顺规。如果在这个坐标系中的旋转,先绕x轴旋转,再绕y轴,最后再绕z轴,则称之为X-Y-Z顺规。以此类推。 对于Unity,从文档中可以看到,使用的是Z-X-Y顺规,这是一种常用的顺规,可以一定程度上避免万向节锁(这一概念我们会在下面讨论)。因此在unity中,使用静态欧拉角旋转(90,90,0),会得到第1小节中的情况: 图5、Unity中静态欧拉角的旋转顺序 2、动态欧拉角动态欧拉角除了上面说到的顺规问题,还有一个额外的疑问:比如一个物体,初始状态记为A,以zxy顺规旋转(90,90,0),由于没有z轴旋转,第一步当然是绕着当前的x轴旋转90度,此时状态记为B,那么第二步要绕着y轴旋转90的时候,是绕着初始状态A时的y轴旋转,还是绕着此时的B状态下的y轴旋转呢? 首先来看两者的区别: 1)以初始状态A时的y轴旋转:
2)以状态B下的y轴旋转:
3)unity中的情况:那么实际中使用的是什么方式呢?运行以下代码,会看到结果如下: [csharp]view plain copy
图6、Unity中动态欧拉角的旋转顺序 可以看到和情况1)相同,所以确认结果为1)。 如果分两次旋转,运行以下代码: [csharp]view plain copy
图7、在Unity中,使用动态欧拉角两次旋转 最终结论是:每次使用Space.self进行rotate时,都是绕着调用时刻的坐标轴进行旋转的。 3、静态欧拉角和动态欧拉角的等价形式这里展开讨论一下,静态欧拉角和动态欧拉角是可以相互转换的。具体的数学公式可以参考这篇博客。 其结论就是:在Space.World中旋转以 Z-X-Y 归顺旋转角度(x、y、z),等价于在Space.Self中分别顺次旋转(0,y,0)、(x,0,0)、(0,0,z)。 从代码上来说,就是以下两段代码等价: [csharp]view plain copy
五、万向节锁(Gimbal Lock)1、什么是万向节锁在讨论欧拉角旋转的时候,一个绕不开的话题,就是万向节锁。 对万向节锁的定义可以参考这个视频。简单来说,就是两个旋转轴发生了重合。如下图:
可以看到绕Y轴和绕Z轴旋转产生的效果是相同的,都是在同一个平面旋转。即 Y 轴和 Z 轴产生了共线。 乍一看这很难以理解:在Editor中旋转是使用的是静态欧拉角,旋转轴是固定的,他们两两正交,怎么可能会共线? 首先我们从直观上来解释。这就要说到上面最后一小节的静态欧拉角和动态欧拉角的互相转换。注意到Unity中的旋转是 Z-X-Y 规顺,其和使用动态欧拉角 Y-X-Z 规顺进行旋转等价。而经过动态欧拉角(0,y,0)、(90,0,0)的旋转之后,Z 轴就和初始状态的 Y轴共线。因此这个时候,绕着 Z 轴旋转,就和在世界坐标系下绕着 Y 轴旋转产生的效果类似了。 从数学上也可以解释这个问题。这里可以参考CandyCat的文章: 可以看到第三维不会产生变化,这就是旋转分量的缺失。 2、如何产生万向节锁从上面的过程就可以看到,要产生万向节锁,只需针对规顺的中间的那个坐标轴,进行90度的旋转,就会使得规顺前后两头的坐标轴产生共线。对于Unity中使用的Z-X-Y规顺,这个中间的坐标轴就是X轴。 3、万向节锁的问题很多文章中都会提到,产生了万向节锁之后,就会导致丢失一个旋转分量。但实际上,就算产生了如上的万向节锁,看似不能在绕着原来的世界坐标系的Z轴旋转,但只要调用 Rotate(0,0,z,Space.World),就仍然可以让该物体旋转,并不会让物体无法旋转。 实际上,万向节锁真正的问题出在做插值动画的时候。 比如,起始状态如下,产生了万向节锁: 图8、初始状态 现在想要让他旋转到以下状态: 图9、结束状态 那么理想的情况如下: 图10、期望的旋转 但是,注意到由于万向节锁的存在,中间旋转角度(x、y、z)需要产生跳变,那么如果使用普通的插值,就会要从(90,0,0)到(150,90,90)进行插值,那么效果如下: 图11、用欧拉角进行插值 可以看到,物体会沿着一条弧线进行旋转。这就是万向节锁的问题。 4、在欧拉旋转中尽力规避万向节锁前面说到,产生万向节锁的关键是规顺的中间的那条坐标轴。只要不绕着这个坐标轴旋转90度,就不会发生旋转分量丢失的问题。这就是为什么大多数软件都将 X 轴作为中间的那条坐标轴的原因:常见的旋转插值是对Camera进行的,而如果绕着X轴旋转90度,则意味着正向上,或是正向下,这两种情况都是非常少见的。也就是说,相比于Y轴和Z轴,绕X轴旋转90度是非常少见的。这样就可以尽量的避免万向节锁。 但这终归是指标不治本的方法,另一种完全解决这个问题的方法,就是不再采用欧拉角表示旋转,这就是我们下面要讨论的四元数。 六、Quaternion(四元数)旋转1、什么是四元数要表示旋转,其实一个更直接的方式是使用他的变换矩阵,但是矩阵需要的保存空间太大,而这个矩阵中有大量的冗余数据。这就产生了四元数。 对于四元数的解释,可以参考CandyCat的文章,写的非常清楚。这里摘录一部分: 给定一个单位长度的旋转轴(x, y, z)和一个角度θ。对应的四元数为: 给定一个Y-Z-X规顺的欧拉旋转(X, Y, Z),则对应的四元数为: 2、用四元数进行旋转Unity中的四元数支持和一个Vector3相乘,如果把这个Vector3看作一个向量,那么左乘一个四元数,就相当于对这个向量进行对应的旋转。 |
|