1 固定功能管线 - tup.com.cn ·...

15
1 固定功能管线 在计算机图形学的开始阶段,读者可使用图形API生成项目。由于本书主要强调OpenGL方面的 图形学内容,因而对应API隶属于OpenGL范畴。本章将首先回顾OpenGL 1.x版本的图形管线。如果 读者曾使用过其他API,其风格可能与OpenGL方案十分接近,这一类API均采用固定功能管线,或者 是针对顶点和片元包含固定操作集的管线。本书的其余部分将考察OpenGL 2.x版本的着色器内容及其 使用方式,并据此生成固定管线难以实现的视觉效果。 传统的视见方案 当通过OpenGL开发图形应用程序时,将定义几何体、视见机制、投影方案以及多种外观属性。 其中,对象几何体可通过其顶点、法线及其图形图元定义且常位于glBegin-glEnd语句对之间,该语 句对常包含顶点、直线、压缩几何体或多边形。另外,视见以及投影均通过特定的函数加以定义。 相应地,对象外观可通过定义颜色、着色、材质、光照以及纹理贴图获得,此类信息通过固定功能 OpenGL系统以较为直观的方式进行处理,对应操作出现于软件或图形卡中。 一种最为简单的OpenGL考察方式是将其视为两个连接操作:顶点处理操作和像素处理操作。在 OpenGL固定管线中,各个操作均包含预定义操作集合。这里,读者应深入理解几何体以及输入指令 在管线中的工作方式。当与着色器结合使用时,理解管线的执行方式将变得十分重要。相应地,读者 所编写的着色器程序将控制上述操作,因而其重要性不言而喻。 顶点操作 当创建场景几何体时,需要定义相关图元、顶点以及作用于各个顶点之上的操作,进而在屏幕

Transcript of 1 固定功能管线 - tup.com.cn ·...

Page 1: 1 固定功能管线 - tup.com.cn · 相应地,对象外观可通过定义颜色、着色、材质、光照以及纹理贴图获得,此类信息通过固定功能 OpenGL系统以较为直观的方式进行处理,对应操作出现于软件或图形卡中。

第 1 章 固定功能管线

在计算机图形学的开始阶段,读者可使用图形API生成项目。由于本书主要强调OpenGL方面的

图形学内容,因而对应API隶属于OpenGL范畴。本章将首先回顾OpenGL 1.x版本的图形管线。如果

读者曾使用过其他API,其风格可能与OpenGL方案十分接近,这一类API均采用固定功能管线,或者

是针对顶点和片元包含固定操作集的管线。本书的其余部分将考察OpenGL 2.x版本的着色器内容及其

使用方式,并据此生成固定管线难以实现的视觉效果。

传统的视见方案

当通过OpenGL开发图形应用程序时,将定义几何体、视见机制、投影方案以及多种外观属性。

其中,对象几何体可通过其顶点、法线及其图形图元定义且常位于glBegin-glEnd语句对之间,该语

句对常包含顶点、直线、压缩几何体或多边形。另外,视见以及投影均通过特定的函数加以定义。

相应地,对象外观可通过定义颜色、着色、材质、光照以及纹理贴图获得,此类信息通过固定功能

OpenGL系统以较为直观的方式进行处理,对应操作出现于软件或图形卡中。

一种最为简单的OpenGL考察方式是将其视为两个连接操作:顶点处理操作和像素处理操作。在

OpenGL固定管线中,各个操作均包含预定义操作集合。这里,读者应深入理解几何体以及输入指令

在管线中的工作方式。当与着色器结合使用时,理解管线的执行方式将变得十分重要。相应地,读者

所编写的着色器程序将控制上述操作,因而其重要性不言而喻。

顶点操作

当创建场景几何体时,需要定义相关图元、顶点以及作用于各个顶点之上的操作,进而在屏幕

Page 2: 1 固定功能管线 - tup.com.cn · 相应地,对象外观可通过定义颜色、着色、材质、光照以及纹理贴图获得,此类信息通过固定功能 OpenGL系统以较为直观的方式进行处理,对应操作出现于软件或图形卡中。

图形着色器——理论与实践(第 2 版)·2·

空间内生成对应像素。随后,经确定的图元将确定所显示的填充

像素,任何定义的外观信息都将用于定义像素在像素处理操作中

的着色方式。顶点处理过程中的几何体部分遵循图1.1所示的流程

图。在确定后的图元中,针对于分组信息中的各个独立顶点,将

执行几何体处理操作。这里,分组信息仅在顶点完成顶点处理操

作之后加以使用。

顶点操作的第1阶段定义了场景的基本几何体,该阶段的输

入数据为顶点定义集(glVertex*、glNormal*以及glTexCoord*函数所调用的内容)和分组定义(glBegin()和glEnd()函数所调用的

内容)。随后,将生成几何体的各部分结构,或在自身的模型空

间内建模。这里,坐标空间可为任意形式,进而方便地定义顶点

以及模型关系。建模函数通常用于模型空间内的数学操作。如前

所述,几何体可能会包含法线向量、纹理坐标、顶点坐标、图元

定义(根据顶点定义像素的组装方式)以及彰显几何体关系的光

照信息。除此之外,当定义几何体对象时,相关数据还包括颜色

模型空间

模型坐标

世界空间

应用程序坐标

眼睛空间

眼睛坐标

剪裁空间

标准化设备坐标

屏幕空间

像素坐标

建模函数

建模转换

视见转换

投影转换

视口转换

图1.1 OpenGL管线中的顶点处理过程

和材质属性。针对场景而言,上述数据可视为外观因素并于随后在顶点管线中加以使用,该阶段的输

出内容为包含模型坐标的顶点集,并辅以几何体信息以及图元信息。

顶点操作的第2阶段定义了世界空间,该空间装载全部场景信息,并将所有的独立模型置于该空

