Unity Quaternion四元数 常用API解析 和 旋转插值动画实现

一、Quaternion常用API

Quaternion.FromToRotation(Vector3 fromDirection, Vector3 toDirection)

官方解析:Creates a rotation which rotates from fromDirection to toDirection
中文意思:创建一个从form方向转到to目标方向所需要的旋转
设想一个这样的场景,我们用Quaternion.FromToRotation()这个API,把下图主物体(m_MainTran) 的z轴(蓝色轴) 朝向目标物体(m_TargetTran)应该怎么办呢。
在这里插入图片描述
代码

            //求出目标朝向
            Vector3 targetDir = m_TargetTran.position - m_MainTran.position;
            
            //求出m_MainTran.forward旋转到目标方向需要的旋转四元数
            Quaternion  q = Quaternion.FromToRotation(m_MainTran.forward, targetDir);
            
            //把求出来的旋转四元数应用到 forward 方向
            Vector3 finalDir = q * m_MainTran.forward; 
            
            //最后让主目标的 forward 朝向计算出来的方向
            m_MainTran.forward = finalDir;

解析都在注释了。接下来看下效果
在这里插入图片描述
同样的,如果我们想让主目标的x轴朝向目标,即right方向。可以这样写

            //求出目标朝向
            Vector3 targetDir = m_TargetTran.position - m_MainTran.position;

            //求出m_MainTran.right旋转到目标方向需要的旋转四元数
            Quaternion q = Quaternion.FromToRotation(m_MainTran.right,targetDir);

            //把求出来的旋转四元数应用到 right 方向
            Vector3 finalDir = q * m_MainTran.right;

            //最后让主目标的 right 朝向计算出来的方向
            m_MainTran.right = finalDir;

效果图
在这里插入图片描述
这在2D游戏很有用。因为2D游戏我们一般是让物体的x或y朝向目标,而不是z轴

Quaternion.LookRotation(Vector3 forward, Vector3 upwards = Vector3.up)

  • 官方解析:Creates a rotation with the specified forward and upwards directions
  • 中文意思:创建一个旋转。使得目标的正方向(z轴)指向目标forward.
  • tip:看向目标时y轴可以在z轴上方,也可以在下方。这里用第2个参数控制,不填的话默认是Vector.up上方。想改变方向可以用Vector.down

同样的场景,我们用Quaternion.LookRotation()来让目标朝向目标方向,上代码

            //求出目标方向
            Vector3 targetDir = m_TargetTran.position - m_MainTran.position;
            
            //计算出z轴朝向目标方向需要的旋转四元数
            Quaternion rotation =  Quaternion.LookRotation(targetDir,Vector3.up);
            
            //让m_MainTran.rotation等于求出的旋转
            m_MainTran.rotation = rotation;

效果图
在这里插入图片描述
可以看到,主物体也成功的朝向了目标。这个方法求出来的旋转表示Z轴朝向目标方向需要的旋转。假如我们想让X轴朝向目标方向怎么办呢。

  • 可以在求出结果后,让自己right等于自己的forward。如以下代码
            Vector3 targetDir = m_TargetTran.position - m_MainTran.position;
            Quaternion rotation =  Quaternion.LookRotation(targetDir,Vector3.up);
            m_MainTran.rotation = rotation;
            
            //上面的代码会使z轴朝向目标。 这里在这基础上让 right 朝向 forward 就可以了
            m_MainTran.right = m_MainTran.forward;

效果图
在这里插入图片描述

二、旋转插值与动画实现

Quaternion.Lerp(Quaternion a, Quaternion b, float t)

  • 官文解析:Interpolates between a and b by t and normalizes the result afterwards. The parameter t is clamped to the range [0, 1]
  • 中文意思:在a和b两个四元数之间进行插值,插值范围t =【0~1】

我们可以用Quaternion.Lerp()来实现旋转动画,注意这里填入的参数是四元数,我们可以用上面的Quaternion.FromToRotation()或Quaternion.LookRotation()来求出我们要的参数。

接下来我们来实现,主目标的Z轴在2秒内转向目标方向

    private bool isRotating = false;
    private float curTime = 0;
    private float AllTime = 3;

    private Quaternion oldQ;
    private Quaternion targetQ;
    
    // Update is called once per frame
    void Update()
    {
    
    
        if (Input.GetKeyDown(KeyCode.A))
        {
    
    
            isRotating = true;
            curTime = 0;
            
            //当前旋转
            oldQ = m_MainTran.rotation;
            //目标旋转
            targetQ = Quaternion.LookRotation(m_TargetTran.position - m_MainTran.position);
        }

        if (isRotating)
        {
    
    
            curTime += Time.deltaTime; 
            float t = curTime / AllTime;
            t = Mathf.Clamp(t, 0, 1);
            
            //用t进行插值
            Quaternion lerpQ = Quaternion.Lerp(oldQ,targetQ,t);
            //设置到目标旋转
            m_MainTran.rotation = lerpQ;

            Debug.Log($"{
      
      GetType()} curT:{
      
      t}");
            if (t >= 1)
            {
    
    
                isRotating = false;
            }
        }
    }

