Unity基础(12)-四元数与欧拉角与矩阵

Unity中表示旋转有三种方式:四元数,欧拉角,矩阵

一、四元数

1、概念

Quaternion中存放了x,y,z,w四个数据成员,可以用下标来进行访问,对应的下标分别是0,1,2,3
其实最简单来说:四元数就是表示一个3D物体的旋转,它是一种全新数学数字,甚至不是复数。
四元数其实就是表示旋转。

1、四元数的书写格式
q = w v  这里w是标量,v是向量
另一种表示格式
q = w(x,y,z) 看到这里我们就熟悉了。

一般我们在Unity中表示旋转的方式:
c.Rotate (new Vector3(10,0,0));
c.Rotate (Quaternion.identity * Vector3.left);
2、Unity中四元数API
  • 1.来把角度转化为相应四元素
Quaternion rot=Quaternion.Euler(30,60,90);
旋转顺序是,先绕Z轴转动90度,再绕X轴转动30度,最后绕Y轴转动60度
  • 2.绕指定轴旋转指定角度相应的四元素
Quaternion rot= Quaternion.AngleAxis(60, transform.forward);
意思为绕自身正方向转60度其相应的四元素,当然了,第一个参数可以是其他方向变量
  • 3.两个四元素相乘,代表按顺序进行两次旋转
Quaternion rot1=Quaternion.Euler(0,30,0);
Quaternion rot2=Quaternion.Euler(0,0,45);
Quaternion rot=rot2*rot1;
意思是先绕y旋转30度再绕z旋转45度,所以,你要先绕哪个四元素先旋转就放最右边
  • 4.四元素与向量相乘,得到的是将原向量旋转后的新向量
Vector3 to = Quaternion.AngleAxis(45, Vector3.up)* Vector3.forward    
to是将Vector3.forward绕Vector3.up旋转45度后得到的新向量
  • 5.已知两个向量,求从一个向量转到另一个向量的四元数:
Quaternion rot=Quaternion.FromToRotation(Vector3.up,Vector3.forward);
这的意思是创建一个从y轴正方向到z轴正方向的旋转角度所对应的四元数
例子:
Vector3 aimDirection=(targetTrans.position – transform.position).normalized;
Quaternion rot = Quaternion.FromToRotation(transform.up,aimDirection);
transform.rotation = rot * transform.rotation;
此游戏对象自身转向这个计算出来的偏转角所对应的四元数,如果将此至于Update中就可以做到不断对准目标
  • 6.创建一个让Z轴正方向和Y轴正方向指向规定方向的旋转
Quaternion rot = Quaternion.LookRotation(Vector3.right, Vector3.down);
这的意思是创建一个让Z轴正方向指向世界坐标x轴正向,y轴正方向竖直向下的旋转角度所对应的四元数
Vector3 vr = new Vector3(1,0,0);  
Quaternion q = Quaternion.LookRotation(vr);  

void SetLookRotation(Vector3 view);
void SetLookRotation(Vector3 view,Vector3 up);

也是根据指定的向前和向上向量创建四元数,本质计算过程和LookRotation一样,
只不过LookRotation是Quaternion上的静态函数,而SetLookRotation则是Quaternion的成员函数。
  • 7.已知两个由四元数代表的旋转角度,求出从一个角度渐变到另一个角度的增量
Quaternion rot=Quaternion.Euler(0,30,0)
Quaternion targetRot= rot*transform.rotation;
transform.rotation= Quaternion.RotateTowards(transform.rotation,targetRot,90*Time.deltaTime);
意思为游戏对象的角度会逐渐向targetRot以90*Time.deltaTime的角速度(90度每秒)逐渐逼近,因为这代码并不能够在一帧中完成所以要放于update中
  • 8.两个四元数之间的线性插值lerp
transform.rotation=Quaternion.Lerp(transform.rotation,Vector3.up,0.5f);
第一个参数是起始的角度所对应的四元数
第一个参数是终点的角度所对应的四元数
第三个参数是这个过程需要多少秒
  • 9.两个四元数之间的球形插值Slerp
