Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十二章:四元数(QUATERNIONS)

原文: Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十二章:四元数(QUATERNIONS)



学习目标

  1. 回顾复数,以及复数相乘如何在平面上表达旋转;
  2. 理解四元数以及它的运算;
  3. 理解单位四元数如何表达3D旋转;
  4. 学习如何转换旋转变量的表达;
  5. 学习如何对单位四元数线性差值,而且理解它等价于几何上的3D角度差值;
  6. 熟悉DirectX Math库中的四元数类和操做。


1 回顾复数

四元数能够看作是一个复数,因此咱们先要回顾一下复数。本章的主要目标是展现一个复数P乘以一个单位复数,获得的是旋转后的P。数组


1.1 定义

有不少种方法介绍复数,咱们采用下面的方法,将它想象成一个2D点或者向量。
一对排序的实数z = (a, b)是一个复数。第一个部分叫实数部分,第二个部分是虚数部分。而且加减乘除定义以下:
在这里插入图片描述
而且很容易证实实数的算术性质对复数也依然有效(交换律,结合律,分配率)(练习1);markdown

若是一个复数形式是(x, 0),那么它就是经过实数x定义,而后写成(x, 0);那么任何实数均可以是一个复数,其虚数部分是0;观察一个实数乘以一个复数x(a, b) = (x, 0)(a, b) = (xa, xb) = (a, b)(x, 0) = (a, b)x,这个形式可让咱们回忆起变量-向量的乘法。
咱们定义单位虚数i = (0, 1),而后使用定义的复数乘法,i^2 = (0, 1)(0, 1) = (−1, 0) = −1,因此i是方程x^2 = −1的解。
复数z = (a, b)的复数共轭表示为z‾\overline{z}z,而且z‾\overline{z}z= (a, −b);一个简单的记住复数除法公式的方法是:分子和分母乘以分母的共轭,这样分母就成为了一个实数:
在这里插入图片描述ide

下面展现一个复数(a, b)能够写为a + ib,咱们有a = (a, 0), b = (b, 0)和i = (0, 1),因此:
在这里插入图片描述函数

使用a + ib形式,咱们能够重作加减乘除:
在这里插入图片描述
而且在这种形式下,z = a + ib的共轭复数为a - ib。oop


1.2 几何解释

咱们将复数a + ib = (a, b)理解为几何上的2D向量或者点(在复平面);复数相加匹配到向量的相加:
在这里插入图片描述
复数的绝对值或者长度由向量的长度来定义:
在这里插入图片描述
咱们将长度为1的复数称之为单位复数:
在这里插入图片描述学习


1.3 极坐标表示和旋转

由于复数能够看作是2D复平面的点或者向量,因此咱们也能够将它表示到极坐标:
在这里插入图片描述
后面的等式就是复数a + ib的极坐标表示。
在这里插入图片描述
令z1 = r1(cosθ1 + isinθ1), z2 = (cosθ2 + isinθ2),那么:
在这里插入图片描述
这里使用了三角定理:
在这里插入图片描述
因此几何上,z1z2的乘积表示长度为r1r2的向量旋转了θ1 + θ2角度;若是其中r2为1,那么乘积就表示将z1旋转了θ2角度。如上图,因此复数乘以单位复数,表示把前面的复数旋转。动画



2 四元数代数


2.1 基本运算的定义

四个有序实数q = (x, y, z, w) = (q1, q2, q3, q4)是一个四元数,一般简写为q = (u, w) = (x, y, z, w),而且咱们称u = (x, y, z)为虚数部分,w为实数部分。那么加减乘除定义以下:
在这里插入图片描述
乘法的定义看起来会比较奇怪,可是这些运算是定义,因此咱们能够定义为咱们想要的形式,而且这些形式颇有用。矩阵的乘法定义一开始也看起来很奇怪,可是结果是它们颇有用。
令p = (u, p4) = (p1, p2, p3, p4)而且q = (v, q4) = (q1, q2, q3, q4),那么u × v =(p2q3 – p3q2, p3q1 – p1q3, p1q2 – p2q1)而且u·v = p1q1 + p2q2 + p3q3。那么组件形式下,四元数的乘积r = pq是:
在这里插入图片描述
能够写成矩阵乘法形式:
在这里插入图片描述
若是你偏心行向量形式,取其转置矩阵:
在这里插入图片描述this


2.2 特殊乘积

