U2L5-1——为什么使用四元数

原先的学习中,用来描述角度的是欧拉角(Transform的角度与旋转),但是接下来将使用四元数,这里通过介绍欧拉角的缺陷来解释为什么用四元数

欧拉角

由三个角度(x,y,z)组成,在特定坐标系下用于描述物体的旋转量
空间中的任意旋转都可以分解成:绕三个互相垂直轴的三个旋转角组成的序列

heading-pitch-bank是一种最常用的旋转序列约定,Y-X-Z约定,也就是先绕Y轴旋转,再绕X轴旋转,最后绕Z轴旋转

  • heading:物体绕自身的对象坐标系的Y轴,旋转的角度
  • pitch:物体绕自身的对象坐标系的X轴,旋转的角度
  • bank:物体绕自身的对象坐标系的Z轴,旋转的角度

Inspector窗口中调节的Rotation就是欧拉角,transform.eulerAngles​ 得到的就是欧拉角角度

欧拉角描述旋转的优点:直观,易于理解,存储空间小(只用三个数描述旋转),可以进行从一个方向到另一个方向旋转大于180的角度

欧拉角的缺陷

一是:同一旋转的表示角度不唯一(90度和450度)

二是:万向节死锁
当一次旋转出现两个轴重合时,某个方向的旋转不能直接做到了!unity对象在绕x轴旋转90度后就会出现万向轮死锁
这个问题难以避免和解决,而带来的问题又极为严重,迫使我们不得不使用新的数据来记录旋转了

为何会出现万向节死锁?

无伤理解欧拉角中的“万向死锁”现象_哔哩哔哩_bilibili欧拉角-万向节死锁问题直观理解_哔哩哔哩_bilibili

在脚本里的欧拉角都是动态欧拉角,**本质上是用三个数来记录旋转的结果,或者说变换,它记录结果,而不记录过程**​

在这里,假设程序设定的旋转的顺序是X -> Y -> Z
例如欧拉角(20,90,0),在程序里执行的是:
初始姿态是(0,0,0),根据欧拉角(20,90,0)
先绕X轴旋转20度
再绕Y轴旋转90度
再绕Z轴旋转0度
最终得到了面向的角度是(20,90,0)的物体

假设这里这样用欧拉角描述旋转(0,0,0)-> (10,0,0)->(10,90,0)->(20,90,0)(这个旋转在第三步就会万向节死锁,)
但是这里程序并不会运行为:
初始姿态为(0,0,0),根据欧拉角(0,0,0)-> (10,0,0)->(10,90,0)->(20,90,0)
先绕X轴旋转10度
再绕Y轴旋转90度
先绕X轴旋转10度
而实际程序会运行为(简略描述):

  1. 初始姿态是(0,0,0),根据欧拉角(10,0,0)
    先绕X轴旋转10度
    再绕Y轴旋转0度
    再绕Z轴旋转0度
  2. 初始姿态是(0,0,0),根据欧拉角(10,90,0)
    先绕X轴旋转10度
    再绕Y轴旋转90度
    再绕Z轴旋转0度
  3. 初始姿态是(0,0,0),根据欧拉角(20,90,0)
    先绕X轴旋转20度
    再绕Y轴旋转90度
    再绕Z轴旋转0度

可以认为,在程序里,每一次用欧拉角变换,都是直接从初始形态(0,0,0)根据欧拉角的三个数按照X-Y-Z的顺序旋转,没有中间形态

原因在于,欧拉角的数据只有三个数字,它只记录对应的三个轴旋转多少度,并不会记录别的东西,也不记录中间形态,更不能记录如何改变旋转顺序!
因此每个欧拉角变换,对应的都是一次独立的“初始形态根据欧拉角的三个数按照X-Y-Z的顺序旋转”的完整流程

这样的运行方式就和平衡环架殊途同归(平衡环架因为机械结构原因也只能按照一定的顺序旋转),而平衡环架能直观的展现为何会死锁

在编辑器中我们用欧拉角旋转物体就不必受到这种固定顺序的桎梏,旋转的中间形态会被保留,还可以变换旋转的坐标系
但是在编程里我们只能用若干组三个数来表示欧拉角,没有别的记录,这样程序也就只能按照上述流程一次一次的进行完整的变换
因此在平衡环架上出现的死锁问题,在程序里也会遇到,并且无法避免

这是在欧拉角形式下,由欧拉旋转定义本身造成的,只要我们只用欧拉角的三个数来表现旋转,就不能避免这个问题

编程里这种死锁难以避免(除非添加三个数以外的参数,但是那样不再符合欧拉角的定义),
而在三维旋转动画里这种死锁会可能会带来极为严重的错误,因此,实际在程序里一般都使用四元数来表示旋转。