Unity中的欧拉角和四元数
笔记主要参考以下的博文
Unity中的欧拉旋转 - Andrew的游戏世界 - CSDN博客
【Unity编程】Unity中关于四元数的API详解 - Andrew的游戏世界 - CSDN博客
Unity中的欧拉角
顺规
在经典力学里,时常用zxz顺规来设定欧拉角;照着第二个转动轴的轴名,简称为x顺规。另外,还有别种欧拉角组。合法的欧拉角组中,唯一的限制是,任何两个连续的旋转,必须绕着不同的转动轴旋转。因此,一共有12种顺规。
Unity的欧拉角定义
Unity文档中Transform.eulerAngles的定义,本身是一个Vector3,就是一个三维矢量, 分别含有xyz三个参数。
xyz代表了三个角度,它们定义了一组有序的旋转,即围绕z轴旋转z度,然后围绕x轴旋转x度,然后围绕y轴旋转y度。你应该只去读取或者直接设置这些数值,不要增加它们,因为当角度超过360度将会失败。应该使用Transform.Rotate去替代执行旋转操作。
在上述文档中定义了在Unity中欧拉旋转的顺规,即先围绕z轴旋转,后绕x轴,最后绕y轴。
Unity内部使用四元数去执行旋转,不会存储欧拉角的累计值,因此它说超过360度会失败是可以理解的,它用四元数执行完运算之后,会更新最后对应的欧拉角数值,而这个结果欧拉角只是代表了等值的旋转变化结果,却无法代表中间过程,由于欧拉角旋转Z轴361度与1度有一样的结果,它便最后只存储了1度,以便于我们观察和使用。
欧拉旋转的轴
Unity中的Transform文档有有这样一个函数
public void Rotate(Vector3 eulerAngles, Space relativeTo = Space.Self);
这个函数提供了一个可选的相对空间坐标系参数:
Space.Self 局部坐标系,意味着本次欧拉旋转以物体当前的局部坐标朝向为基础出发执行旋转。
Space.World 世界坐标系,意味着本次欧拉旋转以物体当前的世界坐标朝向为基础出发执行旋转。
最重要的倒不是它有可选的世界坐标系,一般而言,常用的旋转都是相对当前局部坐标系执行的。并且在本次欧拉旋转过程中,它的相对轴是始终不变的。
比如我们可以指定一组欧拉旋转(90,60,30),通过前述的顺规我们知道,先绕Z轴旋转30度,再绕X轴旋转90度,再绕Y轴旋转60度,虽然有这样的顺序,但是Z旋转后相对X轴、Y轴,都是执行本组欧拉旋转前的那个轴向,它没有发生变动,所以我称它为“当前轴”。
而什么情况下是相对轴不变的呢?举个例子,指定(90,60,30)这组欧拉旋转,执行Transform.Rotate(new Vector3(90,60,30)) 是执行了一次欧拉旋转,在这过程中它的相对轴保持不变,但如果是这样的旋转方式:
Transform.Rotate(new Vector3(0,0,30));
Transform.Rotate(newVector3(90,0,0));
Transform.Rotate(new Vector3(0,60,0));
这就是执行了三次欧拉旋转,在执行后两次时其相对轴都会发生变化,显然结果不是我们需要的(90,60,30)的欧拉角旋转。
总结
Unity中的欧拉旋转是按照Z、X、Y顺规执行的旋转,一组欧拉旋转过程中,相对的轴向不会发生变化。 Transform.Rotate(new Vector3(90,60,30)),它代表执行了一组欧拉旋转,它相对的是旋转前的局部坐标朝向。正是这种顺规和轴向的定义,导致了万向节死锁的自然形成。
Unity中的四元数
四元数实际上和矩阵类似,不同的只是它的表示方式以及运算方式。
那么在Unity里如何利用四元数进行旋转呢?
首先是创建四元数,Unity里提供了非常多的方式来创建一个四元数。例Quaternion.AngleAxis(float angle, Vector3 axis),它可以返回一个绕轴线axis旋转angle角度的四元数变换。我们可以一个Vector3和它进行左乘,就将得到旋转后的Vector3。在Unity里只需要用一个“ * ”操作符就可以进行四元数对向量的变换操作,相当于我们上述讲到的p′=qpq−1操作。如果我们想要进行多个旋转变换,只需要左乘其他四元数变换即可,例如
Vector3 newVector = Quaternion.AngleAxis(90, Vector3.up) * Quaternion.LookRotation(someDirection) * someVector;
Unity中提供了关于四元数旋转的API,即Quarternion类。Quaternion(四元数)用于计算Unity旋转。它们计算紧凑高效,不受万向节锁的困扰,并且可以很方便快速地进行球面插值。 Unity内部使用四元数来表示所有的旋转。四元数是基于复数的,因而理解起来难度很大,好在我们并不需要去给出参数的值,而是只需要获取和使用现有的旋转(例如来自“Transform”),或者用四元数来构造新的旋转(例如,在两次旋转之间平滑插入)即可。
一些常用的函数:
Quaternion.LookRotation 前方、上方标注矢量法创建四元数
Quaternion.Angle 计算两个四元数前方矢量之间的夹角度数
Quaternion.Euler 欧拉角表示创建四元数
Quaternion.Slerp 球面插值
Quaternion.FromToRotation A向到B向相对旋转表示法创建四元数
Quaternion.identity 为单位四元数,即默认的无旋转状态
Quaternion 是一个结构体,本身成员变量相对简单,可以作为函数参数高效传递。
Unity的方向
Unity中使用左手坐标系,假如把世界坐标系跟东南西北进行结合起来看,大致如下图所示:
+x 东(右) +y 上 +z 前(北)
-x 西(左) -y 下 -z 后(南)
以自己身体为例,你站立在地面上,面朝北方,此时就是默认方向,也就是Unity中的方向就是面向+Z轴方向,那么此时+X轴在东方,+Y轴对应正上方。此时对应的欧拉角是(0,0,0),此时对应的前方矢量是(0,0,1),上方矢量是(0,1,0)。这里的上下左右的概念与Vector3类、Transform类中的相应的方向函数也是相对应的。
确定方向的方法
1.欧拉角表示法
Unity中的欧拉旋转方向为固定的zxy顺规。
2.前方、上方标定矢量法
根据对局部坐标轴的描述, 构造对应的代表旋转程度的四元数。
指的是从默认方向到LookRotation参数的变化量。例如 Quaternionq2 = Quaternion.LookRotation(Vector3.down ,Vector3.forward); 是指从Unity默认方向到这个的变化量,按照左手坐标系,绕x转90,从Z+到Y-。
3. 绕轴旋转界定法
使物体沿某一指定的轴向旋转一定的角度。这个方法也可以确定一个相对旋转,它以从默认方向(此时前方+Z,上方+Y)出发,沿着指定的轴向进行指定角度的旋转。旋转后的前方和上方是确定的,所以可以确定朝向。
4.A向到B向相对旋转表示法
从A向到B向的相对旋转,这种表示了一个旋转的相对变化。比如A为(0,1,0),B为(0,0,1),也就是相对旋转量代表原来的上方被旋转到了前方,这样的一个四元数也可以用欧拉角表示成(90,0,0),也就是沿着+X轴旋转了90度。
利用几种表示方法在Untiy中创建四元数
Quaternion q1=Quaternion.Euler(90, 0, 0); //方法1—欧拉角表示
Quaternion q2 = Quaternion.LookRotation(Vector3.down ,Vector3.forward); //方法2—前方上方标注矢量法
Quaternion q3 = Quaternion.AngleAxis(90,Vector3.right); //方法3—绕轴旋转界定法
Quaternion q4 = Quaternion.FromToRotation(Vector3.up, Vector3.forward); // 方法4—A向到B向相对旋转表示法
成员变量
eulerAngles 欧拉角,返回当前四元数所对应的欧拉角
this[int] 可以使用类似数组和下标的形式从四元数中获取四个四元数参数
四元数的具体参数x,y,z,w分量一般不手动设置,易出错。
identity 单位四元数,也就是默认的无旋转状态,此时与世界坐标相同,前方指向+Z,上方指向+Y
成员函数
静态函数
1.static float Angle(Quaternion a, Quaternion b) 计算两个四元数前方矢量之间的夹角度数
2.static Quaternion AngleAxis(float angle, Vector3 axis) 构建一个四元数,它表示沿着一个轴旋转固定角度,即上述表示法3
3.static float Dot(Quaternion a, Quaternion b) 计算两个四元数之间的点积,返回一个标量,这个函数一般用不到
4.static Quaternion Euler(float x, float y, float z) 构建一个四元数,用欧拉角度表示
5.static Quaternion FromToRotation(Vector3 fromDirection, Vector3 toDirection) 构建一个四元数,它表示从指向fromDirection方向到指向toDirection方向的相对旋转量,见上述表示法4
6.static Quaternion Inverse(Quaternion rotation) 构建一个四元数,它是指定的四元数的逆,也就是逆向旋转,比如原四元数表示相对+X轴旋转了90度,那么此函数结果就是相对+X轴旋转了-90度
7.static Quaternion Lerp(Quaternion a, Quaternion b, float t) 构建一个四元数,表示从四元数a到b的球面插值,所谓的插值也就是中间旋转量,从a作为起点,此时对应t为0,到b为终点,此时对应t为1。当t取0-1之间的小数时,就代表了中间的插值结果。这个方法与Slerp相同,计算速度快,但是精度低,如果相对旋转变化量很小,则效果不理想
8.static Quaternion LerpUnclamped(Quaternion a, Quaternion b, float t) 与Lerp相同,区别是,Lerp的t值会被钳制在[0,1]之间,而此方法则不会,t允许超出计算
9.static Quaternion LookRotation(Vector3 forward, Vector3 upwards = Vector3.up) 构建一个四元数,使用前方上方矢量确定朝向,也就是上述表示法2
10.static Quaternion RotateTowards(Quaternion from, Quaternion to, float maxDegreesDelta) 构建一个四元数,表示从一个四元数from(的前方)向着另外一个四元数(的前方)旋转,但不能超出指定的角度,也就是如果两个前方矢量夹角超过指定角度,则旋转到达指定角度时就停止,若是夹角本身不足的话,则结果直接为目标四元数to,与上述表示法4的意思很接近
11.static Quaternion Slerp(Quaternion a, Quaternion b, float t) 球面插值,与Lerp功能相同,t值也被钳制,计算精度高,但是速度相对较慢
12.static Quaternion SlerpUnclamped(Quaternion a, Quaternion b, float t) 与Slerp功能相同,只是t值不被钳制,允许超出计算
13.static Quaternion operator * (Quaternion lhs, Quaternion rhs) 乘法运算符重载,当表示两个连续的旋转时,可以使用lhs * rhs的形式得出连续旋转的结果,lhs为左值,rhs为右值。注意左值是先进行的旋转,叠加右值旋转。用法示例:lhs = lhs * rhs;
14.static Vector3 operator *(Quaternion rotation, Vector3 point) 乘法运算符重载,表示对一个矢量point施加旋转rotation,得出旋转后的结果矢量。用法示例:Vector3 result=rotation * point
对于一些方法的详细描述可以参考
Unity中的四元数类详解【详解】 - qq_36684665的博客 - CSDN博客
Unity3D入门篇——Quaternion类 - u014086857的专栏 - CSDN博客