Chapter 2 The Graphics Rendering Pipeline图形渲染管线

"A chain is no stronger than its weakest link."----Anonymous

这一章介绍了实时图形的核心组成,即图形渲染管线(graphics rendering pipeline),也简称为管线。管线主要的功能,就是在给定一个虚拟的摄像机,一堆三维物体,若干光源,和一些其他细节,然后生成,或者说渲染,一张二维图片。因为渲染管线是事实渲染的基础工具。使用渲染的管线的过程如图2.1所描绘。图片中物体的位置和形状由其几何信息,环境特征,和摄像机的位置所决定。物体的外观则受到材质属性,光源,纹理(应用于表面的图片)和着色方程影响。


图2.1 在左侧的图片里,一个虚拟的相机放置于金字塔形的顶点(这里是四条线交汇的地方)。只有在视体(view volume)内部的图元(primitive)才会被渲染。对于一张以透视模式渲染的图像来说(这里的例子就是),视体是一个截头锥体(frustum),复数形式为frusta,举例而言,一个以矩形为底面的截取顶部的金字塔形。右图显示摄像机看到的画面。可以注意到左侧图片里红色的甜甜圈形状在没有被渲染到右侧的图片,因为他的位置在视锥体(view frustum)外面。同样的,左图中这个扭曲的蓝色棱体也被裁剪掉了椎体平面上方的部分。

我们接下来将会解释渲染管线不同的阶段,主要聚焦于功能解释而非实现。如果实现这些阶段的相关细节在后续的章节内。

2.1 Architecture 架构

在真实世界里,管线这个概念在不同领域有不同的解释,从工厂的集装线到快餐厅。同样也应用于图形渲染领域。一个管线由几部分组成,每部分都展示了一个更庞大工作的一部分。

管线的各阶段是并行执行的,每个阶段依赖于前面阶段的执行结果。理想状态下,有一个非流水线(nonpipelined)的系统,然后将其分为n个流水线化(pipelined)的阶段,可以带来n倍的速度提升。这个对于性能的提升是使用流水线技术的主要理由。举例而言,大量的三明治可以快速的由一系列分工好的人制作完成——一个专门准备面包,另一个负责夹肉,还有一个加酱料(topping)。每个人都把自己处理完的结果传递给这条线上下一个环境的人,并且迅速的开始做下一个三明治中自己负责的部分。如果每个人需要20秒来完成一次工作,最快的速度就是每20秒可以完成一个三明治,一个分三个,这是有可能的。渲染的管线的各阶段并行执行,但是他们会被拖住,直到最慢的那个阶段完成任务。举例而言,就说夹肉的环节最费力,需要30秒。那么现在能达到的最快生产速度就是一分钟2个。对于这个特定的三明治生产管线,夹肉这一步是瓶颈(bottleneck)所在,因为这一步决定了整个生产的速度。加酱料这一步在等待上一步夹肉完成的过程中,被称为饥饿的(starve的),也可以称为消费者(customer)。

这种管线的构造也存在于计算机图形学实时渲染领域。一个粗糙的划分可以把实时渲染管线分为四个阶段——应用阶段(application),几何处理阶段(geometry),光栅化阶段(rasterization),和像素处理阶段(pixel processing)——如图2.2中所展示的。这个结构是核心部分——渲染管线的引擎——被应用于实时渲染的计算机图形应用软件中,因为这是后续章节讨论的一个核心基础。每一个阶段内部都有一个小的管线,意为着也由数个细分阶段组成(substage)。我们要区分这里展示的功能上的阶段(functional stage),和他们的具体实现结构。一个功能上的阶段拥有一个固定要执行的任务,但是不会具体制定这个人物如何在管线里执行。给的的一个实现,可能会把两个功能上的阶段合并为一个单元里执行,或者是使用可编程的多个核心,与此同时也可能把另一个非常耗时的阶段分给数个硬件单元里执行。


图2.2. 基础的渲染管线构造,由四个阶段组成:应用,几何处理,光栅化,像素处理。每个阶段内部都可能有一个小的管线,如图所示在几何处理阶段下发。也可能一个管线是全部或者部分并行化处理的,入像素处理阶段下面。在这里,应用阶段是一个单一的处理过程,但是这个阶段也能是流水线化,或者并行的。提一下光栅化会逐个图元寻找里面的像素,比如三角形

