一、为何使用四元数
1.欧拉角
定义:由三个角度(x,y,z)组成 在特定坐标系下用于描述物体的旋转量
注意:空间中的任意旋转都可以分解成绕 三个互相垂直轴的三个旋转角组成的序列
2.欧拉角旋转约定
**heading-pitch-bank:**是一种最常用的旋转序列约定 (Y-X-Z约定)
heading:物体绕自身的对象坐标系的Y轴,旋转的角度
pitch:物体绕自身的对象坐标系的X轴,旋转的角度
bank:物体绕自身的对象坐标系的Z轴,旋转的角度
3.Unity中的欧拉角
Inspector窗口中调节的Rotation就是欧拉角
this.transform.eulerAngles得到的就是欧拉角角度


4.欧拉角的优缺点
(1)优点
直观、易理解
存储空间小(三个数表示)
可以进行从一个方向到另一个方向旋转大于180度的角度
(2)缺点
同一旋转的表示不唯一
万向节死锁
**5.**万向节死锁
当某个特定轴达到某个特殊值时 ,绕一个轴旋转可能会覆盖住另一个轴的旋转,从而失去一维自由度
例如:Unity中X轴达到90度时 会产生万向节死锁
Tips:四元数旋转不存在万向节死锁问题
二、四元数是什么
四元数是什么
1.四元数构成
(1)概念
四元数是简单的超复数, 由实数加上三个虚数单位组成 ,主要用于在三维空间中表示旋转
(2)构成

(3)轴-角对
含义:在3D空间中,任意旋转都可以表示 ,绕着某个轴旋转一个旋转角得到。
注意:该轴并不是空间中的x,y,z轴 ,而是任意一个轴
对于给定旋转,假设为绕着n轴,旋转β度,n轴为(x,y,z) ,那么可以构成四元数为:
四元数Q则表示绕着轴n,旋转β度的旋转量
2.Unity中的四元数
Quaternion 是Unity中表示四元数的结构体
(1)Unity中的四元数初始化方法

cs
#region 知识点一 四元数 Quaternion
//四元数Q = [cos(β/2), sin(β/2)x, sin(β/2)y, sin(β/2)z]
//计算原理
//Quaternion q = new Quaternion(Mathf.Sin(30 * Mathf.Deg2Rad), 0, 0, Mathf.Cos(30 * Mathf.Deg2Rad));
//提供的轴角对 初始化 四元数的方法
Quaternion q = Quaternion.AngleAxis(60, Vector3.right);
//创建一个立方体
GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
obj.transform.rotation = q;
#endregion
3.四元数和欧拉角相互转化
欧拉角转四元数
四元数转欧拉角

cs
#region 知识点二 四元数和欧拉角转换
//1.欧拉角转四元数
Quaternion q2 = Quaternion.Euler(60, 0, 0);
GameObject obj2 = GameObject.CreatePrimitive(PrimitiveType.Cube);
obj2.transform.rotation = q2;
//2.四元数转欧拉角
print(q2.eulerAngles);
#endregion
4.四元数弥补的欧拉角缺点

三、四元数
1.单位四元数
(1)含义

例如:[1,(0,0,0)]和[-1,(0,0,0)]都是单位四元数 表示没有旋转量
cs
#region 知识点一 单位四元数
print(Quaternion.identity);
//testObj.rotation = Quaternion.identity;
Instantiate(testObj, Vector3.zero, Quaternion.identity);
#endregion
2.插值运算

cs
//无限接近 先快后慢
A.transform.rotation = Quaternion.Slerp(A.transform.rotation, target.rotation, Time.deltaTime);
//匀速变化 time>=1到达目标
time += Time.deltaTime;
B.transform.rotation = Quaternion.Slerp(start, target.rotation, time);
3.向量方向 转换为 对应四元数角度
Quaternino.LookRotation(面朝向量);
LookRoataion方法可以将传入的面朝向量
转换为对应的四元数角度信息

