最新文章
泰课新年学课蛇来运转欢度春节活动
02-01 20:25
共庆2024圣诞、元旦泰课双蛋活动
12-16 10:21
泰课共庆75周年国庆活动!
10-05 21:24
暑假双月联动学习计划 7月15 - 8月21日
07-14 23:09
泰课在线劳动光荣,勤学快乐之五月勤学季活动
04-30 21:19
2024年青春绽放开学季活动
03-11 13:01
Unity transform.rotation和四元数的基本理解应用
在一位同学多次提问后,我终于明白他的困惑所在:四元数具体对应到 transform.rotation 上,是否表示旋转轴和旋转角度呢?
1. 属性面板设置上的 rotation
在属性面板中,Rotation 方便我们直接设置角度。这三个数值代表三个轴向的欧拉角度数。当我们设置这些角度后,Unity 会将其转换为四元数,并赋值给物体的 transform.rotation 属性。
2. 脚本中 transform.rotation 属性
在代码里,transform.rotation 属性是一个四元数,用 (x, y, z, w) 表示。四元数是一种复数,由三个虚数和一个实数组成。需要注意的是,这里的 x、y、z 并非坐标,w 也不是旋转角度。
例如,当物体绕 Z 轴旋转 60 度时,打印 transform.rotation 得到的结果是 (0.000, 0.000, 0.500, 0.866)。其中,前三个数是与旋转轴相关的虚数,第四个数是与角度相关的实数(在 Unity 的日志输出中,四元数表达式第一个是实数,后面三个是虚数)。
这四个数的计算方式如下:
设 q 表示一个四元数,axis 表示旋转轴向量,例如绕 Z 轴旋转时,axis = [0, 0, 1]。
q.w = cos(60° / 2) = 0.866
q.x = axis.x * sin(60° / 2) = 0 * 0.5 = 0
q.y = axis.y * sin(60° / 2) = 0 * 0.5 = 0
q.z = axis.z * sin(60° / 2) = 1 * 0.5 = 0.5
3. Unity 中最常用的四元数运算
3.1 四元数乘以四元数
形式为 Quaternion * Quaternion,例如 q = t * p。这表示将一个点先进行 t 操作旋转,然后再进行 p 操作旋转。
3.2 四元数乘以向量
形式为 Quaternion * Vector3,例如 p 为 Vector3 类型,t 为 Quaternion 类型,q = t * p。这是将点 p 进行 t 操作旋转。
4. 学习建议
对于对四元数感到陌生和恐惧的同学,建议借助 Unity 中属性面板上 Rotation 的各轴角度,结合代码中输出的 transform.rotation 进行观察。先建立直观认识,了解 x、y、z、w 分别与什么相关以及如何计算得出,这是一个很好的起步。
5. Unity 中 Quaternion 类的实用方法
5.1 Euler 方法
如果需要围绕 X 轴旋转 50 度,可以使用 Euler 方法计算出绕 X 轴旋转 50 度的四元数。这里使用一个 Vector3 类型的向量,其 x、y、z 的值表示要绕对应轴旋转的角度。所以这个 Vector3 并非坐标,而是一种表示哪个轴旋转多少度的方式。
Quaternion.Euler(new Vector3(50, 0, 0));
5.2 AngleAxis 方法
通过 AngleAxis 方法,可以获得物体绕 X 轴顺时针旋转指定度数后的 rotation 四元数。
Quaternion.AngleAxis(50, box.transform.right);
6. 脚本示例练习
下面通过一个脚本示例进行练习。代码虽不多,但需要深入思考。建议认真观察体会后,根据自己的理解进行修改或编写代码尝试。
using System;
using UnityEngine;
public class RT : MonoBehaviour
{
public GameObject box;
// Start is called before the first frame update
void Start()
{
// 通常的 x, y, z 轴分三次旋转
// 不同软件的三次旋转可能不同,如旋转顺序、相对轴是否随物体转动、本地坐标还是世界坐标旋转等,结果都会不同
// 例如下面的示例,分三次旋转和直接设置 rotation = eulerAngles(new Vector3(50, 20, 60)) 结果不同
Quaternion qx = Quaternion.AngleAxis(50, box.transform.right);
box.transform.localRotation *= Quaternion.Euler(new Vector3(50, 0, 0));
Quaternion qy = Quaternion.AngleAxis(20, box.transform.up);
box.transform.localRotation *= Quaternion.Euler(new Vector3(0, 20, 0));
Quaternion qz = Quaternion.AngleAxis(60, box.transform.forward);
box.transform.localRotation *= Quaternion.Euler(new Vector3(0, 0, 60));
// qxyz3 与物体三次旋转后的 transform.rotation 相等
// 重点:三次旋转可以用三个四元数相乘,但每次都要获取当前物体的旋转轴,而非旋转开始前的固定轴
// 注意:当前的四元数乘以另一个四元数,会按这个四元数可换算出的各轴角度旋转
// 分三次旋转不同于直接设置 rotation = eulerAngles(new Vector3(50, 20, 60))
Quaternion qxyz3 = qz * qy * qx; // Unity 是 z, y, x 的顺序相乘
Debug.Log($"{qxyz3.ToString("f3")} <== qxyz3");
Debug.Log($"{box.transform.rotation.ToString("f3")} <== transform.rotation");
// 自定义 eulerAngles
Quaternion qeuler = eulerAngles(new Vector3(50, 20, 60));
Debug.Log($"{qeuler.ToString("f3")} <== my eulerAngles");
// 引擎的 eulerAngles
Quaternion qeuler2 = new Quaternion();
qeuler2.eulerAngles = new Vector3(50, 20, 60);
Debug.Log($"{qeuler2.ToString("f3")} <== unity eulerAngles");
}
public Quaternion eulerAngles(Vector3 eulers)
{
return Euler(eulers);
}
public Quaternion Euler(Vector3 eulers)
{
return FromEulerRad(eulers * Mathf.Deg2Rad);
}
// 将欧拉角转为弧度后计算完成旋转后的四元数
public Quaternion FromEulerRad(Vector3 euler, string order = "ZYX")
{
var _x = euler.x * 0.5; // theta θ
var _y = euler.y * 0.5; // psi ψ
var _z = euler.z * 0.5; // phi φ
float cX = (float)Math.Cos(_x);
float cY = (float)Math.Cos(_y);
float cZ = (float)Math.Cos(_z);
float sX = (float)Math.Sin(_x);
float sY = (float)Math.Sin(_y);
float sZ = (float)Math.Sin(_z);
return new Quaternion(
sX * cY * cZ + cX * sY * sZ,
cX * sY * cZ - sX * cY * sZ,
cX * cY * sZ - sX * sY * cZ,
cX * cY * cZ + sX * sY * sZ);
}
}