本章由wzy翻译,感谢wzy对本章的用心翻译。

翻译人:wzy
翻译人联系方式:838168242@qq.com
代为发布人与审核人:StubbornHuang

6 变换矩阵

线性代数的原理可以用来在三维场景中排列对象、使用相机查看对象以及将其显示在屏幕上等等。像旋转、平移、缩放和投影这样的几何变换,可以通过矩阵乘法来实现,而用于实现这一点的变换矩阵就是本章的主题。

我们将向您展示,如果一组点由相对于原点的偏移向量表示,它们是如何被变换的,我们将使用图6.1中所示的时钟作为点集的示例。把时钟想象成一堆点,这些点是向量的头,而向量的尾巴在原点。我们还会讨论这些变换对位置(点)、位移向量和曲面法向量所产生的不同作用。

6.1 二维线性变换

我们可以使用2\times2矩阵来变换二维向量:

\begin{bmatrix}
a_{11}& a_{12} \\
a_{21}& a_{22} \\

\end{bmatrix}\begin{bmatrix}
x \\
y \\

\end{bmatrix}=\begin{bmatrix}
a_{11}x +a_{12}y\\
a_{21}x+a_{22}y \\

\end{bmatrix}

这种运算是一种线性变换,它接受一个二维向量,并通过简单的矩阵乘法生成另一个二维向量。

​ 通过这个简单的公式,我们可以实现各种有用的转换,这取决于我们在矩阵中输入的内容,这将在以下部分中讨论。就目前来说,我们要考查沿着X轴的水平移动和沿Y轴的垂直运动。

6.1.1 缩放

沿坐标轴的缩放是最基本的变换。此变换可以改变长度,有时也会改变方向:

scale(s_x,s_y)=\begin{bmatrix}
s_x& 0\\
0& s_y\\

\end{bmatrix}

注意此矩阵对具有笛卡尔分量(x,y)的向量的作用:

\begin{bmatrix}
s_x& 0\\
0& s_y\\

\end{bmatrix}\begin{bmatrix}
x \\
y \\

\end{bmatrix}=\begin{bmatrix}
s_xx \\
s_yy \\

\end{bmatrix}

所以,从这个沿着轴线进行缩放的矩阵中,我们就可以读出两个缩放系数。

Example.

能够将xy均匀收缩2倍的矩阵为(图6.1):

scale(0.5,0.5)=\begin{bmatrix}
0.5&0\\
0&0.5
\end{bmatrix}

水平方向缩短一半,垂直方向伸长为1.5倍的矩阵为(见图6.2):

scale(0.5,1.5)=\begin{bmatrix}
0.5&0\\
0&1.5
\end{bmatrix}

6.1.2 剪切

剪切变换是一种把东西推到一边的变换,产生的效果就像一副放在桌子上的牌,用手推他;底部的牌保持位置不变,而越靠近牌组顶部的牌,移动得就越多。水平和垂直方向的剪切变换矩阵如下所示:

shear-x(s)=\begin{bmatrix}
1&s\\
0&1
\end{bmatrix},shear-y(s)=\begin{bmatrix}
1&0\\
s&1
\end{bmatrix}

Example.

水平方向的剪切变换使原本垂直的线变为向右倾斜45度的线(见图6.3):

shear-x(1)=\begin{bmatrix}
1&1\\
0&1
\end{bmatrix}

垂直方向的变换类似(见图6.4):

shear-y(1)=\begin{bmatrix}
1&0\\
1&1
\end{bmatrix}

在这两种情况下,被剪切时钟的方形边框变成为平行四边形,事实上,在经过任何矩阵的变换下,圆都会变成椭圆。被剪切的时钟的圆形表盘也变成了一个椭圆。

​ 另一种理解剪切变换的方法是依据只旋转垂直(或水平)轴。将垂直轴按顺时针方向倾斜\phi角的剪切变换为:

\begin{bmatrix}
1&\tan{\phi}\\
0&1
\end{bmatrix}

类似的,按逆时针旋转水平轴,旋转角度为\phi的剪切变换为:

\begin{bmatrix}
1&0\\
\tan{\phi}&1
\end{bmatrix}

6.1.3 旋转

假设我们想将一个向量a逆时针旋转一个角度\phi,得到向量b(图6.5)。如果ax轴成一个角度\alpha,且其长度为r=\sqrt{x_a^2+y_a^2},则我们知道:

x_a=r\cos{\alpha},\\
y_a=r\sin{\alpha}.

因为b是由a旋转得到的,所以它的长度也是r。因为b是从a旋转一个角度\phi得来的,所以bx轴所成的角度是(\alpha+\phi)。使用三角函数的加法恒等式有(第2.3.3节):

x_b=r\cos{(\alpha+\phi)}=r\cos{\alpha}\cos{\phi}-r\sin{\alpha}\sin{\phi},\\
y_b=r\sin{(\alpha+\phi)}=r\sin{\alpha}\cos{\phi}+r\cos{\alpha}\sin{\phi}

x_a=r \cos{\alpha}y_a=r \sin{\alpha}进行替换得到:

x_b=x_a\cos{\phi}-y_asin{\phi},\\
y_b=y_a\cos{\phi}+x_asin{\phi}.

用矩阵形式表达的话,ab的变换为:

rotate(\phi)=\begin{bmatrix}
\cos{\phi}&-\sin{\phi}\\
\sin{\phi}&\cos{\phi}
\end{bmatrix}.

Example.

能够将向量旋转\pi/4弧度(45度)的矩阵为(见图6.6):

\begin{bmatrix}
\cos{\pi/4}&-\sin{\pi/4}\\
\sin{\pi/4}&\cos{\pi/4}
\end{bmatrix}=\begin{bmatrix}
0.707&-0.707\\
0.707&0.707
\end{bmatrix}.

按顺时针方向旋转\pi/6弧度(30度)的矩阵,和按逆时针旋转-\pi/6的矩阵,是一样的。(见图6.7):