令i = (1, 0, 0, 0), j = (0, 1, 0, 0), k = (0, 0, 1, 0)为四元数,那么咱们有一些特殊乘积,会让咱们回忆起叉积:
在这里插入图片描述
这些等式是直接从四元数乘积公式得来的,好比:
在这里插入图片描述atom


2.3 特性

四元数的乘法不具有交换律,下图证实ij = −ji。四元数乘积具备结合律;因此四元数能够联想为矩阵的乘积,具备结合律,不具备交换律。四元数e = (0, 0, 0, 1)用以乘法单位:
在这里插入图片描述
四元数具备乘法分配律p(q + r) = pq + pr 和 (q + r)p = qp + rp。spa


2.4 转化

咱们将实数、向量与四元数经过下面的方式来关联。令s是实数x,u = (x, y, z)是向量,那么:
在这里插入图片描述
能够说任意实数是一个有0向量的四元数,任意向量是一个具备0实数的四元数;另外单位四元数为1 = (0, 0, 0, 1);一个四元数具备0实数称之为纯四元数(pure quaternion)。

使用四元数乘法的定义,一个实数乘以四元数是标量相乘,具备交换律:
在这里插入图片描述
在这里插入图片描述


2.5 共轭和标范数

四元数q = (q1, q2, q3, q4) = (u, q4)的共轭由q定义:
在这里插入图片描述
也就是直接将虚数部位取反;相比于共轭复数,它具备下面特性:
在这里插入图片描述
其中q + q
和qq* = q*q等于实数。

四元数的范数(长度),定义为:
在这里插入图片描述
范数为1的四元数为单位四元数,范数具备下面的特性:
在这里插入图片描述
特性2表示2个单位四元数相乘,依然是单位四元数;若是||p|| = 1,那么||pq|| = ||q||。

共轭和范数的特性能够直接经过定义推导出来,好比:
在这里插入图片描述


2.6 逆(Inverses)

由于矩阵和四元数乘法不具备交换律,因此不能直接定义除法运算。可是每一个非0四元数具备逆,令q = (q1, q2, q3, q4) = (u, q4)是一个非0的四元数,那么它的逆经过q−1q^{-1}q1来定义:
在这里插入图片描述
很容易证实它是复数的逆:
在这里插入图片描述
能够看出若是q是单位四元数,那么∣∣q∣∣2=1||q||^2 = 1q2=1,而且q−1=q∗q^{-1} = q^*q1=q
而且符合下面的特性:
在这里插入图片描述


2.7 极坐标表达

若是q = (q1, q2, q3, q4) = (u, q4)是单位四元数,那么:
在这里插入图片描述
在这里插入图片描述
上图表示,对于θ∈[0, π],q4 = cosθ,根据三角定义sin2θ + cos2θ = 1:
在这里插入图片描述
因此
在这里插入图片描述
如今求单位向量:
在这里插入图片描述
因此u = sinθn,如今咱们能够写出单位四元数q = (u, q4)的极坐标表达,其中n是单位向量:
在这里插入图片描述

若是咱们将−θ带入等式中的θ,只是取反了向量部分:
在这里插入图片描述
下节将会介绍,n表示旋转的轴向。



3 单位四元数和旋转


3.1 旋转运算

令q = (u, w)是一个单位四元数而且令v是一个3D点或者向量,而后咱们认为v是一个纯四元数p = (v, 0)。当q是一个单位四元数时,q−1=q∗q^{−1} = q^*q1=q。回顾四元数乘法公式:
在这里插入图片描述
如今考虑下面的乘法:
在这里插入图片描述
对其长度稍做简化,咱们把实数部分和向量部分分开计算。咱们作下面的符号替换:
在这里插入图片描述
实数部分:
在这里插入图片描述
其中u · (v × u) = 0,由于根据叉积的定义,(v × u)是和u正交的。

虚数部分:
在这里插入图片描述
其中对u × (u × v)应用了乘法定义:a × (b × c) = (a · c)b − (a · b)c。因此:
在这里插入图片描述
计算的结果是一个向量或者点,其实数部分为0。因此随后的等式中,咱们放弃实数部分。
由于q是一个单位四元数,因此能够写为:
在这里插入图片描述
带入上面公式后:
在这里插入图片描述
为了进一步简化,咱们带入三角定义:
在这里插入图片描述
对比第三章的旋转公式,咱们发现它和旋转公式基本一致,它将向量v沿着n轴旋转2θ度。
在这里插入图片描述
因此咱们定义四元数旋转运算:
在这里插入图片描述
因此若是你要沿着n轴旋转θ度,那么你能够构建对于的旋转四元数:
在这里插入图片描述
而后应用到旋转公式中Rq(v)R_q(v)Rq(v)


