《Fundamentals of Computer Graphics》5th(计算机图形学基础/虎书),中文翻译。
第 7 章 Transformation Matrices 变换矩阵
线性代数的工具可以用于表达许多在三维场景中排列对象、使用相机查看它们并将它们放到屏幕上所需的操作。几何变换 (Geometric transformations) 如旋转、平移、缩放和投影可以通过矩阵乘法来完成,而用于完成这些操作的变换矩阵 (transformation matrices) 是本章的主题。
我们将展示一组点如何进行变换,如果将这些点表示为从原点偏移的向量,则将时钟表示为示例点集(参见图 7.1)。因此,将时钟视为一堆点,这些点是以原点为尾的向量的端点。我们还讨论了这些变换对位置(点)、位移向量和表面法向量的不同作用方式。
7.1 二维线性变换
我们可以使用一个 2 × 2 2\times2 2 × 2 的矩阵来改变或者变换二维向量:
[ a 11 a 12 a 21 a 22 ] [ x y ] = [ a 11 x + a 12 y a 21 x + a 22 y ] \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} [ a 11 a 21 a 12 a 22 ] [ x y ] = [ a 11 x + a 12 y a 21 x + a 22 y ]
这种操作通过简单的矩阵乘法接收一个二维向量并生成另一个二维向量,是一种线性变换。
通过这个简单的公式,我们可以实现各种有用的变换,具体取决于我们将矩阵的输入参数填充成什么,这将在接下来的小节中讨论。对于我们的目的,考虑沿着 x 轴进行水平移动并沿着 y 轴进行垂直移动。
7.1.1 Scaling 缩放
最基本的变换是沿坐标轴进行缩放。这种变换可以改变长度和可能的方向:
scale ( s x , s y ) = [ s x 0 0 s y ] \text{scale}(s_x,s_y)=
\begin{bmatrix}
s_x & 0 \\
0 & s_y
\end{bmatrix} scale ( s x , s y ) = [ s x 0 0 s y ]
请注意,这个矩阵对具有笛卡尔分量 ( x , y ) (x,y) ( x , y ) 的向量进行了什么操作:
[ s x 0 0 s y ] [ x y ] = [ s x x s y 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} [ s x 0 0 s y ] [ x y ] = [ s x x s y y ]
因此,只需查看与轴对齐的缩放的矩阵,就可以读取出两个缩放因子。
例 9:将 x 和 y 均匀缩小一半的矩阵为(见图 7.1)
scale ( 0.5 , 0.5 ) = [ 0.5 0 0 0.5 ] \text{scale}(0.5,0.5)=
\begin{bmatrix}
0.5 & 0 \\
0 & 0.5
\end{bmatrix} scale ( 0.5 , 0.5 ) = [ 0.5 0 0 0.5 ]
图 7.1。在每个轴上均匀缩小一半:轴对齐的缩放矩阵具有对角线元素中每个元素的变化比例以及在非对角线元素中的零。
一个在水平方向上缩小为原来的一半,在垂直方向上扩大三分之二的矩阵为(见图 7.2)
scale ( 0.5 , 1.5 ) = [ 0.5 0 0 1.5 ] \text{scale}(0.5,1.5)=
\begin{bmatrix}
0.5 & 0 \\
0 & 1.5
\end{bmatrix} scale ( 0.5 , 1.5 ) = [ 0.5 0 0 1.5 ]
图 7.2。在 x 和 y 上进行非均匀缩放:缩放矩阵为对角矩阵,其元素不相等。请注意,时钟的方形轮廓变成了矩形,圆形表盘变成了椭圆形。
7.1.2 Shearing 剪切
剪切是指将事物向侧面推动,产生类似于手推卡片牌堆的效果;底部的卡片保持不变,越靠近牌堆顶部的卡片移动得越多。水平和垂直剪切矩阵分别是:
shear-x ( s ) = [ 1 s 0 1 ] , shear-y ( s ) = [ 1 0 s 1 ] . \text{shear-x}(s)=
\begin{bmatrix}
1 & s \\
0 & 1
\end{bmatrix},
\qquad
\text{shear-y}(s)=
\begin{bmatrix}
1 & 0 \\
s & 1
\end{bmatrix}. shear-x ( s ) = [ 1 0 s 1 ] , shear-y ( s ) = [ 1 s 0 1 ] .
例 10:使垂直线变为向右倾斜的 45° 线的水平剪切变换为(见图 7.3)
shear-x ( 1 ) = [ 1 1 0 1 ] . \text{shear-x}(1)=
\begin{bmatrix}
1 & 1 \\
0 & 1
\end{bmatrix}. shear-x ( 1 ) = [ 1 0 1 1 ] .
图 7.3。x-剪切矩阵按其 y 坐标比例向右移动点。现在,时钟的正方形轮廓变成了平行四边形,并且与缩放一样,时钟的圆形表盘变成了椭圆形。
垂直的类似变换为(见图 7.4)
shear-y ( 1 ) = [ 1 0 1 1 ] . \text{shear-y}(1)=
\begin{bmatrix}
1 & 0 \\
1 & 1
\end{bmatrix}. shear-y ( 1 ) = [ 1 1 0 1 ] .
图 7.4。y-剪切矩阵按其 x 坐标比例向上移动点。
在这两种情况下,剪切时钟的正方形轮廓变成了平行四边形,时钟的圆形表盘变成了椭圆形。
另一种思考剪切的方式是仅考虑垂直(或水平)轴的旋转。将垂直轴顺时针倾斜角度 ϕ \phi ϕ 的剪切变换为:
事实上,任何矩阵变换下的圆的图像都是椭圆。
对于将水平轴逆时针旋转角度 ϕ \phi ϕ 的剪切矩阵为:
[ 1 tan ϕ 0 1 ] . \begin{bmatrix}
1 & \tan \phi \\
0 & 1
\end{bmatrix}. [ 1 0 tan ϕ 1 ] .
同样地,将垂直轴顺时针旋转角度 ϕ \phi ϕ 的剪切矩阵为:
[ cos ϕ − sin ϕ sin ϕ cos ϕ ] . \begin{bmatrix}
\cos \phi & -\sin \phi \\
\sin \phi & \cos \phi
\end{bmatrix}. [ cos ϕ sin ϕ − sin ϕ cos ϕ ] .
7.1.3 旋转
假设我们想要将向量 a \mathbf{a} a 逆时针旋转角度 ϕ \phi ϕ 得到向量 b \mathbf{b} b (见图 7.5)。如果 a \mathbf{a} a 与 x 轴夹角为 α \alpha α ,长度为 r = x a 2 + y a 2 r=\sqrt{x_a^2+y_a^2} r = x a 2 + y a 2 ,则我们知道:
x a = r cos α , y a = r sin α 。 x_a=r\cos\alpha,\quad y_a=r\sin\alpha。 x a = r cos α , y a = r sin α 。
因为 b \mathbf{b} b 是 a \mathbf{a} a 的旋转,它也具有长度 r r r 。因为它从 a \mathbf{a} a 旋转了角度 ϕ \phi ϕ ,所以 b \mathbf{b} b 与 x 轴的夹角为 ( α + ϕ ) (\alpha+\phi) ( α + ϕ ) 。使用三角求和公式(第 2.3.3 节):
x b = r cos ( α + ϕ ) = r cos α cos ϕ − r sin α sin ϕ , y b = r sin ( α + ϕ ) = r sin α cos ϕ + r cos α sin ϕ 。 (7.1) x_b=r\cos(\alpha+\phi)=r\cos\alpha\cos\phi-r\sin\alpha\sin\phi, \\ \quad
y_b=r\sin(\alpha+\phi)=r\sin\alpha\cos\phi+r\cos\alpha\sin\phi。 \tag{7.1} x b = r cos ( α + ϕ ) = r cos α cos ϕ − r sin α sin ϕ , y b = r sin ( α + ϕ ) = r sin α cos ϕ + r cos α sin ϕ 。 ( 7.1 )
图 7.5 显示了公式 (7.1) 所示几何形状。
代入 x a = r cos α x_a=r\cos\alpha x a = r cos α 和 y a = r sin α y_a=r\sin\alpha y a = r sin α ,得到
x b = x a cos ϕ − y a sin ϕ , y b = y a cos ϕ + x a sin ϕ 。 x_b=x_a\cos\phi-y_a\sin\phi,\quad y_b=y_a\cos\phi+x_a\sin\phi。 x b = x a cos ϕ − y a sin ϕ , y b = y a cos ϕ + x a sin ϕ 。
矩阵形式的旋转变换为:
rotate ( ϕ ) = [ cos ϕ − sin ϕ sin ϕ cos ϕ ] 。 \text{rotate}(\phi)=
\begin{bmatrix}
\cos\phi & -\sin\phi \\
\sin\phi & \cos\phi
\end{bmatrix}。 rotate ( ϕ ) = [ cos ϕ sin ϕ − sin ϕ cos ϕ ] 。
例 11:将向量逆时针旋转 π / 4 π/4 π /4 弧度(45°)的矩阵为(见图 7.6)
[ cos π 4 − sin π 4 sin π 4 cos π 4 ] = [ 0.707 − 0.707 0.707 0.707 ] 。 \begin{bmatrix}
\cos\frac{\pi}{4} & -\sin\frac{\pi}{4} \\
\sin\frac{\pi}{4} & \cos\frac{\pi}{4}
\end{bmatrix}=
\begin{bmatrix}
0.707 & -0.707 \\
0.707 & 0.707
\end{bmatrix}。 [ cos 4 π sin 4 π − sin 4 π cos 4 π ] = [ 0.707 0.707 − 0.707 0.707 ] 。
图 7.6。旋转 45 度。请注意,旋转是逆时针的,而 c o s ( 45 ° ) = s i n ( 45 ° ) ≈ . 707 cos(45°) = sin(45°) ≈ .707 cos ( 45° ) = s in ( 45° ) ≈ .707 。
一个按顺时针方向旋转 π / 6 π/6 π /6 弧度(30°)的矩阵在我们的框架中是按 – π / 6 –π/6 – π /6 弧度旋转的(见图 7.7):
[ cos − π 6 − sin − π 6 sin − π 6 cos − π 6 ] = [ 0.866 0.5 − 0.5 0.866 ] 。 \begin{bmatrix}
\cos\frac{-\pi}{6} & -\sin\frac{-\pi}{6} \\
\sin\frac{-\pi}{6} & \cos\frac{-\pi}{6}
\end{bmatrix}=
\begin{bmatrix}
0.866 & 0.5 \\
-0.5 & 0.866
\end{bmatrix}。 [ cos 6 − π sin 6 − π − sin 6 − π cos 6 − π ] = [ 0.866 − 0.5 0.5 0.866 ] 。
图 7.7。按 –30° 旋转。请注意,旋转是顺时针方向的,而 c o s ( – 30 ° ) ≈ 0.866 , s i n ( – 30 ° ) = – 0.5 cos(–30°) ≈ 0.866,sin(–30°) = –0.5 cos ( –30° ) ≈ 0.866 , s in ( –30° ) = –0.5 。
因为旋转矩阵的每一行的范数都是 1(s i n 2 ϕ + c o s 2 ϕ = 1 sin^2\phi+cos^2\phi = 1 s i n 2 ϕ + co s 2 ϕ = 1 ),且这些行是正交的(c o s ϕ ( − s i n ϕ ) + s i n ϕ c o s ϕ = 0 cos\phi(-sin\phi)+sin\phi cos\phi=0 cos ϕ ( − s in ϕ ) + s in ϕ cos ϕ = 0 ),所以可以看出旋转矩阵是正交矩阵(见第 6.2.4 节)。通过观察矩阵,我们可以读出两对标准正交向量:两列向量,它们是转换发送到规范基向量 ( 1 , 0 ) (1,0) ( 1 , 0 ) 和 ( 0 , 1 ) (0,1) ( 0 , 1 ) 的向量;以及行向量,它们是转换发送到规范基向量的向量。
简而言之,对于具有列向量u i u_i u i 和行向量v i v_i v i 的旋转,R ⋅ e i = u i R \cdot e_i = u_i R ⋅ e i = u i 并且 R ⋅ v i = u i R\cdot v_i = u_i R ⋅ v i = u i 。
7.1.4 反射
我们可以通过使用一个负比例因子的缩放来将向量反射到任意坐标轴上(见图 7.8 和 7.9):
reflect-y = [ − 1 0 0 1 ] , reflect-x = [ 1 0 0 − 1 ] \text { reflect-y }=\left[\begin{array}{rr}
-1 & 0 \\
0 & 1
\end{array}\right], \text { reflect-x }=\left[\begin{array}{rr}
1 & 0 \\
0 & -1
\end{array}\right] reflect-y = [ − 1 0 0 1 ] , reflect-x = [ 1 0 0 − 1 ]
图 7.8。通过将所有 x 坐标乘以 -1 实现关于 y 轴的反射。
图 7.9。通过将所有 y 坐标乘以 -1 实现关于 x 轴的反射。
虽然人们可能期望在对角线上具有 -1 的矩阵也是一种反射,但实际上它只是绕原点旋转 π π π 弧度。
这种旋转也可以称为“经过原点的反射”。
7.1.5 变换的组合和分解
图形程序通常会对对象应用多个变换。例如,我们可能希望先应用缩放 S,然后应用旋转 R。这可以分为两步完成对 2D 向量v 1 v_1 v 1 的处理:
f i r s t , v 2 = S v 1 , t h e n , v 3 = R v 2 \bold{first}, \bold v_2=\bold S\bold v_1, \bold{then}, \bold v_3=\bold R\bold v_2 first , v 2 = S v 1 , then , v 3 = R v 2
另一种写法是
v 3 = R ( S v 1 ) 。 \bold v_3=\bold R(\bold S\bold v_1)。 v 3 = R ( S v 1 ) 。
因为矩阵乘法是可结合的,所以我们也可以写成
v 3 = ( R S ) v 1 。 \bold v_3=(\bold R\bold S)\bold v_1。 v 3 = ( RS ) v 1 。
换句话说,我们可以使用相同大小的单个矩阵来表示按顺序使用两个矩阵变换向量的效果,这个矩阵可以通过将两个矩阵相乘来计算:M = R S M=RS M = RS (见图 7.10)。
非常重要的一点是记住这些变换是从右侧先应用的。因此,矩阵 M = R S \bold M=\bold R\bold S M = RS 首先应用 S,然后应用 R。
图 7.10。按顺序应用这两个变换矩阵等同于一次应用这些矩阵的乘积。这是大多数图形硬件和软件的关键概念。
例子 12
假设我们想要先在竖直方向上缩小一半,然后再绕 π / 4 \pi/4 π /4 弧度(45°)旋转。得到的矩阵是
[ 0.707 − 0.707 0.707 0.707 ] [ 1 0 0 0.5 ] = [ 0.707 − 0.353 0.707 0.353 ] \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} [ 0.707 0.707 − 0.707 0.707 ] [ 1 0 0 0.5 ] = [ 0.707 0.707 − 0.353 0.353 ]
始终记住矩阵乘法不满足交换律非常重要。因此,变换的顺序确实很重要。在这个例子中,先旋转再缩放会得到一个不同的矩阵(见图 7.11):
[ 1 0 0 0.5 ] [ 0.707 − 0.707 0.707 0.707 ] = [ 0.707 − 0.707 0.353 0.353 ] \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} [ 1 0 0 0.5 ] [ 0.707 0.707 − 0.707 0.707 ] = [ 0.707 0.353 − 0.707 0.353 ]
图 7.11。通常情况下,两个变换应用的顺序非常重要。在这个例子中,我们先进行 y 轴方向上的 1/2 缩放,然后再旋转 45 度。反转这两个变换所应用的顺序会得到不同的结果。
通过一个 +45° 的旋转来实现。将这些操作组合在一起,完整的变换为: r o t a t e ( − 45 ∘ ) s c a l e ( 1 , 5 , 1 ) r o t a t e ( 45 ∘ ) rotate(−45∘)scale(1,5,1)rotate(45∘) ro t a t e ( − 45 ∘ ) sc a l e ( 1 , 5 , 1 ) ro t a t e ( 45 ∘ )
请记住从右到左阅读变换。
在数学符号中,可以写成 R S R T \bold R\bold S\bold R^T RS R T 。将三个矩阵相乘的结果是
[ 1.25 − 0.25 − 0.25 1.25 ] \begin{bmatrix}
1.25 & -0.25 \\
-0.25 & 1.25 \\
\end{bmatrix} [ 1.25 − 0.25 − 0.25 1.25 ]
这个矩阵对称并不是巧合 - 尝试将转置-乘积规则应用于公式 RSRT
。
实际上,从旋转和缩放变换建立变换对于任何线性变换都适用,并且这个事实引导了一种关于这些变换的强大思维方式,如下一节所探讨的。
7.1.6 变换的分解
有时需要“撤销”一种变换组合,将一个变换拆分成更简单的部分。例如,通常需要用独立的旋转和缩放因子来呈现一个变换供用户操作,但是变换可能在内部仅表示为一个矩阵,其中旋转和缩放已经混合在一起。如果可以计算地将矩阵分解为所需的部分,则可以实现这种操作,调整部分并通过再次将它们相乘重新组合矩阵。
事实证明,无论矩阵中的条目如何,这种分解或因式分解都是可能的,这个事实为思考变换以及它们对其变换的几何形状做了什么提供了一个富有成果的方式。
对称特征值分解 (Symmetric EigenvalueDecomposition)
让我们从对称矩阵开始。回想一下第 6.4 节,对称矩阵总是可以使用特征值分解拆分成形式为
A = R S R T \bold A=\bold R\bold S\bold R^T A = RS R T
的乘积,其中 R 是正交矩阵,S 是对角矩阵;我们将 R 的列(即特征向量)称为 v1 和 v2 的名称,并将 S 的对角线条目(即特征值)称为 λ1 和 λ2 的名称。
几何上,我们现在可以将 R 识别为旋转,S 识别为缩放,因此这只是一个多步几何变换(图 7.12):
图 7.12。当单位圆通过任意矩阵 A 进行变换时会发生什么?两个垂直的向量 v1 和 v2(它们是 A 的右奇异向量)被缩放并改变方向以匹配左奇异向量 u1 和 u2。从基本变换的角度来看,这可以看作是先将右奇异向量旋转到规范基础上,进行轴对齐缩放,然后将规范基础旋转到左奇异向量。
将 v1 和 v2 旋转到 x 轴和 y 轴上(通过 R T \bold R^T R T 变换)。
在 x 和 y 上按比例缩放λ1 和λ2(通过 S \bold S S 变换)。
将 x 轴和 y 轴旋转回 v1 和 v2(通过 R \bold R R 变换)。
如果您想计算维度:对称的 2 × 2 2 \times 2 2 × 2 矩阵有 3 3 3 个自由度,特征值分解将它们重新写成旋转角度和两个缩放因子。
结合这三次变换的效果,我们可以看到它们导致了一对轴沿非均匀的缩放效应。与轴对齐缩放类似,这些轴是相互垂直的,但它们不是坐标轴,而是 A \bold A A 的特征向量。这告诉我们一些关于对称矩阵的含义:对称矩阵只是缩放操作——尽管可能是非均匀和非轴对齐的缩放操作。
例 14 回顾一下第 6.4 节中的示例:
因此,根据其特征值分解,上面的矩阵沿着离三点钟方向(x 轴)逆时针 31. 7 ∘ 31.7^\circ 31. 7 ∘ 的方向进行缩放。这是图 7.13 所证实的。
图 7.13。对称矩阵总是沿某个轴向进行缩放。在这种情况下,它沿着 ϕ = 31.7 ° ϕ=31.7° ϕ = 31.7° 的方向进行缩放,这意味着该矩阵的实特征向量在该方向上。
我们也可以反转对角化过程;要按 ( λ 1 , λ 2 ) (\lambda_1,\lambda_2) ( λ 1 , λ 2 ) 进行缩放,并使第一个缩放方向与 x 轴顺时针夹角为 ϕ \phi ϕ ,我们有
我们应该振作起来,因为我们知道这是一个对称矩阵,因为我们是从对称特征值分解构造它的。
奇异值分解(Singular Value Decomposition,SVD)也可以用于非对称矩阵,这与对称特征值分解非常相似,详见第 6.4.1 节。不同之处在于,在对角线矩阵两侧的矩阵不再相同:
A = U S V T \bold A = \bold U\bold S\bold V^T A = US V T
代替单个旋转 R 的两个正交矩阵被称为 U 和 V,它们的列分别被称为 u i u_i u i (左奇异向量)和 v i v_i v i (右奇异向量)。在这种情况下,S \bold S S 的对角元素被称为奇异值而不是特征值。其几何解释非常类似于对称特征值分解(图 7.14)。
如果您想计算维度:一般的 2 × 2 2 \times 2 2 × 2 矩阵有 4 4 4 个自由度,而奇异值分解将它们重新写成两个旋转角度和两个缩放因子。还需要一个比特位来跟踪反射,但这并不增加维度。
将 v 1 v_1 v 1 和 v 2 v_2 v 2 旋转到 x x x 轴和 y y y 轴上(通过 V T \bold V^T V T 变换)。
在 x x x 和 y y y 上按比例缩放 ( σ 1 , σ 2 ) (\sigma_1,\sigma_2) ( σ 1 , σ 2 ) (通过 S \bold S S 变换)。
将 x x x 轴和 y y y 轴旋转回 u 1 u_1 u 1 和 u 2 u_2 u 2 (通过 U \bold U U 变换)。
图 7.14 展示了在任意对称矩阵 A(也称非轴对齐的非均匀缩放)下,单位圆所发生的变换。两个垂直的向量 v 1 v_1 v 1 和 v 2 v_2 v 2 是 A 的特征向量,在方向上保持不变,但被缩放了。从基本的变换角度来看,这可以看作首先将特征向量旋转到规范基底,进行一个轴对齐的缩放,然后将规范基底旋转回特征向量。
主要区别在于单个旋转和两个不同的正交矩阵之间的差异。这种差异引起了另一个不太重要的差异。由于 SVD 在两个侧面具有不同的奇异向量,因此不需要负奇异值:我们总是可以翻转奇异值的符号,翻转其中一个相关的奇异向量的方向,最终再次得到相同的变换。因此,SVD 始终产生所有正元素的对角矩阵,但是不能保证矩阵 U \bold U U 和 V \bold V V 为旋转矩阵——它们也可能包含反射矩阵。在像图形学这样的几何应用中,这是一种不便,但很小的不便:可以通过检查行列式来区分旋转和反射,行列式为 +1 表示旋转,-1 表示反射;如果需要旋转,则可以否定其中一个奇异值,从而得到旋转-缩放-旋转的序列,其中反射与缩放结合在一起,而不是与旋转之一结合。
例 15 第 6.4.1 节中使用的示例实际上是一个剪切矩阵(如图 7.15 所示):
存在 SVD 的一个直接结果是,我们所见过的所有 2D 变换矩阵都可以由旋转矩阵和缩放矩阵构成。剪切矩阵是一种方便,但不是表达变换所必需的。
总之,每个矩阵都可以通过 SVD 分解为旋转乘以缩放乘以另一个旋转。只有对称矩阵可以通过特征值对角化分解为旋转乘以缩放乘以逆旋转,这样的矩阵是任意方向上的简单缩放。对称矩阵的 SVD 将通过稍微更复杂的代数运算得到与特征值分解相同的三重积。
图 7.15 展示了一种切变矩阵的奇异值分解(SVD)方法。任何 2D 矩阵都可以分解为旋转、缩放、旋转的乘积。请注意,钟表的圆形面必须变成椭圆,因为它只是一个旋转和缩放的圆。
Paeth 旋转分解
另一种分解使用剪切来表示非零旋转(Paeth, 1990)。以下恒等式允许这样做:
[ cos ϕ − sin ϕ sin ϕ cos ϕ ] = [ 1 cos ϕ − 1 sin ϕ ] [ 1 0 sin ϕ 1 ] [ 1 − cos ϕ 1 sin ϕ ] . \begin{bmatrix}\cos\phi & -\sin\phi \\ \sin\phi & \cos\phi \\\end{bmatrix} = \begin{bmatrix}1 & \cos\phi \\ -1 & \sin\phi \\\end{bmatrix} \begin{bmatrix}1 & 0 \\ \sin\phi & 1 \\\end{bmatrix} \begin{bmatrix}1 & -\cos\phi \\ 1 & \sin\phi \\\end{bmatrix}. [ cos ϕ sin ϕ − sin ϕ cos ϕ ] = [ 1 − 1 cos ϕ sin ϕ ] [ 1 sin ϕ 0 1 ] [ 1 1 − cos ϕ sin ϕ ] .
例如,一个 π / 4 π/4 π /4 (45°)的旋转是(参见图 7.16)
rotate ( π 4 ) = [ 1 1 − 2 0 1 ] [ 1 0 2 2 1 ] [ 1 1 − 2 0 1 ] (7.2) \operatorname{rotate}\left(\frac{\pi}{4}\right)=\left[\begin{array}{cc}
1 & 1-\sqrt{2} \\
0 & 1
\end{array}\right]\left[\begin{array}{cc}
1 & 0 \\
\frac{\sqrt{2}}{2} & 1
\end{array}\right]\left[\begin{array}{cc}
1 & 1-\sqrt{2} \\
0 & 1
\end{array}\right] \tag{7.2} rotate ( 4 π ) = [ 1 0 1 − 2 1 ] [ 1 2 2 0 1 ] [ 1 0 1 − 2 1 ] ( 7.2 )
图 7.16。任意 2D 旋转都可以通过三个剪切依次完成。在这种情况下,一个 45°的旋转被分解如公式 7.2 所示。
这个特殊变换对于栅格旋转非常有用,因为剪切是图像的一种高效栅格操作;它会引入一些锯齿状,但不会留下空洞。关键观察是,如果我们取一个栅格位置(i,j)并对其应用水平剪切,则
[ 1 s 0 1 ] [ i j ] = [ i + s j j ] \left[\begin{array}{ll}
1 & s \\
0 & 1
\end{array}\right]\left[\begin{array}{l}
i \\
j
\end{array}\right]=\left[\begin{array}{c}
i+s j \\
j
\end{array}\right] [ 1 0 s 1 ] [ i j ] = [ i + s j j ]
如果我们将 sj 四舍五入到最近的整数,则相当于将图像中的每一行向侧面移动一些量——对于每一行都是不同的量。由于在行内是相同的位移,这允许我们旋转而不会在结果图像中留下空隙。类似的操作也适用于垂直剪切。因此,我们可以轻松实现简单的栅格旋转。
7.2 三维线性变换
线性三维变换是二维变换的扩展。例如,沿笛卡尔坐标轴进行缩放的矩阵为:
s c a l e ( s x , s y , s z ) = [ s x 0 0 0 s y 0 0 0 s z ] (7.3) \mathrm{scale}(s_x, s_y, s_z)=\begin{bmatrix}s_x & 0 & 0 \\ 0 & s_y & 0 \\ 0 & 0 & s_z \end{bmatrix} \tag{7.3} scale ( s x , s y , s z ) = ⎣ ⎡ s x 0 0 0 s y 0 0 0 s z ⎦ ⎤ ( 7.3 )
在三维空间中,旋转比二维复杂得多,因为有更多可能的旋转轴。然而,如果我们只想绕 z 轴旋转,这将只改变 x 和 y 坐标,我们可以使用二维旋转矩阵,而不对 z 进行任何操作:
rotate-z ( ϕ ) = [ cos ϕ − sin ϕ 0 sin ϕ cos ϕ 0 0 0 1 ] . \operatorname{rotate-z}(\phi)=\left[\begin{array}{ccc}
\cos \phi & -\sin \phi & 0 \\
\sin \phi & \cos \phi & 0 \\
0 & 0 & 1
\end{array}\right] . rotate-z ( ϕ ) = ⎣ ⎡ cos ϕ sin ϕ 0 − sin ϕ cos ϕ 0 0 0 1 ⎦ ⎤ .
同样地,我们可以构造绕 x 轴和 y 轴旋转的矩阵:
rotate-x ( ϕ ) = [ 1 0 0 0 cos ϕ − sin ϕ 0 sin ϕ cos ϕ ] \operatorname{rotate-x}(\phi)=\left[\begin{array}{ccc}
1 & 0 & 0 \\
0 & \cos \phi & -\sin \phi \\
0 & \sin \phi & \cos \phi
\end{array}\right] rotate-x ( ϕ ) = ⎣ ⎡ 1 0 0 0 cos ϕ sin ϕ 0 − sin ϕ cos ϕ ⎦ ⎤
rotate-y ( ϕ ) = [ cos ϕ 0 sin ϕ 0 1 0 − sin ϕ 0 cos ϕ ] \text { rotate-y }(\phi)=\left[\begin{array}{ccc}
\cos \phi & 0 & \sin \phi \\
0 & 1 & 0 \\
-\sin \phi & 0 & \cos \phi
\end{array}\right] rotate-y ( ϕ ) = ⎣ ⎡ cos ϕ 0 − sin ϕ 0 1 0 sin ϕ 0 cos ϕ ⎦ ⎤
要理解为什么 y 轴旋转中左下方有一个负号,请想象一下三个轴按循环顺序排列:y 在 x 之后;z 在 y 之后;x 在 z 之后。
我们将在下一节中讨论绕任意轴的旋转。
与二维变换类似,我们可以沿特定轴进行剪切,例如:
shear-x ( d y , d z ) = [ 1 d y d z 0 1 0 0 0 1 ] \text { shear-x }\left(d_{y}, d_{z}\right)=\left[\begin{array}{ccc}
1 & d_{y} & d_{z} \\
0 & 1 & 0 \\
0 & 0 & 1
\end{array}\right] shear-x ( d y , d z ) = ⎣ ⎡ 1 0 0 d y 1 0 d z 0 1 ⎦ ⎤
与 2D 变换一样,任何 3D 变换矩阵都可以使用 SVD 分解为旋转、缩放和另一个旋转。任何对称的 3D 矩阵都有旋转、缩放和逆旋转的特征值分解。最后,一个 3D 旋转可以分解为一系列 3D 剪切矩阵的乘积。
7.2.1 任意 3D 旋转
与二维变换类似,三维旋转是正交矩阵。从几何上讲,这意味着矩阵的三行是三个相互正交的单位向量的笛卡尔坐标,如第 2.4.5 节所述。列是三个可能不同的相互正交的单位向量。有无限多个这样的旋转矩阵。我们来写下这样一个矩阵:
R u v w = [ x u y u z u x v y v z v x w y w z w ] . \mathrm{R}_{uvw}=\begin{bmatrix}x_u & y_u & z_u \\ x_v & y_v & z_v \\ x_w & y_w & z_w \\\end{bmatrix}. R uv w = ⎣ ⎡ x u x v x w y u y v y w z u z v z w ⎦ ⎤ .
其中,u = x u x + y u y + z u z u=x_u\mathbf{x}+y_u\mathbf{y}+z_u\mathbf{z} u = x u x + y u y + z u z ,依此类推对于 v , w \bold v,\bold w v , w 。由于这三个向量是正交的且模为 1,我们知道
u ⋅ u = v ⋅ v = w ⋅ w = 1 u ⋅ v = v ⋅ w = w ⋅ u = 0 \begin{array}{l}
\mathbf{u} \cdot \mathbf{u}=\mathbf{v} \cdot \mathbf{v}=\mathbf{w} \cdot \mathbf{w}=1 \\
\mathbf{u} \cdot \mathbf{v}=\mathbf{v} \cdot \mathbf{w}=\mathbf{w} \cdot \mathbf{u}=0
\end{array} u ⋅ u = v ⋅ v = w ⋅ w = 1 u ⋅ v = v ⋅ w = w ⋅ u = 0
通过将旋转矩阵应用于向量 u , v , w \bold u,\bold v,\bold w u , v , w ,我们可以推断出一些它的行为。例如,
注意到 R u v w u \mathrm{\bold R}_{uvw}\bold u R uv w u 的这三行都是点积:
R u v w u = [ u ⋅ u v ⋅ u w ⋅ u ] = [ 1 0 0 ] = x \mathbf{R}_{u v w} \mathbf{u}=\left[\begin{array}{c}
\mathbf{u} \cdot \mathbf{u} \\
\mathbf{v} \cdot \mathbf{u} \\
\mathbf{w} \cdot \mathbf{u}
\end{array}\right]=\left[\begin{array}{l}
1 \\
0 \\
0
\end{array}\right]=\mathbf{x} R uv w u = ⎣ ⎡ u ⋅ u v ⋅ u w ⋅ u ⎦ ⎤ = ⎣ ⎡ 1 0 0 ⎦ ⎤ = x
同样地,R u v w v = y \mathrm{R}_{uvw}v=\mathbf{y} R uv w v = y ,R u v w w = z \mathrm{R}_{uvw}w=\mathbf{z} R uv w w = z 。因此,R u v w \mathrm{R}_{uvw} R uv w 通过旋转将基向量 u , v , w \bold u,\bold v,\bold w u , v , w 变换为相应的笛卡尔坐标轴。
如果 R u v w R_{uvw} R uv w 是一组行向量正交的旋转矩阵,那么 R u v w T \bold R^{\bold T}_{uvw} R uv w T 也是一组列向量正交的旋转矩阵,并且实际上是 R u v w \bold R_{uvw} R uv w 的逆矩阵(正交矩阵的逆矩阵总是其转置)。一个重要的点是,在变换矩阵中,代数逆矩阵也是几何逆矩阵。因此,如果 R u v w \bold R_{uvw} R uv w 将 u \boldsymbol{\bold u} u 映射到 x \boldsymbol{\bold x} x ,则 R u v w T R^T_{uvw} R uv w T 将 x \boldsymbol{\bold x} x 映射回 u \boldsymbol{\bold u} u 。对于 v \boldsymbol{\bold v} v 和 y \boldsymbol{\bold y} y 也应该如此,我们可以验证:
因此,我们总是可以从标准正交基创建旋转矩阵。
如果我们想围绕任意向量 a \boldsymbol{\bold a} a 旋转,我们可以用 w = a \boldsymbol{\bold w}=\boldsymbol{\bold a} w = a 创建一个正交基,将该基旋转到标准基 XYZ,绕 Z 轴旋转,然后将标准基旋转回 u v w \bold u\bold v\bold w uvw 基。矩阵形式下,绕 w \bold w w 轴旋转角度 ϕ \phi ϕ 的矩阵为:
这里,我们有 w \boldsymbol{\bold w} w 是指向 a \boldsymbol{\bold a} a 的单位向量(即 a \boldsymbol{\bold a} a 除以自身长度)。但是 u \boldsymbol{\bold u} u 和 v \boldsymbol{\bold v} v 是什么呢?在 2.4.6 节中介绍了寻找合理 u \boldsymbol{\bold u} u 和 v \boldsymbol{\bold v} v 的方法。
如果我们有一个旋转矩阵,并且我们希望将其表示为轴角形式,则可以计算一个实特征值(将是 λ = 1 \lambda=1 λ = 1 ),相应的特征向量是旋转的轴。这是唯一一个不会被旋转改变的轴。
另外,见 16.2.2 节,比较最常用的除旋转矩阵之外的表示旋转的几种方法。
在大多数情况下,我们使用的 3D 向量代表位置(相对于原点的偏移向量)或方向,例如光线来自何处,但有些向量代表表面法线。表面法线垂直于表面的切平面。当底层表面发生变换时,这些法线不会按照我们所希望的方式进行变换。例如,如果通过矩阵 M 变换表面上的点,则与表面相切且乘以 M 的向量 t 将保持与变换后的表面相切。然而,通过 M 变换的表面法线向量 n 可能不垂直于变换后的表面(图 7.17)。
图 7.17 当使用相同的矩阵来变换物体上的点时,将法向量变换后得到的结果可能不垂直于表面,正如这里对于倾斜的矩形所示。然而,切向量在变换后会转化为与变换后表面相切的向量。
我们可以推导出一个变换矩阵 N,它确实将 n 变换为垂直于变换后的表面的向量。解决此问题的一种方法是注意到表面法线向量和切向量是垂直的,因此它们的点积为零,表示为矩阵形式为
n T t = 0. (7.4) \bold n^{\bold T} \bold t=0. \tag{7.4} n T t = 0. ( 7.4 )
如果我们将期望的变换向量表示为 t M = M t t_M=\bold M\bold t t M = Mt 和 n N = N n n_N=\bold N\bold n n N = Nn ,则我们的目标是找到 N \bold N N ,使得 n N T t M = 0 n^{\bold T}_Nt_M=0 n N T t M = 0 。我们可以通过一些代数技巧找到 N \bold N N 。
首先,我们可以通过将单位矩阵伪装成点积,并利用 M − 1 M = I \bold M^{-1}\bold M = \bold I M − 1 M = I 的性质:
n T t = n T I t = n T M − 1 M t = 0 \mathbf{n}^{\mathrm{T}} \mathbf{t}=\mathbf{n}^{\mathrm{T}} \mathbf{I} \mathbf{t}=\mathbf{n}^{\mathrm{T}} \mathbf{M}^{-1} \mathbf{M} \mathbf{t}=\mathbf{0} n T t = n T It = n T M − 1 Mt = 0
虽然上述操作似乎没有明显的进展,但请注意,我们可以加括号使上述表达式更容易理解为一个点积:
( n T M − 1 ) ( M t ) = ( n T M − 1 ) t M = 0 \left(\mathbf{n}^{\mathrm{T}} \mathbf{M}^{-1}\right)(\mathbf{M t})=\left(\mathbf{n}^{\mathrm{T}} \mathbf{M}^{-1}\right) \mathbf{t}_{M}=\mathbf{0} ( n T M − 1 ) ( Mt ) = ( n T M − 1 ) t M = 0
这意味着与 t M tM tM 垂直的行向量是上述表达式的左部分。该表达式适用于切平面中任何一个切向量。由于只有一个方向在 3D 中(以及它的反向),垂直于所有这些切向量,因此我们知道上述表达式的左部分必须是 n N n_N n N 的行向量表达式;即它是 n T N n_{TN} n TN ,因此这使我们能够推断 N N N :
n N T = n T M − 1 \mathbf{n}_{N}^{\mathrm{T}}=\mathbf{n}^{\mathrm{T}} \mathbf{M}^{-1} n N T = n T M − 1
因此,我们可以对其进行转置来得到
n N = ( n T M − 1 ) T = ( M − 1 ) T n . (7.5) \mathbf{n}_{N}=\left(\mathbf{n}^{\mathrm{T}} \mathbf{M}^{-1}\right)^{\mathrm{T}}=\left(\mathbf{M}^{-1}\right)^{\mathrm{T}} \mathbf{n} . \tag{7.5} n N = ( n T M − 1 ) T = ( M − 1 ) T n . ( 7.5 )
因此,我们可以看出,正确变换法向量使其保持正常的矩阵是 N = ( M − 1 ) T \bold N = (\bold M^{-1})^{\bold T} N = ( M − 1 ) T ,即逆矩阵的转置。由于这个矩阵可能会改变 n \bold n n 的长度,我们可以将其乘以任意标量,它仍然会产生具有正确方向的 n N \bold n_N n N 。回顾第 6.3 节中的内容,矩阵的逆是余子式矩阵的转置除以行列式。因为我们不关心法向量的长度,所以我们可以跳过除法,并找到对于一个 3 × 3 3 \times 3 3 × 3 矩阵,
N = [ 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 ] . \mathbf{N}=\left[\begin{array}{lll}
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{array}\right] . N = ⎣ ⎡ m 11 c m 21 c m 31 c m 12 c m 22 c m 32 c m 13 c m 23 c m 33 c ⎦ ⎤ .
其中,假设 M \bold M M 中第 i i i 行和第 j j j 列的元素为 m i j m_{ij} m ij 。因此,N \bold N N 的完整表达式为
7.3 平移和仿射变换
我们一直在研究使用矩阵 M 改变向量的方法。在二维空间中,这些变换的形式为
x ′ = m 11 x + m 12 y , y ′ = m 21 x + m 22 y . x' = m_{11} x + m_{12} y, \\
y' = m_{21} x + m_{22} y. x ′ = m 11 x + m 12 y , y ′ = m 21 x + m 22 y .
我们无法使用这些变换来移动对象,只能用于缩放和旋转。特别地,在线性变换下原点 ( 0 , 0 ) (0, 0) ( 0 , 0 ) 始终不变。要通过将所有点同时移动来移动或平移对象,我们需要一个如下的变换:
x ′ = x + x t , y ′ = y + y t . x' = x + x_t, \\
y' = y + y_t. x ′ = x + x t , y ′ = y + y t .
但是,无论如何都不能通过将 ( x , y ) (x,y) ( x , y ) 乘以 2 × 2 2\times 2 2 × 2 矩阵来实现这种变换。其中一种添加平移操作的可能性是将每个变换矩阵与一个单独的平移向量关联起来,让矩阵负责缩放和旋转,向量负责平移。这完全可行,但薄记很麻烦,并且组合两个变换的规则没有线性变换那么简单明了。
相反,我们可以使用一个巧妙的技巧,使单个矩阵乘法同时完成两个操作。其思想很简单:将点 ( x , y ) (x, y) ( x , y ) 表示为三维向量 [ x y 1 ] T [x\; y\; 1]^{\bold T} [ x y 1 ] T ,并使用下面形式的 3 × 3 3\times 3 3 × 3 矩阵
[ m 11 m 12 x t m 21 m 22 y t 0 0 1 ] . \begin{bmatrix}
m_{11} & m_{12} & x_t \\
m_{21} & m_{22} & y_t \\
0 & 0 & 1
\end{bmatrix}. ⎣ ⎡ m 11 m 21 0 m 12 m 22 0 x t y t 1 ⎦ ⎤ .
来实现缩放/旋转和平移的组合操作。这样做可以比将每个变换矩阵与单独的平移向量关联起来更简单。
固定的第三行用于将 1 复制到转换向量中,以便所有向量在最后一位都有一个 1,并且前两行计算 x 和 y 作为 x、y 和 1 的线性组合:
单个矩阵实现了线性变换和平移!这种类型的变换称为仿射变换,通过添加一个额外的维度来实现仿射变换的方式称为齐次坐标(homogeneous coordinates. Roberts,1965;Riesenfeld,1981;Penna&Patterson,1986)。齐次坐标不仅使变换代码更简洁,而且这种方案使得如何组合两个仿射变换非常明显:只需要将矩阵相乘即可。
这个名字“齐次”得以解释:位置和方向的平移、旋转和缩放都适合于单一的系统。
当我们需要变换的向量不是位置时,这种新形式主义就会出现问题——它们代表位置之间的方向或偏移量。当我们平移一个物体时,表示方向或偏移量的向量不应该改变。幸运的是,我们可以通过将第三个坐标设置为零来解决这个问题:
[ 1 0 x t 0 1 y t 0 0 1 ] [ x y 0 ] = [ x y 0 ] \left[\begin{array}{ccc}
1 & 0 & x_{t} \\
0 & 1 & y_{t} \\
0 & 0 & 1
\end{array}\right]\left[\begin{array}{l}
x \\
y \\
0
\end{array}\right]=\left[\begin{array}{l}
x \\
y \\
0
\end{array}\right] ⎣ ⎡ 1 0 0 0 1 0 x t y t 1 ⎦ ⎤ ⎣ ⎡ x y 0 ⎦ ⎤ = ⎣ ⎡ x y 0 ⎦ ⎤
这正是我们希望对向量的行为,所以它们可以顺畅地适应这个系统:额外的(第三个)坐标将根据我们是编码位置还是方向而是 1 或 0。实际上,我们确实需要存储齐次坐标,以便我们可以区分位置和其他向量。例如,
稍后,当我们进行透视查看时,我们将看到允许齐次坐标采用除 1 或零以外的值是有用的。
几乎普遍使用齐次坐标来表示图形系统中的变换。特别是,在图形硬件实现的渲染器的设计和操作中,齐次坐标是基础。在第 8 章中,我们将看到,齐次坐标还使得轻松地在透视场景中绘制成为可能,这是它们受欢迎的另一个原因。
齐次坐标也广泛应用于计算机视觉。
齐次坐标可以被认为只是处理平移簿记的巧妙方式,但还有一种不同的几何解释。关键观察是,当我们基于 z 坐标进行 3D 剪切时,我们得到这个变换:
[ 1 0 x t 0 1 y t 0 0 1 ] [ x y z ] = [ x + x t z y + y t z 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} ⎣ ⎡ 1 0 0 0 1 0 x t y t 1 ⎦ ⎤ ⎣ ⎡ x y z ⎦ ⎤ = ⎣ ⎡ x + x t z y + y t z z ⎦ ⎤
请注意,在 x 和 y 中它几乎具有我们想要的 2D 平移形式,但是有一个不在 2D 中有意义的 z。现在来做出关键决定:我们将在所有 2D 位置上添加一个坐标 z = 1。这给了我们
[ 1 0 x t 0 1 y t 0 0 1 ] [ x y 1 ] = [ x + x t y + y t 1 ] . \left[\begin{array}{ccc}
1 & 0 & x_{t} \\
0 & 1 & y_{t} \\
0 & 0 & 1
\end{array}\right]\left[\begin{array}{l}
x \\
y \\
1
\end{array}\right]=\left[\begin{array}{c}
x+x_{t} \\
y+y_{t} \\
1
\end{array}\right] . ⎣ ⎡ 1 0 0 0 1 0 x t y t 1 ⎦ ⎤ ⎣ ⎡ x y 1 ⎦ ⎤ = ⎣ ⎡ x + x t y + y t 1 ⎦ ⎤ .
通过将 (z = 1)-坐标与所有 2D 点相关联,我们现在可以将平移编码成矩阵形式。例如,要先在 2D 中平移 ( x t , y t ) (x_t,y_t) ( x t , y t ) ,然后旋转角度 ϕ \phi ϕ ,我们将使用矩阵
M = [ cos ϕ − sin ϕ 0 sin ϕ cos ϕ 0 0 0 1 ] [ 1 0 x t 0 1 y t 0 0 1 ] \mathbf{M}=\left[\begin{array}{ccc}
\cos \phi & -\sin \phi & 0 \\
\sin \phi & \cos \phi & 0 \\
0 & 0 & 1
\end{array}\right]\left[\begin{array}{ccc}
1 & 0 & x_{t} \\
0 & 1 & y_{t} \\
0 & 0 & 1
\end{array}\right] M = ⎣ ⎡ cos ϕ sin ϕ 0 − sin ϕ cos ϕ 0 0 0 1 ⎦ ⎤ ⎣ ⎡ 1 0 0 0 1 0 x t y t 1 ⎦ ⎤
请注意,2D 旋转矩阵现在是 3 × 3 3 \times 3 3 × 3 的,其中“平移槽”中有零。使用这种类型的形式主义(即使用沿着 z = 1 z=1 z = 1 的剪切来编码平移),我们可以将任意数量的 2D 剪切、2D 旋转和 2D 平移表示为一个组合的 3D 矩阵。该矩阵的底行始终为 ( 0 , 0 , 1 ) (0, 0, 1) ( 0 , 0 , 1 ) ,因此我们实际上不必存储它。我们只需要在两个矩阵相乘时记住它就好了。
在 3D 中,同样的技术也适用:我们可以添加第四个坐标,即齐次坐标,然后就有了平移:
[ 1 0 0 x t 0 1 0 y t 0 0 1 z t 0 0 0 1 ] [ x y z 1 ] = [ x + x t y + y t z + z t 1 ] . \left[\begin{array}{cccc}
1 & 0 & 0 & x_{t} \\
0 & 1 & 0 & y_{t} \\
0 & 0 & 1 & z_{t} \\
0 & 0 & 0 & 1
\end{array}\right]\left[\begin{array}{c}
x \\
y \\
z \\
1
\end{array}\right]=\left[\begin{array}{c}
x+x_{t} \\
y+y_{t} \\
z+z_{t} \\
1
\end{array}\right] . ⎣ ⎡ 1 0 0 0 0 1 0 0 0 0 1 0 x t y t z t 1 ⎦ ⎤ ⎣ ⎡ x y z 1 ⎦ ⎤ = ⎣ ⎡ x + x t y + y t z + z t 1 ⎦ ⎤ .
同样,在方向向量中,第四个坐标为零,因此向量不受平移的影响。
例 16(窗口变换)在图形学中,我们经常需要创建一个变换矩阵,以将点从矩形 [ x l , x h ] × [ y l , y h ] [x_l, x_h] \times [y_l, y_h] [ x l , x h ] × [ y l , y h ] 变换到另一个矩形 [ x l ′ , x h ′ ] × [ y l ′ , y h ′ ] [x_l', x_h'] \times [y_l', y_h'] [ x l ′ , x h ′ ] × [ y l ′ , y h ′ ] 。这可以通过一系列单一的缩放和平移来完成。但是,更直观的方法是通过一系列三个操作来创建变换(见图 7.18):
将点 ( x l , y l ) (x_l,y_l) ( x l , y l ) 移动到原点。
将矩形缩放为与目标矩形相同大小。
将原点移动到点 ( x l , y l ) (x_l,y_l) ( x l , y l ) 。
图 7.18。把一个矩形(窗口)映射到另一个矩形的过程,首先将左下角移到原点,然后按比例缩放到新的大小,最后将原点移动到目标矩形的左下角。
记住右手边的矩阵首先被应用,我们可以写成
对于一些读者来说,这个结果看起来像这样并不奇怪,但通过三个矩阵的构造过程,没有什么疑问。
完全类似的方法可以用来定义 3D 窗口变换,将箱子 [ x l , x h ] × [ y l , y h ] × [ z l , z h ] [x_l, x_h] \times [y_l, y_h] \times [z_l, z_h] [ x l , x h ] × [ y l , y h ] × [ z l , z h ] 映射到箱子 [ x l ′ , x h ′ ] × [ y l ′ , y h ′ ] × [ z l ′ , z h ′ ] [x_l', x_h'] \times [y_l', y_h'] \times [z_l', z_h'] [ x l ′ , x h ′ ] × [ y l ′ , y h ′ ] × [ z l ′ , z h ′ ] :
有趣的是,如果我们将由比例,剪切和旋转组成的任意矩阵与简单的平移相乘(平移在第二个),我们会得到
因此,我们可以查看任何矩阵并将其视为一个缩放/旋转部分和一个平移部分,因为这些组件很好地分离开来。
一类重要的变换是刚体变换。它们仅由平移和旋转组成,因此它们不会拉伸或收缩物体。这样的变换对于上面的 a i j a_{ij} a ij 来说将具有纯旋转。
7.4 变换矩阵的逆矩阵
虽然我们总是可以代数地求一个矩阵的逆,但如果我们知道变换的作用,就可以用几何方法来求逆矩阵。例如,s c a l e ( s x , s y , s z ) scale(s_x, s_y, s_z) sc a l e ( s x , s y , s z ) 的逆是 s c a l e ( 1 / s x , 1 / s y , 1 / s z ) scale(1/s_x, 1/s_y, 1/s_z) sc a l e ( 1/ s x , 1/ s y , 1/ s z ) 。旋转的逆是相反角度的同一个旋转。平移的逆是方向相反的平移。如果我们有一系列矩阵 M = M 1 M 2 ⋯ M n \mathbf{M}=\mathbf{M}_{1} \mathbf{M}_{2} \cdots \mathbf{M}_{n} M = M 1 M 2 ⋯ M n ,则 M − 1 = M n − 1 ⋯ M 2 − 1 M 1 − 1 . \mathbf{M}^{-1}=\mathbf{M}_{n}^{-1} \cdots \mathbf{M}_{2}^{-1} \mathbf{M}_{1}^{-1} . M − 1 = M n − 1 ⋯ M 2 − 1 M 1 − 1 . 。
此外,某些类型的变换矩阵很容易求逆。我们已经提到了对角矩阵缩放;第二个重要的例子是正交矩阵旋转。回想一下(第 6.2.4 节),正交矩阵的逆是它的转置。这使得求解旋转和刚体变换的逆变得容易(见练习 6)。另外,知道带有底部行 [ 0001 ] [0 0 0 1] [ 0001 ] 的矩阵具有一个也有底部行 [ 0001 ] [0 0 0 1] [ 0001 ] 的逆矩阵是有用的(见练习 7)。
有趣的是,我们也可以使用奇异值分解(SVD)来求解矩阵的逆。因为我们知道任何矩阵都可以分解为一个旋转乘以一个缩放再乘以一个旋转,所以求矩阵的逆就很简单。例如,在三维空间中,我们有:
M = R 1 scale ( σ 1 , σ 2 , σ 3 ) R 2 \mathbf{M}=\mathbf{R}_{1} \operatorname{scale}\left(\sigma_{1}, \sigma_{2}, \sigma_{3}\right) \mathbf{R}_{2} M = R 1 scale ( σ 1 , σ 2 , σ 3 ) R 2
从以上规则,很容易得出
M − 1 = R 2 T scale ( 1 / σ 1 , 1 / σ 2 , 1 / σ 3 ) R 1 T \mathbf{M}^{-1}=\mathbf{R}_{2}^{\mathrm{T}} \operatorname{scale}\left(1 / \sigma_{1}, 1 / \sigma_{2}, 1 / \sigma_{3}\right) \mathbf{R}_{1}^{\mathrm{T}} M − 1 = R 2 T scale ( 1/ σ 1 , 1/ σ 2 , 1/ σ 3 ) R 1 T
7.5 坐标变换
前面的讨论都是关于使用变换矩阵来移动点。我们还可以将它们看作是仅仅改变表示点的坐标系。例如,在图 7.19 中,我们可以看到两种可视化运动的方式。在不同的上下文中,任意一种解释可能会更加合适。
图 7.19。对点 (2,1) 应用变换“平移 (–1,0)” 。在右上方是我们将这个变换视为物理运动的想象图像,在右下方是我们将其视为坐标变换(在这种情况下是原点的移动)的想象图像。人造边界只是一种手法,在任何情况下,坐标轴和点的相对位置都是相同的。
例如,驾驶游戏可能有一个城市模型和一辆汽车模型。如果玩家看到的是从车窗外看出去的视角,车内的物体总是在屏幕上的同一位置绘制,而街道和建筑物则随着玩家驾驶而向后移动。在每一帧中,我们都会对这些对象应用一种变换,将它们移得比前一帧更远。可以把这个操作看作是将建筑物向后移动,也可以将其看作是建筑物保持不动,但我们想要绘制它们的坐标系——与汽车相连的坐标系——正在移动。在第二种解释中,变换是将城市几何的坐标更改,将其表示为与汽车坐标系中的坐标。两种方法都会导致应用于汽车外部几何的矩阵完全相同。
如果游戏还支持俯视视图以显示汽车在城市中的位置,则需要在固定位置绘制建筑物和街道,同时汽车需要在一帧帧中移动。同样适用两种解释:我们可以将变换视为将汽车从其规范位置移动到世界中的当前位置;或者我们可以将变换视为仅更改汽车几何的坐标,该坐标最初是表示为与汽车相关的坐标系中的坐标,现在则改为表示为与城市相关的固定坐标系中的坐标。坐标变换解释清楚了在这两种模式下使用的矩阵(从城市到汽车坐标变换与从汽车到城市坐标变换)是彼此的逆矩阵。
改变坐标系的概念就像编程中的类型转换一样。在我们将浮点数与整数相加之前,我们需要将整数转换为浮点数或浮点数转换为整数,具体取决于我们的需求,以使类型匹配。在绘制城市和汽车之前,我们需要将城市转换为汽车坐标或将汽车转换为城市坐标,具体取决于我们的需求,以使坐标匹配。
管理多个坐标系时,很容易混淆对象的坐标,导致它们出现在意想不到的位置。但是通过系统地思考坐标系之间的转换,您可以可靠地正确进行转换。
几何上来说,一个坐标系或坐标框架由一个原点和一组三个向量构成。正交规范基底非常方便,除非另有说明,否则我们通常假设框架是正交规范的。在具有原点 p 和基底 { u , v , w } \{\bold u, \bold v, \bold w\} { u , v , w } 的框架中,坐标 ( u , v , w ) (u,v,w) ( u , v , w ) 描述了点 p + u u + v v + w w \bold p+u\bold u+v\bold v+w\bold w p + u u + v v + w w 。
当然,在 2D 中,有两个基向量。
当我们将这些向量存储在计算机中时,它们需要用某个坐标系表示。为了开始工作,我们必须指定一些标准坐标系,通常称为“全局”或“世界”坐标系,该坐标系用于描述所有其他坐标系。在城市示例中,我们可以采用街道网格,并使用约定,即 x 轴沿着主街道指向,y 轴指向上,z 轴沿着中央大街指向。然后,当我们根据这些坐标编写汽车框架的起点和基础时,就清楚我们的意思了。
在 2D 中,我们的约定是使用点 o 作为原点,x 和 y 作为
在 2D 中,右手坐标系意味着 y 轴从 x 轴逆时针旋转。右手坐标系的正交规范基向量 x 和 y 如图 7.20 所示。
图 7.20。点 p 可以用任何一种坐标系表示。
另一个坐标系可能有一个原点 e 和右手正交规范基底向量 u 和 v。请注意,通常不会显式存储标准数据 o、x 和 y。它们是所有其他坐标系的参照系。在该坐标系中,我们经常将 p 的位置写成有序对,这是一个完整向量表达式的简写:
p = ( x p , y p ) ≡ o + x p x + y p y \bold p=(x_p,y_p)≡\bold o+x_p\bold x+y_p\bold y p = ( x p , y p ) ≡ o + x p x + y p y
例如,在图 7.20 中,( x p , y p ) = ( 2.5 , 0.9 ) (x_p,y_p)=(2.5,0.9) ( x p , y p ) = ( 2.5 , 0.9 ) 。请注意,有序对 ( x p , y p ) (x_p,y_p) ( x p , y p ) 隐含地假定原点 o \bold o o 。同样,我们可以用另一个方程表达 p \bold p p :
p = ( u p , v p ) ≡ e + u p x + v p v \bold p=(u_p,v_p)≡\bold e+u_p\bold x+v_p\bold v p = ( u p , v p ) ≡ e + u p x + v p v
在图 7.20 中,这个方程的解为 ( u p , v p ) = ( 0.5 , − 0.7 ) (u_p,v_p)=(0.5,-0.7) ( u p , v p ) = ( 0.5 , − 0.7 ) 。同样,原点 e 作为与 u 和 v 相关的坐标系的一个隐含部分。
我们可以使用矩阵机器来表达这个关系,就像这样:
请注意,这假定我们已经将点 e 和向量 u 和 v 存储在标准坐标中;(x,y)坐标系是众多坐标系中的第一个。根据本章讨论的基本变换类型,这是一个旋转(涉及到 u 和 v)后跟一个平移(涉及到 e)。看一下旋转和平移的矩阵,你会发现它非常容易写出来:我们只需将 u、v 和 e 放入矩阵的列中,第三行通常为 [0 0 1]。为了使这更清晰,我们可以像这样写出矩阵:
p x y = [ u v e 0 0 1 ] p u v \mathbf{p}_{x y}=\left[\begin{array}{lll}
\mathbf{u} & \mathbf{v} & \mathbf{e} \\
0 & 0 & 1
\end{array}\right] \mathbf{p}_{u v} p x y = [ u 0 v 0 e 1 ] p uv
“框架到标准”名称是基于将向量的坐标从一个系统转换到另一个系统的思考方式。从移动向量的角度考虑,框架到标准矩阵将标准框架映射到(u,v)框架。
我们称这个矩阵为(u,v)框架的框架到标准矩阵。它将用(u,v)框架表示的点转换为在标准框架中表示的相同点。
要反过来,我们有
这是一个平移后跟一个旋转;它们是我们用来构建框架到标准矩阵的旋转和平移的逆变换,并且当它们相乘时,它们产生框架到标准矩阵的逆变换,这个逆变换(不出所料)被称为标准到框架矩阵:
p u v = [ u v e 0 0 1 ] − 1 p x y \mathbf{p}_{u v}=\left[\begin{array}{lll}
\mathbf{u} & \mathbf{v} & \mathbf{e} \\
0 & 0 & 1
\end{array}\right]^{-1} \mathbf{p}_{x y} p uv = [ u 0 v 0 e 1 ] − 1 p x y
标准到框架矩阵将在标准框架中表示的点转换为在(u,v)框架中表示的相同点。我们将这个矩阵写成框架到标准矩阵的逆矩阵形式,因为不能立即使用 e、u 和 v 的标准坐标来写出标准到框架矩阵。但请记住,所有的坐标系都是等价的;只有我们按照 x 和 y 坐标存储向量的约定才会产生这种看似不对称的情况。标准到框架矩阵可以简单地用 o、x 和 y 的(u,v)坐标表达:
p u v = [ x u v y u v o u v 0 0 1 ] p x y \mathbf{p}_{u v}=\left[\begin{array}{ccc}
\mathbf{x}_{u v} & \mathbf{y}_{u v} & \mathbf{o}_{u v} \\
0 & 0 & 1
\end{array}\right] \mathbf{p}_{x y} p uv = [ x uv 0 y uv 0 o uv 1 ] p x y
所有这些想法在 3D 中严格类似,我们有
和
常见问题
我不能只硬编码变换而不使用矩阵形式吗?
可以,但在实践中,它更难推导、更难调试,而且并没有更高效的方法。此外,所有当前的图形 API 都使用这个矩阵形式,所以即使使用图形库也必须理解它。
矩阵的底行总是 ( 0 , 0 , 0 , 1 ) (0,0,0,1) ( 0 , 0 , 0 , 1 ) 。我必须存储它吗?
除非包括透视变换(第 8 章),否则不需要存储它。
备注
这里的法线变换属性的推导基于表面法线变换属性 (Turkowski,1990)。在许多处理方法中,直到 1990 年代中期,向量被表示为行向量并进行预乘,例如,b = a M \bold b = \bold a\bold M b = aM 。在我们的符号表示中,这将是 b T = a T M T \bold b^{\bold T} = \bold a^{\bold T}\bold M^{\bold T} b T = a T M T 。如果要找到一个旋转矩阵 R \bold R R ,使一个向量 a a a 转变成相同长度的向量 b:b = R a \bold b = \bold R\bold a b = Ra ,你可以使用由正交规范基构造的两个旋转。一种更高效的方法是在 Efficiently Building a Matrix to Rotate One Vector to Another(Akenine-Möller、Haines 和 Hoffman,2008) 中提供的。
练习题
写出 4 × 4 4×4 4 × 4 3D 矩阵,将物体移动 ( x m , y m , z m ) (x_m, y_m, z_m) ( x m , y m , z m ) 。
写出绕 y 轴旋转 θ 角度的 4 × 4 3D 矩阵。
写出 4 × 4 4 × 4 4 × 4 3D 矩阵,将物体在所有方向上缩放 50%。
写出顺时针旋转 90 度的 2D 旋转矩阵。
将练习 4 中的矩阵写成三个剪切矩阵的乘积。
求刚体变换的逆:
[ R t 000 1 ] \left[\begin{array}{ccc}
R & t \\
000 & 1
\end{array}\right] [ R 000 t 1 ]
其中 R 是一个 3 × 3 3 × 3 3 × 3 的旋转矩阵,t 是一个 3 向量。
证明仿射变换的逆矩阵也具有相同的形式(底部除了右下角为 1 之外都是零)。
用语言描述这个 2D 变换矩阵的作用:
[ 0 − 1 1 1 0 1 0 0 1 ] \left[\begin{array}{ccc}
0 & -1 & 1 \\
1 & 0 & 1 \\
0 & 0 & 1
\end{array}\right] ⎣ ⎡ 0 1 0 − 1 0 0 1 1 1 ⎦ ⎤
写出一个 3 × 3 3 × 3 3 × 3 的矩阵,使得一个点绕点 p = ( x p , y p ) \bold p = (x_p, y_p) p = ( x p , y p ) 旋转 θ θ θ 角度。
写下一个 4 × 4 4 × 4 4 × 4 的旋转矩阵,将正交的 3D 向量 u = ( x u , y u , z u ) \bold u = (x_u, y_u, z_u) u = ( x u , y u , z u ) ,v = ( x v , y v , z v ) \bold v = (x_v, y_v, z_v) v = ( x v , y v , z v ) 和 w = ( x w , y w , z w ) \bold w = (x_w, y_w, z_w) w = ( x w , y w , z w ) 映射到正交的 3D 向量 a = ( x a , y a , z a ) \bold a = (x_a, y_a, z_a) a = ( x a , y a , z a ) ,b = ( x b , y b , z b ) \bold b = (x_b, y_b, z_b) b = ( x b , y b , z b ) 和 c = ( x c , y c , z c ) \bold c = (x_c, y_c, z_c) c = ( x c , y c , z c ) ,使得 M u = a , M v = b , M w = c M_{\bold u} = \bold a,M_{\bold v} = \bold b,M_{\bold w} = \bold c M u = a , M v = b , M w = c 。
上一个问题的逆矩阵是什么?