\begin{bmatrix}
\cos{-\pi/6}&-\sin{-\pi/6}\\
\sin{-\pi/6}&\cos{-\pi/6}
\end{bmatrix}=\begin{bmatrix}
0.866&0.5\\
-0.5&0.866
\end{bmatrix}.

因为对于旋转变换矩阵的每一行都有\sin^2{\phi}+\cos^2{\phi}=1,并且,每行又都是正交的(\cos{\phi}(-\sin{\phi})+\sin\phi\cos\phi)=0),所以我们能够得到,旋转变换矩阵,其实是正交矩阵。通过观察矩阵,我们能够找到两对相互正交的向量,其中,一对列向量,是变换向其发送规范基向量(1,0)和(0,1)的向量;一对行向量,是变换发送到规范基向量的向量。

6.1.4 反射

我们可以通过带有一个负的缩放系数的矩阵来根据任一坐标轴反射变换一个向量(见图6.8和6.9):

reflect-y=\begin{bmatrix}
-1&0\\
0&1
\end{bmatrix},reflect-x=\begin{bmatrix}
1&0\\
0&-1
\end{bmatrix}.

有的人可能认为,将矩阵对角线上的所有元素都乘以-1能得到放映变换,但实际上那只是做了一个\pi弧度的旋转。

6.1.5 变换的合成与分解

图形程序通常需要对一个物体运用多个变换。例如,我们可能希望首先施加一个缩放变换S,然后施加一个旋转变换R。这将分两步施加在一个二维向量v_1上:

first,v_2 = Sv_1, then,v_3 = Rv_2.

另一种写法是:

v_3 = R (Sv_1).

由于矩阵乘法的结合律,我们也可以写成:

v_3 = (RS) v_1.

换句话说,我们可以用一个同样尺寸的矩阵,达到与两个矩阵按一定顺序变换向量同样的效果,我们可以通过将两个变换矩阵相乘来得到这个矩阵:M=RS(图6.10)。

​ 请务必记住,这些变换首先是从右侧开始施加的。所以矩阵M=RS首先施加变换S,然后施加变换R

Example.

假如我们想在垂直方向上缩小一半,然后旋转\pi/4弧度(45度)。得到的矩阵是:

\begin{bmatrix}
0.707&-0.707\\
0.707&0.707
\end{bmatrix}\begin{bmatrix}
1&0\\
0&0.5
\end{bmatrix}=\begin{bmatrix}
0.707&-0.353\\
0.707&0.353
\end{bmatrix}

务必记住矩阵乘法不是可交换的。所以变换的顺序很重要。在本例中,先旋转,然后缩放,会产生不同的矩阵(见图6.11):

\begin{bmatrix}
1&0\\
0&0.5
\end{bmatrix}\begin{bmatrix}
0.707&-0.707\\
0.707&0.707
\end{bmatrix}=\begin{bmatrix}
0.707&-0.707\\
0.353&0.353
\end{bmatrix}

Example.

根据我们之前提到的缩放矩阵,沿坐标轴只能进行非均匀缩放。如果我们想把时钟沿其中一条对角线拉伸50%,以便8点到1点在西北方向,2点到7点在东南方向,我们可以将旋转矩阵与沿轴的缩放矩阵结合使用,以获得我们想要的结果。其思路是使用旋转变换将缩放轴与坐标轴对齐,然后沿该轴进行缩放,然后再旋转回来。在我们的示例中,缩放轴是正方形的“反斜杠”对角线,我们可以旋转+45度使其与x轴平行。将这些操作整合在一起,完整的变换是:

rotate(−45^◦)scale(1.5, 1)rotate(45^◦).

如果用数学符号来表达,这个过程可以写成RSR^T。将这三个矩阵相乘的结果是

\begin{bmatrix}
1.25&-0.25\\
-0.25&1.25
\end{bmatrix}

用旋转和缩放来合成变换实际上可以胜任任何线性变换,这一点很重要,下一节我们将着重讨论。

6.1.6 变换的分解

有时,有必要“撤消”转换的组合,将转换分解为更简单的部分。例如,当向用户展示变换过程时,对单独的旋转和缩放系数进行操作是很有好处的,但往往一整个变换过程被简单的整合在了一个矩阵的内部,旋转变换和缩放变换被混合在一起。如果可以通过计算将变换矩阵分解为所需的片段,调整片段,并通过将调整后的片段再次相乘来重新组装成矩阵,就可以实现这种操作。

​ 事实证明,无论矩阵中的元素是什么,这种分解或者说是因式分解都是可能被实现的,这一事实提供了一种富有成效的方式来思考变换以及变换对几何体所带来的影响。

对称特征值分解

让我们从对称矩阵开始。回想第5.4节,对称矩阵总是可以通过特征值分解为乘积的形式,如下:

A = RSR^T

其中R是正交矩阵,S是对角矩阵;我们将以v_1v_2的名字称呼R的列向量(特征向量),用名称\lambda_1\lambda_2称呼S的对角项(特征值)。

​ 在几何方面,我们现在可以将R视为旋转,将S视为缩放,因此这只是一个多步骤几何变换(图6.13):

  1. v_1v_2旋转到x轴和y轴(通过R^T进行变换)。
  2. (\lambda_1,\lambda_2)xy上缩放(通过S进行变换)。
  3. x轴和y轴旋转回v_1v_2(通过R进行变换)。

同时观察这三种变换的效果,我们可以看到它们具有沿一对轴的非均匀缩放的效果。与沿坐标轴缩放一样,轴是垂直的,但它们不是坐标轴;反而是A的特征向量。这告诉我们对称矩阵的本质是:对称矩阵只是缩放操作,尽管这个缩放可能是非均匀和非沿坐标轴的操作。

Example.

回顾5.4节的例子:

\begin{bmatrix}
2 & 1 \\
1 & 1 \\