间内。这里,通过模型转换,如缩放、旋转以及平移转换,各几何体图元将定位于世界空间内。因

此,该阶段的输入内容表示为模型转换集合(即glRotatef()、glTranlatef()以及glScalef()函数所调用的

内容),这一类转换行为将独立的模型空间坐标转换至单一的世界或应用程序空间坐标集合。需要说

明的是,上述行为并不会影响颜色、材质定义、纹理坐标以及分组信息,但会改变顶点、法线以及光

照几何体数据。通常情况下,光源定义于世界空间内。当定义光源时,建模过程中的任何变化均会对

光源产生影响,如位置和方向。该阶段的输出内容为调整后的顶点和法线数据集合,并以此表现不同

空间内的原始几何体。

顶点操作的第3阶段定义了眼睛空间,当定义当前场景的视见信息时,将构建眼睛空间。该阶段

的输入数据为视见环境定义,通常会使用GLU函数gluLookAT()。该函数定义了视见信息,并用于调

整当前场景,进而生成标准的场景视见内容,亦即,包含位于原点处的眼睛系统的坐标系统,以及右

手3D方向中的x,y,z坐标轴。该转换操作将调整顶点、法线以及光照信息,因而该阶段的输出内容

为包含原始信息的、经调整后的几何体,以及体现标准视见空间的几何体。针对后续处理的全部深度

信息均来自该眼睛空间的z坐标值。这里,定义了模型视见矩阵,并用于转换基于几何计算的顶点数

据。除此之外,转换工作还包括法线值、光源位置以及基于光照计算的光源位置。

在眼睛空间中,其他信息将发挥作用。作为定义顶点数据时的部分内容,还需提供某些外观因

素(如glColor3f)或其他信息(如光源或材质)。这里,该信息可用于设置顶点颜色值。当编写与颜

色相关的语句或执行光照计算时,可设置顶点的颜色数据。若支持光照计算,则光照参数、光源位置

和方向、法线向量以及材质数据均可定义各个顶点的颜色值。

顶点操作的最后一个步骤将使用视口信息,并针对屏幕空间内的各个顶点构造像素-空间表达方

式。此处存在两种主要操作:首先将根据透视定义并在剪裁空间边界上对几何体实施剪裁操作。如果

Page 3: 1 固定功能管线 - tup.com.cn · 相应地,对象外观可通过定义颜色、着色、材质、光照以及纹理贴图获得,此类信息通过固定功能 OpenGL系统以较为直观的方式进行处理,对应操作出现于软件或图形卡中。

第 1 章 固定功能管线 ·3·

存在相关剪裁操作,则随着顶点像素的添加或删除,将生成新的图元或对原有图元进行适当的调整。

在剪裁过程中,新增顶点像素应包含新的颜色值或纹理坐标,对应插值计算类似于渲染过程中的边插

值计算。其次,3D剪裁空间坐标将转换至特定视口的2D整型——这可视为x,y坐标以及齐次除法中

的一类简单的比例转换操作,并于随后将实数截取为整型数据。同时,z坐标转换为深度值(通常为

整型值)并用于渲染操作。该阶段的输出内容(即全部顶点管线的输出内容)为整型像素x,y坐标的

顶点集合,并包含分组信息、法线、深度值、纹理坐标以及颜色等信息。

图1.1描述了顶点的管线行为,这对于查看对应视觉效果十分有用。针对于3顶点所描述的简单三

角形,图1.2则显示了整体图形管线的工作方式,对应数据将通过CPU发送至顶点管线中,且通过顶

点处理器转换至屏幕空间,经过适当装配后进入光栅器,进而通过片元处理器转换为像素数据。

图1.2 图形管线的整体行为

上述图形管线包括大量的转换操作,如多个模型转换、视见转换以及投影转换。OpenGL管线实

现采用了更为统一的转换版本;模型和视见转换则整合为模型视见转换,且新的模型转换在定义时乘

以该模型视见转换矩阵。针对于模型视见和投影转换,需要维护一个转换栈,并在栈顶包含当前转

换版本。相应地,glPushMatrix()和glPopMatrix()函数负责存储或恢复模型环境。另外,模型-视见-投影转换则是模型-视见以及投影转换的产物,并随着模型-视见转换或投影转换的变化而更新。模型-视见-投影转换可应用于个体顶点上,并将其转换至剪裁空间中。同时,系统还将维护另一个转换,即

法线转换,并计算为模型-视见转换的转置逆操作,该操作使得法线数据可正确地应用于光照以及其

他操作中,具体实现稍后解释。

管线的片元处理

前面曾引用了“像素处理”这一术语,而实际情况则是“片元处理”。何为像素?在GLSL中,

像素是指一组外观信息集合(通常包含红色、绿色、蓝色、Alpha值、z深度值等内容),并写入至帧

缓冲区中。相应地,何为片元?片元是指某一像素的信息值,进而可计算该像素的红色值、绿色值、

Page 4: 1 固定功能管线 - tup.com.cn · 相应地,对象外观可通过定义颜色、着色、材质、光照以及纹理贴图获得,此类信息通过固定功能 OpenGL系统以较为直观的方式进行处理,对应操作出现于软件或图形卡中。

图形着色器——理论与实践(第 2 版)·4·

蓝色值、Alpha值、z深度值等数据——由于将使用到全部信息并生成像素外观,因而该操作称为“片

图1.3 经适当简化后的OpenGL渲染管线

元处理”。下面将进一步考察该操作与整体图形管线之

间的工作方式。

图形管线使用屏幕空间内的顶点数据,并构造基

于OpenGL渲染命令的、分组外观中所定义的区域,该

过程可通过图1.3中经简化后的渲染管线示意图进行描

述,并可视为图1.1中顶点管线最后一步的输入内容。

这里,将考察顶点操作时的对应步骤,并确定各

个阶段的输入和输出内容。针对于此,此处可从顶点操

