> For the complete documentation index, see [llms.txt](https://hine.gitbook.io/foundations-of-3d-cg/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hine.gitbook.io/foundations-of-3d-cg/iii-xiang-ji-yu-guang-zha-hua/di-shi-zhang-tou-ying.md).

# 第十章 投影

目前，我们已经在三维空间中描述了物体（objects）和视点（eye）。我们下一步的目标，是要理解如何将这些描述信息，转换为一张从视点处观察看到的二维图像。为此，我们需要建立一个简单的摄影机模型。通篇我们假设摄影机位于视点坐标系（$$\vec{\mathbf{e}}^t$$）的原点，并且看向视点的负$$z$$轴方向。我们使用$$\[x\_e, y\_e, z\_e, 1]^t$$记号来表示视点坐标系下的一点。

## 10.1 针孔相机

最简单的摄影机形式是针孔相机（图10.1）。光线朝着摄影机的成像面运动，大部分会被一个位于$$z\_e = 0$$的不透光平面所阻挡。但是，我们在该平面的中心设置一个非常小的孔洞，它位于视点坐标系的$$\[0, 0, 0, 1]^t$$处。

![一个 2D 针孔相机。从场景中几何体表面射出的光线（图中虚线表示）穿过小孔，并将其颜色记录在位于 ze = 1 处的成像面。其中部分经此着色的像素会被显示出来。图中蓝色的六面体被遮挡住，因此没有出现在画面中。](https://3079340014-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MR9b16cwt_hvqTAbEF_%2F-MYJT-DqEjQ6O7CDPOvi%2F-MYJV4tijcP1KTAuJryK%2FFigure_10-1.jpg?alt=media\&token=520a86d2-7510-48c3-be08-9b25b9f8e791)

在真实的物理世界中，任何真实的相机都需要具有一个有限大小的光圈，从而定量的可被测量的光能够从光圈中通过，并抵达摄影机成像面。并且，一旦光圈是有限大小的量，就需要一个镜头来辅助更好的将入射光“组织”并聚焦。不过，这些问题现在并不对我们造成困扰，因为我们不是要依据物理法则制造真实的相机。第 21 章有关于此话题更详细的内容。

在针孔相机中，摄影机成像面上的数据需要进行翻转，才能得到我们想要的照片。从数学角度看，通过将针孔相机与摄影机成像面置于针孔的前方，例如$$z\_e = -1$$平面（图10.2），我们可以省略掉翻转图像这个步骤。在真实世界中，这是不现实的，但对于针孔相机的数学模型来说，这样做完全可行。

![基本的针孔相机可以建模为成像面位于 ze = -1 处，即位于原点前方。图像平面上最上方的点 yn 在归一化设备坐标系中的值为 1。而最下方的 yn 则为 -1。位于同一虚线上的点，会映射到图像的同一像素上。](https://3079340014-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MR9b16cwt_hvqTAbEF_%2F-MYJT-DqEjQ6O7CDPOvi%2F-MYJWUO3SLO5hHEbIygA%2FFigure_10-2.jpg?alt=media\&token=a375e201-0080-4a97-a946-b72040e829c1)

当创建好图像之后，如果我们将它固定在$$z\_e = -1$$平面，并且在原点处观察（图10.3），对我们来说观察到的结果会与直接观察原始场景相同。我们精确再现了从原点处观察场景时，抵达我们眼部的数据。如果在空间中移动这幅图像，例如离眼睛近一点或远一点，我们就不再精确再现原始场景的视觉刺激信息了，但是它仍旧会看起来是一个视觉上可信的原始场景。

![如果成像面被最终得到的画面替代，而我们的视点位于视角坐标系原点，那么抵达的数据，与原始的场景带来的数据将会没有区别。](https://3079340014-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MR9b16cwt_hvqTAbEF_%2F-MYJT-DqEjQ6O7CDPOvi%2F-MYJXYtLFJx_9XTjBQtr%2FFigure_10-3.jpg?alt=media\&token=22224753-4ee6-4d59-84cf-05684fc06d0f)

## 10.2 基础数学模型

针孔相机在数学上非常容易建模。我们使用坐标系$$\[x\_n, y\_n]^t$$来表示摄影机成像面上的点。目前，我们在摄影机成像面上选定了一个二维坐标系统，它恰好与视点坐标系相匹配（也就是，$$x\_n = x\_e$$ 且 $$y\_n=y\_e$$），但是我们很快会放宽这一设定。

给定空间中一点 $$p$$ 在视点坐标系下的坐标 $$\[x\_e, y\_e, z\_e, 1]^t$$，可以轻易看出（根据相似三角形定理）从点$$p$$ 射向原点的射线，与成像面相交的位置为：

$$
x\_n = - \frac{x\_e}{z\_e} \tag{10.1}
$$

$$
y\_n = - \frac{y\_e}{z\_e} \tag{10.2}
$$

我们可以将其用矩阵运算表达为：

$$
\left\[ \begin{matrix}
1 & 0 & 0 & 0 \\
0 &1 & 0 & 0 \\

* & - & - & - \\
  0 & 0 & -1 & 0
  \end{matrix} \right]
  \left\[ \begin{matrix} 	x\_e \ y\_e \ z\_e \ 1 	\end{matrix} \right] 	=
  \left\[ \begin{matrix} 	x\_c \ y\_c \ - \ w\_c 	\end{matrix} \right] 	=
  \left\[ \begin{matrix} 	x\_nw\_n \ y\_nw\_n \ - \ w\_n 	\end{matrix} \right] 	，
  \tag{10.3}
  $$

其中横线（—）意味着“我们不关心此处的值”。这个矩阵称作*投影矩阵（projection matrix）*。矩阵相乘的原始输出，$$\[x\_c, y\_c, -, w\_c]^t$$，称作点 $$\tilde{p}$$ 的*裁切坐标系向量（clip coordinates）*。（如此命名，是因为这个原始数据随后会在裁切阶段使用，是第12.1节的内容）。$$w\_n = w\_c$$ 是一个新的变量，称作$$w$$分量（$$w$$-coordinate）。在这样的裁切坐标空间里，第四个分量不一定必须为 0 或 1。

我们说 $$x\_nw\_n = x\_c$$，并且 $$y\_nw\_n = y\_c$$。如果想要单独提取 $$x\_n$$，必须进行除法 $$x\_n = \frac{x\_nw\_n}{w\_n}$$，（对 $$y\_n$$ 也是这样）。当这么做的时候，我们恢复了等式 10.1 中的计算，即简单摄影机模型使用的计算。（译者注：因为此时 $$-z\_e = w\_c = w\_n$$）

我们得到的附有脚标 $$n$$ 的输出坐标系向量，称作*归一化设备坐标系（normalized device coordinates）*，因为它用与具体像素数无关的抽象单位，表达了图像上的点。在计算机图形学中，我们将所有的图像数据都维持在*正则正方形内（canonical square）* $$-1 \leq x\_n \leq +1，-1 \leq y\_n \leq +1$$，并最终将其映射到屏幕上的一个窗口中。这个正方形之外的数据，不会被记录或显示。这正是我们在附录 A 中用来描述 2D OpenGL 绘制的模型。

## 10.3 变体

通过改变投影矩阵中的项，我们可以对摄影机的几何变换进行微调。

### 10.3.1 缩放

如果我们将摄影机成像面移动到 $$z\_e= n$$ 处，其中$$n$$ 是一个负数（图10.4），我们可以将该摄影机建模为

$$
x\_n = \frac{x\_en}{z\_e}
$$

$$
y\_n = \frac{y\_en}{z\_e}
$$

![在这个摄影机模型中，我们将成像面移动至  平面。我们仍然在成像面内保持比例 -1 < yn = ye < 1。这一做法相当于对长焦镜头的模拟。](https://3079340014-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MR9b16cwt_hvqTAbEF_%2F-MYJT-DqEjQ6O7CDPOvi%2F-MYJYGSry7py3Xdt_Q88%2FFigure_10-4.jpg?alt=media\&token=8683c1fe-e3d9-4975-b034-bee3653181fe)

这样做类似于镜头上的变焦。用矩阵来表达则是

$$
\left\[ \begin{matrix}
x\_nw\_n \ y\_nw\_n \ - \ w\_n
\end{matrix} \right] 	=
\left\[ \begin{matrix}
-n & 0 & 0 & 0 \\
0 & -n & 0 & 0 \\

* & - & - & - \\
  0 & 0 & -1 & 0
  \end{matrix} \right]
  \left\[ \begin{matrix} 	x\_e \ y\_e \ z\_e \ 1 	\end{matrix} \right]
  \tag{10.3}
  $$

这样实际上相当于，在原先成像面位于 $$z\_e = -1$$ 的相机的基础上，将记录到的图像以 $$-n$$ 为系数进行缩放，并最终只保留图像位于正则正方形内的部分（如图10.5）。因此，后面我们最好不再将归一化设备坐标系向量（normalized device coordinates），看作位于成像面上、与某一视点坐标系相吻合的坐标系，而是简单的认为它是图像平面上固有的一个坐标系。

![经过变焦的摄影机，等同于一个成像面位于 ze = -1，但图像坐标系经过缩放 yn = - n\*ye 的摄影机。](https://3079340014-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MR9b16cwt_hvqTAbEF_%2F-MYJT-DqEjQ6O7CDPOvi%2F-MYJirIpWusJ8PRGlDrC%2FFigure_10-5.jpg?alt=media\&token=8375663d-7326-4e16-976a-74eaeb1e4ea4)

控制缩放系数的一个好方法是，通过给定目标摄影机的垂直*视场角度（field of view）*&#x6765;决定 $$-n$$ 。尤其是当我们想要让摄影机的视场角为 $$\theta$$ 度的时候。这时我们就可以设定 $$-n = \frac{1}{\tan(\frac{\theta}{2})}$$（如图10.6），由此我们得到投影矩阵为

$$
\left\[ \begin{matrix}
\frac{1}{\tan(\frac{\theta}{2})} & 0 & 0 & 0 \\
0 & \frac{1}{\tan(\frac{\theta}{2})} & 0 & 0 \\

* & - & - & - \\
  0 & 0 & -1 & 0
  \end{matrix} \right]
  \tag{10.4}
  $$

![摄影机的缩放可以方便的使用垂直视场角：θ 来表示。此时，我们可以认为位于 ze = -1 的成像面的边界为 -tan(θ/2) < ye < tan(θ/2)，映射到归一化设备坐标系的 -1 < yn < 1。](https://3079340014-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MR9b16cwt_hvqTAbEF_%2F-MYJT-DqEjQ6O7CDPOvi%2F-MYJjRyfDjKtOM_d33Nw%2FFigure_10-6.jpg?alt=media\&token=ee9fa07e-73ef-47ff-ae66-d2e918574dab)

我们可以直接验证，任意以原点为起点，并与负 $$z$$ 轴在垂直方向形成的夹角为 $$\frac{\theta}{2}$$ 度的射线，都会被映射到成像面上正则正方形的边界上，因此，该相机的视场角是 $$\theta$$ 度。 例如，视点坐标系向量 $$\[0, \tan(\frac{\theta}{2}), -1, 1]^t$$ 映射到归一化设备坐标系为 $$\[0, 1]^t$$。

更通用的处理问题，我们可以用不同的水平和垂直缩放系数 $$s\_x$$ 和 $$s\_y$$ 来分别缩放原始图像，从而得到如下的摄影机模型：

$$
\begin{eqnarray}
\left\[ \begin{matrix}
x\_nw\_n \ y\_nw\_n \ - \ w\_n
\end{matrix} \right]  &=&
\left\[ \begin{matrix}
s\_x & 0 & 0 & 0 \\
0 & s\_y & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 1 & 0
\end{matrix} \right]
\left\[ \begin{matrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\

* & - & - & - \\
  0 & 0 & -1 & 0
  \end{matrix} \right]
  \left\[ \begin{matrix} 	x\_e \ y\_e \ z\_e \ 1 	\end{matrix} \right]
  \\
  &=&
  \left\[ \begin{matrix}
  s\_x & 0 & 0 & 0 \\
  0 & s\_y & 0 & 0 \\
* & - & - & - \\
  0 & 0 & -1 & 0
  \end{matrix} \right]
  \left\[ \begin{matrix} 	x\_e \ y\_e \ z\_e \ 1 	\end{matrix} \right]
  \end{eqnarray}
  $$

在计算机图形学中，这种不等比的缩放在处理不为正方形的屏幕窗口时会用到。假设窗口的宽大于高。在摄影机变换中，我们需要在水平方向上挤压物体，以便更宽的水平视场角可以被容纳进我们留存的正则正方形内。当数据随后映射到窗口中时，它会被相应的拉伸从而看起来没有变形。

定义 $$a$$ ，即屏幕的*宽高比（aspect ratio）*&#x4E3A;它的宽度除以高度（例如以像素为单位）。我们可以将投影矩阵设置为

$$
\left\[ \begin{matrix}
\frac{1}{a\tan(\frac{\theta}{2})} & 0 & 0 & 0 \\
0 & \frac{1}{\tan(\frac{\theta}{2})} & 0 & 0 \\

* & - & - & - \\
  0 & 0 & -1 & 0
  \end{matrix} \right]
  \tag{10.5}
  $$

这个相机的垂直方向与等式（10.4）中保持不变，但是在生成图像时，采用了相应更宽的水平视场角。

当窗口的高大于宽，也就是 $$a < 1$$ 时，我们依然可以使用等式 10.5 中的矩阵，但我们也许并不满意这样做得到的水平视场角，因为它很狭窄。如果我们希望 $$\theta$$ 为垂直或是水平视场角中最小的那个，那么，当 $$a < 1$$ 时，我们需要使用如下的投影矩阵：

$$
\left\[ \begin{matrix}
\frac{1}{\tan(\frac{\theta}{2})} & 0 & 0 & 0 \\
0 & \frac{a}{\tan(\frac{\theta}{2})} & 0 & 0 \\

* & - & - & - \\
  0 & 0 & -1 & 0
  \end{matrix} \right]
  \tag{10.5}
  $$

这正是在 6.2 小节中我们调用 `makeProjection` 所产生的矩阵。

在计算机图形学中，选用视场角时常常需要去平衡不同的因素。一方面，更广的视场角，例如在一个游戏环境中，可以让观察者看到四周更多的东西。另一方面，在一个典型的观察环境下（除非有人把脸直接贴在屏幕前面），屏幕只占据了观察者四周环境的很小一部分角度。在这种情况下，观察者看到的几何关系会与图像环境中的不一致（就像图 10.3 中那样），并且图像看起来会有点变形（例如球体可能显示时不是圆形）。

### 10.3.2 移轴

不太常见的是，也许我们会希望用 $$\[c\_x, c\_y]^t$$ 来移动 2D 归一化设备坐标系。这可以用投影矩阵建模为

$$
\begin{eqnarray}
\left\[ \begin{matrix}
x\_nw\_n \ y\_nw\_n \ - \ w\_n
\end{matrix} \right]  &=&
\left\[ \begin{matrix}
1 & 0 & 0 & c\_x \\
0 & 1 & 0 & c\_y \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1
\end{matrix} \right]
\left\[ \begin{matrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\

* & - & - & - \\
  0 & 0 & -1 & 0
  \end{matrix} \right]
  \left\[ \begin{matrix} 	x\_e \ y\_e \ z\_e \ 1 	\end{matrix} \right]
  \\
  &=&
  \left\[ \begin{matrix}
  1 & 0 & -c\_x & 0 \\
  0 & 1 & -c\_y & 0 \\
* & - & - & - \\
  0 & 0 & -1 & 0
  \end{matrix} \right]
  \left\[ \begin{matrix} 	x\_e \ y\_e \ z\_e \ 1 	\end{matrix} \right]
  \end{eqnarray}
  $$

对应到摄影机上，这个矩阵就描述了一个*移轴的成像面（shifted film plane）*（如图10.7）。它也许看起来很罕见，但实际上，因为制造工艺以及光学的问题，大部分真实的相机内部都确实有一些微量的偏移。

![在移轴摄影机上，我们平移归一化设备坐标系，将 \[-1 ... 1\] 的坐标区域保持在移动后的坐标系内。](https://3079340014-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MR9b16cwt_hvqTAbEF_%2F-MYJT-DqEjQ6O7CDPOvi%2F-MYJla3RE_rY1mCWZ9_x%2FFigure_10-7.jpg?alt=media\&token=48e64670-fe82-4390-9407-c5bc6d266e18)

在计算机图形学中，移轴相机的主要用途是平铺显示（如图10.8），我们将多个显示屏并列放置来组成一个更大的屏幕。在这种情况下，这些每一个子图像都以一个适当的移轴相机来建立数学模型。移轴的另一个应用是创建一对图像，用于单屏幕上的立体显示。

![在这样的设定中，观察者看到的是由两个显示平面共同创建的图像。每一个单独的画面应建模为移轴相机。](https://3079340014-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MR9b16cwt_hvqTAbEF_%2F-MYJT-DqEjQ6O7CDPOvi%2F-MYJlxXW9v4_w5lu6WH5%2FFigure_10-8.jpg?alt=media\&token=0d080c16-084c-4782-8d2b-4f7933d51f05)

通常在计算机图形学中，表示移轴（以及缩放）的相机时，会首先给定一个近切平面 $$z\_e = n$$。通过指定一个视点坐标系下，与坐标轴平行的长方形，可以在这个平面上确定一个长方形。（为了得到没有变形的输出结果，这个长方形的宽高比应该与最终的窗口相吻合。）用 $$l, r$$ 的值来表示这个长方形在 $$x\_e$$ 坐标轴上的左右边界，并且用 $$t, b$$ 来表示它在 $$y\_e$$ 坐标轴上顶和底部的边界。总的来说，这些参数描述了空间中一个 *3D 视锥（frustum）*&#x7684;形状。穿过这个长方形并射入原点的射线，会被映射到正则正方形图像内，使用如下投影矩阵描述

$$
\left\[ \begin{matrix}
-\frac{2n}{r-l} & 0 & \frac{r+l}{r-l} & 0 \\
0 & -\frac{2n}{t-b} & \frac{t+b}{t-b} & 0 \\

* & - & - & - \\
  0 & 0 & -1 & 0
  \end{matrix} \right]
  \tag{10.6}
  $$

（如图 10.9）。

![通过指定近切平面的矩形图像，可以定义 3D 视锥。](https://3079340014-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MR9b16cwt_hvqTAbEF_%2F-MYJT-DqEjQ6O7CDPOvi%2F-MYJmMTnAvJyWANni02I%2FFigure_10-9.jpg?alt=media\&token=b9c1d678-8cd1-4b4e-add7-a71f0746e359)

### 10.3.3 其他

在矩阵式（10.6）左上角 2 乘 2 的区域中，留有两个零在我们的摄影机模型中没有涉及到，这是一个典型的情况。这块 2 乘 2 的区域作为一个整体，可以表示像素网格的旋转与切变。切变在真实摄影机中并不常见，在计算机图形学中也不常用。当然，我们可以围绕着摄影机的光轴来旋转整个摄影机，但是这种旋转，在最初定义视点坐标系 $$\vec{\mathbf{e}}^t$$ 时，就可以用适当的摄影机旋转来表示！

## 10.4 应用情境（Context）

我们所描述的投影操作是一种映射，它可以在视点坐标系下应用在任何给定的点上，从而得到该点的归一化设备坐标系。尽管在 OpenGL 中，这一映射只被应用于三角形的顶点上。一旦得到了三角形上三个顶点的归一化设备坐标系向量，通过计算图像平面上位于三角形内部的所有像素，就可以简单的得到三角形内部的点。

## 练习

**10.1** 假设我们给一个冰做的立方体拍照。将它正面的投影绘制出来，用实线表示靠前的面，用虚线表示背部的面。假设用 40，30 和 20 度的视场角来分别拍摄三张图像。摄影机的其他参数保持不变。那么以下的图像序列哪一个比较可信：

![](https://3079340014-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MR9b16cwt_hvqTAbEF_%2F-MYJT-DqEjQ6O7CDPOvi%2F-MYJmdft7La-7NlLAfNw%2FFigure_10-10.jpg?alt=media\&token=402e9652-dad7-4996-a279-f14f59897c83)

**10.2** （发散思考）看以下两张照片： 你能否看出这些照片是如何拍摄的？两张照片使用的视点坐标系和投影矩阵有何不同？（提示：记住我们可以制造出移轴相机。）

（版权信息：图片来自\[26]，版权属于 Phillip Greenspun）

![](https://3079340014-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MR9b16cwt_hvqTAbEF_%2F-MYJT-DqEjQ6O7CDPOvi%2F-MYJmhX1YGFEA6-DU7M_%2FFigure_10-11.jpg?alt=media\&token=763a898e-f735-4743-b271-eab498595191)

**10.3** （发散思考）设 $$\vec{\mathbf{e}}^t =  \vec{\mathbf{w}}^tE$$ ，且 $$P$$ 是一个摄影机矩阵，是一个任意的 4 乘 4 矩阵，且第三行以"—"填充。给定 6 个点的世界坐标以及它们的归一化设备坐标，如何计算矩阵 $$PE^{-1}$$ 中的 12 个未知项？（注意：必须包含未知的缩放系数才能解出这个问题）（提示：用 0 建立一个适当的右手性齐次线性系统）
