四元数的差、对数、指数、幂以及差值

概要

对于四元数的学习基本上都是参照《3D数学基础-图形与游戏开发这本书的内容的》对于这本书前面的部分还是很好理解的,但是从四元数的差这里开始,就过于抽象了,不配合实例很难去理解。
因此,这一段被我单独提取出来,在实践中进一步去理解。
差,对数,指数等是定义式,不要试图在四元数的这些操作中找到这些数学符号的本身含义,它只是被定义成这样子的,没有为什么。。

四元数的差

四元数的差表示四元数的两个四元数的角位移,比如ad=b,则d就定义为a和b的差。
d = a 1 b d=a^{-1}b

四元数的对数

这个定义有点玄乎,我们定义 α = θ / 2 \alpha = \theta/2
则四元数的定义就变成了[cosα xsinα ysinα zsinα]
我们定义其对数为
logq = log([cosα nsinα]) ≡ [0 nα]
是的,这样定义我也觉得很奇怪,但是既然有这个定义,那一定是有理由的,暂且先不管为什么要有这种操作,继续学习。

四元数的指数

指数的定义是和对数的定义严格相反的
设p = [0 nα]
exp p = exp [0,nα] ≡ [cosα nsinα]

四元数求幂

设四元数为q,则有四元数的幂运算 q t q^t
不同于上面的运算看似毫无意义,这个运算的定义是有几何意义的, q t q^t 的含义是绕轴n旋转t* θ \theta 个角度
更通俗一点,比如说q是绕某轴旋转w°,则 q 2 q^2 是旋转2w°, q 1 3 q^{\frac{1}{3}} 是旋转三分之一w, q 1 q^{-1} 是旋转-w。
根据定义我们也很容易写求幂的函数,也就是通过arccosw获得 θ / 2 \theta/2 ,然后把得到的角度乘上t,再返回代入即可。

四元数差值 slerp

slerp是球面线性差值的缩写,它可以在两个四元数之间进行平滑差值,而欧拉角并不能这样,可以差值也是我们选择四元数的一个理由。
四元数的差值可以用到前面提到的差和求幂的操作,总结下来步骤如下:
1、计算两个值的差 Δ q = q 0 1 q 1 \Delta q = q_0^{-1}q_1
2、对差进行差值,也就是用之前的求幂操作 Δ q t \Delta q^t
3、开始值加上差的一部分
所以公式为 q 0 ( q 0 1 q 1 ) t q_0(q_0^{-1}q_1)^t
当然这是理论上的公式,实际上,我们不这样算,我们通过一种更有效的方法来计算。
在这里插入图片描述
我们从二维的弧长差值来描述这种思想,如上图所示,我们需要在v0和v1之间进行差值,w为v0到v1之间的夹角,vt为v0和v1之间的一个差值,有v0和vt之间的夹角为tw,存在一组线性组合,使得
v t = k 0 v 0 + k 1 v 1 v_t=k_0v_0+k_1v_1
对于k1v1为斜边的直角三角形来说,有
s i n w = s i n t w k 1 sinw = \frac{sintw}{k_1}
因此有:
k 1 = s i n t w s i n w k_1=\frac{sintw}{sinw}
同理,只需要把图倒着看,也能得到
k 0 = s i n ( 1 t ) w s i n w k_0=\frac{sin(1-t)w}{sinw}
可以将同样的思想映射到四元数,则有
s l e r p ( q 0 , q 1 , t ) = s i n ( 1 t ) w s i n w q 0 + s i n t w s i n w q 1 slerp(q_0,q_1,t) = \frac{sin(1-t)w}{sinw}q_0+\frac{sintw}{sinw}q_1
具体实现的时候需要考虑两个细节问题:
1.q和-q表示相同的方向,因此,我们开始要通过q0和q1相乘,确认其符号相同
2.如果sinw的值非常小的时候,我们的除法就会出现一些问题,这里的解决方案是放sinw非常小的时候,我们使用线性差值。

结合以上讨论,我们的测试unity源码如下:
里面也实现了求幂以及slerp的操作,可以通过按键来测试其旋转。
通过给物体设置初始旋转值,按下M则旋转值会在初始旋转值到初始旋转值的t倍之间进行index的差值,按下空格,初始旋转值会变成t倍

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class QuaternionTest : MonoBehaviour
{
    public Vector4 Quaternion0;
    public Vector4 Quaternion1;
    public float t;
    [Range(0,1)]
    public float index;
    void Update()
    {
        Quaternion0 = new Vector4(transform.rotation.x, transform.rotation.y, transform.rotation.z, transform.rotation.w);
        Quaternion1 = Power(t, Quaternion0);
        if (Input.GetKeyUp(KeyCode.Space))
            transform.rotation = new Quaternion(Quaternion1.x, Quaternion1.y, Quaternion1.z, Quaternion1.w) ;
        if (Input.GetKeyUp(KeyCode.M))
        {
            Vector4 slp = Slerp(Quaternion0, Quaternion1,index);
            transform.rotation = new Quaternion(slp.x, slp.y, slp.z, slp.w);
        }
    }
    Vector4 Power(float t, Vector4 q)
    {
        
        float alpha = Mathf.Acos(q.w);
        if (Mathf.Abs(q.w) < 0.99999)
        {
            float newAlpha = alpha * t;
            q.w = Mathf.Cos(newAlpha);
            float mult = Mathf.Sin(newAlpha)/Mathf.Sin(alpha);
            q.x *= mult;
            q.y *= mult;
            q.z *= mult;
        }
        else
            Debug.Log("除零错误");
        return q;
    }

    Vector4 Slerp(Vector4 begin, Vector4 end,float lerp)
    {
        Vector4 tempQ = new Vector4();
        //保证begin和end符号相同
        float cosOmega = begin.x * end.x + begin.y * end.y + begin.z * end.z + begin.w * end.w;
        if (cosOmega < 0.0f)
        {
            begin.x = -begin.x;
            begin.y = -begin.y;
            begin.z = -begin.z;
            begin.w = -begin.w;
            cosOmega = -cosOmega;
        }
        float k0, k1;
        if (cosOmega > 0.9999f)
        {
            k0 = 1 - lerp;
            k1 = lerp;
        }
        else
        {
            float sinOmega = Mathf.Sqrt(1.0f-cosOmega*cosOmega);
            float omega = Mathf.Atan2(sinOmega,cosOmega);
            float oneOverSinOmega = 1.0f / sinOmega;
            k0 = Mathf.Sin((1.0f- lerp) *omega)*oneOverSinOmega;
            k1 = Mathf.Sin(lerp * omega)*oneOverSinOmega;
        }
        tempQ.x = begin.x * k0 + end.x * k1;
        tempQ.y = begin.y * k0 + end.y * k1;
        tempQ.z = begin.z * k0 + end.z * k1;
        tempQ.w = begin.w * k0 + end.w * k1;
        return tempQ;
    }
}

发布了31 篇原创文章 · 获赞 4 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43813453/article/details/102852582
今日推荐