Monthly Archives: November 2013

像素相关的操作

本篇文章主要包括四个部分,介绍与像素有关的一些操作,第1节,介绍光栅化在图形管线中所处的位置,像素图的概念和像素操作;第2节,介绍颜色混合在OpenGL中的应用,即像素操作在OpenGL当中的应用;第3节,主要介绍光栅位置和位图绘制,简单的介绍了glRasterPos*()和glBitmap()两个函数使用;第4节,主要图像管线,OpenGL当中像素是怎么处理,纹理图像如何在硬件端处理等,还介绍了其中主要的三个函数glDrawPixels()、glReadPixels()、glCopyPixels()用法。 示例代码的下载地址:https://github.com/twinklingstar20/twinklingstar_cn_demo_operation_about_pixels/ 一.      像素操作 光栅化就是把几何数据和像素数据转换为片段的过程,每个片段对应于帧缓冲区中的一个像素,每个片段包括颜色、深度、纹理坐标信息。如图1所示,很多步骤和测试会在“per fragment operations”中进行,之后片段会将像素值写进帧缓冲区(frame buffer)中。 图1 图形管线中的光栅阶段 一张图片在内存中是以像素图(pixmap)的形式保存,即一个矩形的数值数组,像素图随意的保存在内存中,能从一个地方复制到另一个地方,当它被复制到帧缓冲区后,会显示在显示器上。在一个像素图上,每个像素占用固定的位数(bit),固定的位数就是该像素的颜色深度(color depth)。如果颜色深度为1,即产生两种颜色的像素图,就称之为位图(bitmap);如果每个像素用一个字节表示,表示从0到255的灰度级,0表示黑色,255表示白色,称为灰度图像(gray-scale pixmap);通过LUT索引号来存储颜色,每个像素用颜色表(color lookup table,LUT)的一个索引来保存,通常颜色表有256个表项,所以索引用一个字节表示就够了;RGB图像,R、G、B三个颜色分量各占一个字节,这种图像可以称之为真彩图;RGBA图像,除了R、G、B三个颜色分量外,还包括一个alpha分量或者称为alpha通道(alpha channel)。 在一些环境下,我们希望将不同的像素图组合起来,生成一个新的像素图,这样的像素操作是非常有用的。用一个数学公式表示这个过程,如公式所示。其中⊕符号并不明确指定两个像素的操作类型,所以它可以是取平均值操作、加操作、减操作等等。 假设像素图C是通过像素图S和D计算得到,其中D表示目标像素图,S表示源像素图,如果C与D相同,则S和D像素操作后,还要将结果写回D中。这样将D中的像素读取到内存当中,通过与S中的像素操作并修改它的值,最后将结果又写回D中的整个操作称为读-修改-写周期,OpenGL对这种操作提供了一个很高效的方法。 回过来叙述下前面提到的RGBA图像,RGBA图像在图像混合上就起到了重要的作用,前面说的alpha值的取值范围是0到255,0表示完全透明,255表示完全不透明,alpha也通过作为一个0到1范围内的缩放因子,即alpha/255。因此在加入alpha通道后,两个图像的混合计算方法如下所示: D.R = a•S.R + (1-a)D.R; D.G = a•S.G + (1-a)D.G; D.B = a•S.B + (1-a)D.B;   其中a=S.A/255 二.      颜色混合在OpenGL中的应用 下面介绍像素混合在OpenGL中的应用,采用的函数主要有: 默认情况下,颜色混合是被禁用的,可以使用参数GL_BLEND将它激活。源图像指的是要画的图元,图元范围外的图像不进行混合操作,例如,要画一个正方形,只有正方形内的像素进行混合操作,正方形外的图像不进行。目标图像是指已存在帧缓存中的像素组成的图像。

spacer

顶点数组

绘制一个简单的几何图元,OpenGL需要很多函数的调用,例如绘制一个20面的多边形,可能就会要求22个函数的调用,因为需要调用glBegin()和glEnd()两个函数;此外,绘制一个相邻多边形时,可能还存在很多顶点的冗余,例如绘制一个正方体,需要绘制6个面,每个顶点就会被处理3次。但是如果有了顶点数组(vertex array),就能把顶点放在一个数组中,然后调用一次函数就可以完成绘制,即使每个顶点存在一个法向量,也可以把法向量放在另一个数组中,并用另外一个函数进行调用。使用数组可以避免上述提到的函数的多次调用和顶点冗余的问题,从而提高性能。使用顶点数组渲染几何图元主要有以下三个步骤: 1.       激活相应类型的数组 共有8种数组:1)顶点坐标,2)平面法向量;3)RGBA颜色值;4)二次色(secondary color);5)颜色索引;6)雾坐标;7)纹理坐标;8)多边形边标志。 通过函数glEnableClientState()(相对应的是glDisableClientState())来激活相应的数组,理论上可以激活8个数组,但是实际上最多激活6个数组,例如GL_COLOR_ARRAY,GL_INDEX_ARRAY不可以同时激活。这里采用gl*ClientState()而不使用glEnble和glDisable()的原因是glEnable()和glDisable()可以存储在显示列表中,但是顶点的数组却不能,因为这些数据保存在客户端。 2.       把数据放在数组中 数组能通过地址来访问,在OpenGL的客户端-服务器模型中,这个数据被存储在客户端地址空间中。OpenGL提供了8个函数进行存储顶点数组中的数据,如下所示: (1)              glVertexPointer(): 顶点坐标数组 (2)              glNormalPointer():  法向量数组 (3)              glColorPointer():  颜色RGB数组 (4)              glIndexPointer():  索引颜色数组 (5)              glTexCoordPointer():  纹理坐标数组 (6)              glEdgeFlagPointer():  边标志数组 (7)              glSecondaryColorPointer():二级颜色数组。 glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid* pointer) size:顶点坐标的维度, 2

