在前面的章节中,OpenGL命令一旦给出就立即执行,这种方式称为即时模式(immediate mode),当存在大量的运算特别是矩阵运算时,应用程序的性能会受到一定程度的影响。为了解决这个问题,提高程序的运行效率,也为了方面使用,OpenGL提供了显示列表。
显示列表是为了方便使用和提高效率的目的而组成的一组OpenGL命令,这些命令先以显示列表的方式保存起来,在以后需要的时候一次调用实现全部功能。显示列表就象函数一样,一次定义,多次使用,简化了代码。
当显示列表被调用时,显示列表中的OpenGL命令就按照存储的顺序执行。通常使用显示列表的运行速度比即时模式绘图要快两倍,因为显示列表的设计目的就是为了优化性能和图形显示。
显示列表从本质上讲是一种命令集的缓存,不是动态数据库,因此它的内容是一次性创建,并且不可改变。对于一系列的几何变换,显示列表保存的是最后一个结果矩阵,避免在调用显示列表时再计算中间过程。如果允许修改显示列表,也就无法保证显示列表唯一的结果,这样也就脱离了显示列表的设计初衷即提高效率,简化运算。
显示列表存储的命令也会根据机器中的具体硬件进行优化。在一个网络中,显示列表通常驻留在服务器上,这样可以利用服务器的资源,降低客户端的开销。相对来说,显示列表的效率提高在网络环境中表现更好,而一个完全在本地机器上运行的显示列表对效率的提高则不是很明显。
由于对显示列表的管理也需要消耗资源,因此如果显示列表太小,其本身的开销反而较大,此时就不适合使用显示列表。显示列表通常用在以下场合:
- 矩阵操作
- 光栅位图和图像
- 光、材质和光照模型
- 纹理
- 多边形的图案填充模式
1.1 创建显示列表
OpenGL提供glNewList和glEndList来定义一个列表,类似glBegin和glEnd。两个函数的原型分别是:
void glNewList(
GLuint list,
GLenum mode
);
void glEndList();
其中list是显示列表的名字,用一个正的整数表示,每一个显示列表应该有唯一的list值。mode表示命令的编译模式,取值为GL_COMPILE或GL_COMPILE_AND_EXECUTE,前者表示命令存储进列表时仅仅作一次编译,后者表示编译进入列表时还要按照即时模式执行一次。
有些OpenGL的命令不能够编译进显示列表,但是可以应用即时模式执行,这些命令是:
glColorPointer
glDeleteLists
glDisableClientState
glEdgeFlagPointer
glEnableClientState
glFeedbackBuffer
glFinish
glFlush
glGenLists
glIndexPointer
glInterleavedArrays
glIsEnabled
glIsList
glNormalPointer
glPopClientAttrib
glPixelStore
glPushClientAttrib
glReadPixels
glRenderMode
glSelectBuffer
glTexCoordPointer
glVertexPointer
glGetBooleanv
glGetDoublev
glGetFloatv
glGetIntegerv
当glTexImage2D()和glTexImage1D()的第一个参数取值分别为GL_PROXY_TEXTURE_2D和GL_PROXY_TEXTURE_1D时也只能采用即时模式执行,不能存在于显示列表中。
如果list值已经被使用,glBeginList()不会给出任何警告,它所作的就是以当前的显示列表替代前面同名的显示列表,旧的显示列表作废。
显示列表的执行命令是glCallList()和glCallLists(),两个函数的原型如下:
void glCallList(
GLuint list
);
void glCallLists(
GLsizei n,
GLenum type,
const GLvoid *lists
);
glCallList()一次只调用一个显示列表,list是显示列表的名字。glCallLists()则一次调用一组显示列表,其中n是要执行的显示列表的数目,type是显示列表lists取值的类型,lists是显示列表数组,因为其取值可以有多种类型,所以定义为GLvoid*,它的实际取值取决于type的取值。type的取值说明如下表6-1所示。
表6-1 glCallLists函数中type取值含义
取值 | 含义 |
GL_BYTE | lists是一个带符号的字节数组,每个元素取值范围为–128~127。 |
GL_UNSIGNED_BYTE | lists每个元素是无符号字节,取值范围为0~ 255. |
GL_SHORT | lists每个元素是两个字节的整数,取值范围为–32768~32767。 |
GL_UNSIGNED_SHORT | lists每个元素是两个字节的无符号整数,取值范围为0~65535。 |
GL_INT | lists每个元素是四个字节的整数,取值范围为-2^31~2^31-1。 |
GL_UNSIGNED_INT | lists每个元素是四个字节的无符号整数,取值范围为0~2^32-1。 |
GL_FLOAT | lists每个元素是四个字节的浮点数 |
GL_2_BYTES | lists每个元素是无符号字节数,其中每一对字节表示一个显示列表的名字,显示列表的值是第一个字节乘以256 加上第二个字节,取值范围为0~65535。 |
GL_3_BYTES | lists每个元素是无符号字节数,其中每三个字节表示一个显示列表的名字,显示列表的值是第一个字节乘以65535加上第二个字节乘以256再加上第三个字节,取值范围为0~16777215。 |
GL_4_BYTES | lists每个元素是无符号字节数,其中每四个字节表示一个显示列表的名字,显示列表的值是第一个字节乘以16777216加上第二个字节乘以65535加上第三个字节乘以256再加上第四个字节,取值范围为0~ 4294967295(2^32-1)。 |
根据前面所述,我们来创建一个显示列表,绘制10个围绕在圆周上的立方体,并且贴上纹理。在CreateLists()中,CUBELIST是我们的列表名字,在文件头使用#define CUBELIST 1来定义。在这个显示列表中,我们使用了glPushMatrix()和glPopMatrix()来操作变换矩阵,其中glPushMatrix()把当前变换矩阵压入矩阵堆栈,glPopMatrix()将矩阵堆栈中最上面的矩阵作为当前变换矩阵使用。DrawCube()不变,仍然是绘制一个带纹理的立方体。下面是CreateLists()的代码:
void CreateLists()
{
glNewList(CUBELIST, GL_COMPILE); //生成一个新的显示列表
for(int i=0; i<10; i++)
{
glPushMatrix() ;
glRotatef(36*i,0.0,0.0,1.0) ;
glTranslatef(10.0,0.0,0.0) ; //向外平移10个单位
DrawCube(); //绘制立方体
glPopMatrix();
}
glEndList();
}
显示列表需要在OpenGL初始化时创建,因此CreateLists()在glInit()中调用。
int glInit()
{
// 启用纹理映射
glEnable(GL_TEXTURE_2D);
//启用阴影平滑(Smooth Shading)。
glShadeModel(GL_SMOOTH);
//设置背景颜色
glClearColor(0.5f,0.5f,0.5f,0.0f);
//设置深度缓冲
glClearDepth(1.0f);
//启动深度测试
glEnable(GL_DEPTH_TEST);
//深度测试的类型
glDepthFunc(GL_LEQUAL);
//进行透视修正
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
//以baby.bmp文件创建一个纹理
g_Texture[0] = CreateTexture("baby.bmp");
if(!g_Texture[0]) //创建失败则返回
{
MessageBox(g_hWnd, "创建纹理失败!", "提示", MB_OK);
return FALSE;
}
CreateLists(); //创建显示列表
return TRUE;
}
执行显示列表很简单,只需要调用glCallList(CUBELIST)即可。
void glMain()
{
static int angle =0;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glTranslatef(0.0f, 0.0f, -40.0f); //将场景推远一些
glRotated(angle, 0, 0, 1); //整个场景绕Z轴旋转
glBindTexture(GL_TEXTURE_2D, g_Texture[0]); // 选择纹理
//调用执行显示列表
glCallList(CUBELIST);
angle++;
SwapBuffers(g_hDC);//交换前后缓冲区
}
程序运行的结果是显示10个带有纹理的立方体,如摩天轮般旋转,效果如图6-1所示。
图6-1 显示列表