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) );