第12课 OpenGL 显示列表

显示列表:

想知道如何加速你的OpenGL程序么?这一课将告诉你如何使用OpenGL的显示列表,它通过预编译OpenGL命令来加速你的程序,并可以为你省去很多重复的代码。

这次我将教你如何使用显示列表,显示列表将加快程序的速度,而且可以减少代码的长度。

当你在制作游戏里的小行星场景时,每一层上至少需要两个行星,你可以用OpenGL中的多边形来构造每一个行星。聪明点的做法是做一个循环,每个循环画出行星的一个面,最终你用几十条语句画出了一个行星。每次把行星画到屏幕上都是很困难的。当你面临更复杂的物体时你就会明白了。

那么,解决的办法是什么呢?用现实列表,你只需要一次性建立物体,你可以贴图,用颜色,想怎么弄就怎么弄。给现实列表一个名字,比如给小行星的显示列表命名为“asteroid”。现在,任何时候我想在屏幕上画出行星,我只需要调用glCallList(asteroid)。之前做好的小行星就会立刻显示在屏幕上了。因为小行星已经在显示列表里建造好了,OpenGL不会再计算如何构造它。它已经在内存中建造好了。这将大大降低CPU的使用,让你的程序跑的更快。

那么,开始学习咯。我称这个DEMO为Q-Bert显示列表。最终这个DEMO将在屏幕上画出15个立方体。每个立方体都由一个盒子和一个顶部构成,顶部是一个单独的显示列表,盒子没有顶。

这一课是建立在第六课的基础上的,我将重写大部分的代码,这样容易看懂。下面的这些代码在所有的课程中差不多都用到了。

下面设置变量。首先是存储纹理的变量,然后两个新的变量用于显示列表。这些变量是指向内存中显示列表的指针。命名为box和top。

然后用两个变量xloop,yloop表示屏幕上立方体的位置,两个变量xrot,yrot表示立方体的旋转。

GLuint    box;                        // 保存盒子的显示列表
GLuint    top;                        // 保存盒子顶部的显示列表
GLuint    xloop;                        // X轴循环变量
GLuint    yloop;                        // Y轴循环变量

接下来建立两个颜色数组

static GLfloat boxcol[5][3]=                // 盒子的颜色数组
{
    // 亮:红,橙,黄,绿,蓝
    {1.0f,0.0f,0.0f},{1.0f,0.5f,0.0f},{1.0f,1.0f,0.0f},{0.0f,1.0f,0.0f},{0.0f,1.0f,1.0f}
};

static GLfloat topcol[5][3]=                // 顶部的颜色数组
{
    // 暗:红,橙,黄,绿,蓝
    {.5f,0.0f,0.0f},{0.5f,0.25f,0.0f},{0.5f,0.5f,0.0f},{0.0f,0.5f,0.0f},{0.0f,0.5f,0.5f}
};

现在正式开始建立显示列表。你可能注意到了,所有创造盒子的代码都在第一个显示列表里,所有创造顶部的代码都在另一个列表里。我会努力解释这些细节。