渲染的速度可以由每秒的帧率表示(frames per second,FPS),即是一秒钟渲染出来的图片数量。也可以用赫兹表示(Hertz,Hz),一个简单的符号代表1/每秒,例如更新频率。直接说每张图片的渲染时间是多少毫秒(millisecond,ms)也是一个常见的表示方法。渲染一张图片的时间各有不同,取决于每一帧里的计算复杂度。FPS可以用于表示一个特定帧的频率,也可以表示一些帧的平均表现。赫兹用于硬件方面,比如显示器,设定于一个固定的刷新率。

如同名字所提示的,应用阶段是由应用程序驱动的,因此典型实现是在软件里,运行在通用CPU上。这些CPU一般包括了多个核心,能够并行的处理多个执行线程(threads of execution)。这使得CPU能够高效处理大量的任务,这也正是应用阶段的职责。有一些任务传统上是由CPU来执行的,包括碰撞检测(collision detection),全局加速算法(global acceleration algorithm),动画(animation),物理模拟(physics simulation),取决于应用的类型,还有其他的很多东西。下一个主要的阶段是几何处理阶段,处理矩阵转换,投影变化,和其他的所有几何处理。这个阶段计算出来那些东西需要被绘制,应该如何被绘制,和应该在哪里绘制。几何阶段通常是由一个图形处理单元GPU来实现的,GPU包含了需要可编程核心,以及一些执行固定操作的硬件。光栅化阶段一般把输入顶点数据,组成三角形,并且寻找被认为属于这个三角形内部的像素。最后,像素处理阶段执行逐像素操作的程序,来决定每个像素的颜色,并且可能执行深度检测来决定是否可见。这个阶段可能也会执行逐像素操作如混合新得到的颜色和之前保留的颜色。光栅化阶段和像素处理阶段也是完全在GPU上面处理的。所有的这些阶段和其内部的管线会在接下来的四章里讨论。更多关于GPU处理这些阶段的细节会在第三章里给出。

2.2 The Application Stage 应用阶段

开发者可完全控制在应用阶段发生的事情,因为这是执行在CPU上的。所以,开发者可以完全决定这里的实现,并且后续可以修改以提高性能。这个阶段的修改也会影响到后续的阶段。举例而言,一个应用程序阶段的算法或者设置有可能降低后续需要渲染的三角形数量。

说到这里,有一些应用程序的工作也可执行在GPU上,使用一种单独的模式叫做计算着色器(compute shader)。这种模式下把GPU当做一个高度并行化的通用处理器,忽略了GPU自身用于渲染图像的特殊性。

在应用阶段结束的时候,需要被渲染的几何数据被传递给几何处理阶段。这些叫做渲染图元(rendering primitives),例如点,线,三角形等最终可能被显示在屏幕上的内容,或者是随便什么输出设备。这就是应用阶段最重要的任务。

使用基于软件(software-based)的实现这个阶段会带来的一个结果是,这个阶段无法像几何处理,光栅化,像素处理阶段那样再分为几个小阶段。但是,为了提高性能,这个阶段通产士是并行执行在多个处理核心上的。在CPU设计中,这个叫做超标量体系结构(superscalar),因为可以在同一阶段同一时间执行多个处理流程。18.5章节介绍了使用多核运算的多种方法。

一个通常在这个阶段实现的处理流程是碰撞检测。在两个物体之间发生的一次碰撞被检测到后,可能会产生一个反馈并传回发生碰撞的物体,也会传给一个力量反馈设备。应用阶段也是关心来自于各种输入设备信息的地方,显示键盘,鼠标,或者头戴式显示器。取决于输入内容,多种不同的行为可能发生。加速算法,显示裁剪算法19章,也会在这里执行,还有各种后续管线处理不了的内容也包含在这个阶段。

2.3 Geometry Processing 几何处理

在GPU上运行的几何处理阶段负责绝大多数的逐三角形(per-triangle)和逐顶点(per-vertex)操作。这个阶段可以进一步分为下面的几个功能性阶段:顶点着色(vertex shading),投影(projection),裁剪(clipping),和屏幕映射(screen mapping)。


图2.3 几何处理阶段划分为一个管线里的多个阶段

