OpenGL------显示列表

咱们已经知道,使用OpenGL其实只要调用一系列的OpenGL函数就能够了。然而,这种方式在一些时候可能致使问题。好比某个画面中,使用了数千个多边形来表现一个比较真实的人物,OpenGL为了产生这数千个多边形,就须要不停的调用glVertex*函数,每个多边形将至少调用三次(由于多边形至少有三个顶点),因而绘制一个比较真实的人物就须要调用上万次的glVertex*函数。更糟糕的是,若是咱们须要每秒钟绘制60幅画面,则每秒调用的glVertex*函数次数就会超过数十万次,乃至接近百万次。这样的状况是咱们所不肯意看到的。
同时,考虑这样一段代码:算法

const int segments = 100;
const GLfloat pi = 3.14f;
int i;
glLineWidth(10.0);
glBegin(GL_LINE_LOOP);
for(i=0; i<segments; ++i)
{
     GLfloat tmp = 2 * pi * i / segments;
     glVertex2f(cos(tmp), sin(tmp));
}
glEnd();

这段代码将绘制一个圆环。若是咱们在每次绘制图象时调用这段代码,则虽然能够达到绘制圆环的目的,可是cos、sin等开销较大的函数被屡次调用,浪费了CPU资源。若是每个顶点不是经过cos、sin等函数获得,而是使用更复杂的运算方式来获得,则浪费的现象就更加明显。

通过分析,咱们能够发现上述两个问题的共同点:程序屡次执行了重复的工做,致使CPU资源浪费和运行速度的降低。使用显示列表能够较好的解决上述两个问题。
在编写程序时,遇到重复的工做,咱们每每是将重复的工做编写为函数,在须要的地方调用它。相似的,在编写OpenGL程序时,遇到重复的工做,能够建立一个显示列表,把重复的工做装入其中,并在须要的地方调用这个显示列表。
使用显示列表通常有四个步骤:分配显示列表编号、建立显示列表、调用显示列表、销毁显示列表。

1、分配显示列表编号
OpenGL容许多个显示列表同时存在,就好象C语言容许程序中有多个函数同时存在。C语言中,不一样的函数用不一样的名字来区分,而在OpenGL中,不一样的显示列表用不一样的正整数来区分。
你能够本身指定一些各不相同的正整数来表示不一样的显示列表。可是若是你不够当心,可能出现一个显示列表将另外一个显示列表覆盖的状况。为了不这一问题,使用glGenLists函数来自动分配一个没有使用的显示列表编号。
glGenLists函数有一个参数i,表示要分配i个连续的未使用的显示列表编号。返回的是分配的若干连续编号中最小的一个。例如,glGenLists(3);若是返回20,则表示分配了20、2一、22这三个连续的编号。若是函数返回零,表示分配失败。
可使用glIsList函数判断一个编号是否已经被用做显示列表。

2、建立显示列表
建立显示列表实际上就是把各类OpenGL函数的调用装入到显示列表中。使用glNewList开始装入,使用glEndList结束装入。glNewList有两个参数,第一个参数是一个正整数表示装入到哪一个显示列表。第二个参数有两种取值,若是为GL_COMPILE,则表示如下的内容只是装入到显示列表,但如今不执行它们;若是为GL_COMPILE_AND_EXECUTE,表示在装入的同时,把装入的内容执行一遍。
例如,须要把“设置颜色为红色,而且指定一个坐标为(0, 0)的顶点”这两条命令装入到编号为list的显示列表中,而且在装入的时候不执行,则能够用下面的代码:编程

glNewList(list, GL_COMPILE);
glColor3f(1.0f, 0.0f, 0.0f);
glVertex2f(0.0f, 0.0f);
glEnd();

//注意:显示列表只能装入OpenGL函数,而不能装入其它内容。例如:
int i = 3;
glNewList(list, GL_COMPILE);
if( i > 20 )
     glColor3f(1.0f, 0.0f, 0.0f);
glVertex2f(0.0f, 0.0f);
glEnd();

其中if这个判断就没有被装入到显示列表。之后即便修改i的值,使i>20的条件成立,则glColor3f这个函数也不会被执行。由于它根本就不存在于显示列表中。

另外,并不是全部的OpenGL函数均可以装入到显示列表中。例如,各类用于查询的函数,它们没法被装入到显示列表,由于它们都具备返回值,而glCallList和glCallLists函数都不知道如何处理这些返回值。在网络方式下,设置客户端状态的函数也没法被装入到显示列表,这是由于显示列表被保存到服务器端,各类设置客户端状态的函数在发送到服务器端之前就被执行了,而服务器端没法执行这些函数。分配、建立、删除显示列表的动做也没法被装入到另外一个显示列表,但调用显示列表的动做则能够被装入到另外一个显示列表。服务器

