计算机图形学基础(3)——观测变换

上一篇文章我们介绍了计算机图形学中的数学基础,包括:2D 变换、3D 变换、齐次坐标等。本文,我们则来介绍将三维模型投影到二维屏幕的数学原理。

观测变换

我们将三维模型投影到二维屏幕的过程称之为 观测变换(Viewing Transformation)。

事实上,观测变换和我们平时拍照一样,总体可以分成三个步骤:

  • 摆放物体。在图形学中称为 模型变换(Model Transformation)
  • 摆放相机。在图形学中称为 视图变换(View Transformation)
  • 拍照。在图形学中称为 投影变换(Project Transformation)

根据这三个步骤的英文缩写,观测变换也可以称为 MVP 变换。不过在图形学中,并不是严格按照这个顺序来执行的,而是先进行视图变换,再进行模型变换。至于为什么,我们稍后再解释。

下面,我们来分别介绍这三种变换。

视图变换

视图变换也称为相机变换(Camera Transformation),视图的内容本质上是由相机的位置决定的,因此这里我们真正要做的是相机变换。

首先,我们使用如下三个向量来描述相机的 原始位置,从而唯一确定其位置、观测方向、画面方向。

  • 位置:\(\vec{e}\)
  • 观测方向:\(\hat{g}\)
  • 向上方向:\(\hat{t}\)

为了方便后续的计算,我们将相机放置到空间坐标系的原点,具体如下:

  • 位置:原点坐标
  • 观测方向:-Z
  • 向上方向:Y

这里我们将变换后的观测方向设置为 -Z,而在有些渲染引擎中观测方向为 Z。这主要取决于空间坐标系的定义,本文我们使用的是右手坐标系。

如何变换?

那么具体我们该如何进行变换呢?一种非常直观的方法,按照四个步骤进行变换:

  • \(\vec{e}\) 平移变换至原点
  • \(\hat{g}\) 旋转变换至 -Z
  • \(\hat{t}\) 旋转变换至 Y
  • \(\hat{g} \times \hat{t}\) 旋转变换至 X

很显然,变换矩阵为平移变换和旋转变换的组合,即 \(M_{view} = R_{view}T_{view}\)。其中,我们很容易就能求解平移变换的变换矩阵,如下。

\[\begin{aligned} T_{view} = \left( \begin{matrix} 1 & 0 & 0 & -x_e \\ 0 & 1 & 0 & -y_e \\ 0 & 0 & 1 & -z_e \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right) \end{aligned}\]

这里的难点在于求解几个旋转变换的变换矩阵 \(R_{view}\)。那么,该如何求解呢?这里我们转换一下思路,考虑将位于原点的目标位置逆向转换至原始位置。通过这种方式我们可以得到 \(R_{view}\) 的逆矩阵 \(R_{view}^{-1}\)。具体求解过程如下所示。

\[\begin{aligned} \left( \begin{matrix} ? & ? & ? & 0 \\ ? & ? & ? & 0 \\ ? & ? & ? & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right) \left( \begin{matrix} 1 \\ 0 \\ 0 \\ 0 \\ \end{matrix} \right) = & \left( \begin{matrix} x_{\hat{g} \times \hat{t}} \\ y_{\hat{g} \times \hat{t}} \\ z_{\hat{g} \times \hat{t}} \\ 0 \\ \end{matrix} \right) \\ \\ \left( \begin{matrix} ? & ? & ? & 0 \\ ? & ? & ? & 0 \\ ? & ? & ? & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right) \left( \begin{matrix} 0 \\ 1 \\ 0 \\ 0 \\ \end{matrix} \right) = & \left( \begin{matrix} x_{t} \\ y_{t} \\ z_{t} \\ 0 \\ \end{matrix} \right) \\ \\ \left( \begin{matrix} ? & ? & ? & 0 \\ ? & ? & ? & 0 \\ ? & ? & ? & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right) \left( \begin{matrix} 0 \\ 0 \\ 1 \\ 0 \\ \end{matrix} \right) = & \left( \begin{matrix} x_{-g} \\ y_{-g} \\ z_{-g} \\ 0 \\ \end{matrix} \right) \\ \\ 解得: R_{view}^{-1} = & \left( \begin{matrix} x_{\hat{g} \times \hat{t}} & x_{t} & x_{-g} & 0 \\ y_{\hat{g} \times \hat{t}} & y_{t} & y_{-g} & 0 \\ z_{\hat{g} \times \hat{t}} & z_{t} & z_{-g} & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right) \end{aligned}\]

由于旋转矩阵是正交矩阵,所以旋转矩阵的逆矩阵就是它的转置矩阵。由此得到:

\[\begin{aligned} R_{view} = \left( \begin{matrix} x_{\hat{g} \times \hat{t}} & y_{\hat{g} \times \hat{t}} & z_{\hat{g} \times \hat{t}} & 0 \\ x_{t} & y_{t} & z_{t} & 0 \\ x_{-g} & y_{-g} & z_{-g} & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right) \end{aligned}\]