\end{bmatrix}=R\begin{bmatrix}
\lambda_1 & 0 \\
0 & \lambda_2 \\

\end{bmatrix}R^T=\\
\begin{bmatrix}
0.8507 & -0.5257 \\
0.5257 & 0.8507 \\

\end{bmatrix}\begin{bmatrix}
2.618 & 0 \\
0 & 0.382 \\

\end{bmatrix}\begin{bmatrix}
0.8507 & 0.5257 \\
-0.5257 & 0.8507 \\

\end{bmatrix}
\\=rotate(31.7^。)scale(2.618,0.382)rotate(-31.7^。)

然后,根据其特征值分解,上述矩阵按逆时针方向从三点钟(即x轴)旋转31.7^。,然后在该方向进行缩放。如图6.14所示,这大概是下午2点之前的方向。

​ 我们也可以反转对角化过程;用(\lambda_1,\lambda_2)进行缩放,第一个缩放方向按顺时针与x轴成一个角度\phi,我们有:

\begin{bmatrix}
\cos{\phi}&\sin{\phi}\\
-\sin{\phi}&\cos{\phi}
\end{bmatrix}\begin{bmatrix}
\lambda_1 & 0 \\
0 & \lambda_2 \\

\end{bmatrix}\begin{bmatrix}
\cos{\phi}&-\sin{\phi}\\
\sin{\phi}&\cos{\phi}
\end{bmatrix}=
\begin{bmatrix}
\lambda_1\cos^2{\phi}+\lambda_2\sin^2{\phi}&(\lambda_2-\lambda_1)\cos{\phi}\sin{\phi}\\
(\lambda_2-\lambda_1)\cos{\phi}\sin{\phi}&\lambda_2\cos^2{\phi}+\lambda_1\sin^2{\phi}
\end{bmatrix}

我们应该牢记,这是一个对称矩阵,因为我们是从对称特征值分解构造它。

奇异值分解

非对称矩阵也可以进行一种非常类似的分解:这就是奇异值分解(SVD),在第5.4.1节中我们已经讨论过了。不同之处在于,对角矩阵两侧的矩阵不再相同:

A = USV^T

替换掉R的是两个正交矩阵UV,它们的列分别是u_i(左奇异向量)和v_i(右奇异向量)。在这种情况下,S的对角线元素被称为奇异值而不是特征值。几何意义与对称特征值分解的几何意义非常相似(图6.15):

  1. v_1v_2旋转到x轴和y轴(通过V^T进行变换)。
  2. (\sigma_1,\sigma_2)xy上缩放(通过S进行变换)。
  3. x轴和y轴旋转回u_1u_2(通过U进行变换)。

主要区别在于单个旋转变换矩阵和两个不同的正交矩阵之间。这种差异导致了另一个不太重要的差异。由于奇异值分解在两侧的奇异向量是不同的,因此不需要负奇异值:我们总是可以翻转奇异值的符号,再反转其中一个奇异向量的方向,然后再次得到相同的变换。也正是由于这个原因,奇异值分解总是产生一个所有项都为正的对角矩阵,但矩阵U和V不能保证只是旋转变换,它们也可能包含了反射变换。在图形学等几何应用中,这是一个不便之处,但也是个小问题:通过检查行列式,就能很容易地区分旋转和反映,旋转的行列式为+1,反映的行列式为-1,如果需要旋转,就可以对其中一个奇异值求反,从而产生旋转-缩放-旋转的变换过程,而反映则隐含在了缩放之中,而不是隐含在旋转中。

Example.

第5.4.1节中使用的示例实际上是个剪切矩阵(图6.12):

\begin{bmatrix}
1&1\\
0&1
\end{bmatrix}=R_2\begin{bmatrix}
\sigma_1&0\\
0&\sigma_2
\end{bmatrix}R_1\\=\begin{bmatrix}
0.8507 & -0.5257 \\
0.5257 & 0.8507 \\

\end{bmatrix}\begin{bmatrix}
1.618 & 0 \\
0 & 0.618 \\

\end{bmatrix}\begin{bmatrix}
0.5257 & 0.8507 \\
-0.8507 & 0.5257 \\

\end{bmatrix}\\=rotate(31.7^。)scale(1.618,0.618)rotate(-58.3^。)

奇异值分解存在的直接结果是,我们所看到的所有二维变换矩阵都可以由旋转矩阵和缩放矩阵构成。剪切矩阵是很方便,但展示变换时不需要它。

​ 总之,每个矩阵都可以通过SVD分解为一个旋转矩阵乘以一个缩放矩阵再乘以另一个旋转矩阵。只有对称矩阵可以通过特征值对角化分解为旋转矩阵乘以缩放矩阵,再乘以逆旋转矩阵,并且是可以在任意方向上的进行的简单缩放。不对称矩阵的奇异值分解,将通过稍微复杂一点的代数操作,产生与特征值分解相同的三重乘积。

旋转矩阵的paeth分解

另一种分解方式是使用剪切来表示非零旋转(Paeth,1990)。操作如下:

\begin{bmatrix}
\cos{\phi}&-\sin{\phi}\\
\sin{\phi}&\cos{\phi}
\end{bmatrix}=\begin{bmatrix}
1&\frac{\cos\phi-1}{\sin\phi}\\
0&1
\end{bmatrix}\begin{bmatrix}
1&0\\
\sin{\phi}&1
\end{bmatrix}\begin{bmatrix}
1&\frac{\cos\phi-1}{\sin\phi}\\
0&1
\end{bmatrix}.

例如,旋转π/4(45度)为(见图6.16):

rotate(\frac{\pi}{4})=\begin{bmatrix}
1&1-\sqrt{2}\\
0&1
\end{bmatrix}\begin{bmatrix}
1&0\\
\frac{\sqrt{2}}{2}&1
\end{bmatrix}\begin{bmatrix}
1&1-\sqrt{2}\\
0&1
\end{bmatrix}.