3、调用显示列表
使用glCallList函数能够调用一个显示列表。该函数有一个参数,表示要调用的显示列表的编号。例如,要调用编号为10的显示列表,直接使用glCallList(10);就能够了。
使用glCallLists函数能够调用一系列的显示列表。该函数有三个参数,第一个参数表示了要调用多少个显示列表。第二个参数表示了这些显示列表的编号的储存格式,能够是GL_BYTE(每一个编号用一个GLbyte表示),GL_UNSIGNED_BYTE(每一个编号用一个GLubyte表示),GL_SHORT,GL_UNSIGNED_SHORT,GL_INT,GL_UNSIGNED_INT,GL_FLOAT。第三个参数表示了这些显示列表的编号所在的位置。在使用该函数前,须要用glListBase函数来设置一个偏移量。假设偏移量为k,且glCallLists中要求调用的显示列表编号依次为l1, l2, l3, ...,则实际调用的显示列表为l1+k, l2+k, l3+k, ...。
例如:网络

GLuint lists[] = {1, 3, 4, 8};
glListBase(10);
glCallLists(4, GL_UNSIGNED_INT, lists);

则实际上调用的是编号为11, 13, 14, 18的四个显示列表。
注:“调用显示列表”这个动做自己也能够被装在另外一个显示列表中。函数

 4、销毁显示列表
销毁显示列表能够回收资源。使用glDeleteLists来销毁一串编号连续的显示列表。
例如,使用glDeleteLists(20, 4);将销毁20,21,22,23这四个显示列表。
使用显示列表将会带来一些开销,例如,把各类动做保存到显示列表中会占用必定数量的内存资源。但若是使用得当,显示列表能够提高程序的性能。这主要表如今如下方面:
一、明显的减小OpenGL函数的调用次数。若是函数调用是经过网络进行的(Linux等操做系统支持这样的方式,即由应用程序在客户端发出OpenGL请求,由网络上的另外一台服务器进行实际的绘图操做),将显示列表保存在服务器端,能够大大减小网络负担。
二、保存中间结果,避免一些没必要要的计算。例如前面的样例程序中,cos、sin函数的计算结果被直接保存到显示列表中,之后使用时就没必要重复计算。
三、便于优化。咱们已经知道,使用glTranslate*、glRotate*、glScale*等函数时,其实是执行矩阵乘法操做,因为这些函数常常被组合在一块儿使用,一般会出现矩阵的连乘。这时,若是把这些操做保存到显示列表中,则一些复杂的OpenGL版本会尝试先计算出连乘的一部分结果,从而提升程序的运行速度。在其它方面也可能存在相似的例子。
同时,显示列表也为程序的设计带来方便。咱们在设置一些属性时,常常把一些相关的函数放在一块儿调用,(好比,把设置光源的各类属性的函数放到一块儿)这时,若是把这些设置属性的操做装入到显示列表中,则能够实现属性的成组的切换。
固然了,即便使用显示列表在某些状况下能够提升性能,但这种提升极可能并不明显。毕竟,在硬件配置和大体的软件算法都不变的前提下,性能可提高的空间并不大。
显示列表的内容就是这么多了,下面咱们看一个例子。oop


假设咱们须要绘制一个旋转的彩色正四面体,则能够这样考虑:设置一个全局变量angle,而后让它的值不断的增长(到达360后又恢复为0,周而复始)。每次须要绘制图形时,根据angle的值进行旋转,而后绘制正四面体。这里正四面体采用显示列表来实现,即把绘制正四面体的若干OpenGL函数装到一个显示列表中,而后每次须要绘制时,调用这个显示列表便可。
将正四面体的四个顶点颜色分别设置为红、黄、绿、蓝,经过数学计算,将坐标设置为:性能

(-0.5, -5*sqrt(5)/48,   sqrt(3)/6),
( 0.5, -5*sqrt(5)/48,   sqrt(3)/6),
(    0, -5*sqrt(5)/48, -sqrt(3)/3),
(    0, 11*sqrt(6)/48,           0)


2007年4月24日修正:以上结果有误,经过计算AB, AC, AD, BC, BD, CD的长度,发现AD, BD, CD的长度与1.0有较大误差。正确的坐标应该是:优化

 

   A点:(   0.5,    -sqrt(6)/12, -sqrt(3)/6)
    B点:( -0.5,    -sqrt(6)/12, -sqrt(3)/6)
    C点:(     0,    -sqrt(6)/12,   sqrt(3)/3)
    D点:(     0,     sqrt(6)/4,            0)


    程序代码中也作了相应的修改

下面给出程序代码,你们能够从中体会一下显示列表的用法。ui

#include <gl/glut.h>

#define WIDTH 400
#define HEIGHT 400

