1. 程式人生 > >Introduction to 3D Game Programming with DirectX 12 學習筆記之 --- 第二章:矩陣代數

Introduction to 3D Game Programming with DirectX 12 學習筆記之 --- 第二章:矩陣代數

學習目標:

  1. 理解矩陣和與它相關的運算;
  2. 理解矩陣的乘法如何被看成是線性組合;
  3. 理解單位矩陣、轉置矩陣、矩陣的行列式和逆矩陣;
  4. 熟悉DirectX Math庫中矩陣相關的類和函式;


1 矩陣的定義

一個m x n的矩陣M是一個有實陣列成的m行n列的矩陣。

  1. 兩個具有相同行數和列數的矩陣,每個對應的元素都相等的情況下,兩個矩陣相等;
  2. 兩個矩陣具有相同的行和列時,才能相加;
  3. 矩陣可以和任意標量相乘;
  4. 矩陣的減法可以由矩陣的加法和矩陣與標量的乘法來定義。
    因為矩陣的加法和標量乘法是按元素的,所以它們的一些屬性可以繼承自實數:
    這裡寫圖片描述


2 矩陣的乘法


2.1 矩陣乘法的定義

如果一個m x n的矩陣A和一個n x p的矩陣B相乘,結果是一個m x p的矩陣C,其第ij個元素的值是A矩陣中第i列向量與B矩陣第j行向量的點積:
C i j =

A i , B
, j
C_{ij} = A_{i,*} \cdot B_{*,j}
這裡寫圖片描述
所以矩陣相乘需要A的列數和B的行數相等,否則點積運算則無法進行。比如上面例子中BA無法進行,所以也驗證了AB != BA,矩陣運算不支援交換律


2.2 向量與矩陣的乘法

考慮下面矩陣的乘法:
這裡寫圖片描述
這裡寫圖片描述
所以
這裡寫圖片描述
上面等式是一個線性組合的例子,可以繼續擴充套件到1 x n乘以 n x n的情況:
這裡寫圖片描述


2.3 結合律

矩陣的乘法具有一些不錯的代數特性,比如乘法分配率:A(B + C) = AB + AC)和(A + B)C = AC + BC,
後續我們將會經常使用乘法結合律,來選擇矩陣相乘的順序:
( A B ) C = A ( B C ) (AB)C = A(BC)



3 轉置矩陣

轉置矩陣是由交換矩陣的行和列得到的,所以m x n的轉置矩陣是n x m,我們使用 M T M^T 來表示矩陣M的轉置矩陣。
轉置矩陣由一些有用的特性:
這裡寫圖片描述



4 單位矩陣

單位矩陣是一類特殊的矩陣,其對角線上的元素值為1,其它元素值為0;
單位矩陣有一個特性:MI = IM = M



5 矩陣的行列式

矩陣的行列式是一個特殊的方法,輸入一個方形矩陣,輸出一個實數;矩陣的行列式用 det A 來表示。
行列式擁有描述盒子容積和在進行轉變後的容積變化的幾何解釋,並且行列式被用來使用克萊姆法則解決線性方程式系統;
我們學習行列式的目的是用來證明一個矩陣是否可逆,一個方形矩陣A當且僅當 det A != 0時可逆。


5.1 Matrix Minors(子矩陣?)

給出一個m x n的矩陣A,其子矩陣 A ˉ i j \bar A_{ij} 是刪除A第i行和第j列元素後的矩陣。
這裡寫圖片描述


5.2 矩陣行列式的定義

矩陣的行列式的定義是遞迴的,4 x 4的矩陣依據3 x 3,直到1 x 1;如果A是一個n x n的矩陣,並且n > 1,那麼:
d e t A = j = 1 n A 1 j ( 1 ) 1 + j d e t A ˉ 1 j det A = \sum_{j = 1}^n A_{1j} (-1)^{1 + j} det \bar A_{1j}
對於2 x 2矩陣,公式如下:
這裡寫圖片描述
對於3 x 3矩陣,公式如下:
這裡寫圖片描述
對於4 x 4矩陣,公式如下:
這裡寫圖片描述
在3D圖形學中,我們主要使用4 x 4矩陣,所以不需要擴充套件到n。



6 伴隨矩陣

令A是一個n x n的矩陣,那麼 C i j = ( 1 ) i + j d e t A ˉ i j C_{ij} = (-1)^{i+j} det \bar A_{ij} 就是 A i j A_{ij} 的cofactor,如果我們用對應的cofactor替換掉矩陣A中所有的元素,得到的 C A C_A 就是矩陣A的cofactor矩陣:
這裡寫圖片描述
那麼 C A C_A 的轉置矩陣就是A的伴隨矩陣:
A = C A T A^* = C_A^T
伴隨矩陣可以幫助定義一個簡潔的逆矩陣公式。