2.3.1 Vertex Shading 顶点着色

顶点着色阶段有两个主要的任务,如同名字所表示的,计算一个顶点的位置信息,以及对于程序员可能想要加到顶点输出数据里的各种东西求值,像是法线和纹理坐标。传统上一个物体的着色主要计算是根据顶点的位置和法线信息计算光照,然后只储存结果颜色在顶点里。这些计算出来的颜色随后会在三角形内部进行差值。因为这个原因,这个可编程的顶点处理单元被命名为顶点着色器。随着现代GPU的出现,以及部分或全部的着色是逐像素操作,这个顶点着色阶段就变得更通用了,可能完全不会涉及任何着色方程求值,完全取决于程序员想怎么用。顶点着色器现在是一个更通用的单元用于设置每个顶点相关的数据。举例而言,顶点着色器阶段可以通过4.4和4.5章节里一些方法做顶点动画(animate the object)。

我们从介绍如何顶点位置信息开始,一个坐标数据集总是要的。在一个模型被渲染到屏幕上的过程中,会被转换到多个不同的空间或者说坐标系。起初,一个模型属于它自己的模型空间(model space),这意味着还没有开始任何坐标变化。每个模型可以被关联上一个模型转换(model transform)使得我们可以改变他的位置和朝向。一个模型可能关联与多个模型转换。这就允许在一个场景里生成一个模型多个复制,称作实例(instance),拥有相同的模型和不同的位置信息,朝向,和尺寸,这就不需要把基础的几何信息复制多份了。

本质上是一个模型的顶点和法线做了模型变化。一个物体的顶点称作模型顶点(model coordinates),并且在执行了模型转换之后,这个模型就叫做放到了世界坐标(world coordinates)下或者叫世界空间(world space)。世界空间是唯一的,在所有模型都执行了他们各自的模型转换之后,就统一位于这个相同的世界空间里。

如之前所提到的,只有摄像机,或者说观察者,看得见的模型才会被渲染出来。摄像机在世界空间里也有一个坐标和朝向,用于放置和调向。为了方便计算投影和裁剪,摄像机和所有的模型都会执行一次观察变换(view transform)。这么做的目的是把摄像机放到坐标原点并对准,正面朝向z轴的负方向,上方是y轴的正方向,x轴的正方向指向右侧。我们使用负z轴的惯例;也有些喜欢朝向正z轴方向。其中的区别更多是语义上的,因为把其中的一个转换为另一个是非常简单的。在执行力视角变换后的实际坐标和方向都是依赖于基本的应用程序接口(API)。因此这里描述的空间就称作相机空间(camera space),或者更普遍的叫做观察空间(view space)或者视野空间(eye space)。一个例子关于观察变换如何影响相机和模型在图2.4中展示。模型转换和视角转换都可以实现为一个4x4的矩阵,这个也是第四章的主题。但是,很重要的一点需要理解的是一个顶点的位置和法线可以按照程序员任意喜欢的方式来计算。


图2.4. 在左边显示里,一个从上往下的视角显示了这个相机的位置和朝向按照用户想要的方式防止,在一个+z轴是上分的世界空间里。观察变换重新调整了世界的朝向,使得相机处于世界坐标原点,朝向负z轴,+y轴是上方,如右侧图中所示。这一步操作是为了使得裁剪和投影变化更加简单和快速。淡蓝色的区域是视野看到的部分。这里,假设是一个透视视角,因为观察区域是一个截头椎体。类似的技术也应用于其他各种投影。

下一步,我们描述第二种顶点着色的输出类型。为了产生一个真实的场景,渲染物体的形状和位置是不够的,还需要他们被建模出来的外观。这种描绘包含了每个物体的材质,还有照射在物体上的各种灯光效果。材质和灯光可以通过各种其他方式创建,从简单点的颜色到求值一些物理表示。

这种决定灯光在材质上产生什么效果的操作叫做着色(shading)。这里包含了在模型的大量顶点上计算着色公式(shading equation)。通常上,一部分操作会在几何处理阶段在模型的顶点上执行,另一部分在逐像素操作。大量的材质数据可以存储于每个顶点上,比如顶点的位置,一个法线信息,一个颜色信息,和其他的任何需要参与着色方程计算的数值信息。顶点着色的结果,可以是颜色、向量、纹理坐标、和任何其他种类的着色信息,会被送往光栅化和像素处理阶段,来插值和计算表面着色。