这种特殊的变换对于光栅旋转很有用,因为剪切是一种非常有效的图像光栅操作;它引入了一些锯齿,但不会留下任何孔洞。关键的观察结果是,如果光栅的位置是(i,j),对其施加水平剪切,我们可以得到:

\begin{bmatrix}
1&s\\
0&1
\end{bmatrix}\begin{bmatrix}
i\\
j
\end{bmatrix}=\begin{bmatrix}
i+sj\\
j
\end{bmatrix}.

如果我们将sj四舍五入到最接近的整数,这相当于获取图像中的每一行并将其横向移动一定量──每一行的移动量不同。由于它在一行中的位移是相同的,这就使得旋转,在最终图像中没有间隙。在垂直剪切中也是类似的,因此,我们可以轻松实现简单的光栅旋转。

6.2 三维线性变换

线性三维变换是二维变换的扩展。例如,沿笛卡尔坐标轴的缩放是;

scale(s_x,s_y,s_z)=\begin{bmatrix}
s_x& 0&0\\
0& s_y&0\\
0&0&s_z
\end{bmatrix}

旋转在三维中要比在二维中复杂得多,因为有更多可能的旋转轴。但是,如果我们只是想绕z轴旋转,这只会改变x和y坐标,我们可以使用二维旋转矩阵,而不需要对z轴进行任何操作:

rotate-z(\phi)=\begin{bmatrix}
\cos{\phi}&-\sin{\phi}&0\\
\sin{\phi}&\cos{\phi}&0\\
0&0&1
\end{bmatrix}

类似地,我们可以构造绕x轴和y轴旋转的矩阵:

rotate-x(\phi)=\begin{bmatrix}
1&0&0\\
0&\cos{\phi}&-\sin{\phi}\\
0&\sin{\phi}&\cos{\phi}\\

\end{bmatrix}

rotate-y(\phi)=\begin{bmatrix}
\cos{\phi}&0&\sin{\phi}\\
0&1&0\\
-\sin{\phi}&0&\cos{\phi}\\

\end{bmatrix}

我们将在下一节讨论绕任意轴的旋转。

​ 就像在二维中一样,我们也可以沿着特定的轴剪切,例如,

shear-x(d_y,d_z)=\begin{bmatrix}
1&d_y&d_z\\
0&1&0\\
0&0&1
\end{bmatrix}

与二维变换一样,任何三维变换矩阵都可以使用SVD分解为旋转、缩放和其他旋转。任何对称的三维矩阵都可以特征值分解为旋转、缩放和逆旋转。最后,可以将三维旋转分解为三维剪切矩阵的乘积。

6.2.1 任意的三维旋转

与二维一样,三维旋转也是一个正交矩阵。在几何上,这意味着矩阵的三个行向量是第2.4.5节中讨论的三个彼此正交的单位向量的笛卡尔坐标。列向量是三个可能不同的彼此正交的单位向量。这样的旋转矩阵有无限多个。让我们写下这样一个矩阵:

R_{uvw}=\begin{bmatrix}
x_u& y_u&z_u\\
x_v& y_v&z_v\\
x_w& y_w&z_w
\end{bmatrix}

在这里面,u=x_ux+y_uy+z_uz,以此类推,v和w也是如此。因为这三个向量是正交的,我们知道:

u · u = v · v = w · w = 1, \\u · v = v · w = w · u = 0.

通过将旋转矩阵作用于向量u、v和w,我们可以推断出一些旋转矩阵的行为。例如,

R_{uvw}u=\begin{bmatrix}
x_u& y_u&z_u\\
x_v& y_v&z_v\\
x_w& y_w&z_w
\end{bmatrix}\begin{bmatrix}
x_u\\
y_u\\
z_u
\end{bmatrix}=\begin{bmatrix}
x_ux_u+ y_uy_u+z_uz_u\\
x_vx_u+ y_vy_u+z_vz_u\\
x_wx_u+ y_wy_u+z_wz_u
\end{bmatrix}

值得注意的是,R_{uvw}中的三个行向量都是以点乘的形式计算的:

R_{uvw}u=\begin{bmatrix}
u·u\\
v·u\\
w·u
\end{bmatrix}=\begin{bmatrix}
1\\
0\\
0
\end{bmatrix}=x

类似的,R_{uvw}v = y, R_{uvw}w = z.所以,R_{uvw}通过旋转将uvw转到相应的笛卡尔坐标轴上。

​ 如果R_{uvw}是具有彼此正交行向量的旋转矩阵,那么R_{uvw}^T就是具有彼此正交列向量的旋转矩阵,实际上就是R_{uvw}的逆矩阵(正交矩阵的逆矩阵总是其转置)。重要的一点是,对于变换矩阵来说,代数逆同时也是几何逆。所以,如果R_{uvw}u转向了x,那么R_{uvw}^T就将x重新转回u。可以推断,这对于vy同样适用:

R_{uvw}^Ty=\begin{bmatrix}
x_u& x_v&x_w\\
y_u& y_v&y_w\\
z_u& z_v&z_w
\end{bmatrix}\begin{bmatrix}
0\\
1\\
0
\end{bmatrix}=\begin{bmatrix}
x_v\\
y_v\\
z_v
\end{bmatrix}=v

所以我们总是可以从正交基创建旋转矩阵。

​ 如果我们打算绕任意向量a旋转,我们可以构建一个w=a的正交基,将该基旋转到标准基xyz,绕z轴旋转,然后将坐标轴旋转回uvw基。在矩阵形式中,围绕w轴旋转\phi角:

\begin{bmatrix}
x_u& x_v&x_w\\
y_u& y_v&y_w\\
z_u& z_v&z_w
\end{bmatrix}\begin{bmatrix}
\cos{\phi}&-\sin{\phi}&0\\
\sin{\phi}&\cos{\phi}&0\\
0&0&1
\end{bmatrix}\begin{bmatrix}
x_u& y_u&z_u\\
x_v& y_v&z_v\\
x_w& y_w&z_w
\end{bmatrix}