#include <math.h>
#define ColoredVertex(c, v) do{ glColor3fv(c); glVertex3fv(v); }while(0)

GLfloat angle = 0.0f;

void myDisplay(void)
{
     static int list = 0;
     if( list == 0 )
     {
         // 若是显示列表不存在,则建立
        /* GLfloat
             PointA[] = {-0.5, -5*sqrt(5)/48,   sqrt(3)/6},
             PointB[] = { 0.5, -5*sqrt(5)/48,   sqrt(3)/6},
             PointC[] = {    0, -5*sqrt(5)/48, -sqrt(3)/3},
             PointD[] = {    0, 11*sqrt(6)/48,           0}; */
        // 2007年4月27日修改
         GLfloat
             PointA[] = { 0.5f, -sqrt(6.0f)/12, -sqrt(3.0f)/6},
             PointB[] = {-0.5f, -sqrt(6.0f)/12, -sqrt(3.0f)/6},
             PointC[] = { 0.0f, -sqrt(6.0f)/12,   sqrt(3.0f)/3},
             PointD[] = { 0.0f,    sqrt(6.0f)/4,              0};
         GLfloat
             ColorR[] = {1, 0, 0},
             ColorG[] = {0, 1, 0},
             ColorB[] = {0, 0, 1},
             ColorY[] = {1, 1, 0};

         list = glGenLists(1);
         glNewList(list, GL_COMPILE);
         glBegin(GL_TRIANGLES);
         // 平面ABC
         ColoredVertex(ColorR, PointA);
         ColoredVertex(ColorG, PointB);
         ColoredVertex(ColorB, PointC);
         // 平面ACD
         ColoredVertex(ColorR, PointA);
         ColoredVertex(ColorB, PointC);
         ColoredVertex(ColorY, PointD);
         // 平面CBD
         ColoredVertex(ColorB, PointC);
         ColoredVertex(ColorG, PointB);
         ColoredVertex(ColorY, PointD);
         // 平面BAD
         ColoredVertex(ColorG, PointB);
         ColoredVertex(ColorR, PointA);
         ColoredVertex(ColorY, PointD);
         glEnd();
         glEndList();

         glEnable(GL_DEPTH_TEST);
     }
     // 已经建立了显示列表,在每次绘制正四面体时将调用它
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     glPushMatrix();
     glRotatef(angle, 1, 0.5, 0);
     glCallList(list);
     glPopMatrix();
     glutSwapBuffers();
}

void myIdle(void)
{
     ++angle;
     if( angle >= 360.0f )
         angle = 0.0f;
     myDisplay();
}

int main(int argc, char* argv[])
{
     glutInit(&argc, argv);
     glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
     glutInitWindowPosition(200, 200);
     glutInitWindowSize(WIDTH, HEIGHT);
     glutCreateWindow("OpenGL 窗口");
     glutDisplayFunc(&myDisplay);
     glutIdleFunc(&myIdle);
     glutMainLoop();
     return 0;
}

在程序中,咱们将绘制正四面体的OpenGL函数装到了一个显示列表中,可是,关于旋转的操做却在显示列表以外进行。这是由于若是把旋转的操做也装入到显示列表,则每次旋转的角度都是同样的,不会随着angle的值的变化而变化,因而就不能表现出动态的旋转效果了。
程序运行时,可能感受到画面的立体感不足,这主要是由于没有使用光照的缘故。若是将glColor3fv函数去掉,改成设置各类材质,而后开启光照效果,则能够产生更好的立体感。你们能够本身试着使用光照效果,惟一须要注意的地方就是法线向量的计算。因为这里的正四面体四个顶点坐标选取得比较特殊,使得正四面体的中心坐标正好是(0, 0, 0),所以,每三个顶点坐标的平均值正好就是这三个顶点所组成的平面的法线向量的值。spa

void setNormal(GLfloat* Point1, GLfloat* Point2, GLfloat* Point3)
{
     GLfloat normal[3];
     int i;
     for(i=0; i<3; ++i)
         normal[i] = (Point1[i]+Point2[i]+Point3[i]) / 3;
     glNormal3fv(normal);
}

小结本课介绍了显示列表的知识和简单的应用。能够把各类OpenGL函数调用的动做装到显示列表中,之后调用显示列表,就至关于调用了其中的OpenGL函数。显示列表中除了存放对OpenGL函数的调用外,不会存放其它内容。使用显示列表的过程是:分配一个未使用的显示列表编号,把OpenGL函数调用装入显示列表,调用显示列表,销毁显示列表。使用显示列表有可能带来程序运行速度的提高,可是这种提高并不必定会很明显。显示列表自己也存在必定的开销。把绘制固定的物体的OpenGL函数放到一个显示列表中,是一种不错的编程思路。本课最后的例子中使用了这种思路。

相关文章
相关标签/搜索