Unity中关于四元数的API详解
Quaternion类
Quaternion(四元数)在Unity里用于处理旋转计算。它具有计算紧凑高效的特点,不会受到万向节锁问题的困扰,还能方便快速地进行球面插值。在Unity内部,所有的旋转都是用四元数来表示的。
四元数是基于复数的概念,理解起来并不直观。不过,在大多数情况下,你几乎不需要直接访问或修改单个四元数参数(x,y,z,w)。通常,你只需要获取和使用现有的旋转(例如从“Transform”组件中获取),或者用四元数来构造新的旋转(比如在两次旋转之间进行平滑插值)。
Quaternion是一个结构体,其成员变量相对简单,作为函数参数传递时效率较高。
Unity默认方向
在深入探究四元数的API之前,我们需要先明确一些基本概念,即方向和旋转是如何表示的。
Unity采用左手坐标系,若将世界坐标系与东南西北方向相结合,大致情况如下:假设以自身身体为例,当你站立在地面上,面朝北方时,这就是Unity中的默认方向,即面向 +Z 轴方向。此时,+X 轴指向东方,+Y 轴指向正上方。对应的欧拉角为(0, 0, 0),前方矢量为(0, 0, 1),上方矢量为(0, 1, 0)。
这里区分左右上下前后的概念很有必要,因为这些概念对应着 Vector3 类和 Transform 类中的相关方向函数。
方向的表示法
① 欧拉角表示法
若使用一组欧拉角来表示旋转,XYZ 三个参数代表相应轴向按照 YZX 顺序的旋转。例如,(0, 90, 90) 表示先绕 +Z 轴旋转 90 度,再绕 +Y 轴旋转 90 度。更多详细内容可参考文章《【Unity编程】Unity中的欧拉旋转》。
② 前方上方矢量界定法
在编程过程中,当需要明确指定方位时,大多会使用这种方法。要确定一个朝向,可以使用两个向量:前方矢量和上方矢量。当一个朝向的前方和上方确定后,这个朝向也就完全确定了。
例如,仅知道面朝北方,这个方向并未完全确定。因为右侧躺在地上看向北方,同样是面朝北方,此时就需要另一个矢量——上方矢量。当给出上方矢量后,朝向才能完全确定。
在 Unity 中,很多时候不需要给出严格的上方朝向。比如,若面朝北方,先给出(0, 0, 1) 作为前方矢量,即使给出的上方矢量不是严格的(0, 1, 0),如(0, 0.5, 0.5) 也是可以的。因为这两个矢量已经确定了一个方向,前方是严格的,实际的上方可以通过前方朝着给出的上方矢量旋转 90 度得出。也就是说,给出(0, 1, 0) 作为上方矢量,与给出在一定弧度范围内(不包含 +Z 和 -Z)所有方向的矢量,最终结果是相同的。
③ 绕轴旋转界定法
第三种定义旋转的方法是围绕某个指定的轴向旋转一定的角度。这种方法可以确定一个相对旋转,它从默认方向(此时前方为 +Z,上方为 +Y)出发,沿着指定的轴向进行指定角度的旋转,旋转后的前方和上方是确定的,因此也可用于确定朝向。
④ A 向到 B 向相对旋转表示法
还有一种方法是从 A 向到 B 向的相对旋转,它表示了一个旋转的相对变化。例如,A 为(0, 1, 0),B 为(0, 0, 1),这个相对旋转量代表原来的上方被旋转到了前方,这样的一个四元数也可以用欧拉角表示成(90, 0, 0),即沿着 +X 轴旋转了 90 度。
需要注意的是,在上述四种表示方法中,有的明确指定了上方矢量,有的似乎只明确了前方矢量。但实际上,它们都是从默认矢量出发的,如果没有明确指定上方朝向,那么默认使用 +Y 方向。
Quaternion 的成员变量和静态成员
成员变量
eulerAngles:返回当前四元数所对应的欧拉角。this[int]:可以使用类似数组和下标的形式从四元数中获取四个四元数参数。x、y、z、w:分别代表四元数的 x、y、z、w 参数。除非你非常了解它们的含义,否则最好不要直接修改这些参数。
静态成员
identity:单位四元数,表示默认的无旋转状态,此时与世界坐标相同,前方指向 +Z,上方指向 +Y。
Quaternion 的成员函数及相关说明
静态函数说明:成员函数中的几个 set 方法多用于将当前四元数设置成目标四元数,目标四元数的构建方法与对应名称的静态函数相同。
验证前方上方矢量表示法
为了验证前方上方矢量表示法中实际上方会重新计算,我们设计了以下小实验。在场景中设置三个物体,它们的朝向是打乱的,从左到右分别对应 1、2、3。可以使用以下代码将三个物体的朝向调整为一致:
// 前方上方矢量界定法的实际上方会重新计算
m_t1.transform.rotation = Quaternion.LookRotation(Vector3.forward, Vector3.up);
m_t2.transform.rotation = Quaternion.LookRotation(Vector3.forward, new Vector3(0, 0.5f, -0.5f));
m_t3.transform.rotation = Quaternion.LookRotation(Vector3.forward, new Vector3(0, 0.5f, 0.5f));
在 Start 方法中执行上述代码后,三个物体的朝向是一致的,这说明上方矢量确实进行了重新计算。
总结几种表示方法
下面使用代码总结几种表示法,对应同样的四元数,大致有四种表示方法:
// 旋转量的 4 种表示形式
Quaternion q1 = Quaternion.Euler(90, 0, 0);
Quaternion q2 = Quaternion.LookRotation(Vector3.down, Vector3.forward);
Quaternion q3 = Quaternion.AngleAxis(90, Vector3.right);
Quaternion q4 = Quaternion.FromToRotation(Vector3.up, Vector3.forward);
showQ("q1", q1);
showQ("q2", q2);
showQ("q3", q3);
showQ("q4", q4);
它们的输出结果表明,这几种形式表示的四元数结果完全相同。
将四元数旋转应用于子弹射击示例
当枪管转动时,为了让子弹仍然沿着正确的朝向发射出去,可以使用以下代码修改之前的逻辑:
Bullet_2 bullet = m_compPool.takeUnit<Bullet_2>();
// 发射时,将子弹的初始位置设置为枪口的当前位置
bullet.m_transform.position = m_transform.position;
// 将子弹的初始化旋转设置为指向当前枪口前方
bullet.m_transform.rotation = Quaternion.LookRotation(m_transform.forward);