对于四元数的学习基本上都是参照《3D数学基础-图形与游戏开发这本书的内容的》对于这本书前面的部分还是很好理解的,但是从四元数的差这里开始,就过于抽象了,不配合实例很难去理解。
因此,这一段被我单独提取出来,在实践中进一步去理解。
差,对数,指数等是定义式,不要试图在四元数的这些操作中找到这些数学符号的本身含义,它只是被定义成这样子的,没有为什么。。
四元数的差表示四元数的两个四元数的角位移,比如ad=b,则d就定义为a和b的差。
有 d = a − 1 b d=a^{-1}b d=a−1b
这个定义有点玄乎,我们定义 α = θ / 2 \alpha = \theta/2 α=θ/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 qt
不同于上面的运算看似毫无意义,这个运算的定义是有几何意义的, q t q^t qt的含义是绕轴n旋转t* θ \theta θ个角度
更通俗一点,比如说q是绕某轴旋转w°,则 q 2 q^2 q2是旋转2w°, q 1 3 q^{\frac{1}{3}} q31是旋转三分之一w, q − 1 q^{-1} q−1是旋转-w。
根据定义我们也很容易写求幂的函数,也就是通过arccosw获得 θ / 2 \theta/2 θ/2,然后把得到的角度乘上t,再返回代入即可。
slerp是球面线性差值的缩写,它可以在两个四元数之间进行平滑差值,而欧拉角并不能这样,可以差值也是我们选择四元数的一个理由。
四元数的差值可以用到前面提到的差和求幂的操作,总结下来步骤如下:
1、计算两个值的差 Δ q = q 0 − 1 q 1 \Delta q = q_0^{-1}q_1 Δq=q0−1q1
2、对差进行差值,也就是用之前的求幂操作 Δ q t \Delta q^t Δqt
3、开始值加上差的一部分
所以公式为 q 0 ( q 0 − 1 q 1 ) t q_0(q_0^{-1}q_1)^t q0(q0−1q1)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 vt=k0v0+k1v1
对于k1v1为斜边的直角三角形来说,有
s i n w = s i n t w k 1 sinw = \frac{sintw}{k_1} sinw=k1sintw
因此有:
k 1 = s i n t w s i n w k_1=\frac{sintw}{sinw} k1=sinwsintw
同理,只需要把图倒着看,也能得到
k 0 = s i n ( 1 − t ) w s i n w k_0=\frac{sin(1-t)w}{sinw} k0=sinwsin(1−t)w
可以将同样的思想映射到四元数,则有
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 slerp(q0,q1,t)=sinwsin(1−t)wq0+sinwsintwq1
具体实现的时候需要考虑两个细节问题:
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;
}
}