GPU顶点着色器形式的顶点着色会在这本书的第三第五章里深入讨论。

顶点着色的一部分,渲染系统会执行投影然后裁剪,把视差体转换到一个标准的立方体(unit cube),顶点在(-1,-1,-1)和(1,1,1)。不同的范围定义了相同的体积能够并且实际用到,例如,0≤z≤1。这个标准立方体叫做规则观察体(canonical view volume)。投影首先执行完成,在GPU上是由顶点着色器来完成。有两种常用的投影模式,一个是正交投影(orthographic),也叫平行投影(parallel),另一个是透视(perspective)投影。实际上,正交投影只是平行投影的一种。其他一些也有用,特别是在建筑领域,比如斜投影和三向投影。一个老的街机上的游戏Zaxxon正是得名于后者。


图2.5. 左侧是一个正交投影,或者叫平行投影。右侧是透视投影。

注意投影是用一个矩阵表示的,章节4.7中提及,所以这一步优势也会和其他的几何变换结合在一起。

正交投影的观察体通常是一个长方体盒子,并且正交投影变换把这个观察体转换为一个单位立方体。正交投影的主要特征就是平行线在变换之后依然保持平行。这个变换包含了平移和缩放操作。

透视投影有一点负责了。在这种类型的投影钟,一个物体摆放位置距离摄像机越远,在投影中出现的样子就会越小。另外,平行线会聚焦于地平线。透视变换因此模拟了我们真实感知到物体的大小。几何上来说,这个观察体,叫做一个截头椎体(frustum),一个截取头部的长方体为底的金字塔形。这个截头椎体也转换成了一个标准的立方体。正交投影和透视投影的转换都可以构建成一个4x4的矩阵,在执行了任意一个转换之后,模型被称作是在裁剪坐标系下(clip coordinates)。这些世界上是齐次坐标系,在第四章中会讨论,因此这些出现在执行除w操作之前。在GPU上执行的顶点着色器必须一直输出这种类型的坐标来传递给下一个功能阶段,裁剪,才能正确的工作。

尽管这些矩阵是把一种体积形状转换为另一个,他们被称作投影是因为在显示后,z坐标的值并非存在生成的图片里,而是在z-buffer中,在2.5章里会讨论。在这种方式下,模型会被从三维投影为二维。

2.3.2 Optional Vertex Processing 额外的顶点处理
每个管线都会有刚刚提到的顶点处理。当这个过程完成,还有一些可选的处理阶段可以在GPU上执行,在这个顺序下:曲面细分(tessellation),几何着色(geometry shading),和流输出(steam output)。这些东西的使用取决于硬件的能力——并非所有GPU都拥有——和程序员的意愿。他们是相互独立的,一般来说他们没有被普遍使用。更多的内容会在第三章里说。

第一个可选的阶段为曲面细分。想象你有一个弹力球,如果你用一组三角形来表示,你可能会陷入画面质量或者性能的问题。你的球可能从5米外看起来比较好,但是当接近的够进时,特别是沿着轮廓,会看到一个个三角形。如果你用更多更细致的三角形来提升质量,你可能会浪费很多处理时间和内容,当这个球离的很远的时候,在屏幕上只有一点像素来表示。有了曲面细分,可以选择合适的三角形数量来生成一个曲面。

我们讨论了很多三角形,但是到目前这步,我们只是在管线里处理了顶点。这些可以用来表示点,线,三角形,或者其他物体。顶点可以用来描述曲面,比如一个球。这样的表面可以由一堆小块表示,每个小块则是由一堆顶点组成。曲面细分阶段是由一堆次级阶段组成——外壳着色器(hull shader),曲面细分器,和域着色器(domain shader)——这些可以把这一堆小块转化为通常是更大的顶点几何,然后用于创建新的三角形集合。场景的摄像机可以决定有多少三角形会被生成:近多远少。

