概要
对于四元数的学习基本上都是参照《3D数学基础-图形与游戏开发这本书的内容的》对于这本书前面的部分还是很好理解的,但是从四元数的差这里开始,就过于抽象了,不配合实例很难去理解。
因此,这一段被我单独提取出来,在实践中进一步去理解。
差,对数,指数等是定义式,不要试图在四元数的这些操作中找到这些数学符号的本身含义,它只是被定义成这样子的,没有为什么。。
四元数的差
四元数的差表示四元数的两个四元数的角位移,比如ad=b,则d就定义为a和b的差。
有
四元数的对数
这个定义有点玄乎,我们定义
则四元数的定义就变成了[cosα xsinα ysinα zsinα]
我们定义其对数为
logq = log([cosα nsinα]) ≡ [0 nα]
是的,这样定义我也觉得很奇怪,但是既然有这个定义,那一定是有理由的,暂且先不管为什么要有这种操作,继续学习。
四元数的指数
指数的定义是和对数的定义严格相反的
设p = [0 nα]
exp p = exp [0,nα] ≡ [cosα nsinα]
四元数求幂
设四元数为q,则有四元数的幂运算
不同于上面的运算看似毫无意义,这个运算的定义是有几何意义的,
的含义是绕轴n旋转t*
个角度
更通俗一点,比如说q是绕某轴旋转w°,则
是旋转2w°,
是旋转三分之一w,
是旋转-w。
根据定义我们也很容易写求幂的函数,也就是通过arccosw获得
,然后把得到的角度乘上t,再返回代入即可。
四元数差值 slerp
slerp是球面线性差值的缩写,它可以在两个四元数之间进行平滑差值,而欧拉角并不能这样,可以差值也是我们选择四元数的一个理由。
四元数的差值可以用到前面提到的差和求幂的操作,总结下来步骤如下:
1、计算两个值的差
2、对差进行差值,也就是用之前的求幂操作
3、开始值加上差的一部分
所以公式为
当然这是理论上的公式,实际上,我们不这样算,我们通过一种更有效的方法来计算。
我们从二维的弧长差值来描述这种思想,如上图所示,我们需要在v0和v1之间进行差值,w为v0到v1之间的夹角,vt为v0和v1之间的一个差值,有v0和vt之间的夹角为tw,存在一组线性组合,使得
对于k1v1为斜边的直角三角形来说,有
因此有:
同理,只需要把图倒着看,也能得到
可以将同样的思想映射到四元数,则有
具体实现的时候需要考虑两个细节问题:
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;
}
}