3.2 四元数旋转运算到矩阵

令q = (u, w) = (q1, q2, q3, q4)是一个单位四元数,根据以前的公式,能够获得:
在这里插入图片描述
上面公式的三个部分能够分别写出矩阵形式:
在这里插入图片描述
将它们相加:
在这里插入图片描述
根据单位四元数的特性(各分组件平方的和为1),作下面的简化:
在这里插入图片描述
最后矩阵能够写为:
在这里插入图片描述


3.3 矩阵到四元数旋转运算

给出一个旋转矩阵:
在这里插入图片描述
咱们但愿找到四元数q = (q1, q2, q3, q4),咱们的策略是,设置矩阵以下:
在这里插入图片描述
而后求解q1, q2, q3, q4;
首先将对角线上的元素相加(最终一个矩阵):
在这里插入图片描述
而后组合对角相反的元素来求解q1, q2, q3:
在这里插入图片描述
若是q4 = 0,那上面这些公式就无心义,因此咱们要找到R的最大对角元素来除,而且选择矩阵元素的其余组合。加入R11是最大的对角:
在这里插入图片描述
若是假设R22 或者 R33为最大对角线,计算模式相似。


3.4 组成

假设p和q是单位四元数,而且对于旋转运算为Rp 和 Rq,令v′=Rp(v)v^{'} = R_p(v)v=Rp(v),那么组合:
在这里插入图片描述
由于p和q都是单位四元数,pq的乘积也是单位四元数||pq|| = ||p||||q|| = 1;因此pq也表示旋转;也就是说说获得的旋转为:Rq(Rp(v))R_q(R_p(v))Rq(Rp(v))



4 四元数的插值

由于四元数是由4个实数组成的,因此几何上,能够把它当作是一个4D向量,单位四元数是4D在单位4D球体表面,除了叉积(只定义了3D向量)。特别的,点积也支持四元数,令p = (u, s)而且q = (v, t),那么:
在这里插入图片描述
其中θ是两个四元数之间的夹角,若是p和q是单位四元数,那么p·q = cosθ,因此点积能够帮助咱们考虑2个四元数之间的夹角。
出于动画考虑,咱们须要在两个方向之间进行插值,为了插值四元数,咱们须要在单位球体上进行弧度差值,因此也须要在单位四元数上差值。为什么推导出公式,以下图所示:咱们须要在a和b中间差值tθ。咱们须要找到权重c1和c2支持p = c1a + c2b,其中||p|| = ||a|| = ||b||。咱们对两个未知项建立两个等式:
在这里插入图片描述
在这里插入图片描述
而后能够导出下面的矩阵:
在这里插入图片描述
考虑到上面的矩阵等式Ax = b,其中A是可逆的,因此根据克莱姆法则xi=detAidetAx_i = \frac{detA_i}{detA}xi=detAdetAi,其中AiA_iAi是经过交换A中第i列的向量到b,因此:
在这里插入图片描述
根据三角毕达哥斯拉定义和加法公式,咱们能够得出:
在这里插入图片描述
因此:
在这里插入图片描述
而且:
在这里插入图片描述
因此咱们定义出球体差值公式:
在这里插入图片描述

若是将单位四元数当作4D向量的话,咱们就能够求解四元数之间的夹角:θ = arccos(a · b)。
若是a和b之间的夹角趋近于0,sinθ趋近于0,那么上面公式中的除法就会引起问题,会致使无限大的结果。这种状况下,对两个四元数进行线性差值,并标准化结果,就是对小θ的一个很好的近似:
在这里插入图片描述
观察下图,线性差值是经过将四元数差值投影回单位球体,其结果是一个非线性速率的旋转。因此若是你对大角度使用线性差值的话,旋转的速度会时快时慢。
在这里插入图片描述
咱们如今支持一个四元数有趣的特性,(sq)= sq而且标量-四元数的乘积是具备交换律的,因此咱们能够得出:
在这里插入图片描述
咱们得出q和-q表示的相同的旋转,也能够经过其余方式来证实,若是
在这里插入图片描述
在这里插入图片描述
Rq表示围绕n旋转θ,R-q表示围绕-n旋转2π − θ。在几何上,一个在4D单位球体上的单位四元数和它的极坐标相反值−q表明的是相同的方向。下图能够看出,这两个旋转到了相同的位置,只是一个旋转了小角度,另外一个旋转了大的角度:
在这里插入图片描述
因此b和-b表达了相同的方向,咱们有2个选择来差值:slerp(a, b, t) 或者 slerp(a, −b, t)。其中一个是从更小的角度直接旋转;另外一个是从更大的角度来旋转。以下图所示,选择哪一个旋转基于哪一个旋转在单位球体上的弧度:选择小弧度表明选择了更直接的路径,选择更长的弧度表明对物体有额外更多的旋转[Eberly01]。
[Watt92]若是要在单位球面上找到四元数最短旋转弧度,咱们能够比较||a – b||2和||a – (−b)||2 = ||a + b||2。若是 ||a + b||2 < ||a – b||2咱们就选择-b,由于-b更接近a:
在这里插入图片描述

// Linear interpolation (for small theta).
public static Quaternion LerpAndNormalize(Quaternion p, Quaternion q, float s)
{
	// Normalize to make sure it is a unit quaternion.
	return Normalize((1.0f - s)*p + s*q);
}

public static Quaternion Slerp(Quaternion p, Quaternion q, float s)
{
	// Recall that q and -q represent the same orientation, but
	// interpolating between the two is different: One will take the
	// shortest arc and one will take the long arc. To find
	// the shortest arc, compare the magnitude of p-q with the
	// magnitude p-(-q) = p+q.
	if(LengthSq(p-q) > LengthSq(p+q))
		q = -q;
		
	float cosPhi = DotP(p, q);
	
	// For very small angles, use linear interpolation.
	if(cosPhi > (1.0f - 0.001))
		return LerpAndNormalize(p, q, s);
		
	// Find the angle between the two quaternions.
	float phi = (float)Math.Acos(cosPhi);
	float sinPhi = (float)Math.Sin(phi);
	
	// Interpolate along the arc formed by the intersection of the 4D
	// unit sphere and the plane passing through p, q, and the origin of
	// the unit sphere.
	return ((float)Math.Sin(phi*(1.0- s))/sinPhi)*p + ((float)Math.Sin(phi*s)/sinPhi)*q;
}


5 DIRECTX MATH四元数函数

DirectX数学库支持四元数。由于四元数的数据是4个实数,因此使用XMVECTOR类型类保存四元数。下面是通用的函数:

// Returns the quaternion dot product Q1·Q2.
XMVECTOR XMQuaternionDot(XMVECTOR Q1, XMVECTOR Q2);

// Returns the identity quaternion (0, 0, 0, 1).
XMVECTOR XMQuaternionIdentity();

// Returns the conjugate of the quaternion Q.
XMVECTOR XMQuaternionConjugate(XMVECTOR Q);

// Returns the norm of the quaternion Q.
XMVECTOR XMQuaternionLength(XMVECTOR Q);

// Normalizes a quaternion by treating it as a 4D vector.
XMVECTOR XMQuaternionNormalize(XMVECTOR Q);

// Computes the quaternion product Q1Q2.
XMVECTOR XMQuaternionMultiply(XMVECTOR Q1, XMVECTOR Q2);

// Returns a quaternions from axis-angle rotation representation.
XMVECTOR XMQuaternionRotationAxis(XMVECTOR Axis, FLOAT Angle);

// Returns a quaternions from axis-angle rotation representation, where the axis
// vector is normalized—this is faster than XMQuaternionRotationAxis.
XMVECTOR XMQuaternionRotationNormal(XMVECTOR NormalAxis,FLOAT Angle);

// Returns a quaternion from a rotation matrix.
XMVECTOR XMQuaternionRotationMatrix(XMMATRIX M);

// Returns a rotation matrix from a unit quaternion.
XMMATRIX XMMatrixRotationQuaternion(XMVECTOR Quaternion);

// Extracts the axis and angle rotation representation from the quaternion Q.
VOID XMQuaternionToAxisAngle(XMVECTOR *pAxis, FLOAT *pAngle, XMVECTOR Q);

// Returns slerp(Q1, Q2, t)
XMVECTOR XMQuaternionSlerp(XMVECTOR Q0, XMVECTOR Q1, FLOAT t);


6 旋转Demo

本章中的Demo,咱们在简单的场景中运动一个骷髅头。位置、方形和缩放都作动画。咱们用四元数来表达骷髅的方向,而后使用球面差值来对方向差值。使用线性差值对位置和缩放差值。它是对下一章中的角色动画作预热。
咱们使用关键帧系统对骷髅作动画:

struct Keyframe
{
	Keyframe();
	˜Keyframe();
	float TimePos;
	XMFLOAT3 Translation;
	XMFLOAT3 Scale;
	XMFLOAT4 RotationQuat;
};

动画是一些列经过实践来排序的关键帧:

struct BoneAnimation
{
	float GetStartTime()const;
	float GetEndTime()const;
	void Interpolate(float t, XMFLOAT4X4& M)const;
	std::vector<Keyframe> Keyframes;
};

GetStartTime函数用来返回第一个帧的时间;GetEndTime函数返回最后一个关键帧的时间。它对于动画何时结束颇有用,咱们能够中止动画。
如今有了一个关键帧列表,对于每两个帧之间使用插值计算:

void BoneAnimation::Interpolate(float t, XMFLOAT4X4& M)const
{
	// t is before the animation started, so just return the first key frame.
	if( t <= Keyframes.front().TimePos )
	{
		XMVECTOR S = XMLoadFloat3(&Keyframes.front().Scale);
		XMVECTOR P = XMLoadFloat3(&Keyframes.front().Translation);
		XMVECTOR Q = XMLoadFloat4(&Keyframes.front().RotationQuat);
		XMVECTOR zero = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
		XMStoreFloat4x4(&M, XMMatrixAffineTransformation(S, zero, Q, P));
	}
	
	// t is after the animation ended, so just return the last key frame.
	else if( t >= Keyframes.back().TimePos )
	{
		XMVECTOR S = XMLoadFloat3(&Keyframes.back().Scale);
		XMVECTOR P = XMLoadFloat3(&Keyframes.back().Translation);
		XMVECTOR Q = XMLoadFloat4(&Keyframes.back().RotationQuat);
		XMVECTOR zero = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
		XMStoreFloat4x4(&M, XMMatrixAffineTransformation(S, zero, Q, P));
	}
	
	// t is between two key frames, so interpolate.
	else
	{
		for(UINT i = 0; i < Keyframes.size()-1; ++i)
		{
			if( t >= Keyframes[i].TimePos && t <= Keyframes[i+1].TimePos )
			{
				float lerpPercent = (t - Keyframes[i].TimePos) /
					(Keyframes[i+1].TimePos - Keyframes[i].TimePos);
					
				XMVECTOR s0 = XMLoadFloat3(&Keyframes[i].Scale);
				XMVECTOR s1 = XMLoadFloat3(&Keyframes[i+1].Scale);
				XMVECTOR p0 = XMLoadFloat3(&Keyframes[i].Translation);
				XMVECTOR p1 = XMLoadFloat3(&Keyframes[i+1].Translation);
				XMVECTOR q0 = XMLoadFloat4(&Keyframes[i].RotationQuat);
				XMVECTOR q1 = XMLoadFloat4(&Keyframes[i+1].RotationQuat);
				XMVECTOR S = XMVectorLerp(s0, s1, lerpPercent);
				XMVECTOR P = XMVectorLerp(p0, p1, lerpPercent);
				XMVECTOR Q = XMQuaternionSlerp(q0, q1, lerpPercent);
				XMVECTOR zero = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
				XMStoreFloat4x4(&M, XMMatrixAffineTransformation(S, zero, Q, P));
				
				break;
			}
		}
	}

下图展现了两个关键帧之间插值的结果:
在这里插入图片描述
插值事后,咱们构造了变换的矩阵,由于在着色器中咱们最终使用矩阵作变换。XMMatrixAffineTransformation函数定义以下:

XMMATRIX XMMatrixAffineTransformation(
	XMVECTOR Scaling,
	XMVECTOR RotationOrigin,
	XMVECTOR RotationQuaternion,
	XMVECTOR Translation);

如今咱们简单的动画系统已经完成,下一步是定义一些关键帧:

// Member data
float mAnimTimePos = 0.0f;
BoneAnimation mSkullAnimation;

//
// In constructor, define the animation keyframes
//
void QuatApp::DefineSkullAnimation()
{
	//
	// Define the animation keyframes
	//
	XMVECTOR q0 = XMQuaternionRotationAxis(XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f),
		XMConvertToRadians(30.0f));
	XMVECTOR q1 = XMQuaternionRotationAxis(XMVectorSet(1.0f, 1.0f, 2.0f, 0.0f),
		XMConvertToRadians(45.0f));
	XMVECTOR q2 = XMQuaternionRotationAxis(XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f),
		XMConvertToRadians(-30.0f));
	XMVECTOR q3 = XMQuaternionRotationAxis(XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f),
		XMConvertToRadians(70.0f));
		
	mSkullAnimation.Keyframes.resize(5);
	mSkullAnimation.Keyframes[0].TimePos = 0.0f;
	mSkullAnimation.Keyframes[0].Translation = XMFLOAT3(-0.0f, 0.0f);
	mSkullAnimation.Keyframes[0].Scale = XMFLOAT3(0.25f, 0.25f, 0.25f);
	XMStoreFloat4(&mSkullAnimation.Keyframes[0].RotationQuat, q0);
	
	mSkullAnimation.Keyframes[1].TimePos = 2.0f;
	mSkullAnimation.Keyframes[1].Translation = XMFLOAT3(0.0f, 2.0f, 10.0f);
	mSkullAnimation.Keyframes[1].Scale = XMFLOAT3(0.5f, 0.5f, 0.5f);
	XMStoreFloat4(&mSkullAnimation.Keyframes[1].RotationQuat, q1);
	
	mSkullAnimation.Keyframes[2].TimePos = 4.0f;
	mSkullAnimation.Keyframes[2].Translation = XMFLOAT3(7.0f, 0.0f, 0.0f);
	mSkullAnimation.Keyframes[2].Scale = XMFLOAT3(0.25f, 0.25f, 0.25f);
	XMStoreFloat4(&mSkullAnimation.Keyframes[2].RotationQuat, q2);
	
	mSkullAnimation.Keyframes[3].TimePos = 6.0f;
	mSkullAnimation.Keyframes[3].Translation = XMFLOAT3(0.0f, 1.0f, -10.0f);
	mSkullAnimation.Keyframes[3].Scale = XMFLOAT3(0.5f, 0.5f, 0.5f);
	XMStoreFloat4(&mSkullAnimation.Keyframes[3].RotationQuat, q3);
	
	mSkullAnimation.Keyframes[4].TimePos = 8.0f;
	mSkullAnimation.Keyframes[4].Translation = XMFLOAT3(-0.0f, 0.0f);
	mSkullAnimation.Keyframes[4].Scale = XMFLOAT3(0.25f, 0.25f, 0.25f);
	XMStoreFloat4(&mSkullAnimation.Keyframes[4].RotationQuat, q0);
}