transform.rotation=Quaternion.Slerp(transform.rotation,Vector3.up,0.5f);
第一个参数是起始的角度所对应的四元数
第一个参数是终点的角度所对应的四元数
第三个参数是这个过程需要多少秒
  • 10.旋转轴和旋转角度算出四元数
Quaternion RotateTowards(Quaternion from,Quaternion to, float maxDegreesDelta);
以maxDegreesDelta作为角度步长计算从from到[to]
根据旋转轴和旋转角度算出四元数
  • 11.四元数对应的三个轴向的欧拉角
Quaternion.eulerAngles
存放四元数对应的三个轴向的欧拉角,分别是绕x轴、y轴、z轴旋转的角度

Quaternion q3 = new Quaternion();  
q3.eulerAngles = new Vector3(10, 30, 20);  
Quaternion qx3 = Quaternion.AngleAxis(10,Vector3.right);          
Quaternion qy3 = Quaternion.AngleAxis(30,Vector3.up);  
Quaternion qz3 = Quaternion.AngleAxis(20,Vector3.forward);            
Quaternion qxyz3 = qz3*qy3*qx3;  
从这里可以看出unity中旋转顺序也是按先绕x轴旋转,然后y,最后z。unity中对向量应用旋转量使用的是向量右乘,即如下:
Vector3 newV = qxyz3*v=qz3*qy3*qx3*v;
四元数实例
  • eulerAngles
public class EulerAngle: MonoBehaviour
{
    // 两个物体A,B 
    public Transform A, B;
    // 默认角度 
    Quaternion rotations=Quaternion.identity;
    Vector3 eulerAngle = Vector3.zero;
    float speed = 10.0f;

    void Update()
    {
        //第一种方式:将Quaternion赋值给transform的rotation
        rotations.eulerAngles = new Vector3(0.0f, speed * Time.time, 0.0f);
        A.rotation = rotations;
        //第二种方式:将三维向量代表的欧拉角直接赋值给transform的eulerAngles
        eulerAngle = new Vector3(0.0f, speed * Time.time, 0.0f);
        B.eulerAngles = eulerAngle;
    }
}
  • SetFormToRotation

注意点:不能直接obj.rotation.SetFromToRotation(A.position,B.position);因为需要实例化才能赋值给transform.rotation.

public class SetFromToRotation : MonoBehaviour {
    public Transform A, B, C;
    Quaternion q1 = Quaternion.identity;
    
    void Update () {
        //不可直接使用C.rotation.SetFromToRotation(A.position,B.position);
        q1.SetFromToRotation(A.position,B.position);
        C.rotation = q1;

        Debug.DrawLine(Vector3.zero,A.position,Color.red);
        Debug.DrawLine(Vector3.zero, B.position, Color.green);
        Debug.DrawLine(C.position, C.position+new Vector3(0.0f,1.0f,0.0f), Color.black);
        Debug.DrawLine(C.position, C.TransformPoint(Vector3.up*1.5f), Color.yellow);
    }
}
  • SetLookRotation : 设置Quaternion实例的朝向。此方法的功能是对一个Quaternion的实例的朝向进行设置。
public class LookRotation_ts : MonoBehaviour
{
    public Transform A, B, C, D;
    Quaternion q1 = Quaternion.identity;

    void Update()
    {
        //使用实例方法
        //不可直接使用C.rotation.SetLookRotation(A.position,B.position);
        q1.SetLookRotation(A.position, B.position);
        C.rotation = q1;
        //使用类方法
        D.rotation = Quaternion.LookRotation(A.position, B.position);
        //绘制直线,请在Scene视图中查看
        Debug.DrawLine(Vector3.zero, A.position, Color.white);
        Debug.DrawLine(Vector3.zero, B.position, Color.white);
        Debug.DrawLine(C.position, C.TransformPoint(Vector3.up * 2.5f), Color.white);
        Debug.DrawLine(C.position, C.TransformPoint(Vector3.forward * 2.5f), Color.white);
        Debug.DrawLine(D.position, D.TransformPoint(Vector3.up * 2.5f), Color.white);
        Debug.DrawLine(D.position, D.TransformPoint(Vector3.forward * 2.5f), Color.white);
    }
}
  • ToAngleAxis :Quaternion实例的角轴表示。其中angle为旋转角,axis为轴向量。
