"A chain is no stronger than its weakest link"。一条铁链的坚固程度取决于它最弱的一个环节

本章将介绍实时渲染的核心概念,渲染管线,或者被称为管线。管线最主要的功能就是根据一个虚拟的相机、3D的物件、光照等信息,渲染生成一个2D的图片。因此,渲染管线是实时渲染的基础工具。管线的整个过程如图2-1所示。图片中物件的位置和形状由它们的几何结构、环境的特性以及环境中的摄像机位置决定。物件的外表是由材质球、光照、纹理(应用于表面的图像)以及着色公式决定。

T02

如上左图所示,虚拟相机位于金字塔的顶端(四条线在这里汇聚)。只有在视锥体内的图元会被渲染。对于以透视方式显示的图像(正如上图所示),视图体为锥形,即具有矩形底的截锥。上右图现实了摄像机看到的图片。注意上左图中红色圆环物件不在上右图的图片中,因为它位于视锥体之外。另外,上左图中蓝色物件也与视锥体的顶平面有交叉,所以被裁减。

下面,我们将介绍整个管线中的不同阶段,重点是功能而非具体实现。应用这些阶段的相关细节将在之后的章节中介绍。

2.1 架构

在现实世界中,管线被用于许多不同的场景,从工厂装配到快餐厨房。同样,它也适用于图形渲染。一个管线可以被分为若干阶段(参考文献[715]),每个部分都会执行一个大工程中的一部分。

渲染管线的各个阶段是并行执行的,每个阶段都依赖于前一阶段的结果。理想情况下,将一个非管线系统划分为n个管线阶段,可以加快n倍速度。性能的提高是使用管线的主要原因。比如:一组人可以很快准备好大量的三明治——一个准备面包,一个添加肉,一个添加配料。每个人将结果交给下一个人,然后就立即可以开始下一个三明治的制作了。如果每个人需要20秒完成他的工作,那么一分钟就可能完成3个三明治的制作了。管线的各个阶段并行执行,但是如果有一个阶段比较慢,其他阶段则会暂停等待。假设,准备肉的阶段最复杂,需要30秒,那么这个管线下一分钟最快可以生成2个三明治。针对这个管线,肉的阶段是瓶颈,因为它决定了整个生产的速度。配料阶段就需要等待肉阶段的完成(顾客也是如此)。

这种管线结构同样适用于实时渲染。如图2-2所示,实时渲染的管线大致被分为四个阶段:应用、几何处理、光珊化、像素处理。这个结构是用于实时渲染的渲染管线的核心,也是后续章节讨论的基础。这里的每个阶段本身也包含了若干个子阶段,比如下图中的几何处理,而像素处理阶段还可以部分平行化,应用程序阶段是一个单独的过程,但是这个阶段也可以是流水线/并行,光珊化是在图元中(比如三角形)确认像素。在这里,我们把功能和具体实现区分开。一个功能阶段具有要执行的具体任务,但是并没有指定其在管线中执行任务的方式。一个具体的实现可以将两个功能阶段合成一个单元,或者使用可编程核心去执行,同时,还可以将一个复杂的功能阶段拆分成多个硬件单元。

T02

渲染速度可以通过每秒帧数(FPS)表示,即每秒渲染的图片数。也可以通过Hz表示,它只是表示1/s的符号,即更新频率。通常用ms来表示绘制一张图片的时长。然而绘制图片的时长通常会有所不同,这取决于每帧执行的计算复杂度。FPS可以用于表示特定帧的速率,或者一段时间内的平均性能。Hz用于硬件,比如设置为固定速率的显示器。

顾名思义,应用阶段是有应用程序驱动,在CPU运行的软件中实现。这些CPU通常包括多个核心,这些核心能够并行处理多个执行线程。这使得CPU可以高效的运行应用程序阶段所负责的各种任务。一般CPU上执行的任务包括碰撞检测、全局加速算法、动画、物理模拟等其它取决于应用程序类型的任务。下个阶段是几何处理,用于处理转换、投影以及其他类型的几何处理。这个阶段计算了渲染什么、如何渲染以及渲染在哪里。几何阶段通常在包含许多可编程核心和固定操作硬件的GPU上执行。光珊化阶段通常将三个顶点作为输入,组成一个三角形,然后找到其内部所有的像素,并将这些像素转发到下个阶段。最终,像素处理阶段逐像素的执行程序以确认其颜色,并执行深度测试确认它是否可见。还有可能去逐像素的执行比如blend等算法,将新计算的颜色与之前的颜色进行混合。光珊化和像素处理阶段也都在GPU中执行。所有的这些阶段以及其内部的管线都将在之后的四个部分介绍。关于GPU如何处理这些阶段的更多细节将在第三章介绍。