在这里插入图片描述
最后一步使根据时间进行插值操做:

void QuatApp::UpdateScene(float dt)
{
	…
	// Increase the time position.
	mAnimTimePos += dt;
	
	if(mAnimTimePos >= mSkullAnimation.GetEndTime())
	{
		// Loop animation back to beginning.
		mAnimTimePos = 0.0f;
	}
	
	// Get the skull’s world matrix at this time instant.
	mSkullAnimation.Interpolate(mAnimTimePos, mSkullWorld);
	…
}

如今骷髅的世界矩阵每一帧都根据动画来更新。



7 总结

  1. 一个有序的4个实时q = (x, y, z, w) = (q1, q2, q3, q4)是一个四元数,通常都简写成q = (u, w) = (x, y, z, w),而且咱们将u = (x, y, z)称为虚向量部分,w为实数部分,进一步它的加减乘除定义为:
    在这里插入图片描述

  2. 四元数乘法不知足交换律,可是知足结合律,四元数e = (0, 0, 0, 1)用以恒等式。四元数支持乘法分配律p(q + r) = pq + pr和(q + r)p = qp + rp;

  3. 咱们能够将任意实数写成四元数s = (0, 0, 0, s),也能够将任意向量转换成四元数u = (u, 0)。实数部分为0的四元数为纯四元数。四元数能够和标量相乘:s(p1, p2, p3, p4) = (sp1, sp2, sp3, sp4) = (p1, p2, p3, p4)s,特殊的地方在于标量和四元数的乘法支持交换律;

  4. 共轭四元数和四元数范式的定义;

  5. 逆四元数的定义和计算;

  6. 单位四元数能够写成极向量表达q = (u, q4),其中n是单位向量;

  7. 若是q是一个单位四元数,那么q = (sinθn,cosθ) for ||n|| = 1 and θ ∈ [0, π],旋转运算为Rq(v)=qvq−1=qvq∗R_q(v) = qvq^{-1} = qvq^*Rq(v)=qvq1=qvq表示将点/向量围绕n旋转2θ。Rq有矩阵表达,任何旋转矩阵均可以转换成四元数用来表达旋转;

  8. 咱们可使用球面插值来对两个用单位四元数表示的方向进行插值。



8 练习

相关文章
相关标签/搜索