OpenGL系列教程之七:OpenGL显示列表

相关主题:顶点缓冲区对象(VBO)

下载:displayList.zip

  • 实现
  • 例子

显示列表是一组被存储或编译的用来以后执行的OpenGL命令的集合。当一个显示列表被创建以后,所有的顶点数据和像素数据被复制到位于服务器端的显示列表内存中。这个过程只进行一次。当显示列表准备好(被编译完成)后,你可以重复使用它而不需要在每帧中重复地传输这些数据。显示列表是最快的一种绘制静态数据的方式,因为顶点数据和OpenGL命令被缓冲在服务器端的显示列表中,这样减少了从客户端到服务器段的数据传输。这意味着减少了执行实际的数据传输的CPU周期。

显示列表另一个重要的功能是显示列表可以被多个客户端所共享,因为它是服务器端的状态。

为了最佳的性能,将矩阵变换,光照,材质计算尽可能地放在显示列表中,这样OpenGL将只会在显示列表创建时执行一次这些高昂开销的计算,并将最终的结果存储在显示列表中。

然而,显示列表有一个缺点。当一个显示列表被编译后,它不能被改变。如果你需要频繁地改变数据或需要动态的数据,使用顶点数组或顶点缓冲区对象。顶点缓冲区对象可以同时处理静态和动态的数据。

注意并不是所有的OpenGL命令都可以存储在显示列表中。由于显示列表是服务器端的状态,所有与客户端状态相关的命令不能被放置在显示列表中。例如:glFlush(),glFinish(),glRenderMode(),glEnableClientState(),glVertexPointer()等这些函数不能放在显示列表中。并且有返回值的OpenGL命令也不能放在显示列表中,因为这些返回值需要被返回到客户端,而不是显示列表中,例如:glIsEnabled(),geGet*(),glReadPixels(),glFeedbackBuffer()等。如果这些命令存储在显示列表中,它们将会被立即执行。



实现

使用显示列表非常简单。首先是使用glGenLists()函数创建一个或多个显示列表对象,它的参数是需要创建的显示列表的数目,它返回连续的显示列表块中的第一个元素的索引。例如,glGenLists()函数如果返回1并且你创建的显示列表的数目为3的话,那么索引1,2,3是可用的。如果OpenGL创建显示列表失败,它将会返回0。如果你不再使用显示列表,可以使用glDeleteLists()删除它们。

第二步,你需要在glNewList()和glEndList()块之间存储OpenGL命令到显示列表中来优先渲染。这个过程叫做编译。glNewList()需要两个参数,第一个参数是glGenLists()函数返回的索引值,第二个参数是指定模式:是只编译还是编译并执行,GL_COMPLILE,GL_COMPLILE_AND_EXECUTE。

到现在为止,准备工作已经做好了。只需要在每帧中使用glCallList()函数或glCallLists()函数来执行显示列表就可以了。

看一下下面这个简单的显示列表的例子:

// 创建一个显示列表对象
GLuint index = glGenLists(1);

// 编译显示列表, 存储一个三角形
glNewList(index, GL_COMPILE);
    glBegin(GL_TRIANGLES);
    glVertex3fv(v0);
    glVertex3fv(v1);
    glVertex3fv(v2);
    glEnd();
glEndList();
...

// 绘制显示列表
glCallList(index);
...

// 如果不再需要这个显示列表,删除它
glDeleteLists(index, 1);

注意只有在glNewLists()和glEndLists()之间的OpenGL命令才会被记录到显示列表中一次,并在每次调用glCallList()时缓存一次。

为了使用glCallLists()执行多个显示列表,你需要做一个额外的工作。你需要使用一个数组来存放那些需要被渲染的显示列表的索引。换句话说,你可以选择渲染所有显示列表中的部分显示列表。glCallLists()需要3个参数:需要绘制的显示列表的个数,索引数组的数据类型,指向索引数组的指针。

注意你可以不需要指定显示列表的准确索引到数组中,你可以指定它们的偏移量,然后使用glListBase()设置基准的显示列表的位置。当glCallLists()被调用时,实际的索引值会通过将基准的位置与偏移量想加得到。下面的例子显示了如何使用glCallLists()以及一些说明:

GLuint index = glGenLists(10);  // 创建10个显示列表
GLubyte lists[10];              // 最多允许渲染10个显示列表

glNewList(index, GL_COMPILE);   // 编译第一个显示列表
...
glEndList();

...// 编译中间的显示列表

glNewList(index+9, GL_COMPILE); // 编译最后一个显示列表
...
glEndList();
...

// 只绘制部分显示列表 (第1个,第3个,第5个,第7个,第9个)
lists[0]=0; lists[1]=2; lists[2]=4; lists[3]=6; lists[4]=8;
glListBase(index);              // 设置基准的位置
glCallLists(5, GL_UNSIGNED_BYTE, lists);

OpenGL为了方便起见提供了glListBase()函数指定当glCallLists()函数执行时添加到显示列表索引的基准。

在上面的例子中,只有5个显示列表会被渲染:第1个,第3个,第5个,第7个,第9个。因此实际的偏移是index+0,index+2,index+4,index+6,index+8,这些偏移会被存储在索引数组中方便后面的渲染。如果glGenLists()函数的返回值是3,那么背渲染的显示列表的实际的索引是:3+0,3+2,3+4,3+6,和3+8。

glCallLists()显示使用字体中相对应的ASCII值做偏移的文字时非常有用。



例子



这个例子使用了显示列表绘制了一个茶壶(6320个多边形)。最开始它使用顶点数组,glDrwaElements(0来渲染茶壶,但是所有的glDrawElements()调用都是放置在显示列表中。你可以看到使用显示列表和顶点数组在性能上的差异。

注意不能放置所有与客户端状态相关的OpenGL命令在显示列表中,glEnableClientState(),glVertexPointer(),和glNormalPointer()不应该被仿制在显示列表中。

下载源文件和可执行文件:displayList.zip

你可能感兴趣的:(OpenGL系列教程之七:OpenGL显示列表)