这里,我们让w成为在a方向上的单位向量(即a除以其自身长度)但uv又是什么?第2.4.6节已经给出了合理的方法。

​ 如果我们有一个旋转矩阵,并且我们希望以轴角的形式表示旋转,我们可以计算一个实特征值(或许是\lambda=1),相应的特征向量就是旋转轴。这是一条不会因为旋转而改变的轴。

​ 除了旋转矩阵外,有关表示旋转的几种最常用方法的比较,请参见第16章。

6.2.2 变换法向量

虽然我们使用的3D向量,大多数情形是表示位置或方向,例如表示灯光的来源,但有些向量是表示曲面法线的。曲面法向量是垂直于曲面切面的向量。当变换曲面时,这些法线不会以我们希望的方式进行变换。例如,如果曲面上的点通过矩阵M变换,则与曲面相切的向量t,在乘以M后将与变换后的曲面相切。但是,通过M变换的曲面法向量n可能不垂直于变换后的曲面(图6.17)。

​ 我们可以取得一个变换矩阵N,它将法向量n变为垂直于变换后曲面的向量。解决此问题的一种方法是注意到曲面法向量和切线向量是垂直的,因此它们的点积为零,以矩阵形式表示为:

n^Tt=0

如果我们将期望的变换向量表示为t_M=Mtn_N=Nn,我们的目的是找到一个变换矩阵N使得n_N^Tt_M=0。我们可以通过一些代数技巧找到N。首先,我们可以将一个单位矩阵塞入点积中,然后利用M^{−1}M=I

n^Tt = n^TIt = n^TM^{−1}Mt = 0.

虽然上面的操作没有什么明显的作用,但请注意,我们可以添加括号,使上面的表达式变成更方便观察的点积形式:

(n^TM^{−1})(Mt) =(n^TM^{-1})t_M=0

这意味着上面表达式的左侧部分就是垂直于t_M的行向量。此表达式适用于切线平面中的任意切线向量。由于3D中只有一个方向(及其相反方向)垂直于所有此类切向量,我们知道上面表达式的左侧部分必须是n_N的行向量表达式,即,它是n_N^T,因此我们可以推断出N

n_N^T=n^TM^{-1}

所以我们可以用它的转置得到:

n_N = (n^TM^{−1})^T = (M^{−1})^Tn.

因此,我们可以看到,能够正确变换法向量,并使其保持垂直的矩阵是N=(M^{−1})^T,即逆矩阵的转置。因为这个矩阵可能会改变n的长度,我们可以将它乘以任意标量,它仍是方向正确的n_N。回顾第5.3节,矩阵的逆是余子式矩阵的转置再除以行列式。因为我们不关心法向量的长度,我们可以跳过除法,发现对于3×3矩阵,

N=\begin{bmatrix}
m_{11}^c&m_{12}^c&m_{13}^c\\
m_{21}^c&m_{22}^c&m_{23}^c\\
m_{31}^c&m_{32}^c&m_{33}^c
\end{bmatrix}

这里假设第i行和第j列中的M元素是m_{ij}。所以N的完整表达式是:

N=\begin{bmatrix}
m_{22}m_{33}-m_{23}m_{32}&m_{23}m_{31}-m_{21}m_{33}&m_{21}m_{32}-m_{22}m_{31}\\
m_{13}m_{32}-m_{12}m_{33}&m_{11}m_{33}-m_{13}m_{31}&m_{12}m_{31}-m_{11}m_{32}\\
m_{12}m_{23}-m_{13}m_{22}&m_{13}m_{21}-m_{11}m_{23}&m_{11}m_{22}-m_{12}m_{21}
\end{bmatrix}

6.3 平移与仿射变换

我们一直在探讨使用矩阵M变换向量的方法。在二维中,这些变换的形式为,

x'= m_{11}x + m_{12}y,\\
y' = m_{21}x + m_{22}y.

我们不能使用这种变换来移动对象,只能缩放和旋转对象。特别是,原点(0,0)在线性变换下是始终保持不变的。要想移动或平移一个物体,使其所有的点都移动相同的量,我们需要对整个形状进行变换,

x' = x + x_t, \\y' = y + y_t.

(x,y)乘以一个2\times2的矩阵是无法做到这一点的。将平移添加到线性变换系统中的一种可能的方法,是简单地将一个单独的平移向量与每个变换矩阵相关联,让矩阵负责缩放和旋转,向量负责平移。这是完全可行的,但是管理工作很麻烦,而且整合这两种变换的规则不像线性变换那样简单明了。

​ 作为替代,我们可以使用一个巧妙的技巧,得到一个矩阵乘法来同时执行这两个操作。思路很简单:用三维向量[x ,y, 1]^T表示点(x,y),并使用该形式的3\times3矩阵:

\begin{bmatrix}
m_{11}&m_{12}&x_t\\
m_{21}&m_{22}&y_t\\
0&0&1
\end{bmatrix}

固定的第三行用于将1复制到变换后的向量中,以便所有向量的最后一个位置都有1,前两行将x'y'则被计算为xy1的线性组合:

\begin{bmatrix}
x'\\
y'\\
1
\end{bmatrix}=
\begin{bmatrix}
m_{11}&m_{12}&x_t\\
m_{21}&m_{22}&y_t\\
0&0&1
\end{bmatrix}\begin{bmatrix}
x\\
y\\
1
\end{bmatrix}=\begin{bmatrix}
m_{11}x+m_{12}y+x_t\\
m_{21}x+m_{22}y+y_t\\
1
\end{bmatrix}

单个矩阵执行线性变换,然后执行平移!这种变换称为仿射变换,这种通过增加额外维度来实现仿射变换的方法称为齐次坐标法。齐次坐标不仅让变换的代码变得清晰明了,而且该方案还明确了如何组合两个仿射变换:简单地将矩阵相乘。齐次坐标也带来了一个问题,当我们需要变换一个不表示位置的向量(它表示方向,和两点之间的偏移量)。当我们在平移物体时,表示方向或偏移的向量不应被改变。幸运的是,我们可以通过将第三个坐标设置为零来实现这一点:

\begin{bmatrix}
1&0&x_t\\
0&1&y_t\\
0&0&1
\end{bmatrix}\begin{bmatrix}
x\\
y\\
0
\end{bmatrix}=\begin{bmatrix}
x\\
y\\
0
\end{bmatrix}

如果矩阵左上角的2\times2项中存在缩放/旋转变换,则该变换将应用于向量,但平移仍然与零相乘,就不见了。此外,零被复制到变换后的向量中,因此方向向量在变换后仍然是方向向量。

​ 这正是我们想要的,因此平移变换可以平滑地进入系统:额外的坐标是1还是0,这取决于我们编码的是位置还是方向。实际上,我们需要存储齐次坐标,以便区分位置向量和其他向量。例如:

\begin{bmatrix}
3\\
2\\
1
\end{bmatrix}是位置向量,而\begin{bmatrix}
3\\
2\\
0
\end{bmatrix}是位移或方向向量

稍后,当我们做透视图时,我们将会看到,当齐次坐标不再采用1或0时,用处更大。

​ 齐次坐标几乎遍布于表示变换的图形系统中。特别是,齐次坐标是在图形硬件中设计和操作渲染器的的基础。我们将在第7章中看到,齐次坐标也让构建透视场景变得容易,这也是它们受欢迎的另一个原因。

​ 齐次坐标可以被认为是处理平移的一种聪明方法,但也有另一种不同的几何解释。关键的是,当我们沿着z坐标进行三维剪切时,我们得到了这个变换:

\begin{bmatrix}
1&0&x_t\\
0&1&y_t\\
0&0&1
\end{bmatrix}\begin{bmatrix}
x\\
y\\
z
\end{bmatrix}=\begin{bmatrix}
x+x_tz\\
y+y_tz\\
z
\end{bmatrix}

注意,这差一点就是我们想要的二维平移形式,但是有一个z,在二维中是没有意义的。现在关键的一步是:我们将向所有二维位置添加坐标z=1。我们能得到:

\begin{bmatrix}
1&0&x_t\\
0&1&y_t\\
0&0&1
\end{bmatrix}\begin{bmatrix}
x\\
y\\
1
\end{bmatrix}=\begin{bmatrix}
x+x_t\\
y+y_t\\
1
\end{bmatrix}

通过将(z=1)赋予所有2D点,我们现在可以将平移变换编码为矩阵形式了。例如,首先在2D中平移(xt,yt),然后旋转角度\phi,我们将使用矩阵:

M=\begin{bmatrix}
\cos{\phi}&-\sin{\phi}&0\\
\sin{\phi}&\cos{\phi}&0\\
0&0&1
\end{bmatrix}\begin{bmatrix}
1&0&x_t\\
0&1&y_t\\
0&0&1
\end{bmatrix}

请注意,二维旋转矩阵现在是3×3的,其中“平移行”中有零。使用这种形式(沿z=1的剪切对平移进行编码),我们可以将任意数量的二维剪切、二维旋转和二维平移混合在一个复合的三维矩阵中。矩阵的底行总是(0,0,1),所以我们不需要存储它。当我们把两个矩阵相乘时,我们只要记得它在那里就行了。

​ 在三维中,这样的技术也是适用:我们可以添加第四个坐标,齐次坐标,然后进行平移:

\begin{bmatrix}
1&0&0&x_t\\
0&1&0&y_t\\
0&0&1&z_t\\
0&0&0&1
\end{bmatrix}\begin{bmatrix}
x\\
y\\
z\\
1
\end{bmatrix}=\begin{bmatrix}
x+x_t\\
y+y_t\\
z+z_t\\
1
\end{bmatrix}

同样,对于方向向量,第四个坐标为零,向量将不受平移的影响。

Example.

在图形学中,我们经常要构建变换矩阵,将矩形[x_l,x_h]\times[y_l,y_h]变换到矩形[x_l',x_h']\times[y_l',y_h']。这可以通过按顺序进行缩放和平移来完成。但是,按以下三个步骤的顺序创建转换更为直观(图6.18):

  1. 将点(x_l,y_l)移动到原点
  2. 将矩形缩放到和目标矩形一样的尺寸
  3. 将原点移回到(x_l',y_l')

记住,右边的矩阵先起作用,我们可以写出:

Windows=translate(s_l',y_l')scale(\frac{x_h'-x_l'}{x_h-x_l},\frac{y_h'-y_l'}{y_h-y_l})translate(-x_l,-y_l)\\
=\begin{bmatrix}
1&0&x_l'\\
0&1&y_l'\\
0&0&1
\end{bmatrix}\begin{bmatrix}
\frac{x_h'-x_l'}{x_h-x_l}&0&0\\
0&\frac{y_h'-y_l'}{y_h-y_l}&0\\
0&0&1
\end{bmatrix}\begin{bmatrix}
1&0&-x_l\\
0&1&-y_l\\
0&0&1
\end{bmatrix}\\
=\begin{bmatrix}
\frac{x_h'-x_l'}{x_h-x_l}&0&\frac{x_l'x_h-x_h'x_l}{x_h-x_l}\\
0&\frac{y_h'-y_l'}{y_h-y_l}&\frac{y_l'y_h-y_h'y_l}{y_h-y_l}\\
0&0&1
\end{bmatrix}

或许对于一些读者来说,结果是这个形式的,一点也不意外,但这三个矩阵的构造过程,正确性是毋庸置疑的。

​ 运用类似的构造来定义一个三维的变换,将立方体[x_l,x_h]\times[y_l,y_h]\times[z_l,z_h]贴到立方体[x_l,x_h]\times[y_l,y_h]\times[z_l,z_h]上:

