您现在的位置是:首页 >技术交流 >Unity 四元数网站首页技术交流

Unity 四元数

七七喝椰奶 2024-06-17 10:31:52
简介Unity 四元数

前言:在场景中,可以用旋转工具改变物体角度,也可以在Inspector窗口中改变物体的X、Y、Z值(欧拉角)来改变物体角度。

虽然用欧拉角表示角度和旋转,但一般人想不到,物体在三维空间的旋转并不是一个简单问题,用3个角度表示是远远不够的


一、万向节锁定

要说明三维物体旋转的复杂性,从"万向节锁定"这一问题入手最有说服力。

6bba4e32e16d4a0daab389baeb912978.png

上图为欧拉角示意图。虽然可以调整欧拉角到任意角度,但欧拉角的3个轴并不是独立的,x,y,z轴之间存在嵌套结构,如上图所示,这种嵌套结构被称为"万向节",意思是可以旋转到任意角度的关节。

当中层轴旋转时,会带动内层的轴跟着旋转。通常三维软件的欧拉角,从外层到哪层是按照y->x->z轴的顺序,也就是说x轴的旋转会影响z轴

这里介绍一下用Unity怎样让万向节锁定 

  1. 在场景中新建一个Cube
  2. 用鼠标在Inspector窗口中朝向的x、y、z这三个字母上拖拽,不要在场景中用鼠标直接旋转物体,这样可以看到3个轴分别是如何旋转的
  3. 将x的旋转改为90,y和z的旋转改为0
  4. 用鼠标在旋转的y和z上拖拽,修改y和z的旋转。这是会发现,绕y和绕z旋转方向变成一样了,缺少了一个自由度

按照上述步骤操作,会顺利进入"万向节锁定"状态。一旦进入此状态,物体旋转就会受限制。如果游戏系统是基于欧拉角设计的,那么在主角会俯仰运动的游戏,万向节锁定问题会显得非常严重,而且在其他类型有种也会带来各种问题。

值得说明的是,Unity内部使用四元数比哦时物体的旋转,这里并不会真的遇到锁定问题,只是在编辑器界面上模拟了万向节锁定的效果。

如果用循转工具在场景里直接旋转物体,就跳过了编辑窗口的限制,物体仍然可以自由旋转

到这里,你们可能还没有完全理解3D旋转的奥秘哦,但它具有的复杂性已经毋庸置疑了。幸运的事,数学家哈密顿提出的"四元数"能够让我们跨越欧拉角,彻底解决旋转难题

二、四元数的概念

四元数包含一个标量分量和一个三维向量分量,四元数Q可以记作:

Q=[w,(x,y,z)]。

下面给出四元数的公式定义。对于旋转轴为n,旋转角度为ß的旋转,如果用四元数表示,则四个分量分别为:

  • w=cos(ß/2)
  • x=sin(ß/2)cos(∂x)
  • y=sin(ß/2)cos(∂y)
  • z=sin(ß/2)cos(∂z)

用四元数表示旋转一旦也不知管,4个分量w、x、y、z与绕各轴的旋转角度并没有直接的对应关系。在实际开发中也不要试图获取和修改某一份量,应当只做整体处理。

前文提到,矩阵也可以表示旋转,而且矩阵也不存在万向节锁定的问题。其实,旋转还可以用欧拉角和四元数表示,但么一种都有自己的优缺点,下面对这三种方式进行对比

 

欧拉角

矩阵

四元数

旋转一个位置点 

不支持

支持

不支持

增量旋转

不支持

支持,运算量大

支持,运算量小

平滑差值

支持(存在问题)

基本不支持

支持

内存占用

3个浮点数

16个数值

4个浮点数

表达式是否唯一

无数种

唯一

互为负的两种表示

潜在问题

欧拉角锁定

矩阵蠕变

误差累计

实际应用中应根据优缺点来应用,并尽可能在引擎层面进行统一,尽量减少开发时的问题

小提示:Unity内部旋转使用四元数,即结构体Quaternion表示的,但在街面上很多地方会转为更直观的偶来奥,以便直接指定旋转的角度,这种方式兼顾了准确性和便利性

三、Quaternion结构体

这部分介绍Quaternion的属性和方法

属性:

属性

说明

x

四元数x的分量,不应直接修改

y

同x

z

同x

w

同x

this[int index]

允许通过下标运算符访问x、y、z、w分量。例如[1]可以访问y

eulerAngles

获得对应的欧拉角