public class ToAngleAxis : MonoBehaviour
{
    public Transform A, B;
    float angle;
    Vector3 axis = Vector3.zero;

    void Update()
    {
        //使用ToAngleAxis获取A的Rotation的旋转轴和角度
        A.rotation.ToAngleAxis(out angle, out axis);
        //使用AngleAxis设置B的rotation,使得B的rotation状态的和A相同
        //可以在程序运行时修改A的rotation查看B的状态
        B.rotation = Quaternion.AngleAxis(angle, axis);
    }
}
  • Angle方法: Quaternion实例间夹角

注意:返回的是GameObject对象从后面状态(a)转换到(b)时需要旋转的最小夹角。

public class Angle : MonoBehaviour {
    void Start()
    {
        Quaternion q1 = Quaternion.identity;
        Quaternion q2 = Quaternion.identity;
        q1.eulerAngles = new Vector3(10.0f, 20.0f, 30.0f);
        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的欧拉角:" + q2.eulerAngles + " q2的rotation:" + q2);
    }
}

  • Dot方法:点乘

用来求参数a和b的点乘。q1(x1,y1,z1,w1),q2(x2,y2,z2,w2)为两个实例则float f = Quaternion.Dot(q1,q2); 等价于f = x1x2 + y1y2 + z1z2 + w1w2.
当f = ±1 时,q1和q2对应的欧拉角是相等的,在Game视图中看他们旋转状态是一样的。
当f=1时,它们的rotation相等;
当f=-1时,说明其中一个rotation比另一个多旋转了360°

public class Dot : MonoBehaviour {
    public Transform A, B;
    Quaternion q1=Quaternion.identity;
    Quaternion q2=Quaternion.identity;
    float f;

    void Start () {
        A.eulerAngles = new Vector3(0.0f,40.0f,0.0f);
        //B比A绕Y轴多转360度
        B.eulerAngles = new Vector3(0.0f, 360.0f+40.0f, 0.0f);
        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("Dot(q1,q2):"+f);
    }
}

  • Euler方法:欧拉角对应的四元数


    Euler
public class Euler : MonoBehaviour
{
    //记录欧拉角,单位为角度,可以在Inspector面板中设置
    public float ex, ey, ez;
    //用于记录计算结果
    float qx, qy, qz, qw;
    float PIover180 = 0.0174532925f;//常量
    Quaternion Q = Quaternion.identity;
    void OnGUI()
    {
        if (GUI.Button(new Rect(10.0f, 10.0f, 100.0f, 45.0f), "计算"))
        {
            Debug.Log("欧拉角:" + " ex:" + ex + "  ey:" + ey + "  ez:" + ez);
            //调用方法计算
            Q = Quaternion.Euler(ex, ey, ez);
            Debug.Log("Q.x:" + Q.x + " Q.y:" + Q.y + " Q.z:" + Q.z + " Q.w:" + 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(" qx:" + qx + " qy:" + qy + " qz:" + qz + " qw:" + qw);
        }
    }
}

-FromToRotation方法:Quaternion变换

public class FromToRotation : MonoBehaviour
{
    public Transform A, B, C, D;
    Quaternion q1 = Quaternion.identity;