GLvoid BuildLists()                    // 创建盒子的显示列表
{

开始的时候我们告诉OpenGL我们要建立两个显示列表。glGenLists(2)建立了两个显示列表的空间,并返回第一个显示列表的指针。“box”指向第一个显示列表,任何时候调用“box”第一个显示列表就会显示出来。

    box=glGenLists(2);                // 创建两个显示列表的名称

现在开始构造第一个显示列表。我们已经申请了两个显示列表的空间了,并且有box指针指向第一个显示列表。所以现在我们应该告诉OpenGL要建立什么类型的显示列表。

我们用glNewList()命令来做这个事情。你一定注意到了box是第一个参数,这表示OpenGL将把列表存储到box所指向的内存空间。第二个参数GL_COMPILE告诉OpenGL我们想预先在内存中构造这个列表,这样每次画的时候就不必重新计算怎么构造物体了。

GL_COMPILE类似于编程。在你写程序的时候,把它装载到编译器里,你每次运行程序都需要重新编译。而如果他已经编译成了.exe文件,那么每次你只需要点击那个.exe文件就可以运行它了,不需要编译。当OpenGL编译过显示列表后,就不需要再每次显示的时候重新编译它了。这就是为什么用显示列表可以加快速度。

    glNewList(box,GL_COMPILE);            // 创建第一个显示列表

下面这部分的代码画出一个没有顶部的盒子,它不会出现在屏幕上,只会存储在显示列表里。

你可以在glNewList()和glEngList()中间加上任何你想加上的代码。可以设置颜色,贴图等等。唯一不能加进去的代码就是会改变显示列表的代码。显示列表一旦建立,你就不能改变它。

比如你想加上glColor3ub(rand()%255,rand()%255,rand()%255),使得每一次画物体时都会有不同的颜色。但因为显示列表只会建立一次,所以每次画物体的时候颜色都不会改变。物体将会保持第一次建立显示列表时的颜色。 如果你想改变显示列表的颜色,你只有在调用显示列表之前改变颜色。后面将详细解释这一点。

        glBegin(GL_QUADS);                            // 开始绘制四边形
            // 底面
            glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
            glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
            glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
            glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);    

            // 前面
            glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
            glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
            glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
            glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);    

            // 后面
            glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
            glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
            glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
            glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);   

            // 右面

            glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
            glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
            glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
            glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);    

            // 左面
            glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
            glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
            glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
            glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
        glEnd();                                // 四边形绘制结束

用glEngList()命令,我们告诉OpenGL我们已经完成了一个显示列表。在glNewList()和glEngList()之间的任何东西就是显示列表的一部分。

    glEndList();                                    // 第一个显示列表结束

现在我们来建立第二个显示列表。在上一个显示列表的指针上加1,就得到了第二个显示列表的指针。第二个显示列表的指针命名为“top”。

    top=box+1;                                    // 第二个显示列表的名称

现在我们知道了第二个显示列表的指针,我们可以建立它了。

    glNewList(top,GL_COMPILE);                            // 盒子顶部的显示列表

下面的代码画出盒子的顶部。

        glBegin(GL_QUADS);                            // 开始绘制四边形
            // 上面
            glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
            glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
            glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
            glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
        glEnd();                                // 结束绘制四边形

然后告诉OpenGL第二个显示列表建立完毕。

    glEndList();                                    // 第二个显示列表创建完毕
}

贴图纹理的代码和之前教程里的代码是一样的。我们需要一个可以贴在立方体上的纹理。我决定使用mipmapping处理让纹理看上去光滑,因为我讨厌看见像素点。纹理的文件名是“cube.bmp”,存放在data目录下。

    if (TextureImage[0]=LoadBMP("Data/Cube.bmp"))

改变窗口大小的代码和第六课是一样的。

初始化的代码只有一点改变,加入了一行BuildList()。请注意代码的顺序,先读入纹理,然后建立显示列表,这样当我们建立显示列表的时候就可以将纹理贴到立方体上了。

BuildLists();                        // 创建显示列表

接下来的三行使灯光有效。Light0一般来说是在显卡中预先定义过的,如果Light0不工作,把下面那行注释掉好了。

最后一行的GL_COLOR_MATERIAL使我们可以用颜色来贴纹理。如果没有这行代码,纹理将始终保持原来的颜色,glColor3f(r,g,b)就没有用了。总之这行代码是很有用的。

    glEnable(GL_LIGHT0);                    // 使用默认的0号灯
    glEnable(GL_LIGHTING);                    // 使用灯光
    glEnable(GL_COLOR_MATERIAL);                // 使用颜色材质

现在到了绘制代码的地方了,我们还是和以前一样,以清除背景颜色为开始。