spacer

显示列表

一.OpenGL状态机 OpenGL是一个状态机,可以设置它的各种状态,这些状态会一直有效直到你改变了这些状态为止。例如,当前的状态就是一种状态,你可以设置当前的颜色为白色,红色等等,接下来画的每个对象都是对应的颜色,如画出的drawCube1(),drawCube2()画出的对象都是(1.0f,0.0f,0.0f)颜色,直到你设置为其它颜色(0.0f,1.0f,0.0f)为止,而颜色就是状态机中的一个状态。如下面的代码片段所示: glColor3f(1.0,0.0,0.0f); drawCube1(); drawCube2(); glColor3f(0.0f,1.0f,0.0f); OpenGL还包括其它状态,分别控制着当前的视点变换矩阵,投影变换矩阵,直线和多边形的点画模式,多边形绘图模式,像素打包转换,光照的位置和特点,物体的材质等。许多状态可以通过glEnable()和glDisable()进行控制。每个状态或者模式都有一个默认的值,在任何位置都能查询系统每个状态的值,典型的可以使用glGetBooleanv(),glGetDoublev(,glGetFloatv(),glGetIntegerv(),glGetPointerv()或者glIsEnabled()来查询。一些状态需要使用特别的查询命令,例如glGetLight*(),glGetError(),glGetPolygonStipple()。另外,你也可以将一组状态保存到属性堆栈(Attribute Stack)中,采用glPushAttrib()或者glPushClientAttrib()临时保存它们,用glPopAtrib()或者glPopClientAttrib()恢复这些状态。对于一些临时的状态改变,尽量采用这些状态保存和恢复的命令,而不是使用状态查询命令,这样更加高效。glPushClientAttrib()和glPopClientAttrib()分别用于存储和恢复客户端的状态变量,glPushAttrib()和glPopAttrib()分别用于存储和恢复服务器端的状态变量。 二.显示列表(Display List) 2.1. 显示列表设计哲学 OpenGL采用客户端-服务器模型,显示列表是服务器的一部分,常驻在服务器端,如果重复使用存储在显示列表中的命令,减小了通过网络发送数据的开销,将常用的命令存储在显示列表中可以提高性能,而一些图形硬件能将显示列表存储在硬件专用内存中或者将数据存储在与图形硬件或软件相兼容的优化格式中。 在硬件产商对OpenGL的实现中为了优化性能,显示列表是一个指令的缓存,而不是动态的数据库,换句话说,显示列表不能被修改。如果可以修改显示列表,则由于要搜索显示列表和内存管理等操作,会导致性能降低。在显示列表中指令的优化方法可能由于实现的不同而不同,例如,简单的命令glRotate*(),需要进行复杂的矩阵运算(涉及平方根,三角函数计算),通过显示列表可以产生显著的改善,因为在显示列表中,可能只有最终的旋转矩阵被存储起来,这就能与它的执行与硬件端执行glMultMatrix*()命令一样快。此外,一些复杂硬件产商的OpenGL实现版本中,可能只将相邻的几个变换转化一个简单的矩阵乘法。虽然不能保证OpenGL的实现能够优化一些特别用途的显示列表,但是执行显示列表不比分开执行包含在显示列表中的命令慢。由于跳转到显示列表需要一些开销,如果某些列表特别短,跳转的开销可能抵消掉执行快的优势。在下面几种情况下,更有可能进行优化: (1)       矩阵操作。很大一部分矩阵操作需要计算矩阵逆,在一些OpenGL实现中,显示列表可能存储计算后的矩阵和它的逆。 (2)       光栅化的位图和图像。一些光栅化的数据格式可能不是硬件的理想存储格式,但是通过显示列表的编译,OpenGL可能将数据转化为更适合硬件的存储格式,这在包含一系列位图的字体绘制、字符串绘制上速度有较大的提高。 (3)       光照、材质属性和光照模型。在绘制一个复杂的光照条件的场景时,可能需要改变每个表项的材质,设置材质是很慢的,如果把材质的定义放在显示列表中,每次变换材质时不需要再次进行计算,只需要存储在显示列表中的结果就行。 (4)       多边形的点画模式 2.2. 显示列表的使用 显示列表的使用的片段,如下所示: listName = glGenList(1); glNewList(listName,GL_COMPILE); glColor3f(1.0f,0.0f,0.0f); glBegin(GL_TRIANGLES); glVertex2f(0.0f,0.0f); glVertex2f(1.0f,0.0f); glVertex2f(0.0f,1.0f); glEnd(); glTranslatef(1.5f,0.0f,0.0f); glEndList(); 至此创建显示列表完成,当需要使用到该显示列表的内容时,调用glCallList()即可。glNewList()命令中可以使用两个参数GL_COMPILE和GL_COMPILE_AND_EXECUTE,GL_COMPILE表示创建显示列表时不立刻执行,GL_COMPILE_AND_EXECUTE表示立刻开始执行,并把显示列表创建起来供以后使用。glGenList (),glNewList(),glEndList(),glCallList()四种方法具体的使用方法,参见相应的文档。 并不是所有的OpenGL命令都能用显示列表存储和执行,例如,一些设置客户状态和查询状态值的命令就不能存储在显示列表中。

spacer