效果图
请添加图片描述
有时我们需要的是其他轴,比如X轴朝向目标,那该怎么做呢。接下来我们来实现一下,上代码

    private bool isRotating = false;
    private float curTime = 0;
    private float AllTime = 3;

    private Vector3 oldDir;
    private Quaternion oldQ;
    private Quaternion targetQ;
    
    // Update is called once per frame
    void Update()
    {
    
    
        if (Input.GetKeyDown(KeyCode.A))
        {
    
    
            isRotating = true;
            curTime = 0;
            
            //记录起始点的朝向
            oldDir = m_MainTran.right;
            //起始插值参数(对于oldDir方向来说,起始不需要旋转。所以这里是Quaternion.identity)
            oldQ = Quaternion.identity;
            //目标插值参数
            targetQ = Quaternion.FromToRotation(m_MainTran.right, m_TargetTran.position - m_MainTran.position);
        }

        if (isRotating)
        {
    
    
            curTime += Time.deltaTime; 
            float t = curTime / AllTime;
            t = Mathf.Clamp(t, 0, 1);
            
            //用t进行插值
            Quaternion lerpQ = Quaternion.Lerp(oldQ,targetQ,t);
            //设置到目标旋转
            m_MainTran.right = lerpQ * oldDir;

            Debug.Log($"{
      
      GetType()} curT:{
      
      t}");
            if (t >= 1)
            {
    
    
                isRotating = false;
            }
        }
    }

效果图:
请添加图片描述注释都加在代码上了。按下A键,可以看到我们的X轴转向了目标

Quaternion.Slerp(Quaternion a, Quaternion b, float t)

这个是球型插值,动画效果可能较Quaternion.Lerp()更好一点。
具体用法和Quaternion.Lerp()一样,只需要把上面代码里的Lerp()改成Slerp()既可以看到效果。这里就不罗列出来了。大伙可以自行替换看看。

Quaternion.RotateTowards(Quaternion from,Quaternion to,float maxDegreesDelta)

  • 官方解析:Rotates a rotation from towards to
  • 中文意思:将旋转从 from 转向 to。 旋转的角度为 maxDegreesDelta ,但最后的旋转不会超过to. 所以这里是理论最大旋转角度。

可以看下源码,其实这里最后也是用Quaternion.SlerpUnclamped()来计算的。先求出总角度,然后把
maxDegreesDelta / 总角度 作为插值t

    public static Quaternion RotateTowards(
      Quaternion from,
      Quaternion to,
      float maxDegreesDelta)
    {
    
    
      float num = Quaternion.Angle(from, to);
      return (double) num == 0.0 ? to : Quaternion.SlerpUnclamped(from, to, Mathf.Min(1f, maxDegreesDelta / num));
    }

有了这个方法,我们可以指定旋转角度来转向目标。接下来我们来试试,上代码

    private bool isRotating = false;
    private float perAngel = 15;//每秒转15度
    private Quaternion targetQ;
    
    // Update is called once per frame
    void Update()
    {
    
    
        if (Input.GetKeyDown(KeyCode.A))
        {
    
    
            isRotating = true;
            curTime = 0;
            
            //目标插值参数
            targetQ = Quaternion.LookRotation(m_TargetTran.position - m_MainTran.position);
        }
        
        if (isRotating)
        {
    
    
            //计算出每帧的旋转
            Quaternion rotQ = Quaternion.RotateTowards(transform.rotation,targetQ,perAngel * Time.deltaTime);
            //设置到目标旋转
            m_MainTran.rotation = rotQ;
            
            if (m_MainTran.rotation.Equals(targetQ))
            {
    
    
                isRotating = false;
                Debug.Log($"{
      
      GetType()} exit rot");
            }
        }
    }

效果图:
请添加图片描述

Quaternion AngleAxis(float angle, Vector3 axis)

  • 官方解析:Creates a rotation which rotates angle degrees around axis

  • 中文意思:创建一个旋转。代表以axis方向作为旋转轴,旋转angle角度

  • 用这个方法,我们可以让我们的方向沿着任意轴旋转任意角度,精确的按我们想要的效果来旋转。
    比如在2D游戏中,我们一般是沿着Vector.forward来旋转;
    在3D空间中,我们应该沿着哪个方向来旋转呢?

我们可以用叉乘来求出旋转轴叉乘的数学意义:Vector c = Vector3.Cross(a,b); 求出来的向量c垂直于向量a与b形成的平面。这个向量c就是我们要的旋转轴。

接下来我们使用Quaternion.AngleAxis()来实现前面的旋转动画。上代码

    private bool isRotating = false;
    private float perAngel = 15;//每秒转15度
    private Vector3 rotAxis;
    
    // Update is called once per frame
    void Update()
    {
    
    
        if (Input.GetKeyDown(KeyCode.A))
        {
    
    
            isRotating = true;

            //求出旋转轴
            rotAxis = Vector3.Cross(m_MainTran.forward,m_TargetTran.position - m_MainTran.position);
        }
        
        if (isRotating)
        {
    
    
            //计算出每帧的旋转
            Quaternion rotQ = Quaternion.AngleAxis(perAngel * Time.deltaTime,rotAxis);
            //应用旋转
            m_MainTran.forward = rotQ * m_MainTran.forward;
            
            //如果旋转到只差2度,就旋转到目标方向,并退出旋转动画
            if (Vector3.Angle(m_MainTran.forward,m_TargetTran.position - m_MainTran.position) <= 2)
            {
    
    
                m_MainTran.forward = m_TargetTran.position - m_MainTran.position;
                isRotating = false;
                Debug.Log($"{
      
      GetType()} exit rot");
            }
        }
    }

效果图:
请添加图片描述
可以看到,主物体也成功的转向目标了。

三、结语

  • 四元数是一个比较抽象的概念,但是掌握上面的API,就可以随心所欲的转转转了~

猜你喜欢

转载自blog.csdn.net/aaa27987/article/details/123093866