7 逆矩陣

矩陣的運算沒有除法,但是可以乘以逆矩陣,下面總結一些逆矩陣的重要資訊:

  1. 只有方形矩陣包含逆矩陣;
  2. 使用 M 1 M^{-1} 表示矩陣M的逆矩陣;
  3. 不是所有方形矩陣都有逆矩陣,有逆矩陣的矩陣我們稱之為可逆的;
  4. 逆矩陣如果有,就是唯一的;
  5. 矩陣乘以它的逆矩陣,結果是單位矩陣;

逆矩陣在求解方程的時候很有用,比如要求解矩陣P:
這裡寫圖片描述
逆矩陣計算公式:這裡寫圖片描述
逆矩陣的一個有用的代數特性:這裡寫圖片描述
其證明如下:
這裡寫圖片描述



8 DIRECTX MATH中的矩陣


8.1 矩陣型別

DirectXMath在DirectXMath.h標頭檔案中使用XMMATRIX來表示4 x 4矩陣(with some minor adjustments)

#if (defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM)) && defined(_XM_NO_INTRINSICS_)
struct XMMATRIX
#else
__declspec(align(16)) struct XMMATRIX
#endif
{
	// Use 4 XMVECTORs to represent the matrix for SIMD.
	XMVECTOR r[4];
	
	XMMATRIX() {}
	
	// Initialize matrix by specifying 4 row vectors.
	XMMATRIX(FXMVECTOR R0, FXMVECTOR R1, FXMVECTOR R2, CXMVECTOR R3)
	{ 
		r[0] = R0; 
		r[1] = R1; 
		r[2] = R2; 
		r[3] = R3;
	}
	
	// Initialize matrix by specifying 4 row vectors.
	XMMATRIX(float m00, float m01, float m02, float m03, 
		float m10, float m11, float m12, float m13,
		float m20, float m21, float m22, float m23,
		float m30, float m31, float m32, float m33);
		
	// Pass array of sixteen floats to construct matrix.
	explicit XMMATRIX(_In_reads_(16) const float *pArray);
	
	XMMATRIX& operator= (const XMMATRIX& M)
	{ r[0] = M.r[0]; r[1] = M.r[1]; r[2] = M.r[2]; r[3] = M.r[3]; return *this; }
	XMMATRIX operator+ () const { return *this; }
	XMMATRIX operator- () const;
	XMMATRIX& XM_CALLCONV operator+= (FXMMATRIX M);
	XMMATRIX& XM_CALLCONV operator-= (FXMMATRIX M);
	XMMATRIX& XM_CALLCONV operator*= (FXMMATRIX M);
	XMMATRIX& operator*= (float S);
	XMMATRIX& operator/= (float S);
	XMMATRIX XM_CALLCONV operator+ (FXMMATRIX M) const;
	XMMATRIX XM_CALLCONV operator- (FXMMATRIX M) const;
	XMMATRIX XM_CALLCONV operator* (FXMMATRIX M) const;
	XMMATRIX operator* (float S) const;
	XMMATRIX operator/ (float S) const;
	friend XMMATRIX XM_CALLCONV operator* (float S, FXMMATRIX M);
};

也可以使用XMMatrixSet代替建構函式初始化資料:

XMMATRIX XM_CALLCONV XMMatrixSet(
float m00, float m01, float m02, float m03,
float m10, float m11, float m12, float m13,
float m20, float m21, float m22, float m23,
float m30, float m31, float m32, float m33);

就像使用XMFLOAT2 (2D),XMFLOAT3 (3D)和XMFLOAT4 (4D)一樣,類的成員變數推薦使用XMFLOAT4X4型別:

struct XMFLOAT4X4
{
	union
	{
		struct
		{
			float _11, _12, _13, _14;
			float _21, _22, _23, _24;
			float _31, _32, _33, _34;
			float _41, _42, _43, _44;
		};
		float m[4][4];
	};
	
	XMFLOAT4X4() {}
	XMFLOAT4X4(float m00, float m01, float m02, float m03,
		float m10, float m11, float m12, float m13,
		float m20, float m21, float m22, float m23,
		float m30, float m31, float m32, float m33);
	explicit XMFLOAT4X4(_In_reads_(16) const float *pArray);
	
	float operator() (size_t Row, size_t Column) const { return m[Row][Column]; }
	float& operator() (size_t Row, size_t Column) { return m[Row][Column]; }
		
	XMFLOAT4X4& operator= (const XMFLOAT4X4& Float4x4);
};

