找回密码
 立即注册
查看: 354|回复: 0

unity旋转与四元数

[复制链接]
发表于 2022-2-4 17:35 | 显示全部楼层 |阅读模式
概述:

表示旋转

四元数通常用来表示旋转,但表示旋转的方法很多,为什么要用更为复杂的四元数呢?
首先来看以下三种常用的旋转表示方法:

  • 矩阵
  • 欧拉角
  • 轴角对
矩阵

即旋转矩阵。一个向量乘以旋转矩阵可以得到旋转后的向量。严格的说,这里单独列为矩阵是不妥当的,因为欧拉角和轴对角、四元数都可以以矩阵的形式表示,并且在unity内部都是用矩阵来计算,区别在于使用的矩阵。
比如在二维空间中,旋转可以用一个单一的角  定义。作为约定,正角表示逆时针旋转。把笛卡尔坐标的列向量关于原点逆时针旋转  的矩阵是:


在三维空间就要复杂一些,因为可以绕三个轴旋转。比如绕x轴旋转  度,旋转矩阵为:


类似的还有绕y轴旋转 、绕z轴旋转 ,此处不再写出矩阵形式。
注意矩阵乘法不满足交换律,如果要同时绕三个轴旋转,需要定义一个旋转顺序。在unity里,这个旋转顺序是 ,这意味着给定 这样的旋转角度时,得到的组合旋转变换矩阵为 。即向量先绕 轴旋转 ,再绕 轴旋转 ,最后绕 轴旋转
在unity的顶点着色器里顶点旋转就是乘以旋转矩阵。实际上顶点变换都是乘以矩阵,并且是以缩放-旋转-平移的形式进行变换。当移动mesh组件物体时,该游戏物体的顶点属性并没有发生变化,是cpu每帧发送变换矩阵给gpu,在顶点shader里对每个顶点进行运算。有一个例外就是对于skinnedMesh的物体,这个下个专题再说。
这种矩阵的缺点在于每一个旋转都需要用3X3的矩阵,即9个float数据表示(这里示范的是齐次坐标多出一个维度),表示方式不够直观,但运算效率高
欧拉角

欧拉角使用三个旋转弧度表示在 轴上的旋转,常用于飞行模拟,比如:

  • 绕右向量的旋转称为:pitch(点头),也叫俯仰角
  • 绕上向量的旋转称为:yaw(摇头),也叫偏航角
  • 绕视方向向量的旋转称为:roll(转头),也叫翻滚角
如下图飞机所示:


欧拉角优点很明显,就是十分直观,而且所需的参数少,只需要三个float型变量。
其实在unity的Transform组件里就是用欧拉角来表示旋转,如下图:


pitch、yaw和roll都可以只用一个float数值来定义,通过不同的值使得飞机做出yaw(偏航),pitch(俯仰)和roll(翻滚)。但是如果你把pitch即x值设置为 时,你会发现调整y值跟z值不会有任何区别,即失去了一个维度,这就是万向锁
当然你可以在unity这样设置旋转:


这里每一个维度的旋转都是旋转其父物体的某一角度,在各自的角度上互不干扰。比如pitch的x值不会影响到yaw的z值,但pitch的x值会影响到所有子物体的x值(相对于世界坐标),不过x、y、z值方向上各自独立,依然可以解决万向锁的问题。
当然在unity里我们不可能给每个物体都设置3个父物体。多说一句,如果在unity里想要实现可绕主角旋转、自身可俯仰、可设置离主角距离的camera,即大多数mmorpg里的camera,可使用这种方法,只不过是将旋转与距离设置分隔开来,写代码就会非常好写。
轴角对

一个以单位矢量定义的旋转轴,再加上一个标量定义的旋转角来表示旋转。通常的表示 ,前面三个表示轴,最后一个表示角度。表示非常直观,也很紧凑,并且不存在万向锁的问题。
轴角最大的一个局限就是不能进行简单的插值,此外,轴角形式的旋转不能直接施于点或矢量,必转换为矩阵或者四元数
接下来有请四元数登场。
四元数

来看上面所说的轴角对表示方法: ,我们把  放在前面,写成 ,现在把 和轴 做一个变换:


把后面的向量直接用轴  来表示,也可以写成


这就是一个四元数。这样的四元数有什么作用呢?
如果有一个向量 绕轴旋转  弧度,那么可以写成如下形式:

,其中
至于这个公式是怎么推导出来的,这


四元数除了可以避免万向锁,还可以表示很好的插值来进行平滑的旋转。(矩阵插值很困难,欧拉角插值可能会遇到万向锁)
unity里提供了Quaternion.Slerp方法来进行两个四元数之间的插值,实现任意两个角度平滑过渡。
四元数的应用

四元数结构紧凑,不受万向锁影响,可以轻松插值。 Unity 内部使用四元数来表示所有旋转,可以使用 Quaternion.operator * 对旋转进行旋转,或对向量进行旋转。比如Transform的Rotate方法,內部实现为:
      Quaternion quaternion = Quaternion.Euler(eulers.x, eulers.y, eulers.z);
      if (relativeTo == Space.Self)
        this.localRotation *= quaternion;
      else
        this.rotation *= Quaternion.Inverse(this.rotation) * quaternion * this.rotation;
由于四元数不直观,通常做简单旋转操作还是以欧拉角为参数,或者通过欧拉角转换得到一个四元数。在Rotate方法里你传入的是欧拉角,但实际进行运算的是四元数。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-22 19:43 , Processed in 0.169809 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表