2.2 应用阶段

开发者可以完全控制应用阶段发生的事情,因为这个阶段基本都是在CPU上执行的。因此,开发者可以完全控制以及修改以提高性能。这里的改动还会影响到之后阶段的性能。比如一个应用阶段的算法或者设置就可以减少渲染的三角形数量。

然而,一些应用阶段的工作也可以通过GPU中的特殊模块compute shader完成。这个模块将GPU当作一个高度并行的通用模块处理,而忽略其专门用于图形渲染的特殊功能。

在应用阶段结束的时候,将渲染所需要的几何传给几何处理阶段。都是一些可能会出现在屏幕上(或者正在使用的任何输出设备)的点、线、三角形等渲染图元。这是应用阶段最主要的任务。

这个阶段基于软件的实现结果是,它不分为子阶段。但是为了提高性能,这个阶段通常在多个处理器核心上并行执行。在CPU的设计中,这被称为超标量结构,可以在同一个阶段同时执行多个进程。第18.5节介绍了使用多处理器内核的各种方法。

碰撞检测基本都是在这个阶段完成。检测两个物件的碰撞,然后反馈给碰撞的物件,如同一个力反馈装置。应用阶段也处理其他来源(如键盘、鼠标、头戴式显示器等)的输入。根据这些输入,可以采取几种不同的操作。加速算法,比如特殊的剔除算法(第19章),以及管线其他部分无法实现的部分都在这里实现。

2.3 几何处理

GPU上的几何处理负责几乎所有的三角形和逐顶点操作。这个阶段被分为如下几个功能阶段:顶点着色、投影、裁剪、屏幕映射。如图2.3所示。

T02

2.3.1 顶点着色

顶点着色最主要有两个任务,计算每个顶点的位置以及判断程序员所希望得到的数据数据,比如顶点法线、纹理坐标等。过去通过对每个顶点的位置、法线应用灯光计算,然后逐顶点的保存计算出来的颜色信息,并对这些颜色在光珊化中进行插值。所以,这个顶点处理单元被称为顶点着色器。然而随着现代GPU的发展,大部分着色计算都将逐像素进行,顶点着色器也就会根据开发者的意愿,可能不再包含着色算法了。顶点着色器也就成了一个专门设置与每个顶点关联的数据。比如,可以通过4.4和4.5节的内容实现顶点动画。

我们从如何计算顶点坐标开始说起,这里需要一系列的坐标系。从模型空间到屏幕空间,一个模型需要经历多个空间变换/坐标系变化。最初,一个模型位于其自身的模型坐标系,这个时候还没有开始被转换。每个模型都有自己的模型坐标转换矩阵,这样就可以被定位和转向。同一个模型可以有多个模型坐标系,这样的话,同一个模型就可以在同一个场景中复制多个副本,具有不同的位置、方向和大小,而无需复制多个基本几何图形。

模型空间转换转换的是模型的顶点和法线。模型的坐标系被称为模型坐标系,而发生了模型空间转换后,该模型将位于世界坐标系/世界空间中。世界空间是独一无二的,也就是说当所有的模型根据自己的模型转换矩阵,完成模型到世界空间转换后,它们都将存在于同一个空间内。

如前所述,只有相机/观察者能看到的物件才会被绘制。相机在世界空间有一个位置和方向,用于放置和瞄准相机。为了方便投影和裁剪,相机以及所有的物件都将被转换到视图空间。视图变换的目的是将摄像机放到原点并进行瞄准,使其向-z轴的方向进行观察,y轴向上,x轴向右。在这里,我们使用-z轴,而有的书使用+z轴,这些都不重要,它们之间的转换也很简单,最终的位置和方向都依赖底层API的实现。这样的空间被称为相机空间或者视图空间。图2-4展示了视图转换前后的相机和模型。模型转换和视图转换都可以通过4*4矩阵表示,这些将在第4章详细说明。最重要的是,需要意识到一个顶点的坐标和法线可以以程序员喜欢的任何方式计算。

