由于四元数是相对于其他类来说是比较难以理解,所以总结该类花了不少时间,例如的四元数和欧拉角转换,四元数的旋转,欧拉角的旋转,其底部实现及其复杂,一堆数学公式,要求对3D数学或者线性代数有基础的同学可以更能理解一点,由于看不到底部的源码只能通过一些描述以及案例或者数学公式进行推导,所以该类我会给出代码,你可以根据如下案例自己尝试去动一动手,或许能帮助你更好的理解!
一. Properties 属性
Quaternion.identity:将四元数所对应的rotation归零,坐标为(0,0,0,1) ,分别代表(x,y,z,w)
Quaternion.eulerAngles:此属性是用来返回或设置Quaternion实例对应的欧拉角,对游戏物体对象的旋转角度进行赋值的方式通常有两种、
1.将四元数赋值给transform的rotation
2.将三维向量代表的欧拉角,直接赋值给transform的eularangle
//第一种,将四元数赋值给transform的rotation
rotations.eulerAngles = new Vector3(0.0f, speed * Time.time, 0.0f);
A.rotation = rotations;
//第二种,将三维向量代表的欧拉角,直接赋值给transform的eularangle
eulurAngle = new Vector3(0.0f, speed * Time.deltaTime);
B.eulerAngles = eulurAngle;
二.Public Methods 公共方法
SetFormRotation(Vector3 formDirection,Vector3 toDirection):创建一个由formDirection到toDirection方向的旋转
下面是这个我对SetFormRotation这个公共方法的测试,做了个小案例,创建一个由formDirection到toDirection方向的旋转
通过A物体旋转到B物体之间夹角所产生的旋转,赋值给C物体的Rotation,说白了就是由A,B两个物体之间产生的夹角的旋转赋值给了C物体,
所有呢,当在场景中移动B物体后,C物体会发生旋转,通过下面我用的Debug函数画的四条线可以看的比较清楚。
public class QuaternionSetFormRotation : MonoBehaviour{
public Transform A, B, C;
Quaternion q1 = Quaternion.identity;
void Update()
{
q1.SetFromToRotation(A.position, B.position);
Debug.Log("q1:"+q1);
C.rotation = q1;
Debug.DrawLine(Vector2.zero, A.position, Color.red);
Debug.DrawLine(Vector3.zero, B.position, Color.green);
Debug.DrawLine(C.position, C.position + new Vector3(0.0f, 10.0f, 0.0f), Color.blue);
Debug.DrawLine(C.position, C.TransformPoint(Vector3.up * 15f), Color.yellow);
}
}
SetLookRotation(Vector3 view, Vector3 up = Vector3.up): 对一个Quaternion的实例的朝向进行设置,View为要看的方向,Up为向上的向量
下面创建了A,B,C三个物体,定义了一个四元数的变量,存储为identity,将这个四元数的实例调用SetLookRotation方法,将A,B两个物体传入,将得以四元数存储的rotation,得到C物体的transform.forward方向与A物体的方向一致,通过Vector3.Angle得到C物体的transform.right与A,B,Vector3.Zero构成的平面垂直,夹角都为90度,而且通过移动B物体可以发现,B物体够决定C物体transform.up的朝向,而移动A物体就能决定C物体朝向A物体,代码如下
public class QuaternionSetLookRotation : MonoBehaviour{
public Transform A, B, C;
Quaternion q1 = Quaternion.identity;
void Update()
{
q1.SetLookRotation(A.position, B.position);
B.rotation = q1;
//分别绘制A,B,C.right的朝向线
Debug.DrawLine(Vector3.zero, A.position, Color.red);
Debug.DrawLine(Vector3.zero, B.position, Color.green);
Debug.DrawLine(C.position,C.TransformPoint(Vector3.right*2.5f),Color.yellow);
Debug.DrawLine(C.position, C.TransformPoint(Vector3.forward * 2.5f), Color.gray);
//分别打印C.right与A,B的交角
Debug.Log("C.right与的A的夹角:" + Vector3.Angle(C.right, A.position));
Debug.Log("C.right与B的夹角:" + Vector3.Angle(C.right, B.position));
//C.up与B的夹角
Debug.Log("C.up与B的夹角:" + Vector3.Angle(C.up, B.position));
}
}
void ToAngleAxis(out float angle, out Vector3 axis): 将用于Quaternion实例转换为角轴表示
如果是transform.rotaton.ToAngleAxis(out angle, out axis)中,输出值angle和axis的含义为:将当前游戏物体
的rotation和从初始值Identity变换到当前状态,就是将游戏物体对象绕着axis轴向,(这个轴指世界坐标系),旋转
angle角度,然后将角度和轴向用out关键字传出。代码如下
public class QuaternionToAngleAxis : MonoBehaviour{
public Transform A, B;
private float angle; //初始化角度
Vector3 axis = Vector3.zero; //初始化轴向
void Update()
{
//使用ToAngleAxis返回当前游戏物体的旋转轴和角度
A.rotation.ToAngleAxis(out angle, out axis);
//使用AngleAxis将传出的角度和轴向设置给B物体,使得A,B物体状态相同
B.rotation = Quaternion.AngleAxis(angle, axis);
}
}
三. Static Methods 静态方法
public static float Angle(Quaternion a, Quaternion b):用于返回从参数a到参数b变换产生的夹角,当然这个夹角不是某个局部坐标系的夹角,而是游戏对象从状态a转换到状态b所需要旋转的最小角度。
下面这个案例是通过 q1.eulerAngles = new Vector3(10, 20, 30)设置q1的欧拉角,然后通过Quaternion.Angle(q1, q2)得到q1到q2的最小角度,然后通过ToAngleAxis(out f2, out v1),传出q1的轴向以及q1的角度,打印后可以发现角度一致,所以返回的float类型的参数为两个四元数之间转换的最小夹角。
public class QuaternionAngle : MonoBehaviour{
void Start() {
Quaternion q1 = Quaternion.identity;
Quaternion q2 = Quaternion.identity;
q1.eulerAngles = new Vector3(10, 20, 30);
float f1 = Quaternion.Angle(q1, q2);
float f2 = 0.0f;
Vector3 v1 = Vector3.zero;
q1.ToAngleAxis(out f2, out v1);
Debug.Log("f1 = " + f1);
Debug.Log("f2 = " + f2);
Debug.Log("q1的欧拉角:" + q1.eulerAngles +" q1的rotation:" + q1);
Debug.Log("q2的欧拉角:" + q1.eulerAngles + " q2的rotation:" + q1);
Debug.Log("v1:" + v1);
}
public static float Dot(Quaternion a, Quaternion b):该方法用于计算参数a和参数b之间的点乘
假设q1(x1,x1,z01,w1),q2(x2,y2,z2,w2),则folat f = Quaternin.Dot(q1,q2),
数学公式为: f = x1*x2 + y1*y2 + z1*z2 + w1*w2; f 的范围为 [-1,1]
当f = 1时,他们的rotation和欧拉角都相等的
当f = -1时,说明其中一个rotation比另外一个多旋转了360度,代码如下:
public class QuaternionDot : MonoBehaviour
{
public Transform A, B;
Quaternion q1 = Quaternion.identity;
Quaternion q2 = Quaternion.identity;
private float f;
void Start()
{
A.eulerAngles = new Vector3(0, 40, 0);
B.eulerAngles = new Vector3(0, 360 + 40, 0);
q1 = A.rotation;
q2 = B.rotation;
f = Quaternion.Dot(q1, q2);
Debug.Log("q1的rotation:" + q1);
Debug.Log("q2的rotation:" + q2);
Debug.Log("q1的欧拉角:" + q1.eulerAngles);
Debug.Log("q2的欧拉角:" + q2.eulerAngles);
Debug.Log("q1,q2点乘的结果:" + f);
}
public static Quaternion Euler(float x, float y, float z)
public static Quaternion Euler(Vector3 euler) :方法用于返回欧拉角Vector3(x,y,z)对应的四元数Quaternion实例
四元数Quaternion(qx,qy,qz,qw)与欧拉角eulerAngles(ex,ey,ez)之间的数学关系如下:
ex = ex * PIover180 /2.0f
ey = ey * PIover180 /2.0f
ez = ez * PIover180 /2.0f
qx = sin(ex)cos(ey)cos(ez) + cos(ex)sin(ey)sin(ez)
qy = cos(ex)sin(ey)cos(ez) - sin(ex)cos(ey)sin(ez)
qz = cos(ex)cos(ey)sin(ez) - sin(ex)sin(ey)cos(ez)
qw = cos(ex)cos(ey)cos(ez) + sin(ex)sin(ey)sin(ez)
根据以上该公式,在Unity中测试代码如下:
public class QuaternionEuler : MonoBehaviour
{
public float ex, ey, ez;
private float qx, qy, qz, qw;
private float PIover180 = 0.0174532925f;
Quaternion Q = Quaternion.identity;
private void OnGUI()
{
if (GUI.Button(new Rect(10.0f, 10.0f, 300.0f, 45.0f), "计算欧拉角与四元数的旋转"))
{
Debug.Log(string.Format("欧拉角:,ex:{0},ey:{1},ez:{2}", ex, ey, ez));
Q = Quaternion.Euler(ex, ey, ez);
Debug.Log(string.Format("四元数:,Qx:{0},Qy:{1},Qz:{2},Qw:{3}",Q.x,Q.y,Q.z,Q.w));
//测试算法
ex = ex * PIover180 / 2.0f;
ey = ey * PIover180 / 2.0f;
ez = ez * PIover180 / 2.0f;
qx = Mathf.Sin(ex) * Mathf.Cos(ey) * Mathf.Cos(ez) + Mathf.Cos(ex) * Mathf.Sin(ey) * Mathf.Sin(ez);
qy = Mathf.Cos(ex) * Mathf.Sin(ey) * Mathf.Cos(ez) - Mathf.Sin(ex) * Mathf.Cos(ey) * Mathf.Sin(ez);
qz = Mathf.Cos(ex) * Mathf.Cos(ey) * Mathf.Sin(ez) - Mathf.Sin(ex) * Mathf.Sin(ey) * Mathf.Cos(ez);
qw = Mathf.Cos(ex) * Mathf.Cos(ey) * Mathf.Cos(ez) + Mathf.Sin(ex) * Mathf.Sin(ey) * Mathf.Sin(ez);
Debug.Log(string.Format("四元数:,qx:{0},qy:{1},qz:{2},qw:{3}", qx, qy, qz, qw));
}
}
}
public static Quaternion FromToRotation(Vector3 fromDirection, Vector3 toDirection) :
该方法用于创建一个参数formDirection到toDirectiond的Quaternion的变换,其功能实例方法SetFromToRotation相同,
只是用法有点区别。以下创建了A,B,C,D四个游戏物体,用SetFromToRotation将A物体和B物体夹角所产生的旋转赋值给了C.rotation
又用FromToRotation将A物体和B物体产生的夹角产生的旋转赋值给了D.rotation,结果运行指之后呢,移动C,D物体对A,B物体的旋转所受的影响是一致的,代码如下:
public class QuaternionFromToRotation : MonoBehaviour
{
public Transform A, B, C, D;
Quaternion q1 = Quaternion.identity;
void Update()
{
q1.SetFromToRotation(A.position, B.position);
C.rotation = q1;
D.rotation = Quaternion.FromToRotation(A.position, B.position);
Debug.DrawLine(Vector3.zero, A.position,Color.white);
Debug.DrawLine(Vector3.zero, B.position, Color.red);
Debug.DrawLine(C.position, C.position + new Vector3(0.0f, 10.0f, 0.0f), Color.blue);
Debug.DrawLine(C.position, C.TransformPoint(Vector3.up * 10.0f), Color.cyan);
Debug.DrawLine(D.position, D.position + new Vector3(0.0f, 10.0f, 0.0f), Color.blue);
Debug.DrawLine(D.position, D.TransformPoint(Vector3.up * 10.0f), Color.cyan);
}
}
public static Quaternion Inverse(Quaternion rotation): 该方法用于返回参数rotation的逆向Quaterniond的值.
假设实例rotatin = (x,y,z,w),则Inverse(rotation) = (-x,-y,-z,w),
假设一个物体的rotation.eulerAngles = (a,b,c),则transform.rotation = Inverse(rotation)
那么将相当于transform依次绕自身坐标系的z轴,x轴,y轴分别旋转-c度,-a度,-b度。
由于是局部坐标系内的转换,所以呢transform的euleAngles所对应的各个分量并不一定等于-a,-b, -c,
代码如下:
public class QuaternionInverse : MonoBehaviour
{
public Transform A, B;
void Start()
{
Quaternion q1 = Quaternion.identity;
Quaternion q2 = Quaternion.identity;
q1.eulerAngles = new Vector3(10, 20, 30);
q2 = Quaternion.Inverse(q1);
A.rotation = q1;
B.rotation = q2;
Debug.Log(string.Format("q1的欧拉角:{0},q1的rotation:{1}", q1.eulerAngles, q1));
Debug.Log(string.Format("q2的欧拉角:{0},q2的rotation:{1}", q2.eulerAngles, q2));
}
public static Quaternion Lerp(Quaternion a, Quaternion b, float t):此方法用于返回从参数a到参数b之间的线性插值
参数t的范围为[0,1],当参数t<=0时 ,返回为a,当参数t>=1时,返回为b,该方法比Slearp快一点
public class QuaternionLerp : MonoBehaviour
{
public Transform A, B, C, D;
private float speed = 0.2f;
void Update()
{
C.rotation = Quaternion.Slerp(A.rotation, B.rotation, Time.time * speed);
D.rotation = Quaternion.Lerp(A.rotation, B.rotation, Time.time * speed);
}
}
public static Quaternion LookRotation(Vector3 forward, Vector3 upwards):使用指定的forward和upwards方向创建旋转,用于返回一个Quaternion的实例
使得游戏物体对象的Z轴, 朝向参数forward方向,与SetLookRotation功能一致,用法稍有不同
该场景中的 C, D 物体的Z轴方向与世界坐标原点指向A物体的向量 (称为向量A) 保存一致
所以是C,D物体的z轴始终朝向A向向量,
而C,D物体的Y轴方向也和界坐标原点指向B物体的向量 ( 称为向量B ) 尽量保持一致,但是只是比较接近,并发完全一致
public class QuaternionLookRotation : MonoBehaviour
{
public Transform A, B, C, D;
Quaternion q1 = Quaternion.identity;
void Update()
{
q1.SetLookRotation(A.position, B.position);
C.rotation = q1;
D.rotation = Quaternion.LookRotation(A.position, B.position);
Debug.DrawLine(Vector3.zero, A.position, Color.white);
Debug.DrawLine(Vector3.zero, B.position, Color.white);
Debug.DrawLine(C.position, C.TransformPoint(Vector3.up * 10), Color.yellow);
Debug.DrawLine(C.position, C.TransformPoint(Vector3.forward * 10), Color.red);
Debug.DrawLine(D.position, D.TransformPoint(Vector3.up * 10), Color.yellow);
Debug.DrawLine(D.position, D.TransformPoint(Vector3.forward * 10), Color.red);
}
}
public static Quaternion RotateTowards(Quaternion from, Quaternion to, float maxDegreesDelta)
该方法用于返回从参数from到to的插值,且返回值的最大角度不超过maxDegreesDelta
其中参数from为起始的Quaternion,参数to为结束的Quaternion,参数maxDegreesDelta为每帧最大的角度
当maxDegreesDelta<0时,将沿着to插值到from,代码如下
public class QuaternionRotateTowards : MonoBehaviour
{
public Transform A, B, C;
private float speed = 10.0f;
void Update()
{
C.rotation = Quaternion.RotateTowards(A.rotation, B.rotation, Time.time * speed );
Debug.Log("C的欧拉角:" + C.eulerAngles);
Debug.Log("A的欧拉角:" + A.eulerAngles);
Debug.Log(string.Format("C与A的欧拉角的差值:{0},maxDegreeDelta:{1}", (C.eulerAngles - A.eulerAngles), (Time.time*speed-40.0f)));
}
}
public static Quaternion Slerp(Quaternion a, Quaternion b, float t) : 该方法用于返回参数a到参数b的球面插值
当参数t<=0时,返回值为a,当参数t>=1时返回值为b,一般情况下不使用球面插值,使用线性插值会更好
public class QuaternionSlerp : MonoBehaviour
{
public Transform A, B, C, D;
private float speed = 0.2f;
void Update()
{
C.rotation = Quaternion.Slerp(A.rotation, B.rotation, Time.time * speed);
D.rotation = Quaternion.Lerp(A.rotation, B.rotation, Time.time * speed);
}
}
四. Quaternion operator 运算符
public static Quaternion operator *(Quaternion lhs, Quaternion rhs):该运算符用于计算两个四元数实例相乘的结果
假设有物体A,B,那么使用该运算符就是A.rotation*= B.rotaion,不过需要注意的是
因为rhs相对于lhs旋转产生的参考系,所以lhs * rhs和rhs * lhs是不相同的
代码在Updata中每执行一次,B都会绕着B的局部坐标系的X,Y,Z,分别旋转A.eulerAngles.x度,A.eulerAngles.y度, A.eulerAngles.z,度
欧拉角的旋转顺序为先是z轴,然后x轴,其次y轴,所以呢当其中一个轴旋转的时候,必定会影响到其他两个轴方向的欧拉角度
除外其他轴都为0,所以当执行下面代码的时候,B物体不断的旋转,当打印出B物体的欧拉角时
虽然每次都是乘A物体欧拉角进行旋转,但是其他两个轴的旋转不为0,所以每次打印的欧拉角各分量的值不固定
public class QuaternionOperator : MonoBehaviour
{
public Transform A, B;
void Start()
{
A.eulerAngles = new Vector3(1.0f, 1.5f, 2.0f);
}
void Update()
{
B.rotation *= A.rotation;
Debug.Log("输出B的欧拉角:" + B.eulerAngles);
}
}
public static Vector3 operator *(Quaternion rotation, Vector3 point):该运算符的作用是对参数坐标点point进行rotation变换
假设A为一个Vector3向量
则transform.position += transform.rotation * A
每次执行一次,transform对应的对象会沿着自身坐标系的向量A的方向进行移动,移动的距离为模的长度
因此transform.rotation与A向量的相乘主要是确定移动的方向和距离
public class QuaternionOperator : MonoBehaviour
{
public Transform A;
private float speed= 0.1f;
void Start()
{
A.position = Vector3.zero;
A.eulerAngles = new Vector3(0, 45, 0);
}
void Update()
{
A.position += A.rotation * (Vector3.up * speed);
Debug.Log("A物体的位置:" + A.position);
}
以上就是对四元数类属性,方法,以及公共方法的总结,如果帮助到你了,希望能给我点个赞!