接着把纹理绑定到立方体,我可以把这些代码加入到显示列表中,但我还是把它留在了显示列表外边,这样我可以随便设置纹理。

int DrawGLScene(GLvoid)                        // 绘制操作开始
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    // 清除背景颜色
    glBindTexture(GL_TEXTURE_2D, texture[0]);        // 选择纹理

现在到了真正有趣的地方了。用一个循环,循环变量用于改变Y轴位置,在Y轴上画5个立方体,所以用从1到5的循环。

    for (yloop=1;yloop<6;yloop++)                // 沿Y轴循环
    {

另外用一个循环,循环变量用于改变X轴位置。每行上的立方体数目取决于行数,所以循环方式如下。

        for (xloop=0;xloop<yloop;xloop++)        // 沿X轴循环
        {

重置模型变化矩阵

            glLoadIdentity();            // 重置模型变化矩阵

边的代码是移动和旋转当前坐标系到需要画出立方体的位置。(原文有很罗嗦的一大段,相信大家的数学功底都不错,就不翻译了)

            // 设置盒子的位置
            glTranslatef(1.4f+(float(xloop)*2.8f)-(float(yloop)*1.4f),((6.0f-float(yloop))*2.4f)-7.0f,-20.0f);
            glRotatef(45.0f-(2.0f*yloop)+xrot,1.0f,0.0f,0.0f);
            glRotatef(45.0f+yrot,0.0f,1.0f,0.0f);

然后在正式画盒子之前设置颜色。每个盒子用不同的颜色。

            glColor3fv(boxcol[yloop-1]);

好了,颜色设置好了。现在需要做的就是画出盒子。不用写出画多边形的代码,只需要用glCallList(box)命令调用显示列表。盒子将会用glColor3fv()所设置的颜色画出来。

            glCallList(box);            // 绘制盒子

然后用另外的颜色画顶部。搞定。

            glColor3fv(topcol[yloop-1]);        // 选择顶部颜色
            glCallList(top);            // 绘制顶部
        }
    }
    return TRUE;                        // 成功返回
}

下面的代码是键盘控制的一些东西

        SwapBuffers(hDC);                // 交换缓存

        if (keys[VK_LEFT])                // 左键是否按下
        {
            yrot-=0.2f;                // 如果是,向左旋转
        }
        if (keys[VK_RIGHT])                // 右键是否按下
        {
            yrot+=0.2f;                // 如果是向右旋转
        }
        if (keys[VK_UP])                // 上键是否按下
        {
            xrot-=0.2f;                // 如果是向上旋转
        }
        if (keys[VK_DOWN])                // 下键是否按下
        {
            xrot+=0.2f;                // 如果是向下旋转
        }
时间: 2024-10-13 01:47:59

第12课 OpenGL 显示列表的相关文章

NeHe OpenGL教程 第十二课:显示列表

转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线教程的编写,以及yarn的翻译整理表示感谢. NeHe OpenGL第十二课:显示列表 显示列表: 想知道如何加速你的OpenGL程序么?这一课将告诉你如何使用OpenGL的显示列表,它通过预编译OpenGL命令来加速你的程序,并可以为你省去很多重复的代码. 这次我将教你如何使用显示列表,显示列表将

OpenGL显示列表

OpenGL显示列表(Display List)是由一组预先存储起来的留待以后调用的OpenGL函数语句组成的,当调用这张显示列表时就依次执行表中所列出的函数语句.前面内容所举出的例子都是瞬时给出函数命令,则OpenGL瞬时执行相应的命令,这种绘图方式叫做立即或瞬时方式(immediate mode).本章将详细地讲述显示列表的基本概论.创建.执行.管理以及多级显示列表的应用等内容. 16.1.显示列表概论     16.1.1 显示列表的优势 OpenGL显示列表的设计能优化程序运行性能,尤其

计算机图形学(二)输出图元_18_显示列表_1_创建和命名OpenGL显示表