\begin{bmatrix}
\frac{x_h'-x_l'}{x_h-x_l}&0&0&\frac{x_l'x_h-x_h'x_l}{x_h-x_l}\\
0&\frac{y_h'-y_l'}{y_h-y_l}&0&\frac{y_l'y_h-y_h'y_l}{y_h-y_l}\\
0&0&\frac{z_h'-z_l'}{y_h-y_l}&\frac{z_l'z_h-z_h'z_l}{z_h-z_l}\\
0&0&0&1
\end{bmatrix}

有意思的是,如果我们将一个由缩放、剪切和旋转复合而成的任意矩阵乘以一个简单的平移(平移排在第二位),我们能得到:

\begin{bmatrix}
1&0&0&x_t\\
0&1&0&y_t\\
0&0&1&z_t\\
0&0&0&1
\end{bmatrix}\begin{bmatrix}
a_{11}&a_{12}&a_{13}&0\\
a_{21}&a_{22}&a_{23}&0\\
a_{31}&a_{32}&a_{33}&0\\
0&0&0&1
\end{bmatrix}=\begin{bmatrix}
a_{11}&a_{12}&a_{13}&x_t\\
a_{21}&a_{22}&a_{23}&y_t\\
a_{31}&a_{32}&a_{33}&z_t\\
0&0&0&1
\end{bmatrix}

因此,我们可以将任何矩阵视为缩放/旋转部分和平移部分所组成,因为这些组件可以很好地彼此分离。

​ 刚体变换是一类重要的变换。它仅由平移和旋转组成,因此它们不会是拉伸或收缩的对象。这样的变换将对上面的a_{ij}进行纯旋转。

6.4 变换矩阵的逆

虽然我们总是可以用代数方法求逆矩阵,但我们可以用变换的几何意义来求逆。例如,scale(sx,sy,sz)的逆矩阵是scale(1/sx,1/sy,1/sz)。旋转的逆是在等角度,反方向的旋转。平移的逆是相反方向的平移。 如果我们有一系列的矩阵M=M_1M_2\cdots M_n,那么逆矩阵就是M=M_n^{-1}\cdots M_2^{-1} M_1^{-1}

​ 当然,某种确定类型的变换矩阵是容易求逆的。我们已经提到了缩放矩阵,它是对角矩阵;第二个重要的例子是旋转矩阵,它是正交矩阵。回想一下(第5.2.4节),正交矩阵的逆是其转置。这样就可以轻松得到旋转和刚体变换的逆(请参见练习6)。另外,一个矩阵的底部行有[0 0 1],它的逆矩阵的底部行也有[0 0 1],知道这一点也是很有用的(请参见练习7)。

​ 有意思的是,我们也可以使用奇异值分解来得到逆矩阵。因为我们知道任何矩阵都可以分解为旋转乘以缩放再乘以旋转,求逆很简单。例如,在三维中,我们有:

M=R_1scale(\sigma_1,\sigma_2,\sigma_3)R_2

从上面的规则很容易得出:

M^{-1}=R_2^Tscale(1/\sigma_1,1/\sigma_2,1/\sigma_3)R_1^T

6.5 坐标变换

前面的所有讨论都是关于使用变换矩阵移动点的。我们也可以把它们看作是简单地改变点所在的坐标系。例如,在图6.19中,我们可以看到两种描绘某个运动的方法。这两种解释适用于不同的情况。

​ 例如,一个驾驶游戏可能有一个城市模型和一个汽车模型。如果向玩家展示挡风玻璃外的视图,则车内的物体始终绘制在屏幕上的同一位置,而当玩家驾驶时,街道和建筑物似乎向后移动。在每一帧上,我们对这些窗外的物体应用一个变换,使它们比上一帧向后移动得更远。能达到这种效果的一种方式是,将建筑物向后移动;另一种方式是,使建筑物保持不动,但能够绘制该运动的坐标系,即附着在汽车上的坐标系,正在移动。在第二种方式中,变换是在改变城市几何体的坐标,将其表示为汽车坐标系中的坐标。这两种方法最后所得到的矩阵,在应用于汽车外部的几何体时,是完全相同的。

​ 如果游戏还支持俯视图来显示汽车在城市中的位置,则需要将建筑和街道绘制在固定的位置上,同时汽车需要从这一帧的位置移动到另一帧的位置。同样的有两种思路可以采用,我们可以想象,将汽车从世界坐标系的原位置移动到世界坐标系的当前位置上;也可以将这种变换想象成,简单的移动汽车几何体的坐标(最初是根据附着在汽车上的坐标系来表示),而不是相对于固定在城市中的坐标系移动。坐标变化的演绎很清楚的表明,这两种模式中使用的矩阵(城市到汽车坐标变化汽车到城市坐标变化)是互相为彼此的逆。

​ 更改坐标系的思路与编程中的类型转换非常相似。在将浮点数加到整数上,我们必须根据需要将整数转换为浮点数或将浮点数转换为整数,以便类型能够匹配。在我们把城市和汽车画在一起之前,我们需要将城市坐标转换成汽车坐标中的坐标或者汽车坐标转换成城市坐标中的坐标,这取决于我们的需要,以便坐标匹配。

​ 在管理多个坐标系时,很容易混淆,并导致对象最终落于错误的坐标中,从而导致它们出现在意外的位置。但是,通过系统地思考坐标系之间的转换,你可以获得准确的转换。

​ 从几何学上讲,坐标系或者是坐标框架,是由一个原点和一组基(三个一组的向量)组成。正交基是非常方便,所以我们通常假设坐标框架是正交的,除非另有要求。在原点为P且基为{U,V,W}的坐标框架中,坐标(u,v,w)描述的点为:

P+uU+vV+wW

