1. 程式人生 > >Introduction to 3D Game Programming with DirectX 12 學習筆記之 --- 第二十二章:四元數(QUATERNIONS)

Introduction to 3D Game Programming with DirectX 12 學習筆記之 --- 第二十二章:四元數(QUATERNIONS)

directx height ebe beginning ++ tip osi 通過 假設

原文: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);

如果一個復數形式是(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\overline{z}= (a, ?b);一個簡單的記住復數除法公式的方法是:分子和分母乘以分母的共軛,這樣分母就成為了一個實數:
技術分享圖片

下面展示一個復數(a, b)可以寫為a + ib,我們有a = (a, 0), b = (b, 0)和i = (0, 1),所以:
技術分享圖片

使用a + ib形式,我們可以重做加減乘除:
技術分享圖片
並且在這種形式下,z = a + ib的共軛復數為a - ib。


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是:
技術分享圖片
可以寫成矩陣乘法形式:
技術分享圖片
如果你偏愛行向量形式,取其轉置矩陣:
技術分享圖片


2.2 特殊乘積

令i = (1, 0, 0, 0), j = (0, 1, 0, 0), k = (0, 0, 1, 0)為四元數,那麽我們有一些特殊乘積,會讓我們回憶起叉積:
技術分享圖片
這些等式是直接從四元數乘積公式得來的,比如:
技術分享圖片


2.3 特性

四元數的乘法不具備交換律,下圖證明ij = ?ji。四元數乘積具有結合律;所以四元數可以聯想為矩陣的乘積,具有結合律,不具有交換律。四元數e = (0, 0, 0, 1)用以乘法單位:
技術分享圖片
四元數具有乘法分配律p(q + r) = pq + pr 和 (q + r)p = qp + rp。


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}來定義:
技術分享圖片
很容易證明它是復數的逆:
技術分享圖片
可以看出如果q是單位四元數,那麽q2=1||q||^2 = 1,並且q?1=q?q^{-1} = 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^*。回顧四元數乘法公式:
技術分享圖片
現在考慮下面的乘法:
技術分享圖片
對其長度稍作簡化,我們把實數部分和向量部分分開計算。我們做下面的符號替換:
技術分享圖片
實數部分:
技術分享圖片
其中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)


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),那麽組合:
技術分享圖片
因為p和q都是單位四元數,pq的乘積也是單位四元數||pq|| = ||p||||q|| = 1;所以pq也表示旋轉;也就是說說得到的旋轉為:Rq(Rp(v))R_q(R_p(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},其中AiA_i是通過交換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^*表示將點/向量圍繞n旋轉2θ。Rq有矩陣表達,任何旋轉矩陣都可以轉換成四元數用來表達旋轉;

  8. 我們可以使用球面插值來對兩個用單位四元數表示的方向進行插值。



8 練習

Introduction to 3D Game Programming with DirectX 12 學習筆記之 --- 第二十二章:四元數(QUATERNIONS)