下一个可选的阶段是几何着色器(geometry shader)。这个着色器位于曲面细分着色器之前,所以GPUs硬件上更常见一些。这个和曲面细分着色器很像,输入多种来源的图元,然后输出新的顶点。这个是一个非常简单的阶段,因为这个创建受限于一个限制很大的输出图元的范围和类型。几何着色器有多种用途,最著名的一个是用于粒子生成。想象模拟一个烟火爆炸,每个火球都可以表示为一个点,一个单一的顶点。几何处理器可以输入一个点,然后转化为一个四边形,由2个三角形组成,方向朝向观察者,会覆盖几个像素的区域,以此提供一个更有说服力的图元给我们来着色。

最后一个可选的阶段叫做流输出。这个阶段是允许我们把GPU当做一个几何处理引擎。不再把处理好的顶点数据发送给后续的管线来往屏幕上渲染,在这里我们可以选择把数据为一个数组用于后续的处理。这些数据可以被CPU或者GPU自己在后面的处理中再用到。这个阶段最典型的应用就是例子模拟,比如我们的烟花例子。

这三个阶段是按照这个顺序——曲面细分,几何着色,和流输出——其中每一项都是额外可选的。不考虑我们具体用了哪些选项,如果我们继续沿着管线处理流程走下去,我们就有了一堆处理好的齐次坐标的顶点数据,将会被执行检测来决定是否会被摄像机看到。

2.3.3 Clipping 裁剪

只有全部或者部分出现在观察体内的图元才需要被传递给光栅化阶段,和后续的像素处理阶段,这些阶段会把图元绘制到屏幕上。一个完全处于观察体内的图元会被传递到下一阶段。完全位于观察体外的图元不会被传递到下一步,因为他们不会被渲染。需要被裁剪的图元是只有一部分位于观察体内部的。举例而言,一条线有一个顶点在外面,另一个在里面,就需要按观察体范围裁剪,所以那个超出范围的顶点需要被替换为一个正好位于线和观察体相交处的点。投影矩阵的使用意味着转换后的图元会按照一个单位立方体的范围裁剪。在裁剪之前执行观察变换和投影变换的好处是可以使得裁剪问题一致。图元总是按照标准图元来裁剪的。

裁剪过程在图2.6中显示。除了观察体的六个裁剪平面之外,用户还可以定义另外的平面去分割物体。一个图片展示了这种可视化,叫做分块sectioning,在818页的图19.1。

图2.6. 在透视转换之后,只有位于标准立方体内的图元(对应就是在观察体内的图元)才需要进一步处理。因此,位于标准立方体之外的图元就被抛弃了,完全位于内部的图元会被保留。图元和立方体相交的就需要执行裁剪,生成新的顶点坐标,抛弃原来的旧坐标。

裁剪的这一步使用了投影计算出的4个值的齐次坐标来做。在透视空间里,一个三角形上的值一般不是线性插值的。所以第四个坐标就是必要的,可以正确的对数据进行插值并且在透视投影时进行裁剪。最后,透视分割perspective division被执行,这一步把得到的结果中的三角形坐标转换到三维的统一设备坐标空间normalized device coordinates。在前面提到过,观察体的范围从(-1,-1,-1)到(1,1,1)。几何处理阶段的最后一步就是从这个空间转换到屏幕坐标上。

2.3.4 Screen Mapping 屏幕映射