使用下面的方法把資料從XMFLOAT4X4載入到XMMATRIX:

inline XMMATRIX XM_CALLCONV XMLoadFloat4x4(const XMFLOAT4X4* pSource);

使用下面的方法把資料東XMMATRIX存到XMFLOAT4X4:

inline void XM_CALLCONV XMStoreFloat4x4(XMFLOAT4X4* pDestination, FXMMATRIX M);

8.2 矩陣的函式

DirectX Math庫包含下面這些有用的函式:

XMMATRIX XM_CALLCONV XMMatrixIdentity(); // Returns the identity matrix I

bool XM_CALLCONV XMMatrixIsIdentity( // Returns true if M is the identity matrix
	FXMMATRIX M); // Input M

XMMATRIX XM_CALLCONV XMMatrixMultiply( // Returns the matrix product AB
	FXMMATRIX A, // Input A
	CXMMATRIX B); // Input B
	
XMMATRIX XM_CALLCONV XMMatrixTranspose( // Returns MT
	FXMMATRIX M); // Input M
	
XMVECTOR XM_CALLCONV XMMatrixDeterminant( // Returns (det M, det M, det M, det M)
	FXMMATRIX M); // Input M
	
XMMATRIX XM_CALLCONV XMMatrixInverse( // Returns M−1
	XMVECTOR* pDeterminant, // Input (det M, det M, det M, det M)
	FXMMATRIX M); // Input M

如果XMMATRIX定義為函式的引數,我們使用XMVECTOR相同的規則,假設有不超過2個FXMVECTOR引數,第一個使用FXMMATRIX,第二個使用CXMMATRIX。下面是32-bit Windows下的例子:

// 32-bit Windows __fastcall passes first 3
XMVECTOR arguments
// via registers, the remaining on the stack.
typedef const XMMATRIX& FXMMATRIX;
typedef const XMMATRIX& CXMMATRIX;

// 32-bit Windows __vectorcall passes first 6
XMVECTOR arguments
// via registers, the remaining on the stack.
typedef const XMMATRIX FXMMATRIX;
typedef const XMMATRIX& CXMMATRIX;

在32-bit Windows with __fastcall下,一個XMMATRIX不能傳遞到SSE/SSE2暫存器,因為只支援3個XMVECTOR,而XMMATRIX有4個;所以矩陣只能傳遞到堆疊。
如果想要了解其它平臺上如何定義這些型別,可以閱讀官方文件中,在“Library Internals”之下的“Calling Conventions”。
上述規則對於建構函式是例外,建構函式一直使用CXMMATRIX,並且不要新增XM_CALLCONV


8.3 DirectX Math矩陣示例程式

#include <windows.h> // for XMVerifyCPUSupport
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <iostream>

using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;

// Overload the "<<" operators so that we can use cout to
// output XMVECTOR and XMMATRIX objects.
ostream& XM_CALLCONV operator << (ostream& os, FXMVECTOR v)
{
	XMFLOAT4 dest;
	XMStoreFloat4(&dest, v);
	os << "(" << dest.x << ", " << dest.y << ", " << dest.z << ", " << dest.w << ")";
	return os;
}

ostream& XM_CALLCONV operator << (ostream& os, FXMMATRIX m)
{
	for (int i = 0; i < 4; ++i)
	{
		os << XMVectorGetX(m.r[i]) << "\t";
		os << XMVectorGetY(m.r[i]) << "\t";
		os << XMVectorGetZ(m.r[i]) << "\t";
		os << XMVectorGetW(m.r[i]);
		os << endl;
	}
	return os;
}

int main()
{
	// Check support for SSE2 (Pentium4, AMD K8, and above).
	if (!XMVerifyCPUSupport())
	{
		cout << "directx math not supported" << endl;
		return 0;
	}
	
	XMMATRIX A(1.0f, 0.0f, 0.0f, 0.0f,
		0.0f, 2.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 4.0f, 0.0f,
		1.0f, 2.0f, 3.0f, 1.0f);
	XMMATRIX B = XMMatrixIdentity();
	XMMATRIX C = A * B;
	XMMATRIX D = XMMatrixTranspose(A);
	XMVECTOR det = XMMatrixDeterminant(A);
	XMMATRIX E = XMMatrixInverse(&det, A);
	XMMATRIX F = A * E;
	
	cout << "A = " << endl << A << endl;
	cout << "B = " << endl << B << endl;
	cout << "C = A*B = " << endl << C << endl;
	cout << "D = transpose(A) = " << endl << D << endl;
	cout << "det = determinant(A) = " << det << endl << endl;
	cout << "E = inverse(A) = " << endl << E << endl;
	cout << "F = A*E = " << endl << F << endl;
	
	return 0;
}