作的输出数据开始,亦即,包含分组信息、颜色值、深

度值以及纹理坐标的屏幕坐标顶点(注意,此处并未考

察法线。由于光照采用逐顶点方式计算,同时,仅合成

后的颜色强度采用逐片元方式计算,因而固定功能管线无须采用法线数据)。首个渲染步骤将使用排

序顶点并生成图元的边数据,顶点处的颜色值、深度值以及纹理坐标将采用插值计算,进而定义沿边

方向上的同一属性。随后,针对各个片元,可从左边至右边执行插值计算。

下一个渲染阶段将处理片元,该阶段使用前述预处理信息并生成外观信息,进而写入至帧缓冲区中。

在图形管线的最后阶段,通过深度测试、混合操作以及遮罩操作(收集最终的帧缓冲区内

容),像素颜色将整合至帧缓冲区中,该过程可能会略去某些像素(通过深度测试,遮罩操作)或者

改变像素的颜色值(通过混合操作)。该阶段的最终输出数据则是帧缓冲区中的真实颜色值。

当然,上述操作中还包含大量的细节内容,这里仅考察其整体处理过程,读者可在OpenGL图形

程序设计中不断获取经验。稍后,本章还将回顾相关知识并引入某些新知识点。同时,本章还将讨论

片元着色器及其使用方式,以使读者掌握大多数细节内容。

图形管线中的状态

为了对OpenGL中的多数操作以及全部选项实施有效的操控,OpenGL负责设置和维护一组状态

信息,对应信息可用于顶点和渲染操作中。相应地,OpenGL存在多个函数,用以设置图像状态信

息。当执行此类操作时,函数将从当前状态中返回对应数据信息。

由于需要替换某些固定功能操作,因而当与着色器协同工作时,需留意OpenGL状态,因而适

宜的语言以及符号将变得十分重要。针对

于此,稍后将引入基于图形环境的符号概

念,并以此描述OpenGL状态。图1.4显示

了该图形环境下的操作示意图。

初始图形环境包含大量的默认值(例

如,直线颜色为白色且宽度为1个像素;背

景颜色为黑色;不存在处于激活状态下的

纹理),当通过诸如glcolor3f()这一类函数设置值时,可将颜色值置入“槽(slot)”中,对应槽包

含了OpenGL状态中的主颜色值。若调用其他函数改变该颜色值,则对应位置将加载新颜色值,原有

图1.4 基于图形上下文环境对象的OpenGL状态

Page 5: 1 固定功能管线 - tup.com.cn · 相应地,对象外观可通过定义颜色、着色、材质、光照以及纹理贴图获得,此类信息通过固定功能 OpenGL系统以较为直观的方式进行处理,对应操作出现于软件或图形卡中。

第 1 章 固定功能管线 ·5·

图1.5 图形硬件中的OpenGL管线

颜色值将丢失。因此,各个“设置点”均加载彼此不同的状态值,并用于图形处理中。除了设置操作

之外,还可对大多数状态值实施查询操作。在讨论着色器操作时,这一类概念还将被再次提及。

传统视见机制的实现方案

在OpenGL系统中,管线的实现可依据功能项划分为不同的种类,图1.5显示了通用图形系统中的

功能分组示意图。

首个功能组控制图1.1中的顶点处理过程,该功

能组的输入内容包括顶点、法线、图元定义、颜色

值、光照(及其参数)、材质以及纹理坐标。对应

输出内容则是基于像素的顶点集,包括相应的颜色

值、深度值以及纹理坐标,可能还包括修正后的图

元数据。

下一个步骤则是光栅化操作,该步骤在图1.3中的渲染管线中实现了顶点-片元操作。光栅器的输入

内容为屏幕坐标中的顶点集,并包含深度值、颜色

值、纹理坐标以及顶点之间的连接方式。光栅化操

作将对顶点实施插值计算,进而生成片元。除此之

外,针对于各片元,还可采用相同的插值过程确定

深度值、颜色值以及纹理坐标。

第二个功能组则是片元处理操作。该功能组的

输入内容是根据图元光栅化后的片元。通过处理颜

色值、深度值以及纹理坐标信息可确定片元的颜色

值。片元处理过程的输出结果是一组操作完毕的像素集,即图1.5中的RGBAZ像素,并包含相应的颜

色值(RGB)、混合值(A)以及深度值(Z),并与随后整合至颜色缓冲区中。

将像素整合至颜色缓冲区中可视为最后一个操作步骤,该步骤对应于图1.3中渲染管线的图元-像素部分。源自片元处理器的像素通过光栅操作被整合至颜色缓冲区中,其中,光栅操作将片元与帧缓

冲区中的像素进行合并。针对于固定功能光线以及着色器,两者行为保持一致。

顶点处理操作

固定功能顶点管线处理包含诸多细节内容,当与着色器协同工作时,读者应深入理解硬件管线

特征。其中,首要内容即是模型视图矩阵,该矩阵实现模型视图转换。

当顶点V发送至顶点处理器时,该顶点将乘以模型视图矩阵M,即V' = M * V,并将该顶点转换至

眼睛空间内,并开始后续处理工作。

投影和视口转换则是顶点管线处理过程中第二项应关注的细节内容。当采用模型视图转换

时,顶点从模型空间转换至眼睛空间后,顶点还需要通过投影转换(如glortho()、glFrustum()或gluPerspective()函数)进一步变换至剪裁空间中。同时,剪裁操作则是一个独立步骤。由于投影转换

Page 6: 1 固定功能管线 - tup.com.cn · 相应地,对象外观可通过定义颜色、着色、材质、光照以及纹理贴图获得,此类信息通过固定功能 OpenGL系统以较为直观的方式进行处理,对应操作出现于软件或图形卡中。

图形着色器——理论与实践(第 2 版)·6·

图1.6 推荐使用的凸多边形(左图)和禁止使

用的非凸多边形(中图和右图)

