Unity3D 四元数插值的实现

Posted 暗光之痕

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity3D 四元数插值的实现相关的知识,希望对你有一定的参考价值。

环境:Unity2018.3 语言:C#

 

总起:

本文主要参考《3D数学基础:图形与游戏开发》。

 

理论:

幂:

首先我们来看q^1/2,这是说明我们只需要q的一半位移,旋转是由θ来表示的,所以需要将w转换为θ(arccos(θ))后就能对其进行幂运算了。

 

平滑插值Slerp:

从q1到q2之间的差值为dq=inv(q1)q2,那么如果只需要t份dq,则表示为dq^t=(inv(q1)q2)^t。

 

最终的公式为:slerp(q0, q1, t) = q0 (inv(q1)q2)^t。

 

因为四元数可以说成是按照某个轴旋转一个角度,即[cos(θ/2) sin(θ/2)n],角度为θ,需要对其进行平滑插值,就是对θ进行插值即可。

 

实现:

这边我用Unity中的Quaternion类来减少一些类似叉乘的方法,专注于Slerp的实现。

 

首先我们来看一下Unity直接使用Quaternion进行插值的效果。

public class TestSlerp : MonoBehaviour

    public Vector3 TargetRot;
    public Quaternion targetRot;
    private Quaternion startRot;
    private float dTime = 0f;

    void OnEnable()
    
        dTime = 0f;
        startRot = transform.rotation;
        targetRot = Quaternion.Euler(TargetRot);
    

    void Update()
    
        dTime += Time.deltaTime;
        if (dTime > 1f)
        
            dTime = 1f;
            enabled = false;
        
        // 进行插值
        transform.rotation = Quaternion.Slerp(startRot, targetRot, dTime);
    

 

 

跟Mathf.Lerp类似的使用。

 

好,现在我们根据上面的公式来实现一下Slerp:

public class TestSlerp2 : MonoBehaviour



    public Vector3 TargetRot;

    public Quaternion targetRot;

    private Quaternion startRot;

    private float dTime = 0f;



    void OnEnable()

    

        dTime = 0f;

        startRot = transform.rotation;

        targetRot = Quaternion.Euler(TargetRot);

    



    void Update()

    

        dTime += Time.deltaTime;

        if (dTime > 1f)

        

            dTime = 1f;

            enabled = false;

        

        // 进行插值

        transform.rotation = MySlerp(startRot, targetRot, dTime);

    



    Quaternion MySlerp(Quaternion a, Quaternion b, float t)

    

        var q = (Quaternion.Inverse(a) * b);

        var result = a * Power(q, t);

        return result;

    



    Quaternion Power(Quaternion q, float t)

    

        float x = q.x;

        float y = q.y;

        float z = q.z;

        float w = q.w;



        // 防止除零

        if (Mathf.Abs(w) < 0.9999f)

        

            // 提取半角alpha = theta/2

            float alpha = Mathf.Acos(w);

            // 计算新的alpha

            float newAlpha = alpha * t;

            // 转换成w

            w = Mathf.Cos(newAlpha);

            // 消去sin(alpha)

            float mult = Mathf.Sin(newAlpha) / Mathf.Sin(alpha);

            x *= mult;

            y *= mult;

            z *= mult;

        



        return new Quaternion(x, y, z, w);

    

 

以上是我直接照搬公式实现的插值方法,实际上书中的方法针对性得优化了一些性能:
 

public Quaternion SlerpBook(Quaternion a, Quaternion b, float t)



    // 使用点乘计算四元数的夹角

    float cosOmega = Quaternion.Dot(a, b);

    // 如果点乘为负,则反转一个四元数取得最短的弧

    if (cosOmega < 0f)

    

        a.x = -a.x;

        a.y = -a.y;

        a.z = -a.z;

        a.w = -a.w;

    



    float k0, k1;

    if (cosOmega > 0.9999f)

    

        // 非常接近 进行线性插值

        k0 = 1.0f - t;

        k1 = t;

    

    else

    

        //使用sin^2(z)+cos^2(z)=1计算sin值

        float sinOmega = Mathf.Sqrt(1.0f - cosOmega * cosOmega);

        // 通过sin和cos计算角度

        float omega = Mathf.Atan2(sinOmega, cosOmega);

        // 计算分母的倒数

        float oneOverSinOmega = 1.0f / sinOmega;

        // 计算插值变量

        k0 = Mathf.Sin((1.0f - t) * omega) * oneOverSinOmega;

        k1 = Mathf.Sin(t * omega) * oneOverSinOmega;

    



    // 最后插值

    return new Quaternion(

        a.x * k0 + b.x * k1,

        a.y * k0 + b.y * k1,

        a.z * k0 + b.z * k1,

        a.w * k0 + b.w * k1);



 

今天研究了一天的四元数,肯定说不上完全理解吧,但是用法和特性都有了大体上的掌握。感触最深的是,其实很多东西以前学过,但是一直不用就完全忘记了,数学也是一门语言,需要时常锻炼对其的感觉。

以上是关于Unity3D 四元数插值的实现的主要内容,如果未能解决你的问题,请参考以下文章

欧拉角与四元数互转,及四元数slerp球面线性插值算法

SLAM练习题(十三)—— 四元数插值

Unity3D 四元数的常用方法

Unity3D_(API)Quaternion四元数中的Quaternion.LookRotation()

python中的四元数运算

四元数quaternion