T02

上图中浅蓝色区域为视锥体,这里假设使用透视视图,所以视锥体是一个截锥。

下面,我们将介绍顶点着色的第二个输出。因为如果想要展示一个真实的场景,只有物件的形状和位置是不够的,同时还需要构建它们的外观。这里包含了每个对象的材质,以及各种光照照射到对象上的效果。材质和灯光可以通过任何方式建模,从简单的颜色到详细的物理描述表达式。

决定灯光对材质球的影响的公式被称为着色。包含了计算物体上不同点的着色方程。通常,其中一些计算是在模型顶点的几何处理过程中执行的,而其他一些计算是在逐像素处理过程中执行的。可以在每个顶点中存储各种材料数据,比如位置、法线、颜色等着色方程所需要的数值。顶点着色结果(比如颜色、向量、纹理坐标等其他类型的着色数据)随后被发送到光珊化和像素处理阶段,用于计算表面着色。

本书中还将继续深入介绍GPU顶点着色,尤其是第3和第5章。

然后,作为顶点着色的一部分,渲染系统还执行了投影和剪裁,将视图体转成了一个单位立方体,顶点位于(-1,-1,-1)和(1,1,1)。从0<=z<=1,这个范围代表着相同的体。单位立方体也被称为规范视图体。首先通过GPU中的顶点着色器完成投影。完成投影有两种投影算法,正交投影(也叫做平行投影)和透视投影,如图2-5所示。实际上正交投影只是平行投影的一种,还有一些,比如建筑领域的斜投影和轴测投影,街机游戏zaxxon就是以后者命名的。

T02

上左图为正交投影,上右图为透视投影。

投影表达式为矩阵(详见4.7节),这样的话可以将其和几何变换的其他部分连接起来。

正交投影的视图体通常为一个矩形框,正交投影将其转成一个单元立方体。其主要特点就是变换后平行线要依然保持平行。这种变换是平移和缩放的组合。

透视投影就有点复杂了。在这种类型的投影中,对象离相机越远,投影后显示的对象越小。所以,平行线可能会在地平线处汇聚。透视投影变换模拟了我们感知物件大小的方式。几何上,视图体被称为截锥,也就是具有矩形底座的截锥。截锥也被转成了单位立方体。正交投影和透视投影都可以用4*4矩阵构造(第4章),并且在变换之后,模型都进入了裁剪空间/裁剪坐标系。这些实际上就是第4章所要说的齐次坐标系,然后进行除以w操作。(Patrick:所以是先裁剪,再除以w,得到NDC。然后再进行窗口映射。那么投影后的坐标,以及ts、gs的输出的w都可能不为1,然后再裁减,再除以w。或者是先除以w,再裁减,总之ts和gs输出的结果w可能不为1)GPU的顶点着色器必须输出这个坐标系下的坐标,并将其传递给下一个功能阶段裁减,才能得到正确的结果。

尽管这些矩阵将一个体积转成了另外一个体积,但是它们被称为投影是因为显示之后,z坐标将不会存储在生成的图像中,而是存在第2.5节提到的zbuffer中。这样,三维模型就被投影成了两维。

2.3.2 可选的顶点处理

每个管线都包含了上述所描述的那些步骤。但是除了这些步骤之外,GPU中还可以执行一些可选的操作,按顺序为:曲面细分TS、几何体着色GS、流输出stream output(TFBO)。需要根据开发者的意愿以及硬件的能力(并非所有GPU都支持)去选择性的使用这些功能。它们之间相互独立,而且使用率不高。第3章会对这些逐一进行详细介绍。

首先聊聊曲面细分TS。想象你有一个弹跳的球,如果用一组三角形表示,可能会遇到质量或者性能问题。如果面数少,那么在5米外看上去不错,但是靠近一看,特别是颜色轮廓看的话,不够精细的问题就会暴露出来。然而如果用更多的面数以提高质量,则当球远离摄像机,仅覆盖屏幕几个像素的时候,就会耗费大量的处理时间和内存。通过曲面细分,就可以使用适当数量的三角形生成合适的表面。