    void Update()
    {
        //使用实例方法
        //不可直接使用C.rotation.SetFromToRotation(A.position,B.position);
        q1.SetFromToRotation(A.position, B.position);
        C.rotation = q1;
        //使用类方法
        D.rotation = Quaternion.FromToRotation(A.position, B.position);
        //在Scene视图中绘制直线
        Debug.DrawLine(Vector3.zero, A.position, Color.white);
        Debug.DrawLine(Vector3.zero, B.position, Color.white);
        Debug.DrawLine(C.position, C.position + new Vector3(0.0f, 1.0f, 0.0f), Color.white);
        Debug.DrawLine(C.position, C.TransformPoint(Vector3.up * 1.5f), Color.white);
        Debug.DrawLine(D.position, D.position + new Vector3(0.0f, 1.0f, 0.0f), Color.white);
        Debug.DrawLine(D.position, D.TransformPoint(Vector3.up * 1.5f), Color.white);
    }
}
  • Lerp方法:线性插值
  • Slerp方法:球面插值

当参数t≤0时返回值为from,当参数t≥1时返回值为to。一般情况下两种方法使用lerp代替

public class Slerp : MonoBehaviour
{
    public Transform A, B, C, D;
    float speed = 0.2f;
    //分别演示方法Slerp和Lerp的使用
    void Update()
    {
        C.rotation = Quaternion.Slerp(A.rotation, B.rotation, Time.time * speed);
        D.rotation = Quaternion.Lerp(A.rotation, B.rotation, Time.time * speed);
    }
}
  • RotateTowards 方法 : Quaternion插值

from为起始Quaternion,参数to为结束Quaternion,参数maxDegreesDelta为每帧最大角度值。方法用于返回从参数from到to的插值,且返回值的最大角度不超过maxDegreesDelta.当maxDegreesDelta < 0 ,将沿着to到from的方向插值计算。

public class RotateTowards : MonoBehaviour {
    public Transform A, B, C;
    float speed = 10.0f;

    void Update()
    {
        C.rotation = Quaternion.RotateTowards(A.rotation, B.rotation, Time.time * speed-40.0f);
        Debug.Log("C与A的欧拉角的差值:" + (C.eulerAngles-A.eulerAngles) + " maxDegreesDelta:" + (Time.time * speed - 40.0f));
    }
}

二、欧拉角

1、概念

1-描述定点转动刚体的位形需要三个独立坐标变量,即x,y,z
2-描述定轴转动的刚体的位形只需要一个独立坐标变量即转角。
3-将定点转动的过程分解为三个相互独立的定轴转动,相应的二三个相互独立的转角,即欧拉角。

2、什么是欧拉角?

Roll 滚动角 Z轴不动

Pitch 俯仰角 X轴不动

Heading(Yaw) 偏航角 : 就是Y轴不动

Paste_Image.png

那么使用欧拉角表示旋转矩阵
绕Z轴旋转


这里的角度是偏航角

绕Y轴旋转


这里的角度是俯仰角

绕X轴旋转


这里的角度是翻滚角
Paste_Image.png
最后乘出来

所以说:

3、局限性

欧拉角会产生万向锁bug
一旦选择pitch角为±90°,就被限制在只能绕垂直轴旋转

三、矩阵

1、概念

矩阵通常用在一些特殊的地方,例如摄像机的非标准投影。unity中仅仅提供了一个4×4的矩阵类Matrix4x4,它可以包含位移T、旋转R和伸缩信息。矩阵中的元素都对应一个mxy的公有成员变量,因而要访问单个元素的话可以直接访问其成员。同时也提供了下标访问,如下

public float this [int row, int column]  
{  
    get  
    {  
       return this [row + column * 4];  
    }  
    set  
    {  
      this [row + column * 4] = value;  
    }  
}  
public float this [int index]  
  • MultiplyPoint方法:投影矩阵变换


    MultiplyPoint

  • MultiplyVector方法:矩阵变换


    矩阵变换

  • SetTRS方法:重设Matrix4x4变换矩阵
    使用Matrix4x4的成员函数SetTRS
    void SetTRS(Vector3 pos,Quaternion q,Vector3 s);
Quaternion q = Quaternion.LookRotation(new Vector3(0,0.5,1));  
Matrix4x4 rot = new Matrix4x4();  
rot.SetTRS(new Vector3(0,0,0),q,new Vector3(1,1,1)); 
  • Ortho方法:创建正交投影矩阵


    Ortho方法


  • perspective方法:创建投影矩阵


    image.png
  • TRS方法:返回Matrix4x4实例


  • 旋转矩阵到四元数
    使用Quaternion类的LookRotation函数
    static Quaternion LookRotation(Vector3 forward,Vector3 upwards);

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

推荐阅读更多精彩内容