模型变换

根据相对性原理,相机完成了特定的变换后,我们也需要对模型进行同样的变换,这样通过相机投影得到的画面才会相对不变。

根据上述的相机变换,我们得到了对应的变换矩阵。根据此变换矩阵,我们再对空间中的所有模型进行变换,即完成了模型变换。之后,我们即可进行投影变换。

由模型和相机要进行相同的变换,因此也将模型变换和视图变换统称为 模型视图变换(ModelView Transformation)。

投影变换

投影变换本质上就是将 3D 模型投影到 2D 画布的过程,具体可以分为两种:

  • 正交投影(Orthographic Projection):一般用于工程制图软件,不具有近大远小的透视效果。
  • 透视投影(Perspective Projection):一般用于游戏引擎、渲染引擎,模拟真实的效果。

事实上,正交投影可以认为是一种特殊的透视投影,即相机位于无限远的位置,如下所示。

正交投影

下面,我们先来介绍一下正交投影的两种方法。

方法一

方法一非常直观,即丢弃 Z 坐标,直接转换成二维坐标系,然后再将其缩放至 \([-1, 1]^2\) 的矩形区域,如下所示。为什么要缩放至 \([-1, 1]^2\) 的矩形区域?事实上,这也是为了方便后续计算,是一种约定俗成的做法。当然,这种方式也存在一个问题,无法直接判断模型之间的远近关系,这个我们后续再讨论。

方法二

不过,更普遍的做法是方法二,包括后续的透视投影也采用了这种方法。

方法二提出了一个 观测空间(View Volumne)的概念,这一点非常重要。对于正交投影,它的观测空间是一个无限长的长方体,其中以 2D 画布为近面,如下所示。

由于 2D 画布可能是任意比例的矩形,为了方便计算,我们将这个长方体的观测空间转换成成一个规范立方体(Canonical Cube),即 \([-1, 1]^3\) 的空间。

在将观测空间转换成规范立方体的过程中,我们会组合平移、缩放等变换,如下所示。

很显然,要将模型转换成标准立方体,我们必须计算出变换矩阵 \(M_{ortho}\)。由于投影变换不涉及旋转,因此变换矩阵相对而言比较容易求解,如下所示。

\[\begin{aligned} M_{ortho} = S_{ortho}T_{ortho} = \left( \begin{matrix} \frac{2}{r-l} & 0 & 0 & 0 \\ 0 & \frac{2}{t-b} & 0 & 0 \\ 0 & 0 & \frac{2}{n-f} & 0 \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right) \left( \begin{matrix} 1 & 0 & 0 & -\frac{r+l}{2} \\ 0 & 1 & 0 & -\frac{t+b}{2} \\ 0 & 0 & 1 & -\frac{n+f}{2} \\ 0 & 0 & 0 & 1 \\ \end{matrix} \right) \end{aligned}\]

在将观测空间转换成规范立方体的过程中,我们计算得到了变换矩阵 \(M_{ortho}\)。根据相对不变性原理,我们要使用 \(M_{ortho}\) 对空间中所有物体进行同样的变换。这个过程,这里我们不再赘述。

透视投影

透视投影则借鉴了正交投影的做法,只不过相对而言,它多了一步压缩过程,也就是说,透视投影 = 压缩 + 正交投影。

下面,我们重点介绍一下压缩。

压缩

透视投影不同于正交投影,它的观测空间是一个无限长的纺锤体,其中以 2D 画布为近面,如下所示。

压缩的本质就是将透视投影的观测空间压缩成正交投影的观测空间,即将纺锥体转换成长方体。然后,透视投影就换转化成了正交投影了。

那么,我们该如何求解压缩变换的变换矩阵 \(M_{persp->ortho}\) 呢?

首先,由相似三角形定理,如上图所示,我们可以得出:

\[\begin{aligned} y^{'} = \frac{n}{z}y ; x^{'} = \frac{n}{z}x \end{aligned}\]

然后,我们基于齐次坐标,结合三角形定理,计算得出投影点的坐标:

\[\begin{aligned} \left( \begin{matrix} x^{'} \\ y^{'} \\ z^{'} \\ 1 \\ \end{matrix} \right) = \left( \begin{matrix} nx/z \\ ny/z \\ ? \\ 1 \\ \end{matrix} \right) = \left( \begin{matrix} nx \\ ny \\ ? \\ z \\ \end{matrix} \right) \end{aligned}\]

接下来,我们准备求解变换矩阵 \(M_{persp->ortho}^{4 \times 4}\),得出一下关系式:

\[\begin{aligned} M_{persp->ortho} \left( \begin{matrix} x \\ y \\ z \\ 1 \\ \end{matrix} \right) = & \left( \begin{matrix} nx \\ ny \\ ? \\ z \\ \end{matrix} \right) \\ 解得: M_{persp->ortho} = & \left( \begin{matrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ ? & ? & ? & ? \\ 0 & 0 & 1 & 0 \\ \end{matrix} \right) \end{aligned}\]

最后,我们来求解第三行的值。我们基于两个以下两个依据:

  • 近平面上的点的值不会变化,即 2D 画布上的值不变。
  • 远平面上的在 Z 轴上的点不会变化。

根据第一个依据,我们可以得出以下关系式。即将 z 替换成 n

\[\begin{aligned} M_{persp->ortho} \left( \begin{matrix} x \\ y \\ z \\ 1 \\ \end{matrix} \right) = M_{persp->ortho} \left( \begin{matrix} x \\ y \\ n \\ 1 \\ \end{matrix} \right) = & \left( \begin{matrix} x \\ y \\ n \\ 1 \\ \end{matrix} \right) = \left( \begin{matrix} nx \\ ny \\ n^2 \\ n \\ \end{matrix} \right) \\ 推导: \left( \begin{matrix} ? & ? & ? & ? \\ \end{matrix} \right) \left( \begin{matrix} x \\ y \\ n \\ 1 \\ \end{matrix} \right) = & n^2 \\ 解得: \left( \begin{matrix} ? & ? & ? & ? \\ \end{matrix} \right) = & \left( \begin{matrix} 0 & 0 & ? & ? \\ \end{matrix} \right) \end{aligned}\]

我们使用 (0, 0, A, B) 抽象表示 (0, 0, ?, ?)。根据两条依据,我们可以得到一个二元一次方程组,如下所示。

\[\begin{aligned} \left( \begin{matrix} 0 & 0 & A & B \\ \end{matrix} \right) \left( \begin{matrix} x \\ y \\ n \\ 1 \\ \end{matrix} \right) = & n^2 => & An + B = n^2 \\ \left( \begin{matrix} 0 & 0 & A & B \\ \end{matrix} \right) \left( \begin{matrix} 0 \\ 0 \\ f \\ 1 \\ \end{matrix} \right) = & \left( \begin{matrix} 0 \\ 0 \\ f^2 \\ f \\ \end{matrix} \right) => & Af + B = f^2 \\ 解得: A = & n + f \\ B = & -nf \end{aligned}\]

综上述,求解得出压缩变换的变换矩阵如下所示,其中 f 是一个动态值,即空间点 (x, y, z)z 值。

\[\begin{aligned} M_{persp->ortho} = \left( \begin{matrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & n+f & -nf \\ 0 & 0 & 1 & 0 \\ \end{matrix} \right) \end{aligned}\]

对于透视投影,我们首先求解观测空间的压缩变换的变换矩阵 \(M_{persp->ortho}\),然后再利用在将转换后的长方体观测空间转换成规范立方体,即上文正交投影中求解的 \(M_{ortho}\)

当然,根据相对不变性原理,我们还要将这两个变换矩阵应用到空间中所有的物体上,对它们进行变换。

屏幕映射

当 MVP 变换完成之后,我们则要开始将投影内容绘制到 2D 画布中,其中包含了裁剪和视口变换两个步骤。

裁剪

无论是正交投影还是透视投影,我们都将观测空间转换成了一个规范立方体,同时将转换矩阵应用到空间中的所有物体中。

之后,我们就可以通过规范立方体对空间进行裁剪,只保留规范立方体内的物体,如下所示。很显然,只有在规范立方体中的部分才是我们可以看见的部分。

视口变换

视口(Viewport)本质上就是我们所说的 2D 画布,即屏幕。我们知道屏幕有各种各样的分辨率,宽高比。为了处理这种情况,我们将 2D 画布抽象成一个 \([-1, 1]^2\) 的规范平面。然后通过视口变换将它映射到真正的视口中。

假设真实视口的宽度是 \(width\),高度是 \(height\),那么视口变换就是将 \([-1, 1]^2\) 的平面转换成 \([0, width] \times [0, height]\) 的平面。

对此,我们很容易求解变换矩阵,如下所示。

\[\begin{aligned} M_{viewport} = \left( \begin{matrix} \frac{width}{2} & 0 & 0 & \frac{width}{2} \\ 0 & \frac{height}{2} & 0 & \frac{height}{2} \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 0 \\ \end{matrix} \right) \end{aligned}\]

总结

本文,我们主要介绍了观测变换的几个重点内容,包括视图变换、投影变换。其中,我们重点介绍了投影变换中的两种:正交投影和透视投影。

投影变换中提到了一个重要概念——观测空间。我们会将观测空间转换成一个规范立方体,根据相对不变性原理,对空间中所有物体做同样的变换。其中透视投影稍有复杂一点,我们会将纺锤体的观测空间转换成长方体的观测空间。

最后,我们将规范立方体以外的内容进行裁剪,并采用视口变换将内容映射到具体的屏幕上。

后面,我们将基于本章的内容继续介绍计算机图形学的相关基础。

参考

  1. 《GAMES 101》
  2. Image Processing and Computer Graphics——Rendering Pipeline, Matthias Teschner.