上面我们讨论的是三角形,然而实际上,我们首先要先处理顶点。因为只有有了顶点,才能生成点、线、三角形或者其他物件,只有有了顶点,才可以描述曲面,比如球。曲面是由一组patch组成,然而每个patch都是由一组顶点组成。而曲面细分阶段也是由一系列的阶段组成:hull shader、tessellator、domain shader。将一组组的patch的顶点转成更多的顶点集合,然后用于生成新的一组组三角形。可以通过场景中的摄像机决定生成多少三角形:距离近的时候多,距离远的时候少。

然后我们聊下几何着色器GS。GS比TS出现的要早,所以在GPU中更加常见。和TS类似,GS根据各种类型的图元,然后生成新的顶点。GS更加简单,因为创造的范围很有限,输出的图元类型也就很有限了。GS最常用的地方是生成粒子。想象如果想模拟一场烟火,每个火球可以通过一个图元点,也就是一个顶点表示。GS可以将每个图元点转成一个面向观察者的方形(billboard),这样的话开发者就可以有更多空间会进行着色了。

最后我们来聊下流输出steam output(TFBO)。这个阶段可以使我们将GPU当作几何处理引擎使用。也就是说,此刻我们可以选择将处理过的顶点输出到数组中进行下一步处理,而非将其按照管线发送给下一个阶段以供渲染到屏幕使用。这些数据可以由CPU或者GPU在之后的过程中使用。这个功能常被用于粒子模拟,比如我们的烟花实例。

这三个阶段的顺序为:曲面细分TS、几何着色器GS、流输出steam output(TFBO),然而每个都是可选的。不管我们使用哪个,如果我们下面要继续管线处理,都会使用相机查看这些具有齐次坐标的顶点是否可见。

2.3.3 裁剪

只有全部或者部分位于视锥体内的图元才会进入光珊化以及后面的阶段,然后会被渲染到屏幕上。完全在视锥体内的图元会被直接按照原样传递到下一个阶段。完全在视锥体外的图元不会被传递到下一个阶段,因为它们压根不会被绘制(Patrick:在应用阶段的culling的时候已经被干掉了吧。话说cpu的culling算法是什么,也是将object从模型空间转到投影空间,然后判断是否在视椎体内(也就是那个方形内)?这样的话,不就相当于已经在cpu做了一遍几何处理了么?那这样的话要culling做什么?直接用gpu的clip好了。还是说cpu的culling算法比较粗暴,只用OBB之类的做culling?)。只有部分位于视锥体内的图元才需要裁剪。比如:一根线的一个顶点在视锥体内,另外一个在视锥体外。那么在外面的顶点就会被线与视锥体交点的这个新的顶点所取代。投影矩阵的用处就是转换后的图元会被裁剪到单位立方体上。在裁剪之前执行视图转换和投影转换的优势就是将裁剪问题一致化处理,将所有图元剪裁到单位立方体上。

裁剪过程如图2-6所示。除了视锥体的六个裁剪平面之外,还可以定义其他裁剪平面来裁剪物件(Patrick:有API么?)。图19-1展示了这种可视化图像,这种方式被称为切片法。

T02 T02

裁剪这个步骤需要用到投影生成的4分量齐次坐标。透视空间的三角形无法进行线性插值,所以需要第4个坐标,以便在透视投影时正确插值和裁剪。最终,进行透视分割后,将生成的三角形位置放置到三维归一化设备坐标中。如前所述,此视图体积的范围为(-1,-1,-1)到(1,1,1)。而几何阶段的最后一个步骤就是将这个空间下的坐标转换到屏幕空间坐标。

T02 T02 T02 T02

2.3.4 屏幕空间映射

只有(裁剪过的)视锥体内的图元会进入屏幕映射阶段,这个时候依然是三维坐标系空间中。图元的x和y坐标会被转成屏幕空间坐标,屏幕空间坐标加上z坐标被称为窗口坐标。假设一个场景会被渲染到一个(x1,y1)到(x2,y2)的窗口中,其中x1<x2,y1<y2,屏幕映射转换其实就是一个缩放操作。新生成的x和y坐标被称为屏幕空间坐标,z坐标(OpenGL中为[-1,1],DirectX中为[0,1])会被映射到[z1,z2],其中默认z1=0,z2=1。这些与具体的API实现有关。然后,窗口坐标(包含新生成的z值)会被传入光珊化阶段。整个屏幕映射的过程如图2-7所示。

T02

如上图所示,经过投影变换后的单位立方体内的图元,经过屏幕空间映射,得到了屏幕中其对应的坐标值。

