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

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

bin 存在 present 文檔 spec 它的 height math.h defined

原文: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行向量的點積:
Cij=Ai,??B?,jC_{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,
後續我們將會經常使用乘法結合律,來選擇矩陣相乘的順序:
(AB)C=A(BC) (AB)C = A(BC)



3 轉置矩陣

轉置矩陣是由交換矩陣的行和列得到的,所以m x n的轉置矩陣是n x m,我們使用MTM^T來表示矩陣M的轉置矩陣。
轉置矩陣由一些有用的特性:
技術分享圖片



4 單位矩陣

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



5 矩陣的行列式

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


5.1 Matrix Minors(子矩陣?)

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


5.2 矩陣行列式的定義

矩陣的行列式的定義是遞歸的,4 x 4的矩陣依據3 x 3,直到1 x 1;如果A是一個n x n的矩陣,並且n > 1,那麽:
detA=j=1nA1j(?1)1+jdetAˉ1jdet 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的矩陣,那麽Cij=(?1)i+jdetAˉijC_{ij} = (-1)^{i+j} det \bar A_{ij}就是AijA_{ij}的cofactor,如果我們用對應的cofactor替換掉矩陣A中所有的元素,得到的CAC_A就是矩陣A的cofactor矩陣:
技術分享圖片
那麽CAC_A的轉置矩陣就是A的伴隨矩陣:
A?=CATA^* = C_A^T
伴隨矩陣可以幫助定義一個簡潔的逆矩陣公式。



7 逆矩陣

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

  1. 只有方形矩陣包含逆矩陣;
  2. 使用M?1M^{-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行向量的點積:Cij=Ai,??B?,jC_{ij} = A_{i,*} \cdot B_{*,j}
  3. 矩陣的乘法不符合交換律,但是滿足結合律;
  4. 轉置矩陣是由交換矩陣的行和列得到的,所以m x n的轉置矩陣是n x m,我們使用MTM^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=(AT)?1(A^{-1})^T = (A^T)^{-1}
    技術分享圖片
  14. 令A,B是n x n的矩陣,在線性代數的書中已經被證明 det(AB) = detA detB,det I = 1,假設A是可逆的,利用上面兩個已經被證明的公式,證明:A?1=1detAA^{-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
運行截圖如下:
技術分享圖片

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