只有(已经裁剪的)图元在视锥体内部的才会传递给屏幕映射阶段,并且这一步的坐标仍然是三维的。每个图元的x轴和y轴的坐标会被转换到屏幕坐标screen coordinates。屏幕坐标和z轴的值也合称为窗口坐标windows coordinates。假设一个场景要被渲染到一个以(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中描述。

图2.7. 经过投影变化后的图元位于标准立方体内,屏幕映射过程负责寻找出现在屏幕上的坐标。

下一步,我们介绍整数和浮点数如何与像素关联(以及贴图坐标)。给定一个数组的像素,使用笛卡尔坐标,最左侧的像素点的左边界是浮点数坐标下的0.0。OpenGL总是使用这套体系,DirectX 10及以后版本也使用。这个像素的中心点是0.5。所以像素[0,9]可以覆盖从[0.0, 10.0)的范围。所以这个转换很简单


d(整数)是离散的像素下标,c(浮点数)是像素内的连续值。

尽管所有的APIS都会把像素坐标值按照从左到右的趋势增长,顶边和底边的零坐标在OpenGL和DirectX之间并不是一致的。OpenGL喜欢完整的笛卡尔坐标,把左下角当做值最小的元素,但是DirectX优势把左上角定义为最小值,取决于具体的环境。这是各自的不同逻辑,没有标准答案。举个例子,OpenGL下一张图片的左下角是(0,0),但是在DirectX里是左上角。这一点非常重要,在切换不同APIs的时候。

2.4 Rasterization 光栅化

给定转换和投影后的顶点坐标,以及相关的着色数据(全部来自于几何处理),下一个阶段的目标就是寻找所有的像素——简称为图形元素picture elements——位于图元内部的,例如一个三角形要被渲染。我们把这个阶段称为光栅化rasterization,并且这里分为2个小阶段:三角形设置(也叫作图元组合)和三角形遍历。这些在图2.8的左边展示。注意到这些也可以处理点和线,但是由于三角形的使用非常普遍,这些子阶段名字里都有三角形。光栅化,也叫作扫描变换scan conversion,是把来自于屏幕空间的二维顶点——每个都有一个z值(深度值)和大量的着色信息——转化为屏幕上的像素。光栅化也可以被认为是同步几何处理和像素处理之间的点,因为在这一步把顶点组成三角形并最终发送给像素处理阶段。

图2.8.左边:光栅化划分为2个功能阶段,叫做三角形组合和三角形遍历。右边:像素主力阶段分为两个功能阶段,像素处理和合并。

三角形是否被认为在像素上重叠取决于如何设置GPU管线。举例而言,你可能通过采样点来决定是否位于内部。最简单的情况使用采样单个点,像素的中间位置,当中心点在三角形内部那么对应的像素也被认为在三角形内部。你也可以使用每个像素不止一次采样的方式,使用超采样supersampling和多重采样multisampling的抗锯齿方式(5.4.2节)。还有另一个一种光栅化方式,定义是否处于内部,取决于至少像素有一部分和三角形重叠(23.1.2节)。

2.4.1 Triangle Setup 三角形设置

在这一步,为分支,边等式,和其他的三角形数据被计算出来。这些数据会被用于三角形遍历(2.4.2节),也用于几何阶段生成的着色数据插值。这个任务会由固定功能的硬件来完成。

2.4.2 Triangle Traversal 三角形遍历

这一步每个中心点(或者一个采样点)被三角形覆盖的像素被检查,并且一个片元fragment生成。更多复杂的采样方法可以在5.4节里找到。寻找哪个采样点或者像素位于三角形内部被叫做三角形遍历triangle traversal。每个三角形片元的属性,都是基于插值三个三角形顶点数据生成的(第五章)。这些属性包括了片元的深度,和任何来自于几何阶段的着色数据。McCormack等人提供了关于三角形遍历的更多信息。这里也有关于透视矫正的三角形插值如何执行(23.1.1节)。所有的像素或者采样点位于图元内部的随后会被送到像素处理阶段,下一节讲。

2.5 Pixel Processing像素处理

在这里,所有的像素点都被认为位于经过之前一系列处理后的三角形或者其他图元内部。这个像素处理阶段分为像素着色pixel shading和合并merging,在图2.8的右边展示。像素处理阶段是对在图元内部的像素或采样点执行逐像素或逐采样点操作的阶段。

2.5.1 Pixel Shading像素着色

任何逐像素着色计算都会在这里执行,使用输入的着色数据来做插值。最终结果是把计算出来的一个或多个颜色值传给下一个阶段。和三角形设置三角形遍历阶段不同,这两个阶段是用专用的固定功能的硬件执行,像素着色阶段是在可编程的GPU核心上执行。为了那个目的,程序员提供一个程序给像素着色器(或者偏远着色器,OpenGL里的叫法),这里面包含了任意想要的计算。大量的技术可以在这一步被应用,最重要的技术之一,是纹理化texturing。第六章里有很细节的纹理化内容。简单地说,对一个物体纹理操作意味着把一个张或者多张图片粘到物体表面,以达到想要的目的。一个关于这个过程的例子在图2.9里展示。图形可能是一维,二维或者三维的,二维图片最为常见。最简单的形式,最终的产出是每个便宜的颜色值,这些会传递给下一个处理阶段。

图2.9.一个没有贴图的龙的模型在左上角展示。纹理图片的各块粘到了龙身上,最终的结果在左下方。

2.5.2 Merging 合并

每个像素的信息存在颜色缓冲color buffer里,这是一个放置颜色信息的矩形数组(每个颜色包含一个红色,一个绿色和一个蓝色的部分)。合并阶段的任务就是把像素着色阶段计算出来的颜色和颜色缓冲里的颜色合并起来。这个阶段也叫作ROP,意思是“光栅操作(管线)raster operation(pipeline)”或者“渲染输出单元render output unit”,看你想要哪个。和着色阶段不同,GPU上执行这个阶段的子节点一般都不是可以完全编程控制的。但是,这是可以高度配置化的,支持很多效果。

这个阶段也负责处理分辨率。意思是当整个场景被渲染的时候,颜色缓冲应该包含这个场景里所有摄像机能看到的图元的颜色信息。对于绝大多数甚至是全部的图形硬件,这一步是通过z缓冲算法完成的z-buffer algorithm(也叫作深度缓冲)。一个z缓冲是和颜色缓冲的尺寸形状都一样的,对于其中的每个像素,存储的是最接近观察者的图元的z值。这意味着当一个图元被渲染到一个固定的像素上时,那个图元上面对应像素的z值就要计算出来并和同一位置现有的z值进行比较。如果新的z值更小,说明这个图元在这一点上更更接近摄像机的位置。因此,那个像素的z值和颜色缓冲执行更小,改成新算出的像素。如果新算出来的z值比现有的大,那就不要动原有的z值和颜色缓冲。z缓冲算法很简单,O(n)的复杂度(n是需要渲染的图元的数量),并且对于对于任意能算出来z值的图元都有用。也要注意到这个算法允许图元以任意次序渲染,这也是这个算法流行的一个原因。但是,z缓冲只是简单的存储了屏幕上每个像素的深度值,所以着无法用于做部分透明的图元。透明物体必须在不透明物体之后渲染,并且用从后往前的顺序,或者用另外的的指令独立的算法(5.5节)。透明物体也是基础的z缓冲做法的主要弱点之一。

我们提到了对于每个像素,颜色缓冲用于存储颜色,z缓冲用于存储z值、但是,还有别的通道和缓冲可以用于过滤和捕捉偏远信息。透明通道alpha channel也和颜色缓冲相关联,存储的是每个像素相关的透明度(5.5节)。在老的APIs中,透明童谣也用于在透明测试这个功能只选择哪些像素需要被舍弃。如今舍弃一个像素的操作可以被安插在像素着色程序里,以任何形式的计算来作为取舍的判断条件。这种测试方式可以用于保证完全透明的便宜不会影响z缓冲(6.6节)。

模板缓冲stencil buffer是一个幕后的缓冲用于记录渲染图元的位置信息。典型的是每个像素包含8位空间。图元可以用多种方式渲染到模板缓冲,这个缓冲里的内容随后可以用于控制颜色缓冲和z缓冲。举例而言,假设一个实心圆画到了模板缓冲里。这可以和别的操作相结合,只允许有圆覆盖的区域的图元画到颜色缓冲里。模板缓冲对于实现一些特殊效果时是特别有用的工具。在管线结束时所有的功能都被成为光栅操作raster operations或者混合操作blend operations。这可以混合颜色缓冲里现有的内容和正在计算出来的颜色。这个可以支持一些功能像是透明,或者加速颜色采样。正如提到的,混合过程一般是通过API进行配置,而不是完全编程控制的。但是,也有些API支持光栅命令浏览,也叫作像素着色指令,可以支持可编程的混合能力。

帧缓冲framebuffer一般是由系统里的所有缓冲组成的。

当图元抵达并通过光栅化阶段,这些从摄像机视角可以看到的内容就展现在屏幕上了。屏幕显示的是颜色缓冲的内容。为了避免使得人类观察者感受到这个光栅化显示的过程,使用了双缓冲技术double buffering。意思是渲染一个场景是发生在幕后的,在一块背景缓冲上back buffer。一旦场景在背景缓冲上绘制完毕了,背景缓冲的内容就和前景缓冲front buffer的内容执行一次交换。这个交换过程一般发生在垂直扫描vertical retrace之间,是一个非常安全的合适时机来做这件事。

获取更多的关于不同缓冲和缓冲技术的信息,看5.4.2,23.6和23.7节。

2.6 Through the Pipeline 回顾整个管线

在一个模型或者物体构建的过程中,点、线和三角形形成了图元。想象一个应用是交互的计算机辅助设计computer aided design程序(CAD),用户正在检查一个华夫饼机。这里我们会用这个模型来遍历整个图形渲染管线,由四个主要阶段组成:应用,几何,光栅化,和像素处理。场景是以透视的方式渲染到屏幕上的一个窗口中。在这个简单的例子中,一个华夫饼机模型包括线(显示各部分的边界)和三角形(显示表面)。华夫饼机有一个可开启的盖子。一些三角形上面有一张内容是生产商logo的贴图。对于这个例子,表面着色完全在几何阶段执行,除了应用贴图这一步,这一步在光栅化阶段。

应用阶段

CAD程序允许用户选择并移动模型的各部分。举例来说,用户可能会选中盖子然后移动鼠标来打开它。应用阶段必须把鼠标移动转化为对应的旋转矩阵,然后能看到的就是这个矩阵正确的被应用在盖子渲染出来的结果上。另一个例子:有一段动画在播放,内容是把摄像机沿着预定的轨迹移动从各个角度展示机器。相机的参数,比如位置和视角方向,必须由应用阶段来随着时间进行更新。对于要渲染的每一帧来说,应用阶段把相机位置,光照,和模型的图元传递给下一个阶段——几何阶段。

几何处理

对于透视观察,我们在这里假设应用阶段提供了一个投影矩阵。同时,杜宇每个物体,应用阶段也计算出来一个矩阵用于描述视角变换和位置和物体的朝向。在我们的例子里,华夫饼机的主体会有一个矩阵,盖子会有另一个。在几何处理阶段,物体的顶点和法线会用这个矩阵转换,把物体放到观察空间里。然后着色或者其他的顶点计算会被执行,使用材质和光源属性。投影随后会用一个单独的用户提供的投影矩阵来执行,把这个物体转换到一个单位立方体的空间里,这里代表了我们能看到的区域。立方体外所有的图元都会被舍弃。所有和立方体相交的图元都会被重新裁剪获得一系列完全位于立方体内的新顶点。随后顶点被映射到屏幕窗口上。在所有的这些逐顶点和逐三角形操作执行之后,结果数据会传递到光栅化阶段。

光栅化

所有通过前一阶段裁剪的图元在这一步被执行光栅化,意思是所有在图元内的像素都会被找出来然后发送给下一步的像素处理部分。

像素处理

这一步的目标是计算每个可视的图元的像素的颜色。这些和任意贴图(图片)相关的三角形会和预期一样进行贴图操作。可视化通过z缓冲算法来决定,以及可能有的裁剪和模板测试。每个物体按次序执行,最终图片显示在屏幕上。

总结

这个管线是在几十年间的目标为事实渲染程序的API和图形硬件革命中形成的。需要重点注意的是这并非唯一可能的渲染管线;离线渲染经历过多个不同的改革路线。渲染电影作品经常是通过micropolygon管线,但是光线追踪和路径追踪随后取代了它。这些技术在11.2.2节里提到,也可能用于建筑和设计预览领域。

很多年来,应用开发者唯一能用的方法是通过一个由图形API定义的固定功能的管线。固定管线这个叫法是因为图形硬件的功能不能被灵活的编程控制。最后一个固定管线的主流机器是任天堂的Wii,2006出现。可编程GPUs,另一方面,使得精确操作管线里的每个小阶段成为可能。在本书第四版中,我们假设所有的开发都在可编程的GPU上。

未来的阅读和资源

Blinn的书《A Trip Down the Graphics Pipeline》是一个本讲从头编写软件渲染器的老书。这是一本好书,用来学习实现渲染管线的一些精妙之处,解释一些关键算法比如裁剪和透视插值。珍贵的(但是经常更新的)OpenGL Programming Guid(也叫红书)提供了完整的渲染管线描述和使用到的相关算法。我们这本书的网页,realtimerendering.com提供了链接,关于大量的管线图,渲染引擎实现等等。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343