identity

获得无旋转的四元数

方法和运算符:

方法

说明

ToAngleAxis

将旋转转换为一个轴和一个角度的形式

SetFromToRotation

与FromToRotation类似,但是直接修改当前四元数对象

SetLookRotation

与LookRotation类似,但是直接修改当前四元数对象

*

四元数相乘,代表依次旋转的操作

==

判断四元数是否相等

!=

判断四元数是否不等

Dot

两个旋转点乘

AngleAxis

根据一个轴和角度获得一个四元数

FromToRotation

获得一个四元数,代表从from到to向量的旋转

LookRotation

给定前方和上方向量,获得一个旋转

Slerp

插值,根据比例在两个四元数之间进行球面插值

Lerp

插值,根据比例在两个四元数之间进行插值并将结果规范化

RotateTowards

将旋转from变到旋转to

Inverse

返回四元数的逆

Angle

返回两个旋转之间的夹角

Euler

转换为对应的欧拉角

四、理解和运用四元数

在前文中,详细介绍了"位置"与"向量"的区别。下面引出与之对应的另一对概念——"朝向"与"旋转"。

朝向和位置都是一种状态,旋转和向量指的是变化量

位置和向量都用Vector3表示,位置和朝向都用四元数表示

理解了Vector3加法的意义,就可以类比出四元数的旋转操作,只是加法变成了乘法

下表列出四元数乘法的含义

元素1

运算符

元素2

一般含义

朝向

*

朝向

意义不明

朝向

*

旋转

从某个朝向开始,旋转一个角,得到新的朝向

旋转

*

旋转

组合两次旋转,得到合并的结果

与向量不同的是,四元数相乘不满足交换律,这也印证了三维空间中物体的旋转不是一个简单的问题。

有意思的是,四元数乘法可以直接作用于向量,这样就可以方便地旋转向量了:

旋转*向量:将向量旋转一个角,得到新的向量

注意:四元数乘以向量时,必须四元数在前,向量在后。

五、四元数的插值

前文通过介绍"万向节锁定",解释了使用四元数的必要性,其实四元数还有另一个大用,就是做动画与插值。

物体的转向动画应该是直接的、均匀的,不能给人"绕远"的感觉。

几何上,在球面上的两点之间移动,沿"大圆"的路径是最短的。大圆的定义是"过球心的平面与球面相交形成的圆",换句话说就是能在球面上画出的最大的那一类圆。

两个朝向之间的旋转动画,当旋转的路径符合大圆时,旋转路径是最短的,看起来也最舒服。

欧拉角在制作旋转动画时具有很大缺陷。如果旋转较为复杂,x、y、z轴都有旋转时,物体不会按照"大圆"的路径运动,得到的结果会很奇怪。矩阵在表现旋转动画时,也很难计算正确的插值点。

而四元数就很擅长插值运算。Unity提供了Quaternion.Slerp()方法,用于旋转的插值。其函数定义如下

Static Quaternion Slerp(Quaternion a,Quaternion b,float t);//t取0到1之间的值

六、朝向与向量

朝向代表空间中的一个方向,而向量也可以表示一个方向,因此有时也可以用向量直接表示物体的朝向。

我们不仅可以通过transform.forward属性获得只想物体前方的单位向量,而且可以直接设置它以改变物体的朝向。

向量可以代表朝向,四元数也可以代表朝向,那么向量也就可以转化为四元数。向量转化为四元数的方法定义如下:

Quaternion Quaternion.LookRotation(Vector3 forward) ;

Quaternion Quaternion.LookRotation(Vector3 forward,Vector3);

Quaternion.LookRotation提供了直接获得朝向的一种方法。

仔细思考会发现——难道四元数可以用向量代替吗?应该不行,如果能替代还要四元数干啥。其实向量有很大的局限性,例如,一个人躺在床上,面朝上,前方是世界坐标系上方,这时,他可以在保持面朝上的情况下在床上水平旋转,让头部朝东朝西。而无论头朝哪里,前方都可以保持向上。

这说明了代表某个向量方向的四元数不是唯一的。如果只用一个向量代表前方,理论上有无数种四元数都指向该方向,但它们的"上方"不同。如果仅指定前方,即使使用LookRotation方法只有一个参数的形式,Unity也会用某种规则假定一个合适的"上方";而如果要精确地控制,则要用Quaternion的另一种形式,用两个参数分别制定前方向量和上方向量,这样就可以得到更符合要求的结果

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。