四元数的意义及代码应用

本文详细介绍了四元数的概念及其在三维空间旋转中的应用。四元数不满足交换律但满足结合律,并且其模的乘积保持不变。四元数的逆运算类似于矩阵,可用于表示旋转。通过四元数可以实现旋转的插值,如slerp算法,用于平滑过渡两个四元数表示的旋转。文中还给出了C++代码实现四元数插值,确保旋转的平滑过渡。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

四元数

四元数的定义是[w(xyz)]=[cos(θ/2)(sin(θ/2)nxsin(θ/2)nzsin(θ/2)nz)][w\quad(x\quad y\quad z)]=[cos(\theta/2)\quad (sin(\theta/2)n_x\quad sin(\theta/2)n_z\quad sin(\theta/2)n_z)][w(xyz)]=[cos(θ/2)(sin(θ/2)nxsin(θ/2)nzsin(θ/2)nz)]

四元数满足结合律但是不满足交换律:
(ab)c=a(bc)ab≠ba (ab)c=a(bc)\\ ab\neq ba (ab)c=a(bc)ab=ba
两个四元数模的积满足∥q1q2∥=∥q1∥∥q2∥\left \| q_1q_2\right \|=\left \| q_1\right \|\left \| q_2\right \|q1q2=q1q2

四元数的逆和矩阵的逆类似
(ab)−1=b−1a−1=(q1q2...qn−1qn)−1=qn−1qn−1−1.,.q2−1q1−1 (ab)^{-1}=b^{-1}a^{-1}= (q_1q_2...q_{n-1}q_n)^{-1}=q_n^{-1}q_{n-1}^{-1}.,.q_2^{-1}q_1^{-1} (ab)1=b1a1=(q1q2...qn1qn)1=qn1qn11.,.q21q11
利用这些性质,我们可以对四元数ppp向任意表示旋转方向的单位向量n^\hat{n}n^旋转表示成四元数之间的乘积
q′=qpq−1 {q}'=qpq^{-1} q=qpq1
其中q=[cosθ/2,n^sinθ/2]q=[cos\theta/2,\hat{n}sin\theta/2]q=[cosθ/2,n^sinθ/2]。特别地,ppp先向四元数aaa映射在三维空间的方向旋转再向四元数bbb映射在三维空间的方向旋转等于向四元数bababa映射在三维空间的方向上旋转,这个方向就是它们各自n^\hat{n}n^在三维空间的方向:
p′=b(apa−1)b−1=(ba)p(a−1b−1)=(ba)p(ba)−1 p^{'}=b(apa^{-1})b^{-1}\\ =(ba)p(a^{-1}b^{-1})\\ =(ba)p(ba)^{-1} p=b(apa1)b1=(ba)p(a1b1)=(ba)p(ba)1
四元数对log的定义为
logq=log([cosαn^sinα])=[0αsinα] log^{q}=log([cos\alpha\quad \hat{n}sin\alpha])=[0\quad \alpha sin\alpha] logq=log([cosαn^sinα])=[0αsinα]
四元数对exp的定义为
exp(p)=exp([0αn^])=[cosαn^sinα] exp(p)=exp([0\quad \alpha \hat{n}])=[cos\alpha\quad \hat{n}sin\alpha] exp(p)=exp([0αn^])=[cosαn^sinα]
从而有
exp(logq)=q exp(log^{q})=q exp(logq)=q
以四元数qqq为底的指数qtq^{t}qt相当于随t从0到1对单位四元数到四元数q旋转的插值而单位四元数定义为[10][1\quad 0][10][−10][-1\quad 0][10]

代码调式如下:

//Quaternion (input and output)
float w,x,y,z;
//Input exponent
float exponent;
//Check for the case of an Identity quaternion,this will protect against divide by zero
if(fabs(w)<.9999f)
{
    float alpha = acos(w);
    float newAlpha = alpha * exponent;
    w=cos(newAlpha);
    float mult=sin(newAlpha)/sin(alpha);
    x*=mult;
    y*=mult;
    z*=mult;
}

如果以四元数q0q_{0}q0到四元数q1q_{1}q1平滑过渡的旋转,则要使用以下代码

float w0,x0,y0,z0;
float w1,x1,y1,z1;

//The interpolation parameter
float t;
float w,x,y,z;
//output quaternion
float cosOmega=w0*w1+x0*x1+y0*y1+z0*z1;

if(cosOmega<0.0f){
	w1=-w1;
	x1=-x1;
	y1=-y1;
	z1=-z1;
	cosOmega=-cosOmega;
}

float k0,k1;
if(cosOmega>0.9999f)
{
	k0=1.0f-t;
	k1=t;
}else{
	float sinOmega=sqrt(1.0f-cosOmega*cosOmega);
	
	float omega=atan2(sinOmega,cosOmega);
	
	flat oneOverSinOmega=1.0f/sinOmega;
	
	k0=sin((1.0f-t)*omega)*oneOverSinOmega;
	k1=sin(t*omega)*sinOverSinOmega;
}

w=w0*k0+w1*k1;
x=x0*k0+x1*k1;
y=y0*k0+wy1*k1;
z=z0*k0+z1*k1;

上述的代码仅仅以单位四元数为基础坐标系描述旋转的插值,根据书本前部分的讨论,从q0q_{0}q0q1q_{1}q1四元数之差Δq=q1q0−1\Delta q=q_{1}q_{0}^{-1}Δq=q1q01,取这段差的一小部分t可表示为(Δq)t(\Delta q)^{t}(Δq)t,以q0q_{0}q0点为坐标系变换到原坐标系则表示为(Δq)tq0(\Delta q)^{t}q_{0}(Δq)tq0,这样可以描述任意四元数到其它四元数旋转插值的过程。

四元数中有一个专门的函数表示这种变换:
slerp(q0,q1,t)=(q1q0−1)tq0 slerp(q_{0},q_{1},t)=(q_{1}q_{0}^{-1})^{t}q_{0} slerp(q0,q1,t)=(q1q01)tq0
这种代数的表示往往需要消耗更多的计算资源.实际上采用的是一种数学上相等的计算形式。我们将采用二维平面扩展到四维超平面的过程来描述这种变换,如图所示

从平面上的点v0v_{0}v0v1v_{1}v1需要旋转ω\omegaω度,我们要找到一种普适的计算来表达slerp(v0,v1,t)slerp(v_{0},v_{1},t)slerp(v0,v1,t),继而扩展到slerp(q0,q1,t)slerp(q_{0},q_{1},t)slerp(q0,q1,t)

对于以k1v1k_{1}v_{1}k1v1为斜边的三角形可以得出
sin(ω)=sin(tω)k1k1=sin(tω)sinω sin(\omega)=\frac{sin(t\omega)}{k_{1}}\\ k_{1}=\frac{sin(t\omega)}{sin\omega} sin(ω)=k1sin(tω)k1=sinωsin(tω)
以红色箭头作为由k0v0k_{0}v_{0}k0v0k1v1k_{1}v_{1}k1v1组成的三角形的中垂线,由v1v_{1}v1k1v1k_{1}v_{1}k1v1平行可知
k0∥v0∥sin(tω)=k1∥v1∥sin(1−t)ω k_{0}\left \| v_{0}\right \|sin(t\omega)=k_{1}\left \| v_{1}\right \|sin(1-t)\omega k0v0sin(tω)=k1v1sin(1t)ω
四元数旋转后模不变可得∥v0∥=∥v1∥\left \|v_{0}\right \|=\left \| v_{1}\right \|v0=v1,那么:
k0sin(tω)=k1sin(1−t)ωk0=k1sin(1−w)sin(w) k_{0}sin(t\omega)=k_{1}sin(1-t)\omega\\ k_{0}=\frac{k_{1}sin(1-w)}{sin(w)} k0sin(tω)=k1sin(1t)ωk0=sin(w)k1sin(1w)
由于k1=sin(tω)sinωk_{1}=\frac{sin(t\omega)}{sin\omega}k1=sinωsin(tω),化简可得k0=sin(1−w)sin(w)k_{0}=\frac{sin(1-w)}{sin(w)}k0=sin(w)sin(1w)

既然如此,vtv_{t}vt可以被表示为
vt=k0v0+k1v1=sin(1−t)wsinwv0+sin(tw)sin(w)v1 v_{t}=k_{0}v_{0}+k_{1}v_{1}=\frac{sin(1-t)w}{sinw}v_{0}+\frac{sin(tw)}{sin(w)}v_{1} vt=k0v0+k1v1=sinwsin(1t)wv0+sin(w)sin(tw)v1
相同的基本思想可以扩展到四元数空间,我们可以将slerp改写为
slerp(q0,q1,t)=sin(1−t)wsinwq0+sin(tw)sin(w)q1 slerp(q_{0},q_{1},t)=\frac{sin(1-t)w}{sinw}q_{0}+\frac{sin(tw)}{sin(w)}q_{1} slerp(q0,q1,t)=sinwsin(1t)wq0+sin(w)sin(tw)q1
C++代码如下

float w0,x0,y0,z0;
float w1,x1,y1,z1;

float t;

float w,x,y,z;

float cosOmega=w0*w1+x0*x1+y0*y1+z0*z1;

if(cosOmega<0.0f){
	w1=-w1;
	x1=-x1;
	y1=-y1;
	z1=-z1;
}

float k0,k1;
if(cosOmega>0.9999f)
{
	k0=1.0f-t;
	k1=t;
}else{
	float sinOmega=sqrt(1.0f-cosOmega*cosOmega); 
	float omega=atan2(sinOmega,cosOmega);
	float oneOverSinOmega=1.0f/sinOmega;
	
	k0=sin((1.0f-t)*omega)*oneOverSinOmega;
    k1=sin(t*omega)*oneOverSinOmega;
}

//interpolation
w=w0*k0+w1*k1;
x=x0*k0+w1*k1;
y=y0*k0+y1*k1;
z=z0*k0+z1*k1;

Reference:<<3D Math Primer for Graphics and Game Development>> P246~P263

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值