OpenGL显示列表 把对象描述成一个命名的语句序列(或任何其他的命令集)并存储起来既方便又高效.在OpenGL中使用称为显示表(display list)的结构可以做到这一点.一旦建立了显示表,就可以用不同的显示操作来多次引用该表.在网格中,描述图形的显示表存放在服务器中,以避免每次显示场景时都要传送表中的命令.我们可以为以后的执行来建立并存储显示表,或指定表中的命令立即执行.显示表对层次式建模特别有用,因为一个复杂的对象可以用一组简单的对象来描述. 创建和命名OpenGL显示表 使用glNe

[OpenGL] 斯坦福兔子与显示列表

1.调整桌子的大小.         在OpenGL绘制长方体,能够通过函数: glutSolidCube(Size)          绘制得到的是一个正方体,再利用缩放矩阵使其变成长方体.使得桌子的大小刚好能够放下16仅仅兔子. 2.兔子的增多降低 使用一个全局变量rabbitNum来记录兔子的数量. 在键盘回调函数中,在按下I,K后令rabbitNum添加或降低,并维护兔子的数量在1~16,等于16或1不再进行对应操作. 绘制兔子时.通过循环控制,每画完一仅仅兔子,平移一段距离,画到第4i

[OpenGL] 兔子与显示列表

1.调整桌子的大小.         在OpenGL绘制长方体,可以通过函数: glutSolidCube(Size)          绘制得到的是一个正方体,再利用缩放矩阵使其变成长方体,使得桌子的大小刚好可以放下16只兔子. 2.兔子的增多减少 使用一个全局变量rabbitNum来记录兔子的数量. 在键盘回调函数中,在按下I,K后令rabbitNum增加或减少,并维护兔子的数量在1~16,等于16或1不再进行相应操作. 绘制兔子时,通过循环控制,每画完一只兔子,平移一段距离,画到第4i+1

OpenGL中glVertex、显示列表(glCallList)、顶点数组(Vertex array)、VBO及VAO区别

OpenGL中glVertex.显示列表(glCallList).顶点数组(Vertex array).VBO及VAO区别 1.glVertex 最原始的设置顶点方法,在glBegin和glEnd之间使用.OpenGL3.0已经废弃此方法.每个glVertex与GPU进行一次通信,十分低效. glBegin(GL_TRIANGLES); glVertex(0, 0); glVertex(1, 1); glVertex(2, 2); glEnd(); 2.显示列表(glCallList) 每个gl

安卓学习第12课——SimpleAdapter

SimpleAdapter一点也不简单,他的参数就带了5个,天哪,晕了.. 今天学的这个适配器, public SimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to) 看这个大概明白,参数分别是第一个:表示访问整个android应用程序接口,基本上所有的组件都需要,一般都写this(改天研究一下),第二个应该是这个List对象

显示列表

显示列表一组已经存储(编译)的OpenGL命令,以供后续执行.一旦显示列表创建出来,所有顶点与像素数据都被赋值且拷贝到服务器段的显示列表内存.这是一次性过程.在显示列表配备(编译)好后,你可以重复使用它,而不需要每帧都重新赋值与重新反复传递数据.显示列表是绘制静态数据的最快方式之一,这是因为顶点数据与OpenGL命令被存储在显示列表中并且最小化客户端到服务器段的数据传输.也就是说,它降低CPU处理实际数据传输的周期. 显示列表的另一个重要性能是:由于显示列表是服务端数据,它可以在多个客户端共享.

【黑马程序员】第12课:文件上传&文件下载&注解

<pre> day12 上节内容回顾 1.jstl的标签 *if  choose *forEach <c:forEachvar="l" items="${list}"> 2.jsp开发模式 *模型一 *模型二(mvc模式) **mvc模式 ***m:模型,使用javabean ***v:视图,使用jsp ***c:控制器,使用servlet *dao模式:数据访问对象,专注于对数据库的操作 **首先创建接口,在接口里面定义操作数据库的方法 *