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)正交投影:立方体的各个边,在投影中互相平行。
(2)透视投影:立方体中的平行线段会在远处收敛于某点。
两种 3D 投影的过程类似,所使用的数学表达方式不同而已,具体采用哪种取决于开发人员。
3.10.2 填充(filling)
多边形(Polygons)
在 3D 图形学中,一般称三角形为「多边形」(Polygons),这是最常用于构建复杂图形的基本形状,因为它简单——空间中三点定义一个平面。
多个多边形构成的集合称为「网格」(mesh),网格越密,表面越光滑,细节越多,同时也带来更多的计算量。
游戏设计者要平衡画面的真实度和多边形数量,如果多边形数量太多,帧率会下降到肉眼可感知,用户会觉得卡顿。
扫描线渲染 (Scanline Rendering)
扫描线渲染 (Scanline Rendering) 是于 1967 年诞生在犹他州大学的经典填充图形算法。
- 将图形铺上一层像素网格。
- 读取多边形的三个点,找最大和最小的 Y 值,只在这两点间工作。
- 从上往下,一次处理一行。计算每一行和多边 形相交的 2 个点,填满 2 个相交点之间的像素。
- 重复逐行填充,直至底部,填充的速度叫 fillrate(填充速率)。
画家算法(Painter’s Algorithm)
3D 场景中会因多边形的重叠错落而产生遮挡(occlusion)现象,其最直接的处理方法时使用排序算法,从远到近排列,然后从远到近渲染。因画家也是先画背景,故名画家算法(Painter’s Algorithm)。
- 从用到近排序,在有序状态下自最远的多边形开始,使用扫描线算法来填充多边形,一次填充一个。
- 重复填充过程即可,注意实际应用中的多边形未必是如示例中一般与屏幕平行。
深度缓冲(Z-Buffering)
深度缓冲(Z-Buffering)是另一种处理遮挡现象的方式,无需排序,速度更快。这种方法会记录场景中每个像素和摄像机的距离,在内存里存一个数字矩阵。
- 每个像素的距离被初始化为"无限大",该算法会从列表里第一个多边形开始处理(即多边形 A),将其距离和 Z-Buffer 中存储的距离进行对比,始终记录更新最小值。
- 按照列表继续向下对比第二个多边形的距离,直至列表结束。因未进行排序,故记录距离的 Z-buffer (缓冲区)中多边形 C 只有一部分值会被覆盖。
- 标记完 Z-buffer 后,搭配改进后的扫描线算法使用。加强版扫描线算法不仅可以勘测两线交叉点,还可确认某像素是否在最终场景中可见,如果不可见则跳过。
在两个多边形距离相同时,采用深度缓冲会使得多边形在内存中不断移动,两者访问顺序的先后会不断变化。再加上浮点数的舍入误差问题,究竟是将哪个多边形画在上方,是无法预测的。因此,这种情况下会导致出现 Z-fighting 效果。
背面剔除(Back-Face Culling)
为节约处理时间,忽略多边形背面的处理优化方式,称为「背面剔除」(Back-Face Culling)。比如在游戏角色的头部或是地面,只能够看到朝外的那一面,这会带来一个 bug —— 进入模型后从内部向外看,头部和地面会消失。
3.10.3 抗锯齿(Antialiasing )
上述例子中三角形填充后边缘都是锯齿,当像素较小时锯齿不明显,但用低配电脑玩游戏时,肯定会出现这种情况。一种减轻锯齿的方法叫抗锯齿(Antialiasing),其通过判断多边形切过像素的程度,来调整填充颜色。
如果像素在多边形内部,就直接涂颜色;如果多边形划过像素,颜色就浅一些。这样抗锯齿的方法可以实现边缘羽化的效果,在字体和图标中广泛使用。
3.10.4 光照(lighting)
光照(lighting)又名明暗处理(shading),在 3D 场景中的物体表面会有明暗变化,添加灯光后会提高物体的真实感。
以茶壶中 3 个不同位置的多边形为例,其不平行与屏幕,各自面对不同方向,该方向称为「表面法线」(Surface Normal),即下图中垂直于表面的箭头方向。
因表面法线的角度不同,光线反射到观察者的强度也不同,不同多边形被照亮的程度也不同。根据距离光源的位置对不同多边形进行着色的方法,称为「平面着色」(Flat Shading),这是最基本的照明算法(lighting algorithm)。
但平面着色的方法使得多边形的边界非常明显,看起来不光滑。因此出现了更多算法来更巧妙地改变颜色、得到更好地效果,比如高洛德着色(Gouraud Shading)和 冯氏着色(Phong Shading)等。
3.10.5 纹理(textures)
纹理(textures)在图形学中指物品的外观而非手感,与照明算法一样,纹理也可以通过多种算法实现各式效果。其中最简单的一种效果称为「纹理映射」(texture mapping)。
纹理映射是指先将多边形的坐标和纹理坐标对应,在使用“扫描线算法”进行填充时,先查看内存中的纹理图像,后从相应区域取平均颜色,再决定该像素使用什么颜色进行填充。
3.10.6 GPU
每个场景中都是由上百万个多边形构成的,渲染如此多个多边形构成的场景需要大量的计算,而无论多大的场景都是需要这样“扫描线填充, 抗锯齿, 光照, 纹理化”一遍一遍地处理。
为了加速渲染过程,有以下几种方法:
(1)为这种特定运算来做专门的硬件(即 GPU)来加快速度。
(2)把3D场景分解成多个小部分,并行渲染而非按顺序渲染。
图形处理单元(Graphics Processing Unit, GPU)时为图形而生的处理器,其在显卡(Graphics Cards)之上,附有专用的 RAM 使得 GPU 的多个核心可以高速访问相关网格和纹理。
现代显卡,如 GeForce GTX 1080 TI,有 3584 个处理核心,提供大规模并行处理,每秒可处理上亿个多边形。