# 3.10 3D 图形（3D Graphics）

## 3.10.1 3D 投影（3 Dimensional Projection）

3D 中使用 X,Y,Z 三点构成某点的坐标，因 2D 电脑屏幕无法将三轴立体坐标完美展示，故使用某种图形算法来将 3D 坐标“拍平”（flattening）显示至 2D 屏幕上，这称为「3D 投影」（3 Dimensional Projection）。

所有的点都从 3D 转成 2D 后，就可以用画 2D 线段的函数来连接这些点，称为「线框渲染」（Wireframe Rendering）。

3D 投影有许多类别，最常见的是正交投影（Orthographic Projection）和透视投影（Perspective Projection）：

（1）正交投影：立方体的各个边，在投影中互相平行。

![正交投影](https://275040345-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FycDQGvckh16RY095vh62%2Fuploads%2FBD4Vg2ANBiRq4SYTOI48%2F0.png?alt=media)

（2）透视投影：立方体中的平行线段会在远处收敛于某点。

![透视投影](https://275040345-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FycDQGvckh16RY095vh62%2Fuploads%2FVs8mEzqjPMlITYRPAx7d%2F1.png?alt=media)

两种 3D 投影的过程类似，所使用的数学表达方式不同而已，具体采用哪种取决于开发人员。

## 3.10.2 填充（filling）

### 多边形(Polygons)

在 3D 图形学中，一般称三角形为「多边形」(Polygons)，这是最常用于构建复杂图形的基本形状，因为它简单——空间中三点定义一个平面。

![三角形](https://275040345-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FycDQGvckh16RY095vh62%2Fuploads%2F6sYOX1EwXLPbmuIYvGe7%2F2.png?alt=media)

多个多边形构成的集合称为「网格」（mesh），网格越密，表面越光滑，细节越多，同时也带来更多的计算量。

游戏设计者要平衡画面的真实度和多边形数量，如果多边形数量太多，帧率会下降到肉眼可感知，用户会觉得卡顿。

### 扫描线渲染 (Scanline Rendering)

扫描线渲染 (Scanline Rendering) 是于 1967 年诞生在犹他州大学的经典填充图形算法。

1. 将图形铺上一层像素网格。

![](https://275040345-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FycDQGvckh16RY095vh62%2Fuploads%2FcdOmcINGYOw1rgREQhCA%2F3.png?alt=media)

1. 读取多边形的三个点，找最大和最小的 Y 值，只在这两点间工作。
2. 从上往下，一次处理一行。计算每一行和多边 形相交的 2 个点，填满 2 个相交点之间的像素。
3. 重复逐行填充，直至底部，填充的速度叫 fillrate（填充速率）。

### 画家算法（Painter’s Algorithm）

3D 场景中会因多边形的重叠错落而产生遮挡（occlusion）现象，其最直接的处理方法时使用排序算法，从远到近排列，然后从远到近渲染。因画家也是先画背景，故名画家算法（Painter’s Algorithm）。

（1）从用到近排序，在有序状态下自最远的多边形开始，使用扫描线算法来填充多边形，一次填充一个。

![](https://275040345-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FycDQGvckh16RY095vh62%2Fuploads%2FqIrwZn0RPrZHneFUJSm5%2F4.png?alt=media)

（2）重复填充过程即可，注意实际应用中的多边形未必是如示例中一般与屏幕平行。

![](https://275040345-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FycDQGvckh16RY095vh62%2Fuploads%2FEDmr9bykwKP0zfgvbPQo%2F5.png?alt=media)

### 深度缓冲（Z-Buffering）

深度缓冲（Z-Buffering）是另一种处理遮挡现象的方式，无需排序，速度更快。这种方法会记录场景中每个像素和摄像机的距离，在内存里存一个数字矩阵。

（1）每个像素的距离被初始化为”无限大”，该算法会从列表里第一个多边形开始处理（即多边形 A），将其距离和 Z-Buffer 中存储的距离进行对比，始终记录更新最小值。

![](https://275040345-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FycDQGvckh16RY095vh62%2Fuploads%2Fj276l3yKKLd3Q3TdU8Gz%2F6.png?alt=media)

（2）按照列表继续向下对比第二个多边形的距离，直至列表结束。因未进行排序，故记录距离的 Z-buffer （缓冲区）中多边形 C 只有一部分值会被覆盖。

![](https://275040345-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FycDQGvckh16RY095vh62%2Fuploads%2Flnr8CwCywtlXvsLzJWts%2F7.png?alt=media)

（3）标记完 Z-buffer 后，搭配改进后的扫描线算法使用。加强版扫描线算法不仅可以勘测两线交叉点，还可确认某像素是否在最终场景中可见，如果不可见则跳过。

![](https://275040345-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FycDQGvckh16RY095vh62%2Fuploads%2FKifRrAm6svJq86aUxhUS%2F8.png?alt=media)

在两个多边形距离相同时，采用深度缓冲会使得多边形在内存中不断移动，两者访问顺序的先后会不断变化。再加上浮点数的舍入误差问题，究竟是将哪个多边形画在上方，是无法预测的。因此，这种情况下会导致出现 [Z-fighting 效果](https://youtu.be/TEAtmCYYKZA?list=PL8dPuuaLjXtNlUrzyH5r6jN9ulIgZBpdo\&t=470)。

### 背面剔除（Back-Face Culling）

为节约处理时间，忽略多边形背面的处理优化方式，称为「背面剔除」（Back-Face Culling）。比如在游戏角色的头部或是地面，只能够看到朝外的那一面，这会带来一个 bug —— 进入模型后从内部向外看，头部和地面会消失。

## 3.10.3 抗锯齿（Antialiasing ）

上述例子中三角形填充后边缘都是锯齿，当像素较小时锯齿不明显，但用低配电脑玩游戏时，肯定会出现这种情况。一种减轻锯齿的方法叫抗锯齿(Antialiasing)，其通过判断多边形切过像素的程度，来调整填充颜色。

如果像素在多边形内部，就直接涂颜色；如果多边形划过像素，颜色就浅一些。这样抗锯齿的方法可以实现边缘羽化的效果，在字体和图标中广泛使用。

## 3.10.4 光照（lighting）

光照（lighting）又名明暗处理（shading），在 3D 场景中的物体表面会有明暗变化，添加灯光后会提高物体的真实感。

以茶壶中 3 个不同位置的多边形为例，其不平行与屏幕，各自面对不同方向，该方向称为「表面法线」（Surface Normal），即下图中垂直于表面的箭头方向。

<figure><img src="https://275040345-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FycDQGvckh16RY095vh62%2Fuploads%2F0qbR0jHBL1bBJUOMJ7HO%2Fimage.png?alt=media&#x26;token=a55041aa-df29-405d-b989-05e95385a216" alt=""><figcaption><p>表面法线</p></figcaption></figure>

因表面法线的角度不同，光线反射到观察者的强度也不同，不同多边形被照亮的程度也不同。根据距离光源的位置对不同多边形进行着色的方法，称为「平面着色」（Flat Shading），这是最基本的照明算法（lighting algorithm）。

![平面着色](https://275040345-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FycDQGvckh16RY095vh62%2Fuploads%2FuqRxpYvWgPanCW1j1HD5%2F9.png?alt=media)

但平面着色的方法使得多边形的边界非常明显，看起来不光滑。因此出现了更多算法来更巧妙地改变颜色、得到更好地效果，比如高洛德着色（Gouraud Shading）和 冯氏着色（Phong Shading）等。

![](https://275040345-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FycDQGvckh16RY095vh62%2Fuploads%2FP7oAP8N9KGy5q03EKL4K%2F10.png?alt=media)

## 3.10.5 纹理（textures）

纹理（textures）在图形学中指物品的外观而非手感，与照明算法一样，纹理也可以通过多种算法实现各式效果。其中最简单的一种效果称为「纹理映射」（texture mapping）。

![纹理映射](https://275040345-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FycDQGvckh16RY095vh62%2Fuploads%2FUrPnOftBcBX0HYCQP3SD%2F11.png?alt=media)

纹理映射是指先将多边形的坐标和纹理坐标对应，在使用“扫描线算法”进行填充时，先查看内存中的纹理图像，后从相应区域取平均颜色，再决定该像素使用什么颜色进行填充。

## 3.10.6 GPU

每个场景中都是由上百万个多边形构成的，渲染如此多个多边形构成的场景需要大量的计算，而无论多大的场景都是需要这样“扫描线填充, 抗锯齿, 光照, 纹理化”一遍一遍地处理。

为了加速渲染过程，有以下几种方法：

（1）为这种特定运算来做专门的硬件（即 GPU）来加快速度。

（2）把3D场景分解成多个小部分，并行渲染而非按顺序渲染。

图形处理单元（Graphics Processing Unit, GPU）时为图形而生的处理器，其在显卡（Graphics Cards）之上，附有专用的 RAM 使得 GPU 的多个核心可以高速访问相关网格和纹理。

现代显卡，如 GeForce GTX 1080 TI，有 3584 个处理核心，提供大规模并行处理，每秒可处理上亿个多边形。