​ 当我们将这些向量存储在计算机中时,它们需要用一些坐标系来表示。作为工作的开始,我们必须指定一些标准坐标系,通常称为“全局”或“世界”坐标,用于描述所有其他的系。在城市示例中,我们可以采用街道格,遵循惯例,使用x轴沿主街道指向、y轴向上指向,z轴沿中央大道指向。然后,当我们根据这些坐标来写汽车坐标系的原点和基准时,事情就很清楚了。

​ 在二维中,我们的惯例是使用点o作为原点,x和y作为右手正交基向量x和y(图6.20)。

​ 另一个坐标系具有原点e和右手正交基向量uv。请注意,标准坐标系的数据oxy通常不会被显式存储。他们是所有其他坐标系的参考系。在该坐标系中,我们通常将P的位置记为有序对,这是完整向量表达式的简写:

P=(x_p,y_p)\equiv o+x_px+y_py

例如,在图6.20中,(x_p,y_p)=(2.5,0.9)。请注意,坐标(x_p,y_p)隐含假定原点为o。类似地,我们可以用另一个方程表示P:

P=(u_p,v_p)\equiv e+u_pu+v_pv

在图6.20中,有(u_p,v_p)=(0.5,−0.7),同样,原点e作为与u和v关联的坐标系的隐含部分,被保留。

​ 我们可以用矩阵来表达同样的关系,如下所示:

\begin{bmatrix}
x_p\\
y_p\\
1
\end{bmatrix}=\begin{bmatrix}
1&0&x_e\\
0&1&y_e\\
0&0&1
\end{bmatrix}\begin{bmatrix}
x_u&x_v&0\\
y_u&y_v&0\\
0&0&1
\end{bmatrix}\begin{bmatrix}
u_p\\
v_p\\
1
\end{bmatrix}=\begin{bmatrix}
x_u&x_v&x_e\\
y_u&y_v&y_e\\
0&0&1
\end{bmatrix}\begin{bmatrix}
u_p\\
v_p\\
1
\end{bmatrix}

值得注意的是,这里假设了我们将点e和向量u和v存储在标准坐标中;(x,y)坐标系是等式中的第一项。根据我们在本章中讨论的变换的基本类型,这是一个旋转(涉及uv),然后是一个平移(涉及e)。同时观察旋转和平移矩阵,您可以看到它很容易写下来:我们只是将u,v,e放在矩阵的列中,而[0,0,1]放在第三行。为了看的更清楚,我们可以这样写矩阵:

p_{xy}=\begin{bmatrix}
u&v&e\\
0&0&1
\end{bmatrix}P_{uv}

我们称这个矩阵为框架到标准坐标系的变换矩阵。它获取用(u,v)坐标系表示的点,并将它们转换为标准坐标系所表示的相同的点。

​ 反过来,我们有:

\begin{bmatrix}
u_p\\
v_p\\
1
\end{bmatrix}=\begin{bmatrix}
x_u&y_u&0\\
x_v&y_v&0\\
0&0&1
\end{bmatrix}\begin{bmatrix}
1&0&-x_e\\
0&1&-y_e\\
0&0&1
\end{bmatrix}\begin{bmatrix}
x_p\\
y_p\\
1
\end{bmatrix}

这是一个先平移,后旋转的变换;它们是之前所用的旋转和平移的逆,当它们相乘时,它们产生了框架到标准坐标系矩阵的逆,这(毫不奇怪)被称为标准坐标系到框架的矩阵:

P_{uv}=\begin{bmatrix}
u&v&e\\
0&0&1
\end{bmatrix}^{-1}P_{xy}

标准坐标系到框架矩阵获取标准坐标系中的点,并将它们转换为(u,v)坐标系中的相同点。我们把这个矩阵写成框架到标准坐标系矩阵的逆矩阵,是因为它不能用e,u,v的标准坐标立即写出。但请记住,所有坐标系都是等效的;根据x坐标和y坐标存储向量只是我们的习惯罢了,是这种习惯创造了这种看似的不对等。标准坐标系到框架的矩阵可以简单地表示为oxy(u、v)坐标:

P_{uv}=\begin{bmatrix}
x_{uv}&y_{uv}&o_{uv}\\
0&0&1
\end{bmatrix}P_{xy}

以上的所有结论在三维空间都是类似的:

\begin{bmatrix}
x_p\\
y_p\\
z_p\\
1
\end{bmatrix}=\begin{bmatrix}
1&0&0&x_e\\
0&1&0&y_e\\
0&0&1&z_e\\
0&0&0&1
\end{bmatrix}\begin{bmatrix}
x_u&x_v&x_w&0\\
y_u&y_v&y_w&0\\
z_u&z_y&z_w&0\\
0&0&0&1
\end{bmatrix}\begin{bmatrix}
u_p\\
v_p\\
w_p\\
1
\end{bmatrix}
p_{xyz}=\begin{bmatrix}
u&v&w&e\\
0&0&0&1
\end{bmatrix}P_{uvw}
\begin{bmatrix}
u_p\\
v_p\\
z_p\\
1
\end{bmatrix}=\begin{bmatrix}
x_u&y_u&z_u&0\\
x_v&y_v&z_v&0\\
x_w&y_w&z_w&0\\
0&0&0&1
\end{bmatrix}\begin{bmatrix}
1&0&0&-x_e\\
0&1&0&-y_e\\
0&0&1&-z_e\\
0&0&0&1
\end{bmatrix}\begin{bmatrix}
x_p\\
y_p\\
z_p\\
1
\end{bmatrix}
P_{uvw}=\begin{bmatrix}
u&v&w&e\\
0&0&0&1
\end{bmatrix}^{-1}P_{xyz}

常见问题

  • 我难道不能直接编码变换而不使用矩阵形式吗?

你当然可以选择那么做,但在实践中,它更难派生,更难调试,而且效率也不高。此外,所有当前的图形API都使用这种矩阵形式,因此即使是使用图形库也必须理解它。

  • 矩阵的底行始终为(0,0,0,1)。我必须储存它吗?

除非包含透视变换(第7章),否则没有必要存储它。