下面,我们描述整数和浮点值与像素(和纹理坐标)的关系。给定一个水平像素数字并使用笛卡尔坐标系,最左边的像素的左边缘在浮点坐标中为0.0(OpenGL以及Dx10以及之后的版本都使用这个方案)。这个像素的中心为0.5。因此,像素范围[0,9]也就覆盖了[0,10)的范围。转换很简单,其中d是像素的离散(整数)索引,c是像素内的连续(浮点)值。d = floor(c),c = d+0.5。

虽然所有的API都是从左到右递增,但是从上到下递增或者递减在OpenGL和DirectX的定义不同。OpenGL符合笛卡尔坐标系,将左下角视为最小值,但是DirectX却视情况而定,有时会认为左上角为最小值。这个也不能说谁对谁错。但是如果在做API级别的porting的时候,就必须考虑这个差异了。

2.4 光珊化

经历了转换和投影等这些几何处理后,每个顶点也都有了属于自己的属性,下一个阶段的工作任务就是找到所有渲染图元(比如三角形)内部的像素点。这个阶段被称为光珊化,共分为两个阶段:图元装配和三角形遍历。如图2-8所示。需要注意的是,这里也可以处理点和线,但是因为最常见的就是三角形,所以下面我们都用三角形来进行说明。光珊化,也被称为扫描转换,是将屏幕空间中的二维顶点(每个顶点也都包含一个z值信息以及其他顶点相关的着色信息)转换成屏幕中的像素点。光珊化也被认为是几何处理和像素处理的连接点,因为在此之前三角形是由三个顶点组成,然后被这个阶段遍历生成像素后,传给下面的像素处理阶段。

T02

判断一个像素是否在三角形中取决于你如何设置GPU管线。比如,可以通过点采样来确定该像素是否在三角形内。最简单的办法就是对该像素的中心点进行点采样,看其是否在三角形内,如果在的话,就认为这个像素在三角形内。还可以通过针对每个像素多个采样点的超采样或者多采样抗锯齿技术(MSAA,详见5.4.2节)(Patrick:为什么说MSAA对手机性能影响很大呢?)。另外一种办法就是使用保守的光珊化,也就是如果该像素的至少一部分与三角形重叠,则判断该像素位于三角形内部。(详见23.1.2节)

2.4.1 图元装配

这个阶段将会计算三角形的微分、边方程和其他数据。这些数据将用于三角形遍历(第2.4.2节),然后对几何阶段生成的各种着色数据进行插值。这个阶段的任务将通过硬件的固定管线完成。

2.4.2 三角形遍历

在这里检查每个像素的中心点或者其他采样点是否被三角形覆盖,然后针对部分像素在三角形内部的像素生成片元fragment。第5.4节会更详细的介绍采样方法。这个确定采样点或者像素是否在三角形内部的阶段被称为三角形遍历。每个片元的属性(包括z值等集合处理阶段生成的着色数据)都会通过三角形的三个顶点插值生成(详见第5章)。McCormack et al.(参考文献[1162])更详细的介绍了三角形扫描。在这里还针对三角形进行了透视矫正(参考文献[694])(详见23.1.1节)。图元中所有的像素或者采样都会被传入下一个像素处理阶段。

2.5 像素处理

在这个时候,经过之前阶段的处理,所有三角形或者其他图元内部的像素都已经被发现了。像素处理阶段被分为像素着色和合并两个阶段,如图2-8所示。像素处理阶段是逐像素或者逐采样点的处理图元中的像素或者采样点。

2.5.1 像素着色

在这里将执行逐像素的着色计算,将前面插值获得的着色数据作为输入。然后将一种或者多种颜色输出给下一个阶段。不同于使用专用硬件执行的图元装配和三角形遍历,像素着色阶段使用的是可编程GPU内核。因此,开发者可以开发一套像素着色程序(在OPenGL中被称为片元着色器),再次进行所需的计算。在这里将包含大量的技术,最重要的就是纹理采样。第6章将对纹理采样进行详细介绍,简单的说,就是将一张或者多张图片,根据各种目的,粘合到该对象上。如图2-9所示就是一个简单的实例。图像可以是一维、二维、三维的,最常见的还是二维的。最简单的情况下,最终输出就是每个片元一个颜色值,然后该值会被传递到下一个子阶段。

T02

