1、多边形填充区
多数图形软件包使用平面片来显示曲面,这是因为平面方程是线性的,而处理线性方程比二次或其他类曲线方程快的多。因此OpenGL和其他图形软件包提供多边形图元来实施曲面逼近,对象用多边形网络来建模,而几何和属性信息的数据库按处理多边形面片的目标来建立。在OpenGL中,可用于此目的的图元有三角形带(triangle strip)、三角形扇形(triangle fan)和四边形带(quad strip)。高性能图形系统使用快速多边形硬件绘制,使得显示速度达到每秒形成百万以上的多边形(通常为三角形),包括使用表面纹理和特殊光照效果。
尽管OpenGL的核心函数库只允许凸多边形,但是OpenGL的实用函数库(GLU)提供有关函数处理凹多边形和其他有线性边界的非凸对象。可使用一组GLU多边形细分子程序来将那些形状转换为三角形、三角形网络、三角形扇形和直线段。一旦那些形状被分解,就可以使用OpenGL函数进行处理。
2、OpenGL顶点数组
复杂场景的描述需要使用几百或者几千个坐标描述。另外还必须为各个对象建立各种属性和观察参数。因此,对象和场景描述要使用大量的函数调用,这对系统资源提出了要求并减慢了图形程序的执行。复杂显示进一步的问题是对象表面通常有共享顶点。
为了简化这些问题,OpenGL提供了一种机制来减少处理坐标信息的函数调用数量,这就是使用顶点数组(vertex array),可以利用很少的函数调用来安排场景的描述信息。
使用vertex array的步骤:
1、引用函数glEnableClientState(GL_VERTEX_ARRAY)激活OpenGL的顶点数组特性;
2、使用函数glVertexPointer指定顶点坐标的位置和数据格式;
3、使用子程序如glDrawElements显示场景,该子程序可处理多个图元而仅需少量的函数调用。
绘制一个立方体的例子:
glLoadIdentity();//将当前的用户坐标系的原点移到了屏幕左下(右x,上y,前z):类似于一个复位操作 glClearColor(0.0, 1.0, 1.0, 0.3);//指定当前清除颜色的颜色值 glClear(GL_COLOR_BUFFER_BIT);//把整个窗口清除为当前的清除颜色 typedef GLint vertex3[3]; vertex3 pt[8] = { {0,0,0},{0,100,0},{100,0,0},{100,100,0},{0,0,100},{0,100,100},{100,0,100},{100,100,100}}; /* *函数名称:glEnableClientState *函数功能:激活客户/服务器系统中的客户端的某种功能(这里指顶点数组) *参数:GLenum:指定的功能代码 *返回值:void */ glEnableClientState( GL_VERTEX_ARRAY ); /* *函数原型:void glVertexPointer( * GLint size,//指定每个顶点对应的坐标维数,只能是2,3,4中的一个,默认是4 * GLenum type,//指定数组中每个顶点坐标的数据格式,可取GL_BYTE、GL_SHORT、GL_FIXED等 * GLsizei stride,//指定连续顶点间的字节排列方式,如果为0,数组中的顶点被认为是按照紧凑方式排列,默认值为0 * const GLvoid * pointer//指向包含坐标值的顶点数组 * ); *函数功能:指定对象顶点坐标的位置和格式 */ glVertexPointer(3, GL_INT, 0, pt); /*顶点坐标的索引数组*/ GLubyte vertexIndex[] = {6,2,3,7,5,1,0,4,7,3,1,5,4,0,2,6,2,0,1,3,7,5,4,6}; glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, vertexIndex); SwapBuffers( ::GetDC(m_hWnd) );
3、像素阵列图元
(1)、位图函数
例子:定义一个10行9列的位图阵列,每行使用16个二进制位来描述,在将图案应用于帧缓存像素时,第9列之后的所有位值均被忽略。每行的位值可以用两个八进制数表示。bitShape的阵列值从矩形网格的底部开始逐行指定,代码如下:
<span style="font-size:14px;">glLoadIdentity();//将当前的用户坐标系的原点移到了屏幕左下(右x,上y,前z):类似于一个复位操作 glClearColor(0.0, 1.0, 1.0, 0.3);//指定当前清除颜色的颜色值 glClear(GL_COLOR_BUFFER_BIT);//把整个窗口清除为当前的清除颜色 //定义位图阵列 GLubyte bitShape[20] ={ 0x1c, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x1c, 0x00 ,0x1c, 0x00, 0xff, 0x80, 0x7f, 0x00, 0x3e, 0x00, 0x1c, 0x00, 0x08, 0x00}; //设置像素存储模式 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); //设置当前光栅位置坐标 //注意:位图的颜色使用glRasterPos被引用时的有效颜色。任何后来的颜色改变不会影响该位图 glRasterPos2i(30, 40); // glBitmap(9, 10, 0.0, 0.0, 20.0, 15.0, bitShape); SwapBuffers( ::GetDC(m_hWnd) );</span>
(2)、像素图函数
函数: glDrawPixel( width, height, dataFormat, dataType, pixMap);
功能:将用彩色阵列定义的图案应用到一块帧缓存的像素位置。
参数width,height分别给出像素位图(pixMap)的列数和行数;参数dataFormat使用OpenGL的常量赋值,指出如何为阵列指定值,例如GL_BLUE可指定所有像素都使用蓝色,使用常量GL_RGB可按蓝、绿、红次序指定颜色分量;参数dataType设定为常量GL_INT、GL_BYTE、GL_FLOAT等,以指定阵列中颜色的数据类型。
注意:该颜色阵列的左下角映射到有glRasterPos设定的当前光栅位置。
例如:显示一个128 * 128 的RGB彩色阵列定义的像素图:
glDrawPixels(128, 128, GL_RGB, GL_UNSIGNED_BYTE, colorShape);
由于OpenGL提供多个缓存,将某个缓存选为glDrawPixels子程序目标即可将一个阵列送进该缓存
(3)、光栅操作
光栅操作(raster operation)用于描述以某种方式处理一个像素阵列的任何功能。
bitblt移动(bit-block transfer):将一个像素阵列的值从一个位置移动到另一个位置的光栅操作,称为像素值的bitblt移动,尤其是在该功能由硬件实现时。
参看:http://blog.csdn.net/ghost129/article/details/4409565
OpenGL提供了简洁的函数来操作像素:
glReadPixels:读取一些像素。当前可以简单理解为“把已经绘制好的像素(它可能已经被保存到显卡的显存中)读取到内存”。
glDrawPixels:绘制一些像素。当前可以简单理解为“把内存中一些数据作为像素数据,进行绘制”。
glCopyPixels:复制一些像素。当前可以简单理解为“把已经绘制好的像素从一个位置复制到另一个位置”。虽然从功能上看,好象等价于先读取像素再绘制像素,但实际上它不需要把已经绘制的像素(它可能已经被保存到显卡的显存中)转换为内存数据,然后再由内存数据进行重新的绘制,所以要比先读取后绘制快很多。
这三个函数可以完成简单的像素读取、绘制和复制任务,但实际上也可以完成更复杂的任务。当前,我们仅讨论一些简单的应用。由于这几个函数的参数数目比较多,下面我们分别介绍。
函数解释:
1*函数原型:glReadPixels(GLint xmin, GLint ymin, GLsizei width, GLsizei height, GLenum dataFormat, GLenum dataType, GLvoid* array);
前四个参数可以得到一个矩形,该矩形所包括的像素都会被读取出来。(第一、二个参数表示了矩形的左下角横、纵坐标,坐标以窗口最左下角为零,最右上角为最大值;第三、四个参数表示了矩形的宽度和高度),第五个参数表示读取的内容,例如:GL_RGB就会依次读取像素的红、绿、蓝三种数据,GL_RGBA则会依次读取像素的红、绿、蓝、alpha四种数据,GL_RED则只读取像素的红色数据(类似的还有GL_GREEN、GL_BLUE,以及GL_ALPHA)。如果采用的不是RGBA颜色模式,而是采用颜色索引模式,则也可以使用GL_COLOR_INDEX来读取像素的颜色索引。目前仅需要知道这些,但实际上还可以读取其它内容,例如深度缓冲区的深度数据等。
第六个参数表示读取的内容保存到内存时所使用的格式,例如:GL_UNSIGNED_BYTE会把各种数据保存为GLubyte,GL_FLOAT会把各种数据保存为GLfloat等。第七个参数表示一个指针,像素数据被读取后,将被保存到这个指针所表示的地址。注意,需要保证该地址有足够的可以使用的空间,以容纳读取的像素数据。例如一幅大小为2568256的图象,如果读取其RGB数据,且每一数据被保存为GLubyte,总大小就是:256*256*3=196608字节,即192千字节。如果是读取RGBA数据,则总大小就是256*256*4=262144字节,即256千字节。
注意:glReadPixels实际上是从缓冲区中读取数据,如果使用了双缓冲区,则默认是从正在显示的缓冲(即前缓冲)中读取,而绘制工作是默认绘制到后缓冲区的。因此,如果需要读取已经绘制好的像素,往往需要先交换前后缓冲。
2*函数原型:glCopyPixels(xmin, ymin, width, height, pixelValues);
从效果上讲,glCopyPixels进行像素复制操作,等价于将像素读入到内存,再从内存绘制到另一块区域,因此可以通过glReasPixels和glDrawPixels组合来实现像素复制的功能,但是像素数据通常数据量很大,例如,一幅1024*768的图像,如果使用24位RGB方式来表示,就需要至少1024*768*3字节,即2.25M字节。这么多的数据要进行一次读操作和一次写操作,并且因为在glReadPixels和glDrawPixels中设置的数据格式不同,很可能涉及到数据格式的转换,这对cpu无疑是一个不小的负担。使用glCopyPixels直接从像素数据复制出新的像素数据,避免了多余的数据格式的转换,并且也可能减少一些数据复制操作(因为数据可能直接由显卡负责复制,不需要经过主内存),因此效率比较高。
第一、二个参数表示待复制像素源的矩形的左下角坐标,第三、四个参数表示矩形的宽度和高度,第五个参数可以是GL_COLOR、GL_DEPTH、GL_STENCIL等,标识要复制的缓存。
绘制一个三角形后,复制像素,并同时进行水平和垂直方向的翻转,然后缩小为原来的一半,并绘制。绘制完毕后,调用前面的grab函数,将屏幕中所有内容保存为grab.bmp。其中WindowWidth和WindowHeight是表示窗口宽度和高度的常量。
void display(void) { // 清除屏幕 glClear(GL_COLOR_BUFFER_BIT); // 绘制 glBegin(GL_TRIANGLES); glColor3f(1.0f, 0.0f, 0.0f); glVertex2f(0.0f, 0.0f); glColor3f(0.0f, 1.0f, 0.0f); glVertex2f(1.0f, 0.0f); glColor3f(0.0f, 0.0f, 1.0f); glVertex2f(0.5f, 1.0f); glEnd(); glPixelZoom(-0.5f, -0.5f); glRasterPos2i(1, 1); glCopyPixels(WindowWidth/2, WindowHeight/2, WindowWidth/2, WindowHeight/2, GL_COLOR); // 完成绘制,并抓取图象保存为BMP文件 glutSwapBuffers(); grab(); }
#define WindowWidth 400 #define WindowHeight 400 #include <stdio.h> #include <stdlib.h> /* 函数grab * 抓取窗口中的像素 * 假设窗口宽度为WindowWidth,高度为WindowHeight */ #define BMP_Header_Length 54 void grab(void) { FILE* pDummyFile; FILE* pWritingFile; GLubyte* pPixelData; GLubyte BMP_Header[BMP_Header_Length]; GLint i, j; GLint PixelDataLength; // 计算像素数据的实际长度 i = WindowWidth * 3; // 得到每一行的像素数据长度 while( i%4 != 0 ) // 补充数据,直到i是的倍数 ++i; // 本来还有更快的算法, // 但这里仅追求直观,对速度没有太高要求 PixelDataLength = i * WindowHeight; // 分配内存和打开文件 pPixelData = (GLubyte*)malloc(PixelDataLength); if( pPixelData == 0 ) exit(0); pDummyFile = fopen("dummy.bmp", "rb"); if( pDummyFile == 0 ) exit(0); pWritingFile = fopen("grab.bmp", "wb"); if( pWritingFile == 0 ) exit(0); // 读取像素 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glReadPixels(0, 0, WindowWidth, WindowHeight, GL_BGR_EXT, GL_UNSIGNED_BYTE, pPixelData); // 把dummy.bmp的文件头复制为新文件的文件头 fread(BMP_Header, sizeof(BMP_Header), 1, pDummyFile); fwrite(BMP_Header, sizeof(BMP_Header), 1, pWritingFile); fseek(pWritingFile, 0x0012, SEEK_SET); i = WindowWidth; j = WindowHeight; fwrite(&i, sizeof(i), 1, pWritingFile); fwrite(&j, sizeof(j), 1, pWritingFile); // 写入像素数据 fseek(pWritingFile, 0, SEEK_END); fwrite(pPixelData, PixelDataLength, 1, pWritingFile); // 释放内存和关闭文件 fclose(pDummyFile); fclose(pWritingFile); free(pPixelData); }
4、字符
存储在计算机中的字体分为两种:位图字体(bitmap font)也称光栅字体(raster font),另一种字体是轮廓字体(outline font)或称笔画字体(stroke font)。
OpenGL字符函数
glutBitmapCharacter(font, character);
glutStrokeCharacter(font, character);
5、OpenGL显示列表
在OpenGL中使用显示表,可以把对象描述为一个命名的语句序列(或其他的命令集)并存储起来。显示表对层次式建模特别有用,因为一个复杂的对象可以用一组简单的对象来描述。
(1)、创建和命名显示表
使用glNewList和glEndList函数对包围一组OpenGL命令就可形成显示表。例如:
glNewList( listID, listMode);
.....
glEndList();
注意:显示表创建后,立即对包含有如坐标位置和颜色分量等参数的表示进行赋值计算,从而使表中仅存储参数的值,因此对这些参数的任何后继修改都不起作用。因为不能修改显示表中的值,因此在显示表中不能包含如顶点表指针等OpenGL命令。
(2)、执行显示表
调用glCallList( listID );
(3)、删除显示表
调用glDeleteLists( startID, nLists);//删除连续的一组显示表。
例子:创建并执行一组显示表
glLoadIdentity();//将当前的用户坐标系的原点移到了屏幕左下(右x,上y,前z):类似于一个复位操作 glClearColor(0.0, 1.0, 1.0, 0.3);//指定当前清除颜色的颜色值 glClear(GL_COLOR_BUFFER_BIT);//把整个窗口清除为当前的清除颜色 const double TWO_PI = 6.2831853; GLuint regHex; GLdouble theta; GLint x, y, k; //产生一个显示表ID(正整数) if(regHex = glGenLists( 1 ) == 0) return ; glNewList(regHex, GL_COMPILE); glBegin( GL_POLYGON ); for( k=0; k<6; k++) { theta = TWO_PI * k /6.0; x = 200 + 150 * cos(theta); y = 200 + 150 * sin(theta); glVertex2i(x, y); } glEnd(); glEndList(); //执行显示表 glCallList( regHex ); SwapBuffers( ::GetDC(m_hWnd) );