OpenGL工作流程
通过上面的介绍,对OpenGL是有了一个总体的印象。下面来阐述在OpenGL中处理数据的三个阶段和这些阶段中OpenGL函数如何使用。
图1
图1详细给出了OpenGL处理流水线(pipe line)的框图。
图中左边有三条竖向的宽箭头,箭头代表了几何顶点(vertexs)和与几何顶点有关的两类原始数据:颜色值和纹理坐标。从图中可以看出处理流程的三个阶段,几何顶点被组合为图元(primitives),然后图元被合成片元(fragments),最后片元被转换为帧缓存中的象素数据(pixels)。在下面的章节中将详细讨论这一过程。
图 OpenGL工作流程(openGL blue book)
15.5.1
几何顶点
这一节讲解流程图中对预顶点进行处理(per-vertex)的相关OpenGL函数,经过这个处理后的几何顶点便可以进入OpenGL处理流程。
1)输入数据
对OpenGL流水线必须提供如下的输入数据类型
几何顶点(Vertices)——几何顶点描述了几何物体的形状。指定几何顶点要用glVertex*()函数,并将其放在glBegin()和glEnd()之间,可以创建点、线或者多边形。可以用glRect*()描述一整个矩形。
边界标志(Edge flag)——默认情况下所有多边形的边缘都是边界。使用glEdgeFlag*()可以明确指定边界。
当前光栅化位置——使用glRasterPos*(),用来指定象素和位图绘制操作的栅格坐标。
当前法线——几何顶点的法向量决定了三维空间中某个面上一点的指向,从而影响了该点接收光线。使用glNormal*()来定义一个法向量。
当前颜色——顶点的颜色,加上设置的光照条件,最终决定了光照颜色。RGB颜色用glColor*(),颜色索引模式用glIndex*()。
当前纹理坐标——用glTexCoord*()定义,纹理坐标将纹理图象的一个特殊位置与几何顶点关联。
当调用glVerex*()时,计算后的几何顶点继承了当前边界标志、法线、颜色和纹理坐标。因此,一旦定义了这些属性,glEdgeFlag*(),glNormal*(),glColor*()和glTexCoord*()在glVertex*()前被调用。
2) 矩阵变换
在生成帧缓冲区中的图象前,几何顶点和法向量需要通过模型矩阵(modelview matrix)和投影矩阵(projection matrix)进行矩阵变换。需要用到glMatrixMode(), glMultMatrix(), glRotate(),glTranslate()和glScale()等函数组合得到想要的变换。用glLoadMatrix() 和glLoadIdentity()可以指定单位矩阵。使用glPushMatrix() 和glPopMatrix()从各自堆栈中保存和取出模型矩阵和投影矩阵。
3)光照和颜色
除了定义颜色和法向量之外,还可以用glLight*() 和glLightModel*()设置想要的光照条件,用glMaterial*()设置想要的材质属性。用glShadeModel(),glFrontFace()和 glColorMaterial()控制光照的计算。
4) 生成纹理坐标
除了精确设置纹理坐标外,还可以用glTexGen*()自动生成几何顶点的纹理坐标。在纹理坐标设置或自动生成后,需要进行纹理矩阵变换。纹理矩阵的操作和上面其他矩阵变换的操作相同。
5) 图元组装
一旦这些计算都完成后,几何顶点被组合为图元(点,线段或多边形)还包括几何顶点的相关边界标志、法线、颜色和纹理坐标。
15.5.2
图元
在下一个处理阶段中,图元被分几步转换为象素片元:图元被适当的裁剪,颜色和纹理数据也相应作出必要的调整,相关的坐标被转换为窗口坐标。最后,光栅化将裁剪好的图元转换为象素片元。
1) 裁剪
在裁剪时点,线段和多边形处理略微不同。对于点,要么保留原始状态(在裁剪体内部),要么被裁掉(在裁剪体外部)。对于线段和多边形来说,如果部分在裁剪体外部,则需要在裁剪点生成新的几何顶点。对于多边形,还需要在新增的顶点间增加完整的边。不论裁剪了线段还是多边形,都需要给新增几何点赋予边界标志、法线、颜色和纹理坐标信息。
裁剪过程时两步:
a 应用程序指定裁剪(Application-specific clipping),一旦组合图元完成后,如果在程序中用glClipPlane()函数定义了任意的裁剪面,就进行裁剪。
b 视景体裁剪(View volume clipping),随后,图元被投影矩阵投影(进入裁剪坐标系),被相应的视景体裁剪。投影矩阵可以由glFrustum() 或者glOrtho()定义,投影矩阵的操作和上面其他矩阵变换的操作相同。
2) 转换到窗口坐标
裁剪坐标在转换为窗口坐标之前,要除以规格化设备坐标(normalized device coordinates)的w值进行规范化。然后对这些规范化数据进行视口变换(viewport)计算生成窗口坐标。可以用glDepthRange()和glViewport()控制视口大小,决定屏幕上显示图象的区域。
3) 光栅化
光栅化是将一个图元转变为一个二维图象的过程。二维图象上每个点都包含了颜色、深度和纹理数据。将该点和相关信息叫做一个片元(fragment)。在这个阶段,对象素绘制和位图进行操作需要用到当前栅格位置(用glRasterPos*()定义)。正如上面讨论的,三种图元的光栅化方法是不同的,另外,象素块和位图也需要光栅化。
a)图元
采用glPointSize(), glLineWidth(), glLineStipple()和 glPolygonStipple()函数可以选择图元的光栅化维数和模式。另外,还可以用glCullFace(), glFrontFace()和glPolygonMode()控制多边形正反面不同的光栅化效果。
b)象素
有几个函数实现象素保存和转换。函数glPixelStore*()用于内存中的象素是如何保存的。
glPixelTransfer*() and glPixelMap*()用于象素在写入帧缓冲区前是如何处理的。glDrawPixels()定义了一个象素矩形。用
glPixelZoom()实现象素的缩放。
c)位图
位图是具有特定片元模式的0和1的矩形。每个片元有相同的相关数据。可以用glBitmap()定义。
d)纹理存储
纹理贴图是将指定的部分纹理图象映射到每个图元上。每个片元(fragment)具有的纹理坐标属性,该坐标与纹理图象坐标对应,得到纹理图象该位置的颜色值来修改片元的RGBA颜色,从而完成这个映射过程。用glTexImage2D()或glTexImage1D()来定义纹理图象。glTexParameter*()和glTexEnv*()来控制纹理如何解释和应用到一个片元上。
e)雾
已经光栅化的片元具有纹理贴图修正后颜色,可以采用融合因子再融合雾颜色,该融合因子大小根据视点和片元间的距离来定。用glFog*()指定雾化颜色和融合因子。
15.5.3
片元
OpenGL允许光栅化生成一个片元,只要该片元通过一系列检测就可以修改帧缓冲区中对应象素。如果它通过测试,片元数据可以直接替换帧缓冲区中的已有值,或者和已有值合并,这取决于设置的模式。
1)象素所有权(ownership)检测
第一个测试是判断在帧缓冲区中的象素所对应的某个片元是否属于当前OpenGL上下文。如果属于,片元进行下一个测试。如果不属于,窗口系统决定是否忽略该片元,或者是否进行下一步片元操作。
2)裁剪检测
用glScissor()函数,可以定义一个任意屏幕校准矩形,在该矩形外的片元将被忽略。
3)Alpha检测
Alpha测试只能在RGBA模式下进行,如果片元的alpha值超出一个固定参照值,片元将被忽略,这个比较函数可以用glAlphaFunc()实现并设定参考值。
4)模版检测
当模版缓冲区的值超出一个参照值,模版测试将有条件的忽略该片元。这个比较函数和固定值可以用glStencilFunc()实现。不论图元通过或没有通过模版测试,模版缓冲区中的值会根据glStencilOp()函数进行修改。
5)深度检测
当深度缓冲区的值与参照值的比较失败,深度测试忽略该片元。GlDepthFuc()用来执行这个比较命令。如果模版启用,深度比较的结果会影响模版缓冲区值的更新。
6)融合
融合合并了一个片元R、G、B和A值和存储在帧缓冲区对应位置的这些值。融合只能在RGBA模式下实现,它的实现需要片元的alpha值和对应当前存储象素,还需要RGB值。用glBendFun()控制,可以修改融合因子的源和目标。
7)抖动
如果启动抖动,片元的颜色或者颜色索引采用抖动算法。这个算法只需要片元的颜色值和它的x和y坐标。
8)逻辑操作
最后,在片元和帧缓冲区对应值之间要进行一个逻辑操作,结果将替换当前帧缓冲区的值。用glLogicOp定义想要的逻辑操作。这个逻辑操作只能在颜色索引模式下运行,而不能在RGBA模式运行。
15.5.4
象素
在OpenGL流水线的上个阶段,片元转换为帧缓冲区中的象素。帧缓冲区实际上是一组逻辑缓冲区——包括颜色缓冲区、深度缓冲区、模版缓冲区和累积缓冲区。颜色缓冲区包括左、前右、后左、后右和一些辅助缓存值(auxiliary buffers)。可以直接从中读取或者复制。对于OpenGL不同上下文,这些缓冲区可能不全
1)帧缓冲区操作
用glDrawBuffer为绘图选择一个颜色缓冲区。另外在预片元化(per-fragment)操作后,可以用四个不同函数保留写入这些逻辑缓冲区的操作,glIndexMask(), glColorMask(), glDepthMask(), and glStencilMask()。glAccum()对累积缓冲区进行操作。最后glClearColor(), glClearIndex(), glClearDepth(), glClearStencil()和glClearAccum().对不同缓冲区中指定相对应的颜色值、颜色索引值、深度值、模板值和累积值。
2)读取和复制象素
用glReadPixel()从帧缓冲区中把象素读到内存中,进行各种操作,保存处理结果。另外,可以用glCopyPixel()从帧缓冲区中复制一块象素到另一个帧混存。glReadBuffer()可以读取和复制颜色缓冲区中的象素。