Unity常用类—Quaternion类

由于四元数是相对于其他类来说是比较难以理解,所以总结该类花了不少时间,例如的四元数和欧拉角转换,四元数的旋转,欧拉角的旋转,其底部实现及其复杂,一堆数学公式,要求对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);

    }

以上就是对四元数类属性,方法,以及公共方法的总结,如果帮助到你了,希望能给我点个赞!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343