這裡寫圖片描述



9 總結

  1. 一個m x n的矩陣M是一個有實陣列成的m行n列的矩陣。具有相同行和列的矩陣可以通過把每個對應元素相加來進行加法運算;把每個元素與一個標量相乘來進行與標量的乘法運算;
  2. 如果一個m x n的矩陣A和一個n x p的矩陣B相乘,結果是一個m x p的矩陣C,其第ij個元素的值是A矩陣中第i列向量與B矩陣第j行向量的點積: C i j = A i , B , j C_{ij} = A_{i,*} \cdot B_{*,j}
  3. 矩陣的乘法不符合交換律,但是滿足結合律;
  4. 轉置矩陣是由交換矩陣的行和列得到的,所以m x n的轉置矩陣是n x m,我們使用 M T M^T 來表示矩陣M的轉置矩陣;
  5. 單位矩陣是一類特殊的矩陣,其對角線上的元素值為1,其它元素值為0;
  6. 矩陣的行列式是一個特殊的方法,輸入一個方形矩陣,輸出一個實數,矩陣的行列式用 det A 來表示;一個方形矩陣A當且僅當 det A != 0時可逆;行列式用來計算逆矩陣;
  7. 一個矩陣乘以它的逆矩陣得到的是一個單位矩陣;一個矩陣如果存在逆矩陣,那麼逆矩陣是唯一的;只有方形矩陣可能具有逆矩陣,也可能是不可逆的;逆矩陣的計算公式如下:這裡寫圖片描述
  8. 在計算的時候,我們使用XMMATRIX型別來進行高效的使用SIMD計算,對於類的成員變數,我們使用XMFLOAT4X4;然後使用Loading和Storage方法在XMMATRIX和XMFLOAT4X4之間載入和儲存;XMMATRIX類中過載了加減法,矩陣乘法和標量乘法的運算,更進一步,DirectX Math庫也提供了計算單位矩陣,矩陣的乘法,矩陣的轉置矩陣,行列式和逆矩陣的有用的方法:
XMMATRIX XM_CALLCONV XMMatrixIdentity();
XMMATRIX XM_CALLCONV XMMatrixMultiply(FXMMATRIX A, CXMMATRIX B);
XMMATRIX XM_CALLCONV XMMatrixTranspose(FXMMATRIX M);
XMVECTOR XM_CALLCONV XMMatrixDeterminant(FXMMATRIX M);
XMMATRIX XM_CALLCONV XMMatrixInverse(XMVECTOR* pDeterminant, FXMMATRIX M);


10 練習題

  1. 解下面的方程:
    這裡寫圖片描述
  2. 計算下面矩陣的乘積:
    這裡寫圖片描述
  3. 計算下面矩陣的轉置矩陣:
    這裡寫圖片描述
  4. 將下列線性組合寫成向量和矩陣的乘積:
    這裡寫圖片描述
  5. 證明:
    這裡寫圖片描述
  6. 證明:
    這裡寫圖片描述
  7. 證明向量的叉積可以解釋為矩陣的乘積:
    這裡寫圖片描述
  8. 證明B是否為A的逆矩陣:
    這裡寫圖片描述
  9. 證明B是否為A的逆矩陣:
    這裡寫圖片描述
  10. 計算下列矩陣的行列式:
    這裡寫圖片描述
  11. 計算下列矩陣的逆矩陣:
    這裡寫圖片描述
  12. 下列矩陣是否是可逆的:
    這裡寫圖片描述
  13. 假設A是可逆的,證明: ( A 1 ) T = ( A T ) 1 (A^{-1})^T = (A^T)^{-1}
    這裡寫圖片描述
  14. 令A,B是n x n的矩陣,線上性代數的書中已經被證明 det(AB) = detA detB,det I = 1,假設A是可逆的,利用上面兩個已經被證明的公式,證明: A 1 = 1 d e t A A^{-1} = \frac{1}{detA}
    這裡寫圖片描述

這裡寫圖片描述
16. 計算由下列向量組成的平行四邊形的面積:
這裡寫圖片描述
17. 證明矩陣的乘法符合結合律:
這裡寫圖片描述
18. 不使用DirectX Math,只使用C++的array,編寫求矩陣轉置矩陣的函式:
19. 不使用DirectX Math,只使用C++的array,編寫求4 x 4矩陣逆矩陣的函式:
18、19題程式碼工程路徑:
https://github.com/jiabaodan/Direct12BookReadingNotes
執行截圖如下:
這裡寫圖片描述