左上图为一个未经过纹理采样的龙的模型,将右面的纹理贴图粘合到该模型上之后,就得到了左下图。

2.5.2 合并

每个像素的信息都存储在颜色缓冲区中,该缓冲区是一个矩形颜色数组(每个颜色都有一个红、绿、蓝组件)。合并阶段的工作就是将像素着色器阶段生成的片元颜色和当前缓冲区存储的颜色相结合。这个阶段也被称为ROP,代表“光珊操作(管线) raster operation (pipeline)”或“渲染输出单元 render output unit”。和着色阶段不同,执行此阶段的GPU子单元通常不完全可编程,但是,它通常高度可配置,因此可以实现多种效果。

这个阶段还要判断像素的可见性。也就是说,当场景渲染完毕后,颜色缓冲区应该包含的是从相机角度可见的场景中图元的颜色信息。针对大部分甚至全部的图形硬件,这个工作是由z缓冲区或者深度缓冲区算法完成(参考文献[238])。z缓冲区的尺寸和形状和颜色缓冲区一致,每个像素都存储的是距离最近的图元的z值。也就是说,当一个图元被渲染到一个像素的时候,先计算出这个图元的z值,并将其和z缓冲区中同一像素去比较。如果新的z值小于z缓冲区上的z值的话,意味着该图元比之前该像素点上绘制的图元距离摄像机更近。然后,该z值和颜色值就会被拿去覆盖该像素点的颜色缓冲区和z缓冲区了。但是如果计算得到的z值大于z缓冲区上的z值,则该像素点的颜色缓冲区和z缓冲区则保持不变。z缓冲区的算法很简单,时间复杂度为O(N)(N为渲染的图元数量),且适用于所有能获得所有像素点z值的图元。且支持乱序渲染,这也是这个算法受欢迎的原因。然而,z缓冲区针对屏幕上的每一点都只能存储一个深度信息,所以不能将其用于半透明图元。半透明图元必须在渲染了所有不透明图元之后渲染,且按照从后到前的顺序,或者使用其他不依赖顺序算法(详见5.5节)。半透明是z缓冲区的主要缺点之一。

我们已经提到了用于存储每个像素颜色的颜色缓冲区以及用于存储每个像素z值的z缓冲区。然而其实还有其他缓冲区用于过滤和保存片元信息。比如alpha通道是与颜色缓冲区关联,保存每个像素的不透明度值的(详见5.5节)。在老的API中,alpha通道还可以通过alpha test的功能选择性的抛弃像素。现在可以通过像素着色器中的任何计算触发抛弃操作。alpha test是用于确保完全透明的片元不会影响到z缓冲区(详见6.6节)。

stencil缓冲器是一个用于记录绘制图元位置的offscreen缓冲区,一般每个像素会有8位。可以通过各种函数将图元渲染到模板缓冲区中,该缓冲区的内容也可以被用于控制往颜色缓冲区和z缓冲区的渲染。比如,假设将一个填充的圆形绘制到stencil buffer中,然后接合一个操作,可以使得只有当该圆圈被绘制的时候,之后的图元才能被绘制到颜色缓冲区中。stencil buffer可以被用于实现各种特殊的效果。这些在管线末端的功能被称为ROP(raster operations)或者blend混合操作。在这里,可以将当前颜色缓冲区中的颜色与当前三角形绘制的颜色进行混合。这样的话就可以实现半透明或者颜色累积等效果。如上所述,blend混合可以使用API配置,而不完全可编程。然而,有些API支持raster order view,也被称为pixel shader ordering,像素着色器顺序,这样就可以实现可编程混合blend。(Patrick:传说中的Programmable blend)

framebuffer是由系统中的所有buffer组成的。

当图元经过光栅化阶段后,相机视角所能看到的图元都会被显示到屏幕上。屏幕展示了颜色缓冲区中的内容。为了避免在光栅化的过程中就被人们看到,使用了双缓冲区机制。也就是说屏幕的渲染发生在offscreen,在back buffer中。当屏幕中的内容在back bffer中被绘制完毕后,将back buffer中的内容与之前在屏幕中展示的front buffer中的内容进行交换。这个交换在垂直同步vertical retrace的时候进行,这个时候是一个安全的时候(Patrick:以防撕裂)

关于其他buffer和buffer方法的更多信息,可以看5.4.2、23.6、23.7节。

2.6 串一遍管线

