显示列表
显示列表一组已经存储(编译)的OpenGL命令,以供后续执行。一旦显示列表创建出来,所有顶点与像素数据都被赋值且拷贝到服务器段的显示列表内存。这是一次性过程。在显示列表配备(编译)好后,你可以重复使用它,而不需要每帧都重新赋值与重新反复传递数据。显示列表是绘制静态数据的最快方式之一,这是因为顶点数据与OpenGL命令被存储在显示列表中并且最小化客户端到服务器段的数据传输。也就是说,它降低CPU处理实际数据传输的周期。
显示列表的另一个重要性能是:由于显示列表是服务端数据,它可以在多个客户端共享。
为了性能优化,可以将矩阵变换、关照与材质计算尽可能地放在显示列表中,因此,OpenGL仅在显示列表创建时进行一次这些耗时的计算并将最终结果保存在显示列表中。
然而,显示列表具有一点不足。显示列表编译后就不能再修改。如果你需要频繁更改或动态数据集,使用顶点数组或顶点缓存对象。顶点缓存对象可以处理静态与动态数据集。
注意,并不是所有OpenGL命令都能够存放在显示列表中。因为,显示列表是服务端状态的一部分,任何客户端状态相关命令都不能存放在显示列表中,如:glFlush()、glFinish()、glRenderMode()、glEnableClientState()、glVertexPointer()等。任何具有返回值的命令也不能够存放在显示列表中,因为这些值将返回给客户端而不在显示列表中,如:glIsEnabled()、glGet*()、glReadPixels()、glFeedbackBuffer()等。如果这些命令在显示列表中调用,它们将立即执行。
实现
使用显示列表非常容易。首先,使用glGenLists()创建一个或多个新显示列表对象。参数为待创建列表数,然后它返回连续显示列表块的起始位置索引。如,glGenLists()返回1且你需要的显示列表数位3,则索引1、2与3都是有效的。如果OpenGL创建新显示列表失败,返回0。如果显示列表不再使用时,它可以通过glDeleteLists()删除。
其次,你需要在渲染前,在glNewList()与glEndList()块间将OpenGL命令保存到显示列表中。这个过程叫做编译。glNewList()需要2个参数。第一个参数为由glGenLists()返回的显示列表索引。第二个参数为mode,它指定仅仅进行编译还是同时运行(渲染):GL_COMPILE、GL_COMPILE_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);
注意,只有在glNewList()与glEndList()之间的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); // 编译第10个 ... glEndList(); ... // 仅绘制基数显示列表(1st, 3rd, 5th, 7th, 9th) 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,则实际被glCallLists()渲染的显示列表为3+0、3+2、3+4、3+6、3+8。
glCallLists()对于以字体中ASCI相关的偏移值的方式显示文本非常有用。
实例
该程序绘制使用显示列表绘制一个茶壶(6320个三角形)。最初它使用顶点数组glDrawElements()来绘制该茶壶,不过所有的glDrawElements()都存放在显示列表中。你可以看到使用顶点数组与使用显示列表的性能不同。
记住:你不能够在显示列表中存放任何客户端状态命令,因此glEnableClientState()、glVertexPointer()与glNormalPointer()不能够保存在显示列表中。