将顶点映射至易于剪裁的坐标空间,因而引出了“剪裁空间”这一概念。最终,齐次除法以及视口转

换将剪裁空间内的顶点转换至整型的屏幕坐标。

注意:

这里,法线矩阵为何表示为模型视图矩阵转置后的逆矩阵?下面考察点P处的表面法线N,并

选取点Q以使向量T = Q - P表示为点P处的表面切向量,则N×T = 0,或采用矩阵乘法NT* T = 0(回

忆一下,如果顶点和法线采用列向量表示,则转置后为行向量,因此结果为1×3和3×1矩阵的乘

积或标量)。这里,如果使用矩阵M且满足P' = M * P和Q' = M * Q,则新切向量定义为T' = Q'- P' = M * Q - M * P = M * (Q - P) = M * T。当前,若定义N'为转换空间的法线,则(N')T * T'为0。因此,

如果矩阵R将法线N转换为法线N',则有:

0 = N'T * T' = (R * N)T * T' = (NT* RT) * (M * T) =NT* (RT * M) * T由于NT * T = 0,则中间项RT * M为单位矩阵,因而有RT * M-1且最终可得到R=(M-1)T

实际上,上述过程并不复杂,由于仅涉及旋转操作,因而矩阵为正交状态。正交矩阵的特征之

一便是其逆矩阵等于转置矩阵。其中,法线与顶点采用相同的旋转转换。

当采用基于顶点坐标的处理操作时,为了获取准确的法线数据,OpenGL使用了法线转换以维护

法线属性:如果法线向量通过法线转换进行操作,则结果依然为转换表面的法线,该过程可通过法线

矩阵加以实现,亦即,针对模型视图矩阵左上角3×3子矩阵,可使用其逆矩阵的转置矩阵。当模型视

图矩阵发生变化时,法线矩阵可自动更新,因而在每次处理法线时,无须重新生成该矩阵。

需要注意的是,顶点光照计算在顶点处理器中进行处理,这一点常被忽略。如果采用光照和材质

(而非针对各个顶点采用简单的颜色值)生成场景颜色,则需要对光源和各对象材质设置多个参数。

随后,顶点处理器可接收此类信息,并使用定义完毕的光照模型计算顶点的颜色值。多数时候,无论

使用光照模型与否,各顶点的颜色数据均会传递至渲染处理阶段,且渲染过程不涉及其计算操作。

渲染处理操作

在渲染处理过程中,源自顶点管线的顶点数据(包括像素位置、深度值、颜色值以及纹理坐

标)用于定义像素集,该集合构成了图形对象,并针对各个像素计算颜色值。同时,该处理过程还与

图元、各顶点所定义的外观信息(如真实纹理数据)以及外观处理方式关联,进而生成结果图像。

图元规范确定了定义几何体对象时的顶点序列应用方式,并可归结为定义单一多边形问题。这里,

多边形可定义为平面、非自相交数据(OpenGL对此并不执行任何检测)。进一步讲,在OpenGL中,多

边形均假设为凸多边形,其特征为:任意端点位于该多边形内的线段,线段自身也将完全位于该多边形

内部,如图1.6所示(严格来讲,最右图并非多边形——该多边形呈现为自相交状态)。如果读者定义了

非凸多边形,其处理结果通常与预期行为大相径庭。

通过选择任意起始顶点并以有序顶点处理方式构造

三角扇,凸多边形通常可实现三角形剖分,或分解为多个

三角形(非凸多边形则不具备此项特征,即使可选择某种

方案根据三角形构造多边形,如图1.6中的中图所示)。

相应地,这一概念也可扩展至其他类型的几何体构造方案

Page 7: 1 固定功能管线 - tup.com.cn · 相应地,对象外观可通过定义颜色、着色、材质、光照以及纹理贴图获得,此类信息通过固定功能 OpenGL系统以较为直观的方式进行处理,对应操作出现于软件或图形卡中。

第 1 章 固定功能管线 ·7·

中,如四边形带。OpenGL四边形带的定义方式可视为三角形带。鉴于OpenGL仅处理凸多边形,因而

可设定多边形为凸多边形。相应地,针对多边形处理过程,可简单地使用三角形作为当前模型。

在渲染过程中,插值计算可视为核心概念。当给定屏幕空间内的一组顶点集并通过分组机制定

义某一多边形后,需要使用插值方案确定包围该多边形的边数据;随后,还将再次使用插值计算以填

充多边形的内部区域。这里,插值方案不仅生成填充位置,还将计算全部附加属性,如深度值、颜色

值以及纹理坐标。通常情况下,图形硬件支持插值计算,在固定渲染管线中,可实现相对简单的插值

计算(如深度值计算或平滑着色)以及透视插值计算(如精确的坐标计算,尤其是纹理坐标)。

平滑着色或深度值可视为顶点处或边端点处的线性插值计算,对应过程首先沿对象边对顶点颜

色实施插值计算,并于随后穿越对象内部对边颜色值进行插值计算。需要注意的是,插值结果可能略

有偏差。图1.7(上图)显示了某一简单的四边形,该四边形包含一个蓝色顶点、一个绿色顶点以及

两个红色顶点,并在四边形内部实施基于固定功能的颜色插值计算。通过观察可知,着色结果像是两

个单独执行插值计算的三角形,且两个三角形分别包含右上角顶点和左下角顶点,如图1.7中的下图

所示 1。不难发现,这是简单插值着色方案的缺陷所在,应尽量避免,具体方案将在第15章讨论。

同样,纹理坐标也包含插值计算。针对各顶点,对纹理坐标执行插值计算可获得边界像素的纹

理坐标,片元端点的纹理坐标经插值计算后可得到片元中各像素的纹理坐标。待各像素的纹理坐标计

算完毕后,纹理坐标将传递至纹理空间内,并返回原纹理定义中的像素信息。

纹理插值方案还取决于纹理质量控制,如果需要获取快速的插值过程,则可选择相对简单的线

性插值方案;如果要求最佳质量,则纹理坐标可选取透视插值方案。针对于四边形(视为两个三角

形)上的纹理坐标,图1.8显示了线性插值和透视插值之间的差别。需要说明的是,许多图形系统并

不能较好地区分“快速”与“最佳”之间的区别,因而读者往往无法获取正确的观察结果。

图1.7 基于多边形的 图1.8 未使用透视修正的棋盘纹理(上图)和

颜色线性插值 基于透视修正的棋盘纹理(下图)

1 由于左上方至右下方对角线仅包含蓝绿颜色,因而四边形的渲染方式并不正确。尽管存在两个红色顶点,但对角线对此并未显示。

Page 8: 1 固定功能管线 - tup.com.cn · 相应地,对象外观可通过定义颜色、着色、材质、光照以及纹理贴图获得,此类信息通过固定功能 OpenGL系统以较为直观的方式进行处理,对应操作出现于软件或图形卡中。

图形着色器——理论与实践(第 2 版)·8·

简单的线性插值可视为一类较为成熟的技术。当给定线段两端点a和b处的、包含fa和fb的数据值f后,则基于线性参数t的线性插值计算通常定义为下列形式:

(1 - t)fa + tfb

如果数据值f包含齐次坐标(r,s,t,q)且q≠1,则f需要除以q,进而将坐标转换为标准形式,以对f/q实施插值计算,如下所示:

(1 - t)fa/qa+ tfb/qb

如果fa和fb为标准的齐次形式,则上述两式相等。

如果t=0,则函数值为fb;若t=1,则函数值为fb。相应地,插值参数t(在插值直线上设置特定像

素)则按如下方式计算:

2

( ) ( ) ( ) ( )|| || ( ) ( )

r a b a r a b a

b a b a b a

p p p p p p p ptp p p p p p

− ⋅ − − ⋅ −= =

− − ⋅ −

其中,pr = (xr,yr)生成像素空间内的像素坐标,pb = (xb,yb)则生成线段像素空间内端点的屏幕坐标

(该线段包含当前像素)。

图形硬件通常支持这一类简单的插值操作,其应用范围包括一些相对简单的插值运算,如深度值

以及平滑着色颜色值。然而,当在剪裁空间内确定此类数据值的图形方案时,模型空间内的线性插值方

案常会产生问题。对于此类数据值的插值计算,如纹理坐标,需要在剪裁空间内执行插值计算。针对于

此,OpenGL采用了修正插值函数(采用与上述参数相同的t值),而非线性插值方案,如下所示:

(1 ) / /(1 ) / /

a a b b

a a b b

t f w tf wt w t wα α− +− +

若未执行纹理插值且纹理坐标(s,t,r,q)中q≠1,则

α=1;否则,αa=qa,αb=qb。进一步讲,wa和wb为端

点α和b在剪裁空间内的第4个坐标。再次说明,如果

t=0,则可简单地获取fa值;若t=1,则可获得fb值(或

者,如果f表示纹理坐标,则可获得其齐次值)。该

过程称作透视插值,当剪裁空间有别于眼睛空间时,

该插值方案将与线性插值存在显著的区别,且多发

生于透视投影中。由于wa和wb通常为z值的倒数,因

此,若原始端点α和b包含不同的z值,则透视插值多

呈现为非线性状态。图1.9显示了两个端点之间的某

一点(此处为坐标值)的透视插值过程。通过观察可

知,对应结果呈现为非线性状态。

通常情况下,可将剪裁空间内的z值视为深度

值,但深度计算的实现过程并非如此简单。亦即,

深度缓冲区并非传统意义上的z缓冲区,基于屏幕空

间内的深度值采用定点形式表达(其效率类似于整

数),其位数至少不低于深度缓冲区。同时,这一类

数据值存储于深度缓冲区中,并在必要时执行截取操

作。因此,当前深度值存在锯齿效应。为了有效地减

图1.9 2D眼睛空间内两点的x坐标插值计算。在3D眼

睛空间内,对应顶点坐标分别为(-3,-1,31)和(3,-1,5,1)

Page 9: 1 固定功能管线 - tup.com.cn · 相应地,对象外观可通过定义颜色、着色、材质、光照以及纹理贴图获得,此类信息通过固定功能 OpenGL系统以较为直观的方式进行处理,对应操作出现于软件或图形卡中。

第 1 章 固定功能管线 ·9·

少锯齿现象,需要定义近剪裁面和远剪裁面,并保持两者间的较小距离值。其中,近剪裁面包含较小

的深度值;远剪裁面具有较大的深度值。基于线段端点或片元深度值的插值计算将生成既定像素的深

度值。

在像素处理的最后阶段,像素在计算完毕后、写入至帧缓冲区之前发送至渲染管线的末端,该阶

段将处理多个操作,包括遮罩操作(masking)、深度测试以及Alpha混合。其中,深度测试将采用整型

值,同时,如果像素的深度值大于深度缓冲区中的现有值,则该像素将被丢弃。如果两个像素的“反

图1.10 z缓冲冲突。其中,两个多边形

相交且包含锯齿问题

走样”深度值相等,则仅使用两者中的一个值——这常

会产生非正常状态的表面行为,例如相交对象之间的非

均匀边界,该过程称作z缓冲冲突。图1.10显示了两个四

边形,两者的深度值稍有不同。通过观察可知,对于多

边形而言,并不存在一致性的深度优先权计算方案。混

合计算则根据颜色设置、材质定义或纹理的Alpha分量值

使用各像素的Alpha值,读者对此应不会感到陌生。遮罩

操作则通过剪切测试、Alpha测试、模板测试以及其他逻

辑操作加以处理。

几何和渲染处理过程包含诸多操作步骤,但OpenGL以合理、可控方式将其进行组织,并在较高层面上向程

序员提供了强有力的图形编程工具。针对于高质量计算

机图形与通用计算环境之间的可访问能力,OpenGL提供

了成功的计算范例,本书将在此基础上向读者提供更为

优质的、应用更为广泛的图形访问操作。

固定管线中的齐次坐标

在计算机图形学的初始阶段,齐次坐标往往无法得到足够的重视。然而,齐次坐标将直接影响

到OpenGL的工作方式,因而深入理解这一概念十分重要。齐次坐标定义为4维实数空间内的向量,

且第4个坐标通常表示为单值。相应地,顶点分量常包含如下命名规则,即(x,y,z,w),且在顶点的标

准形式中,w=1。这里,读者可尝试在图形程序中使用20或30个顶点,但OpenGL内部则将其视为4维顶点。这里,如果通过glvertex2f(x,y)定义顶点,则OpenGL将该点表示为(x,y,0,1);类似地,如果通过

glvertex3f(x, y, z)定义一点,则OpenGL将该点表示为(x, y, z, 1);另外,若采用glvertex4f(x,y,z,w)定义

某一向量,则3D点的表现形式为(x/w, y/w, z/w, 1)。例如,齐次点(1, 2, 3, 1)、(2, 4, 6, 2)以及(-1, -2, -3, -1)均表示同一点(1, 2, 3)。

3D空间和4D空间的差异以及基于w的单位值制定方案常使问题变得复杂化,然而,其原因在

于,齐次坐标支持矩阵机制中的透视除法。例如,OpenGL将调用下列函数:

glFrustum(left,right,bottom,top,near,far )

并构造下列矩阵:

Page 10: 1 固定功能管线 - tup.com.cn · 相应地,对象外观可通过定义颜色、着色、材质、光照以及纹理贴图获得,此类信息通过固定功能 OpenGL系统以较为直观的方式进行处理,对应操作出现于软件或图形卡中。

图形着色器——理论与实践(第 2 版)·10·

2* near right left0 0right left right left'

2* near top+bottom' 0 0top bottom top bottom

'(far+near) 2* far * near0 0' 1far near far near

0 0 1 0

x xy yz zw

+ − −

= − − − − − − −

这将生成w' = -z,即透视中的除数。

与相对简单的3D空间相比,该方案提供了通用几何体的协同工作方式。除此之外,还存在另一

种齐次坐标的考察方式。例如,考察下列4个齐次坐标点:(1, 2, 3, 1)、(1, 2, 3,0.1)、(1, 2, 3, 0.01)以及

(1, 2, 3, 0.001)。在标准形式中,上述点分别为(1, 2, 3, 1)、(10, 20, 30, 1)、(100, 200, 300, 1)以及(1000, 2000,3000, 1)。而在数学中,4维空间点的齐次坐标为点-原点直线的3维透视空间的表达方式,且点(1, 2, 3, 0)表示为(1, 2, 3)方向上的“无限点”。

某些时候,两端点定义的向量也十分重要。通常情况下,向量可通过端点坐标之间的向量减法

获得。然而,这对于4维空间,特别是齐次坐标,并不准确,因而需要对该问题格外重视。 若顶点定义于4维空间内,当计算3维空间内两个顶点之间的差时,首先需要考察4维空间中的顶

点,并将其转换至3维空间中,待计算差之后,还需要获取其适宜的表达方式,如下所示:

( , , ) ( , , ) ( , , ) ( , , )( , , , ) ( , , , ) b b b a a a a b a b a b b a b a b a

b b b b a a a ab a a b

x y z x y z w x w y w z w x w y w zx y z w x y z ww w w w

−− = − =

其中,右侧分母表示标量,因此,如果仅需要单位方向向量,则可简单地将分子按如下方式标

准化:

v=normalize(waxb-wbxa,wayb-wbya,wazb-wbza)如果原向量为齐次形式,且wa和wb皆为1,则问题归结为计算两向量差的标准形式。 光源位置定义为齐次坐标且包含4个分量值,以在4维空间内实现光源的真实定位。如果w分量不

为0,则光源位置可视为3D世界空间内的普通一点,当该点转换为标准齐次形式后,则给定x,y,z坐标。然而,若光源位置齐次坐标的w分量为0,则光源视为方向光源,即光源位置位于透视空间内的

无限远处。模型和视见转换将对方向光源产生影响,但并不会改变光源的位置。

类似于顶点数据,纹理坐标也存储于实数4元向量中,但纹理坐标可能会包含一维情形。与顶点

相同,纹理坐标分量也包含相应的命名规则,如(s, t, p, q)。这里,字母p用于第3个纹理坐标分量,而

非r,以避免同颜色中的红色分量产生混淆。另外,如果定义了包含s值的1D纹理,则t、p设置为0,q设置为1。同样,2D和3D纹理坐标也采用相同的方式进行设置。

颜色值也以4元(维)以及RGBA方式加以存储,如果仅以RGBf方式定义颜色值,则Alpha分量设

置为1。法线向量通常定义为3元(维)形式,如glNormal3f(x,y,z),因而法线不存在齐次坐标问题。

基于4元向量的图形卡支持4个浮点数宽度的统一数据通道,因而可视为一类高效的阵列处理

器。除此之外,图形卡还可加速管线的处理过程。

顶 点 数 组

为了更为清晰地阐述相关概念,曾探讨了基于简单顶点和图元操作的图形管线。然而,在实际

Page 11: 1 固定功能管线 - tup.com.cn · 相应地,对象外观可通过定义颜色、着色、材质、光照以及纹理贴图获得,此类信息通过固定功能 OpenGL系统以较为直观的方式进行处理,对应操作出现于软件或图形卡中。

第 1 章 固定功能管线 ·11·

应用程序中,还存在其他技术用以提升管线的处理速度,顶点数组便是一例,下面将对顶点数组进行

简要的介绍。

顶点数组创建于CPU上以存储顶点坐标以及顶点属性。顶点数组传递至图形卡中并包含索引信

息,其中,索引定义了图元中实现正确连接的顶点编号。通过顶点数组,各顶点只需传送一次,因此

可减少函数调用次数。

顶点数组可通过下列方式开启:

glEnableClientState(type)

其中,type包含下列内容:

GL_VERTEX_ARRAYGL_COLOR_ARRAYGL_NORMAL_ARRAYGL_SECONDARY_COLOR_ARRAYGL_TEXTURE_COORD_ARRAY

该函数可开启全部顶点数组并一次描述顶点数据。

相应地,可通过下列函数禁用顶点类型:

glDisableClientState(type)

一旦激活了顶点状态,即可通过简单的数组操作加载数组,如下列顶点数据所示:

static GLfloat Vertices[ ][3] = {{ { 1., 2., 3.}, { 4., 5 ., 6.}, …};

类似操作还可用于加载颜色数组、法线数组以及纹理坐标数组。这里,可使用下列函数定义顶

点数组:

glVertexPointer( size, type, stride, array);glColorPointer( size, type, stride, array);glNormalPointer( type, stride, array);glSecondaryColorPointer( size, type, stride , array);glTexCoordPointer( size, type, stride, array);

图1.11 立方体。其中,顶点均已编号

并与RGB立方体匹配

进而确定所用数组为顶点坐标、颜色值、法线数据等。其

中,size表示顶点尺寸并可分别设置为2、3或4;type为对应

数据数组的名称;变量stride为数组中连续数据项之间的位偏

移值(0表示紧凑型),并可通过sizeof()函数进行设置。

这里,可绘制标准RGB立方体作为相关示例。通过定义

顶点坐标以及顶点颜色,图1.11显示了立方体顶点的索引方

式。其中,顶点0为黑色;顶点1、2、4分别为红色、绿色和

蓝色;顶点3、6、5分别为黄色、蓝绿色和洋红色;顶点7为黑色。

Page 12: 1 固定功能管线 - tup.com.cn · 相应地,对象外观可通过定义颜色、着色、材质、光照以及纹理贴图获得,此类信息通过固定功能 OpenGL系统以较为直观的方式进行处理,对应操作出现于软件或图形卡中。

图形着色器——理论与实践(第 2 版)·12·

下列语句可用于构造数组:

static GLfloat CubeVertices[ ][3] ={ { -1.,-1.,-1. }, { 1.,-1.,-1. }, { -1.,1.,-1. }, { 1.,1.,-1. }, { -1.,-1.,1. }, { 1.,-1.,1. }, { -1.,1.,1. }, { 1.,1.,1. }} ;static GLfloat CubeColors[ ][3]{ { 0.,0.,0. }, { 1.,0.,0. }, { 0.,1.,0. }, { 1.,1.,0. }, { 0.,0.,1. }, { 1.,0.,1. }, { 0.,1.,1. }, { 1.,1.,1. }} ;

随后,可利用glArrayElement()函数绘制RGB立方体,并通过索引列举全部顶点数据。随后,可

使用各顶点的几何数据以及颜色值,仿佛glVertex()和glColor()设置于各顶点上,如下所示:

glEnableClientState( GL_VERTEX_ARRAY );glEnableClientState( GL_COLOR_ARRAY );glVertexPointer( 3, GL_FLOAT, 0, CubeVertices );glColorPointer( 3, GL_FLOAT, 0, CubeColors );glBegin( GL_QUADS ); glArrayElement( 0 ); glArrayElement( 2 ); glArrayElement( 3 ); glArrayElement( 1 ); glArrayElement( 4 ); glArrayElement( 5 ); glArrayElement( 7 ); glArrayElement( 6 ); glArrayElement( 1 ); glArrayElement( 3 ); glArrayElement( 7 ); glArrayElement( 5 ); glArrayElement( 0 ); glArrayElement( 4 ); glArrayElement( 6 ); glArrayElement( 2 ); glArrayElement( 2 );

Page 13: 1 固定功能管线 - tup.com.cn · 相应地,对象外观可通过定义颜色、着色、材质、光照以及纹理贴图获得,此类信息通过固定功能 OpenGL系统以较为直观的方式进行处理,对应操作出现于软件或图形卡中。

第 1 章 固定功能管线 ·13·

glArrayElement( 6 ); glArrayElement( 7 ); glArrayElement( 3 ); glArrayElement( 0 ); glArrayElement( 1 ); glArrayElement( 5 ); glArrayElement( 4 );glEnd( );

上述代码冗长、低效且缺乏应有的优雅性。针对于此,可定义立方体6个面中的顶点索引数组,

并调用glDrawElements()函数,如下所示:

static GLuint CubeIndices[ ][4] ={ { 0,2,3,1 }, { 4,5,7,6 }, { 1,3,7,5 }, { 0,4,6,2 }, { 2,6,7,3 }, { 0,1,5,4 }};

glEnableClientState( GL_VERTEX_ARRAY );glEnableClientState( GL_COLOR_ARRAY );

glVertexPointer( 3, GL_FLOAT, 0 , CubeVertices );glColorPointer( 3, GL_FLOAT, 0, CubeColors );glDrawElements( GL_QUADS , 24, GL_UNSIGNED_INT, CubeIndices );

上述代码短小且不失优雅性。需要注意的是,数组CubeIndices简单地表示为普通的索引数组,

图1.12 代码所生成的RGB立方体(添加

了坐标轴)

且并未采用指针方式命名。对应渲染结果如图1.12所示。

该示例相对简单,在实际操作中,此类数组常会加载

数百个顶点,并涵盖场景中的大部分内容(存储于独立数组

中),这大大提升了数据的封装性以及复用性,并可实现场

景的快速渲染。

顶点数组可存储于客户端或主机系统一端(总线侧),

这也意味着,无法像图形卡那样高效地访问顶点数组中的数

据。针对于此,顶点缓冲对象(VBO,其操作类似于顶点数

组,但位于图形卡一侧)可视为一类更为高效的图形几何体

封装方式。除了某些细微差别之外,VBO的创建和使用方式

与顶点数组基本相同,具体细节内容可参考OpenGL“红宝

书”(参见本书参考文献[41])。

本 章 小 结

对于应用广泛的计算机图形学而言,固定管线在建模方面显示出了其自身的价值,并可分别通

Page 14: 1 固定功能管线 - tup.com.cn · 相应地,对象外观可通过定义颜色、着色、材质、光照以及纹理贴图获得,此类信息通过固定功能 OpenGL系统以较为直观的方式进行处理,对应操作出现于软件或图形卡中。

图形着色器——理论与实践(第 2 版)·14·

过软件和硬件方式得以实现,一如预测的那样,其应用遍及各种计算平台。固定管线的整体计算行为

对大多数图形操作提供了优化方案并将其移至芯片。同时,设计良好的数据管线可令图形操作以并行

方式加以处理,图形卡的并行体系结构使得渲染处理器可同步处理多个像素。图形卡中,顶点和片元

处理器的数量仍然处于上升阶段,在编写本书时,该数量已高达128个,这将显著地提升片元的处理

速度。

当考察传统的固定功能管线时,某些图形操作已然难以处理,并需要在特定的内建计算机图形

系统中完成(科研环境)。同时,这也是计算机图形API需完成的目标,进而提供更为强大的计算能

力,相关操作包括下列内容:

z 眼睛空间建模。其中,对象仅相对于“眼睛”加以定义,例如彩虹,并严格依赖于水滴对象

以定义眼睛和光线之间的角度。

z 与世界空间、模型空间以及眼睛空间的协同工作能力。

z Phong着色。其中,法线将基于多边形实施插值计算;同时,通过基于逐像素方式的标准光

照模型(而非顶点像素的颜色插值计算),可确定像素的颜色。

z 各向异性着色。其中,光线相对于对象采取不同方式进行反射,而非环境-漫反射-镜面光照

模型。

z 与比例无关的纹理效果。针对于此,可适当地放大纹理几何体,并获取与当前尺寸相仿的纹理。

z 非相片级渲染。其中,渲染过程生成的视觉效果在几何体以及外观信息中表现得并不明显。

z 访问并与纹理中单一值协同工作的某些图像处理技术。

z 通过几何着色器创建几何体,进而生成对应视觉效果,例如某些细节内容,并根据图像屏幕

空间特征获取适宜的效果。

z 根据相对简单的对象定义生成细节内容。

相关内容将在后续章节中加以讨论。

本 章 练 习

1.对象至剪裁空间内的透视转换可通过如下方式进行:x,y坐标(以及w坐标)除以各顶点的z坐标。这里,可尝试创建一个对象,采用透视方式对其进行观察,并手工计算经透视转换后的替代对

象。亦即,生成一个新模型,其中,原模型的剪裁空间为新模型的世界空间。随后,可尝试绘制两个

模型并对两者的结果进行比较。

2.本章曾讨论了转换操作的逆操作,并假设当前转换仅包含旋转、缩放以及平移操作。这里,

试采用相关符号对此进行验证,并使用OpenGL矩阵进行数值验证。

3.当对图形对象采用固定着色时,可在定义首个顶点之前设置颜色值。从理论上讲,可为各

顶点设置独立的颜色值。相应地,读者可尝试下列操作:生成一个图形对象,在各顶点之前调用

glColor3f()函数并使用不同的颜色值。当颜色值针对某一对象设置时,试给出对应结论。例如,颜色

值是否是在最后一次调用glColor3f()函数时设置?抑或是首次调用glColor3f()函数时设置?试对结果

进行比较,以检测OpenGL系统中的一致性。

4.图1.7中的颜色插值方式表明,四边形可作为两个三角形进行绘制。首先,考虑到四边形实现

Page 15: 1 固定功能管线 - tup.com.cn · 相应地,对象外观可通过定义颜色、着色、材质、光照以及纹理贴图获得,此类信息通过固定功能 OpenGL系统以较为直观的方式进行处理,对应操作出现于软件或图形卡中。

第 1 章 固定功能管线 ·15·

方式的不同,读者应对自身的OpenGL系统进行检测;其次,可添加不同颜色值的顶点以对当前四边

形进行扩展,进而生成多边形。这里,读者可尝试识别多边形的实现方式(在我们的系统中,多边形

以三角扇的方式加以实现)。

5.尝试定义非凸多边形,亦即,在各像素处通过颜色值和光照信息定义多边形,查看OpenGL系统的绘制结果并解释其中的原因。

6.当多边形定义为平面时,可向OpenGL系统提供一组非共面顶点集(采用GL_QUADS或GL_POLYGON图元)。针对四边形或多边形,试解释为何渲染结果依然看似合理?

7.绘制两个以小角度相交的多边形并考察z冲突问题,如图1.10所示。针对于该问题,可查看投

影视见体的深度值,调整其前、后平面以尽可能地获取较小的深度值。该行为是否可在一定程度上缓

解z冲突问题?抑或可完全消除该问题?

8.试根据顶点、顶点颜色值以及法线构造模型,并通过顶点数组加以存储。试通过下列方式显

示该模型:不使用着色技术;采用顶点颜色值;调用glDrawElements()函数而非glBegin()-glEnd()函数。随后,试调整当前模型并使用单颜色值、法线以及平滑着色渲染该模型。

9.试编写渲染效率基准程序。创建适度大小的几何体,并分别通过立即模式glBegin()-glEnd()函数、显示列表模式glBegin()-glEnd()函数、顶点数组以及顶点缓冲对象渲染该几何体,并对当前显示

卡及其驱动程序加以分析。