点、线、三角形是组成模型/物件的基本图元。想象一下该应用程序是一个交互式计算机辅助设计应用程序,用户使用它来检查华夫饼机器。我们使用这个例子来走完整个流程,包含四个阶段:应用程序、几何处理、光栅化、像素处理。整个场景是通过透视的方式映射到屏幕上。在这个例子中,华夫饼机器包含线段(显示零件的边缘)和三角形(显示表面)。华夫饼机器有个可以打开的盖子。其中一些三角形上显示着制造商的2D图片logo。整个例子中,表面着色器基本是由几何处理阶段完成,除了纹理采样在光栅化阶段完成(Patrick:开玩笑么。。纹理采样肯定是在像素处理阶段哇)

应用阶段

该应用程序允许用户选择和移动模型的一部分。比如,用户可以选择盖子,然后通过移动鼠标来打开它。应用阶段就应该将鼠标转移到一个合适的旋转矩阵上,然后确保该矩阵在渲染时正确的应用于盖子上。另外一个例子:播放一个动画,使得相机沿着预定义的路径移动,从不同的视角观察华夫饼机器。然后,应用阶段必须根据时间更新摄像机的位置、方向等参数。每帧,应用阶段都应该将摄像机的位置、光照、模型的图元传递给管线中的下一个阶段:几何处理阶段。

几何处理阶段

针对透视视图,我们假设应用阶段已经提供了一个透视矩阵。并且,针对每个物件,应用阶段都已经计算了一个用于描述视图转换以及模型本身位置和方向的矩阵。在我们的例子中,华夫饼机器本身有一个矩阵,盖子还有一个矩阵。在几何处理阶段,模型的顶点和法线都将通过这个矩阵的计算,将物件转换到视图空间中。然后将通过材质球和光源,针对顶点进行着色或者其他计算。然后通过用户提供的投影矩阵将物件转换到单位立方体空间用于展示眼睛所看到的内容。所有立方体之外的图元都将被抛弃。所有与立方体有交互的图元都将被裁剪,以得到一组完全位于立方体内部的图元。然后这些顶点将被映射到屏幕上。在执行了所有这些逐三角形和逐顶点的操作后,所有的数据都将被传递到光栅化阶段。

光栅化

经过上面的裁剪后,所有幸存的图元都将被进行光栅化,也就是图元中的所有像素都会被找出来,传递给管线中的下个步骤像素处理。

像素处理

这一步的目的是计算出所有可见图元的所有可见像素的颜色。关联了纹理的三角形都会在这里进行正确的采样。可见性是通过z缓冲区进行判断的,同步进行的还有可选的alpha test和stencil test。每个物件依次进行,然后就会在屏幕上看到最终的图片。

总结

当前这个管线是基于针对实时渲染数十年以来图形硬件和API发展来的。需要知道的是,这并非是唯一的渲染管线,离线渲染也经历了不一样的发展路线。针对电影产品的渲染之前是通过微多边体micropolygon管线(参考文献[289]、[1734]),而最近被光线追踪和路径追踪所取代。这些技术会在11.2.2进行介绍,这些可用于建筑和设计的视觉预览。

在过去的很多年中,开发者只能通过图形API提供的固定管线完成上述操作。之所以被称为固定管线,是因为当时的图形硬件是由一堆无法自由编程的组件组成。而最后一个主要使用固定管线的产品为2006年生成的Nintendo的Wii。可编程GPU使得我们可以精准的定义管线中每个子阶段的具体实现。由于这已经是本书的第4版了,所以我们假设所有的开发都是通过可编程管线完成。

更多资源

Blinn的《A Trip Down the Graphics Pepeline》(参考文献[165])是一本从头开始描述软件渲染器的老书。其中介绍了大量实现渲染管线微妙之处,并解释关键算法,比如裁剪和透视插值,是一本绝佳资源。OpenGL Programming Guide(也被称为红书,参考文献[885])提供了相关的图形管线和算法的详细描述,这本书也很老,不过经常更新。本书的网站www.realtimerendering.com上也包含了大量的管线图片以及渲染引擎的具体实现等链接。


虽然并非全部原创,但还是希望转载请注明出处:电子设备中的画家|王烁 于 2019 年 4 月 4 日发表,原文链接(http://geekfaner.com/shineengine/Translation2_RealTime_Rendering_4th_Edition1.html)