cs
#region LookRotation
//Quaternion q = Quaternion.LookRotation(lookB.position - lookA.position);
//lookA.rotation = q;
lookA.MyLookAt(lookB);
#endregion
四、四元数练习题
1.题目
(1)利用四元数的LookRotation方法,实现LookAt的效果
(2)将之前摄像机移动的练习题中的LookAt换成LookRotation实现
并且通过Slerp来缓慢看向玩家
2.实现
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class Tools
{
public static void MyLookAt(this Transform obj, Transform target)
{
Vector3 vec = target.position - obj.position;
obj.transform.rotation = Quaternion.LookRotation(vec);
}
}
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraMove : MonoBehaviour
{
public float zOffect = 4;
public float yOffect = 7;
public Transform target;
private Vector3 targetPos;
public float moveSpeed;
private Vector3 startPos;
private float time;
private Quaternion targetQ;
public float roundSpeed;
private float roundTime;
private Quaternion startQ;
// Start is called before the first frame update
void Start()
{
}
void LateUpdate()
{
//先快后慢的移动
//if(targetPos != target.position + -target.forward * zOffect + target.up * yOffect)
//{
// targetPos = target.position + -target.forward * zOffect + target.up * yOffect;
//}
////摄像机的位置 等于目标的位置 进行向量偏移
////先朝目标对象的 面朝向的反方向平移4米 再朝目标的头顶位置 平移7米
//this.transform.position = Vector3.Lerp(this.transform.position, targetPos, Time.deltaTime*moveSpeed);
//匀速移动
if (targetPos != target.position + -target.forward * zOffect + target.up * yOffect)
{
targetPos = target.position + -target.forward * zOffect + target.up * yOffect;
startPos = this.transform.position;
time = 0;
}
time += Time.deltaTime;
this.transform.position = Vector3.Lerp(startPos, targetPos, time* moveSpeed);
//用目标的位置 减去 摄像机的位置 得到新的面朝向向量
//targetQ = Quaternion.LookRotation(target.position - this.transform.position);
//先快后慢
//this.transform.rotation = Quaternion.Slerp(this.transform.rotation, targetQ, Time.deltaTime* roundSpeed);
//匀速旋转
if( targetQ != Quaternion.LookRotation(target.position - this.transform.position))
{
targetQ = Quaternion.LookRotation(target.position - this.transform.position);
roundTime = 0;
startQ = this.transform.rotation;
}
roundTime += Time.deltaTime;
this.transform.rotation = Quaternion.Slerp(startQ, targetQ, roundTime * roundSpeed);
//this.transform.LookAt(target);
}
}
五、四元数计算
1.四元数相乘

2.四元数相乘向量

3.总结

六、四元数习题案例
1.题目
模拟飞机发射不同类型子弹的方法 单发,双发,扇形,环形
2.实现
效果演示
子弹轨迹
代码实现
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum E_FireType
{
//单发
One,
//双发
Two,
//扇形
Three,
//环形
Round
}
public class shoot : MonoBehaviour
{
private E_FireType nowType = E_FireType.One;
public GameObject bullet;
public int roundNum = 4;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Alpha1))
{
nowType = E_FireType.One;
}
else if (Input.GetKeyDown(KeyCode.Alpha2))
{
nowType = E_FireType.Two;
}
else if (Input.GetKeyDown(KeyCode.Alpha3))
{
nowType = E_FireType.Three;
}
else if (Input.GetKeyDown(KeyCode.Alpha4))
{
nowType = E_FireType.Round;
}
if (Input.GetKeyDown(KeyCode.Space))
{
Fire();
}
}
private void Fire()
{
switch (nowType)
{
case E_FireType.One:
Instantiate(bullet, this.transform.position, this.transform.rotation);
break;
case E_FireType.Two:
Instantiate(bullet, this.transform.position - this.transform.right * 0.5f, this.transform.rotation);
Instantiate(bullet, this.transform.position + this.transform.right * 0.5f, this.transform.rotation);
break;
case E_FireType.Three:
//朝自己的面朝向发射
Instantiate(bullet, this.transform.position, this.transform.rotation);
//朝自己左侧旋转20度再发射------知识点 四元数*四元数=一个新的四元数 相当于是旋转量的叠加
Instantiate(bullet, this.transform.position, this.transform.rotation * Quaternion.AngleAxis(-20, Vector3.up));
//朝自己左侧旋转20度再发射------知识点 四元数*四元数=一个新的四元数 相当于是旋转量的叠加
Instantiate(bullet, this.transform.position, this.transform.rotation * Quaternion.AngleAxis(20, Vector3.up));
break;
case E_FireType.Round:
float angle = 360 / roundNum;
for (int i = 0; i < roundNum; i++)
Instantiate(bullet, this.transform.position, this.transform.rotation * Quaternion.AngleAxis(i * angle, Vector3.up));
break;
}
}
}
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class bullet : MonoBehaviour
{
public float moveSpeed = 10;
void Start()
{
Destroy(this.gameObject, 5);
}
void Update()
{
this.transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime);
}
}