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. 矩阵的减法可以由矩阵的加法和矩阵与标量的乘法来定义。

    因为矩阵的加法和标量乘法是按元素的,所以它们的一些属性可以继承自实数:

    Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数


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}Cij​=Ai,∗​⋅B∗,j​

Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数

所以矩阵相乘需要A的列数和B的行数相等,否则点积运算则无法进行。比如上面例子中BA无法进行,所以也验证了AB != BA,矩阵运算不支持交换律


2.2 向量与矩阵的乘法

考虑下面矩阵的乘法:

Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数

Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数

所以

Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数

上面等式是一个线性组合的例子,可以继续扩展到1 x n乘以 n x n的情况:

Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数


2.3 结合律

矩阵的乘法具有一些不错的代数特性,比如乘法分配率:A(B + C) = AB + AC)和(A + B)C = AC + BC,

后续我们将会经常使用乘法结合律,来选择矩阵相乘的顺序:

(AB)C=A(BC) (AB)C = A(BC) (AB)C=A(BC)



3 转置矩阵

转置矩阵是由交换矩阵的行和列得到的,所以m x n的转置矩阵是n x m,我们使用MTM^TMT来表示矩阵M的转置矩阵。

转置矩阵由一些有用的特性:

Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数



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ˉij​是删除A第i行和第j列元素后的矩阵。

Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数


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}detA=j=1∑n​A1j​(−1)1+jdetAˉ1j​

对于2 x 2矩阵,公式如下:

Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数

对于3 x 3矩阵,公式如下:

Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数

对于4 x 4矩阵,公式如下:

Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数

在3D图形学中,我们主要使用4 x 4矩阵,所以不需要扩展到n。



6 伴随矩阵

令A是一个n x n的矩阵,那么Cij=(−1)i+jdetAˉijC_{ij} = (-1)^{i+j} det \bar A_{ij}Cij​=(−1)i+jdetAˉij​就是AijA_{ij}Aij​的cofactor,如果我们用对应的cofactor替换掉矩阵A中所有的元素,得到的CAC_ACA​就是矩阵A的cofactor矩阵:

Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数

那么CAC_ACA​的转置矩阵就是A的伴随矩阵:

A∗=CATA^* = C_A^T A∗=CAT​

伴随矩阵可以帮助定义一个简洁的逆矩阵公式。



7 逆矩阵

矩阵的运算没有除法,但是可以乘以逆矩阵,下面总结一些逆矩阵的重要信息:

  1. 只有方形矩阵包含逆矩阵;
  2. 使用M−1M^{-1}M−1表示矩阵M的逆矩阵;
  3. 不是所有方形矩阵都有逆矩阵,有逆矩阵的矩阵我们称之为可逆的;
  4. 逆矩阵如果有,就是唯一的;
  5. 矩阵乘以它的逆矩阵,结果是单位矩阵;

逆矩阵在求解方程的时候很有用,比如要求解矩阵P:

Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数

逆矩阵计算公式:Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数

逆矩阵的一个有用的代数特性:Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数

其证明如下:

Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数



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;
}

Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数



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}Cij​=Ai,∗​⋅B∗,j​;
  3. 矩阵的乘法不符合交换律,但是满足结合律;
  4. 转置矩阵是由交换矩阵的行和列得到的,所以m x n的转置矩阵是n x m,我们使用MTM^TMT来表示矩阵M的转置矩阵;
  5. 单位矩阵是一类特殊的矩阵,其对角线上的元素值为1,其它元素值为0;
  6. 矩阵的行列式是一个特殊的方法,输入一个方形矩阵,输出一个实数,矩阵的行列式用 det A 来表示;一个方形矩阵A当且仅当 det A != 0时可逆;行列式用来计算逆矩阵;
  7. 一个矩阵乘以它的逆矩阵得到的是一个单位矩阵;一个矩阵如果存在逆矩阵,那么逆矩阵是唯一的;只有方形矩阵可能具有逆矩阵,也可能是不可逆的;逆矩阵的计算公式如下:Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数
  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. 解下面的方程:

    Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数
  2. 计算下面矩阵的乘积:

    Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数
  3. 计算下面矩阵的转置矩阵:

    Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数
  4. 将下列线性组合写成向量和矩阵的乘积:

    Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数
  5. 证明:

    Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数
  6. 证明:

    Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数
  7. 证明向量的叉积可以解释为矩阵的乘积:

    Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数
  8. 证明B是否为A的逆矩阵:

    Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数
  9. 证明B是否为A的逆矩阵:

    Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数
  10. 计算下列矩阵的行列式:

    Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数
  11. 计算下列矩阵的逆矩阵:

    Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数
  12. 下列矩阵是否是可逆的:

    Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数
  13. 假设A是可逆的,证明:(A−1)T=(AT)−1(A^{-1})^T = (A^T)^{-1}(A−1)T=(AT)−1

    Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数
  14. 令A,B是n x n的矩阵,在线性代数的书中已经被证明 det(AB) = detA detB,det I = 1,假设A是可逆的,利用上面两个已经被证明的公式,证明:A−1=1detAA^{-1} = \frac{1}{detA}A−1=detA1​

    Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数

Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数

16. 计算由下列向量组成的平行四边形的面积:

Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数

17. 证明矩阵的乘法符合结合律:

Introduction to 3D Game Programming with DirectX 12  学习笔记之 --- 第二章:矩阵代数

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  学习笔记之 --- 第二章:矩阵代数

上一篇:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第九章:贴图


下一篇:java.lang.RuntimeException: Unable to instantiate activity ComponentInfo异常(转)