这是一篇一看到底的文章,主要是全面剖析矩阵。众所周知,矩阵是一个由数值排列成的矩形阵列。矩阵在CG图形流水线中起着重要作用,你会在 3D 应用程序的代码中经常看到它们的使用。矩阵没有什么复杂的;如果你害怕它们,那可能是因为你还没有完全理解它们的工作机理。所以让我们来一起解决它,这篇文章我将会把所有关于矩阵的知识罗列出来。希望后续同学有关于矩阵的知识都可以在下面留言,不断丰富文章。
为了更易于理解矩阵的概念,我们将从实际的矩阵示例开始,而不是使用抽象的数学定义。通过观察具体的例子,我们可以更容易地将这些概念扩展到其通用/数学形式。在计算机图形学中,我们通常使用二维数组来表示矩阵。这些数组使用标准符号 mxn
进行定义,其中 m 和 n 是表示数组大小的两个数字。在矩阵中,m 表示矩阵的行数,而 n 则表示矩阵的列数。行代表着二维数组中水平的数字行,而列则代表垂直的数字列。下面是一个 [3x5] 矩阵的示例:
$$ \begin{bmatrix} 11&3&72&9&90\\ 32&3&20&8&3\\ 92&1&10&0&1 \end{bmatrix} $$
进而我们可以生成下面通用的公式
$$ \begin{bmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m,1} & a_{m,2} & \cdots & a_{m,n} \end{bmatrix} $$
上述代码将会生成一个 m×n
的矩阵,其中每个元素都用 Ai,j
表示。i指的是行号和j指的是列号。
矩阵秩是矩阵理论中的一个重要概念,它描述了矩阵所包含的线性无关向量的个数。
矩阵秩的通俗易懂定义是:一个矩阵的秩就是它所包含的线性无关列向量(或行向量)的最大数量。可以简单地理解为一个矩阵中不重复的列向量(或行向量)的数量。
举例来说,考虑以下3x3
矩阵:
$$ A = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{bmatrix} $$
我们可以观察到第一列是(1,4,7)
,第二列是(2,5,8
,第三列是 (3,6,9)
。我们可以看出,这些列向量并不是线性无关的。为了证明这一点,我们可以使用矩阵的初等变换将矩阵变换为其行简化阶梯形式:
$$ \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{bmatrix} \to \begin{bmatrix} 1 & 2 & 3 \\ 0 & -3 & -6 \\ 0 & -6 & -12 \end{bmatrix} \to \begin{bmatrix} 1 & 2 & 3 \\ 0 & -3 & -6 \\ 0 & 0 & 0 \end{bmatrix} $$
从这里可以看出,矩阵的第一列包含了基本变量,而其他两列不包含任何基本变量,因此 $A$ 的秩是 $1$。
换句话说,我们可以将 $A$ 看作是一组线性相关的向量 (1,4,7)
、(2,5,8)
和 (3,6,9)
构成的集合。因此,A
的秩为 2
。
但是通常通常,在 CG 中,我们会对·3x3
或 4x4
矩阵感兴趣(也就是我们数学中常说的方阵),我们将在下一章中告诉您它们是什么以及如何使用它们。这些矩阵通常称为方阵(如果 m = n
,则矩阵 [mxn]
是方阵)。这是一种简化,因为 m 和 n 可以取任何值并且不必相等。例如,您可以创建 3x1
矩阵、6x6
矩阵或 4x2
矩阵。它们都是有效的矩阵。但正如我们所说,我们将主要在 CG 中使用 3x3
和 4x4
矩阵。
(1)3x3
矩阵
$$ \begin{bmatrix} 7&4&3\\ 2&0&3\\ 3&9&1\\ \end{bmatrix} $$
(2)4x4
矩阵
$$ \begin{bmatrix} 7&1&4&3\\ 2&0&0&3\\ 3&1&9&1\\ 6&6&5&4\\ \end{bmatrix} $$
下面我们看在着色器(类C)中实现矩阵:
myMatrix3x3 = mat3(1.0, 2.0, 3.0,
4.0, 5.0, 6.0,
7.0, 8.0, 9.0);
myMatrix4x4 = mat4(1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
冰哥说:在C++中表示方法和shader中是类似的,就是API方式不一样。但是在javascript
中不能直接表示矩阵,而是通过数组形式表示。通过API截取不同长度的数组值,去划分矩阵的行和列。
顾名思义,在矩阵正对角线不为0,其余位置都为0的方阵就是对角矩阵。可以参考下面的矩阵例子:
$$ \begin{bmatrix} 7&0&0\\ 0&1&0\\ 0&0&1\\ \end{bmatrix} $$
shader中实现对角矩阵如下代码所示:
mat4 diagonalMatrix = mat4(2.0, 0.0, 0.0, 0.0,
0.0, 3.0, 0.0, 0.0,
0.0, 0.0, 4.0, 0.0,
0.0, 0.0, 0.0, 1.0);
单位矩阵是一种特殊的对角矩阵。对角元素为 1,其他元素为0。请看下面的矩阵例子:
$$ \begin{bmatrix} 1&0&0\\ 0&1&0\\ 0&0&1\\ \end{bmatrix} $$
shader中一般这样声明单位矩阵:
mat3 Matrix = mat3(1.0);
冰哥说:单位矩阵非常特殊,用任意一个矩阵乘以单位矩阵,都将得到原矩阵。我们回忆大学的线性代数,我们一般用单位矩阵去推导公式,两个互为逆矩阵相乘得到单位矩阵,反之我们可以将单位矩阵分解为两个逆矩阵去推导其他公式。
奇异矩阵是线性代数的概念,就是该矩阵的秩不是满秩。
首先,看这个矩阵是不是方阵(即行数和列数相等的矩阵,若行数和列数不相等,那就谈不上奇异矩阵和非奇异矩阵)。如是方阵,再看此矩阵的行列式|A|是否等于0,若等于0,称矩阵A为奇异矩阵;若不等于0,称矩阵A为非奇异矩阵。
伴随矩阵(Adjoint matrix)也称作伴随阵或伴随矩阵,是方阵的一个重要概念。设 A 是 n 阶方阵,A 的伴随矩阵记为 adj(A),其定义如下:
我们来看看下面这个例子:
假设有矩阵
$$ A = \begin{pmatrix} 2 & 3 \\ 5 & 4 \end{pmatrix} $$
首先,我们需要计算A
的行列式,就是
$$ |A| = 2 \times 4 - 3 \times 5 = -7 $$
接下来,我们需要计算 A
的代数余子式,也就是每个元素的代数余子式。对于这个矩阵,代数余子式的计算方法是交替取符号的次序行列式,而每个次序行列式就是去掉对应行和列后的矩阵的行列式。
因此,我们可以得到:
$$ A_{11} = (-1)^{1+1} \begin{vmatrix} 4 \end{vmatrix} = 4 \\ A_{12} = (-1)^{1+2} \begin{vmatrix} 5 \end{vmatrix} = -5 \\ A_{21} = (-1)^{2+1} \begin{vmatrix} 3 \end{vmatrix} = -3 \\ A_{22} = (-1)^{2+2} \begin{vmatrix} 2 \end{vmatrix} = 2 $$
然后,我们将这些代数余子式放入矩阵中,得到矩阵 A
的伴随矩阵:
$$ adj(A) = \begin{pmatrix} A_{11} & A_{21} \\ A_{12} & A_{22} \end{pmatrix}^T = \begin{pmatrix} 4 & -3 \\ -5 & 2 \end{pmatrix} $$
逆矩阵是指对于一个给定的方阵A,如果存在一个方阵B使得A乘以B等于B乘以A等于单位矩阵I,则称方阵B为A的逆矩阵,记作
$$ A^{-1} $$
其中,我们上面讲到单位矩阵指的是对角线上所有元素都为1,其余元素都为0的方阵。
冰哥说:要求一个矩阵A的逆矩阵,需要保证以下两个条件都满足:
如果以上两个条件都满足,则可以使用伴随矩阵求解逆矩阵。具体步骤如下:
计算逆矩阵A的值为
$$ A^{-1}=\frac{1}{|A|}adj(A) $$
需要注意的是,当矩阵A是奇异矩阵时,即行列式为0时,该矩阵没有逆矩阵。此外,逆矩阵不一定存在,但如果存在,则是唯一的。
我们来看看shader中或者其他矩阵转换库中如何实现逆矩阵转换,具体转换思路和上面过程是类同的,我将在代码层面注明关键部分:
mat3 inverse(mat3 m) {
//将3X3矩阵中各个元素的值分解开来,方便后续使用
float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2];
float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2];
float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2];
//求伴随矩阵
float b01 = a22 * a11 - a12 * a21;
float b11 = -a22 * a10 + a12 * a20;
float b21 = a21 * a10 - a11 * a20;
//求行列式
float det = a00 * b01 + a01 * b11 + a02 * b21;
//求逆矩阵 伴随矩阵中各个元素分别除以行列式的值
return mat3(
b01, (-a22 * a01 + a02 * a21), (a12 * a01 - a02 * a11),
b11, (a22 * a00 - a02 * a20), (-a12 * a00 + a02 * a10),
b21, (-a21 * a00 + a01 * a20), (a11 * a00 - a01 * a10)
) / det;
}
冰哥说:我们以三阶矩阵为例子,因为常用的就三阶矩阵或者四阶矩阵。
考虑一个RXC
矩阵 M。M 的转置记作MT,是一个RXC
矩阵,它的列由 M 的行组成。可以从另一方面理解,MT=M
,即沿着矩阵的对角线翻折。如下面这个例子
$$ \left[\begin{matrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \\ \end{matrix}\right]^{T} = \left[\begin{matrix} 1 & 4 & 7 \\ 2 & 5 & 8 \\ 3 & 6 & 9 \\ \end{matrix}\right] $$
冰哥说:对于任意矩阵 M,(MT)T=M
。从另一方面来说,将一个矩阵转置后,再转置一次,便会得到原矩阵。这条法则对向量也适用,对于任意对角矩阵 D,都有 DT =D
,包括单位矩阵I也如此。
我们来看看shader中或者其他矩阵转换库中如何实现矩阵转置,具体转换思路和上面过程是类同的,只不过用代码实现了一遍,我将在代码层面注明关键部分:
mat4 transpose(mat4 m) {
return mat4(m[0][0], m[1][0], m[2][0], m[3][0],
m[0][1], m[1][1], m[2][1], m[3][1],
m[0][2], m[1][2], m[2][2], m[3][2],
m[0][3], m[1][3], m[2][3], m[3][3]);
}
我们基本将矩阵里的常见概念做了总结和梳理,后续我们继续讨论关于矩阵的高级用法!
666
讲得太好了。