在一个同学的多次提问之下终于知道他不理解的点在哪了:
那这个四元数,具体对应到transform.rotation上,其实是。旋转轴和旋转角度的意思嘛?
属性面板设置上的rotation
是这样的,Rotation在属性面板上,那是为了方便我们直接设置角度,那三个数值是三个轴向的euler角度数。
咱们设置角度后,他会转换为四元数设置给物体的transform.rotation属性。
脚本中transform.ratation属性
而代码中的transform.ratation属性,这是一个四元数 (x, y, z, w)。
四元数是一个复数,由三个虚数,加一个实数构成。
但是不要理解为这是x,y,z坐标,w也不是旋转角度
比如物体绕Z轴旋转60度,打印出来transform.ratation就是(0.000, 0.000, 0.500, 0.866)
前面三个是与旋转轴相关的虚数,
第四个是与角度相关的实数(这只是unity中log输出,四元数的表达式第一个是实数,后面三个是虚数)
重点来了,这四个数分别是这样计算出来的:
q表示一个四元数,aix表示旋转轴向量,举个例是z轴 aix = [0 0 1]
q.w=cos(60°/2) = 0.866
q.x=Aix.x*sin(60°/2) = 0*0.5=0
q.y=Aix.y*sin(60°/2) = 0*0.5=0
q.z=Aix.z*sin(60°/2) = 1*0.5=0.5
Unity中最常用的四元数运算
(1) 四元数乘以四元数:quaternion * quaternion , 例如 q = t * p;
这是将一个点先进行t 操作旋转,然后进行p操作旋转。
(2) 四元数乘以向量:Quaternion * Vector3, 例如 p : Vector3, t : Quaternion , q : Quaternion; q = t * p;
这是将点p 进性t 操作旋转。
建议:对四元数陌生,恐惧的同学只需要借助unity中属性面板上Rotation的各轴角度,结合代码中输出 transform.rotation进行观察。
尽量先建立一个直观,并知道 x,y,z,w分别与什么相关,是怎么计算了来的,这就是一个很好的起步了!
Unity中Quaternion类最有用和最能帮助你不断增加对四元数的理解的几个方法
通过Euler方法,可以明白,如果需要 围绕X轴旋转一个50的角度, 那要用Euler方法算出绕X轴转50的四元数出来:
用一个向量x,y,z的值来表示要绕这个轴旋转多少度。所以这个Vector3并不是一个坐标,而是一种哪个轴多少度的表达。
Quaternion.Euler(new Vector3(50,0,0))
通过AngleAxis方法,可以获得物体饶X轴顺时针旋转多少度后的rotation四元数:
Quaternion.AngleAxis(50,box.transform.right);
接下来我们通过一个脚本示例进行练习
代码不多但是充满思考,你一定认真看或有体会了再回来看看观察,或根据自己的理解进行修改或写代码进行尝试。
- 练习分三次对x,y,z轴进行旋转:box.transform.localRotation *= Euler(new Vector3(50,0,0)); 这其实就是transform带的Rotate方法中的实现,不过这只 本地坐标旋转
- Quaternion.eulerAngles 来一下完成三次旋转
- 练习用三个四元数相乘,得到完成旋转后物体的rotation四元数
- 练习中带有自定义的Euler,eulerAngles 方法,可以多一些理解Unity的Quaternion自带的方法里面干了什么。
不然不在预期的数字结果,会干扰你对四元数的理解。
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 *= Euler(new Vector3(50,0,0));
Quaternion qy = Quaternion.AngleAxis(20,box.transform.up);
box.transform.localRotation *= Euler(new Vector3(0,20,0));
Quaternion qz = Quaternion.AngleAxis(60,box.transform.forward);
box.transform.localRotation *= Euler(new Vector3(0,0,60));
// qxyz3与物体三次旋转后的transform.rotation相等
// 划重点,三次旋转,可以用三个四元数相乘,
// 但获得这三个四元数轻松,比如上面三次就每次都要获取当前物体的旋转轴,而不是用旋转开始前那个固定的旋转轴
// 划重点,当前的四元数乘以另一个四元数,就会按这个四元数可换算出的各轴角度旋转
// 注意以上分三次旋转,不同于直接设置rotation = eulerAngles(new Vector3(50, 20, 60))
Quaternion qxyz3 = qz*qy*qx; // unity是z,y,z的循序相乘
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);
// if (order == "ZXY") {
// return new Quaternion(
// cX * cY * cZ - sX * sY * sZ,
// sX * cY * cZ - cX * sY * sZ,
// cX * sY * cZ + sX * cY * sZ,
// cX * cY * sZ + sX * sY * cZ
// );
// }
// if (order == "XYZ") {
// return new Quaternion(
// cX * cY * cZ - sX * sY * sZ,
// sX * cY * cZ + cX * sY * sZ,
// cX * sY * cZ - sX * cY * sZ,
// cX * cY * sZ + sX * sY * cZ);
// }
// if (order == "YXZ") {
// return new Quaternion(
// cX * cY * cZ + sX * sY * sZ,
// sX * cY * cZ + cX * sY * sZ,
// cX * sY * cZ - sX * cY * sZ,
// cX * cY * sZ - sX * sY * cZ);
// }
// if (order == "ZYX") {
// return new Quaternion(
// cX * cY * cZ + sX * sY * sZ,
// sX * cY * cZ - cX * sY * sZ,
// cX * sY * cZ + sX * cY * sZ,
// cX * cY * sZ - sX * sY * cZ);
// }
// if (order == "YZX") {
// return new Quaternion(
// cX * cY * cZ - sX * sY * sZ,
// sX * cY * cZ + cX * sY * sZ,
// cX * sY * cZ + sX * cY * sZ,
// cX * cY * sZ - sX * sY * cZ);
// }
// if (order == "XZY") {
// return new Quaternion(
// cX * cY * cZ + sX * sY * sZ,
// sX * cY * cZ - cX * sY * sZ,
// cX * sY * cZ - sX * cY * sZ,
// cX * cY * sZ + sX * sY * cZ);
// }
// return new Quaternion(0,0,0,0);
}
}
可延伸的阅读:
Unity中关于四元数的API详解 https://www.jianshu.com/p/9c3f11eeda1f
四元数(Quaternion)和欧拉角(Eulerangle)讨论 https://blog.csdn.net/qq_15020543/article/details/82834885
Unity中的欧拉旋转 https://blog.csdn.net/AndrewFan/article/details/60866636
浅谈Unity中的rotation和Quaternion的乘法 https://blog.csdn.net/rurouni/article/details/22808109