• Qt OpenGL 显示列表


    想知道如何加速我们的OpenGL程序么?这次教程中,我将告诉你如何使用OpenGL的显示列表,它通过预编译OpenGL命令来加速我们的程序,并可以为我们省去很多重复的代码,听起来是不是很棒呢!
    当我们在制作游戏里的小行星场景时,每一层至少需要两个行星,你可以用OpenGL中的多边形来构造每一个行星。但要知道每次把行星画到屏幕上都是很麻烦的,当我们面临复杂的场景时,要靠代码的绘画方式一个个画出所有的行星,这对于绝大多数人来说都是一个噩梦。那么,解决办法是什么呢?用显示列表,我们只需要一次性建立物体,可以贴图,用颜色,想怎么弄就怎么弄。然后给显示列表一个名字,比如给小行星的显示列表命名为“asteroid”。现在,任何时候,我们想在屏幕上画出行星,我们只需要调好位置后,调用glCallList(asteroid),之前做好的小行星就会立刻显示在屏幕上了。由于小行星已经在显示列表里建造好了,OpenGL不会再计算如何构造它。它已经在内存中建造好了,这将大大降低CPU的使用,让你的程序跑得更快。
    程序运行时效果如下:
    下面进入教程:
    我们这次同样将在第06课的基础上修改代码,我们只会解释增加部分的代码,首先打开myglwidget.h文件,将类声明更改如下:
     1 #ifndef MYGLWIDGET_H
     2 #define MYGLWIDGET_H
     3  
     4 #include <QWidget>
     5 #include <QGLWidget>
     6  
     7 class MyGLWidget : public QGLWidget
     8 {
     9     Q_OBJECT
    10 public:
    11     explicit MyGLWidget(QWidget *parent = 0);
    12     ~MyGLWidget();
    13  
    14 protected:
    15     //对3个纯虚函数的重定义
    16     void initializeGL();
    17     void resizeGL(int w, int h);
    18     void paintGL();
    19  
    20     void keyPressEvent(QKeyEvent *event);           //处理键盘按下事件
    21  
    22 private:
    23     void buildLists();                              //初始化盒子的显示列表
    24  
    25 private:
    26     bool fullscreen;                                //是否全屏显示
    27  
    28     GLfloat m_xRot;                                 //绕x轴旋转的角度
    29     GLfloat m_yRot;                                 //绕y轴旋转的角度
    30     QString m_FileName;                             //图片的路径及文件名
    31     GLuint m_Texture;                               //储存一个纹理
    32  
    33     GLuint m_Box;                                   //保存盒子的显示列表
    34     GLuint m_Top;                                   //保存盒子顶部的显示列表
    35 };
    36  
    37 #endif // MYGLWIDGET_H
    我们新增了两个用于显示列表的变量m_Box、m_Top,这两个变量是用于储存指向显示列表的指针。另外我们 多了一个buildLists()函数,这个函数是用于初始化两个显示列表的(注意我去掉了变量m_zRot,但其实影响不大)。
    接下来,我们需要打开myglwidget.cpp,修改构造函数同时添加buildLists()函数的定义,具体代码如下:
     1 MyGLWidget::MyGLWidget(QWidget *parent) :
     2     QGLWidget(parent)
     3 {
     4     fullscreen = false;
     5     m_xRot = 0.0f;
     6     m_yRot = 0.0f;
     7     m_FileName = "D:/QtOpenGL/QtImage/Cube.bmp";        //应根据实际存放图片的路径进行修改
     8  
     9     QTimer *timer = new QTimer(this);                   //创建一个定时器
    10     //将定时器的计时信号与updateGL()绑定
    11     connect(timer, SIGNAL(timeout()), this, SLOT(updateGL()));
    12     timer->start(10);                                   //以10ms为一个计时周期
    13 }
     1 void MyGLWidget::buildLists()                           //创建盒子的显示列表
     2 {
     3     m_Box = glGenLists(2);                              //创建两个显示列表的空间
     4     glNewList(m_Box, GL_COMPILE);                       //开始创建第一个显示列表
     5         glBegin(GL_QUADS);
     6             glTexCoord2f(0.0f, 0.0f);
     7             glVertex3f(1.0f, -1.0f, 1.0f);              //右上(底面)
     8             glTexCoord2f(1.0f, 0.0f);
     9             glVertex3f(-1.0f, -1.0f, 1.0f);             //左上(底面)
    10             glTexCoord2f(1.0f, 1.0f);
    11             glVertex3f(-1.0f, -1.0f, -1.0f);            //左下(底面)
    12             glTexCoord2f(0.0f, 1.0f);
    13             glVertex3f(1.0f, -1.0f, -1.0f);             //右下(底面)
    14  
    15             glTexCoord2f(1.0f, 1.0f);
    16             glVertex3f(1.0f, 1.0f, 1.0f);               //右上(前面)
    17             glTexCoord2f(0.0f, 1.0f);
    18             glVertex3f(-1.0f, 1.0f, 1.0f);              //左上(前面)
    19             glTexCoord2f(0.0f, 0.0f);
    20             glVertex3f(-1.0f, -1.0f, 1.0f);             //左下(前面)
    21             glTexCoord2f(1.0f, 0.0f);
    22             glVertex3f(1.0f, -1.0f, 1.0f);              //右下(前面)
    23  
    24             glTexCoord2f(0.0f, 0.0f);
    25             glVertex3f(1.0f, -1.0f, -1.0f);             //右上(后面)
    26             glTexCoord2f(1.0f, 0.0f);
    27             glVertex3f(-1.0f, -1.0f, -1.0f);            //左上(后面)
    28             glTexCoord2f(1.0f, 1.0f);
    29             glVertex3f(-1.0f, 1.0f, -1.0f);             //左下(后面)
    30             glTexCoord2f(0.0f, 1.0f);
    31             glVertex3f(1.0f, 1.0f, -1.0f);              //右下(后面)
    32  
    33             glTexCoord2f(1.0f, 1.0f);
    34             glVertex3f(-1.0f, 1.0f, 1.0f);              //右上(左面)
    35             glTexCoord2f(0.0f, 1.0f);
    36             glVertex3f(-1.0f, 1.0f, -1.0f);             //左上(左面)
    37             glTexCoord2f(0.0f, 0.0f);
    38             glVertex3f(-1.0f, -1.0f, -1.0f);            //左下(左面)
    39             glTexCoord2f(1.0f, 0.0f);
    40             glVertex3f(-1.0f, -1.0f, 1.0f);             //右下(左面)
    41  
    42             glTexCoord2f(1.0f, 1.0f);
    43             glVertex3f(1.0f, 1.0f, -1.0f);              //右上(右面)
    44             glTexCoord2f(0.0f, 1.0f);
    45             glVertex3f(1.0f, 1.0f, 1.0f);               //左上(右面)
    46             glTexCoord2f(0.0f, 0.0f);
    47             glVertex3f(1.0f, -1.0f, 1.0f);              //左下(右面)
    48             glTexCoord2f(1.0f, 0.0f);
    49             glVertex3f(1.0f, -1.0f, -1.0f);             //右下(右面)
    50         glEnd();
    51     glEndList();                                        //第一个显示列表结束
    52  
    53     m_Top = m_Box + 1;                                  //m_Box+1得到第二个显示列表的指针
    54     glNewList(m_Top, GL_COMPILE);                       //开始创建第二个显示列表
    55         glBegin(GL_QUADS);
    56             glTexCoord2f(1.0f, 1.0f);
    57             glVertex3f(1.0f, 1.0f, -1.0f);              //右上(顶面)
    58             glTexCoord2f(0.0f, 1.0f);
    59             glVertex3f(-1.0f, 1.0f, -1.0f);             //左上(顶面)
    60             glTexCoord2f(0.0f, 0.0f);
    61             glVertex3f(-1.0f, 1.0f, 1.0f);              //左下(顶面)
    62             glTexCoord2f(1.0f, 0.0f);
    63             glVertex3f(1.0f, 1.0f, 1.0f);               //右下(顶面)
    64         glEnd();
    65     glEndList();
    66 }
    构造函数不解释了,就删掉了m_zRot的初始化。buildLists()函数中,我们会将创造盒子的代码都放在第一个显示列表里,所有创造顶部的代码都在另一个显示列表里。开始时,我们告诉OpenGL我们要建立两个显示列表,glGenLists(2)创建了两个显示列表的空间,并返回第一个列表的指针,我们把它储存在m_Box中,任何时候我们调用glCallList(m_Box)第一个显示列表就会绘制出来。
    接下来我们开始构造第一个显示列表。我们已经申请了两个显示列表的空间了,并且有m_Box指针指向第一个显示列表,所以我们需要做的是告诉OpenGL要建立什么类型的显示列表。我们用glNewList()命令来做这件事情,注意到m_Box是第一个参数,这表示OpenGL将把列表储存到m_Box所指向的内存空间。而第二个参数GL_COMPILE告诉OpenGL我们想预先在内存中构造这个列表,这样每次画的时候就不必重新计算怎么构造物体了。
    GL_COMPILE类似于编程。在我们写程序的时候,把它装载到编译器里,我们每次运行程序都需要重新编译。而如果它已经编译成了.exe文件,那么每次我们只需要点击那个.exe文件就可以运行它了,不需要编译。当OpenGL编译过显示列表后,就不需要再每次显示的时候重新编译它了。这就是为什么用显示列表可以加快速度。
    下面我们就画了一个没有顶部的盒子,它不会出现在屏幕上,只会储存在显示列表里。我们可以在glNewList()和glEndList()中间加上任何你想加上的代码,可以设置颜色,贴图等等。但是,如果是你想在绘画过程发生改变的代码就不能加进去,这是由于 显示列表一旦建立,就不能改变它。比如我们想绘制不同颜色的物体,所以我们加上了glColor3ub(rand()%255,  rand()%255, rand()%255),但因为显示列表只会建立一次,所以每次画出来的物体都是同一种颜色,也就是说在储存进列表时glColor3ub的三个参数值就固定下来了。
    然后我们用glEndList()命令告诉OpenGL我们已经完成了一个显示列表。在 glNewList()和glEndList()之间的任何东西就是显示列表的一部分。接着,我们来建立第二个显示列表,在上一个显示列表的指针上加一,就得到了第二个显示列表的指针,并储存在m_Top中。最后同样建立这个显示列表就不解释了。
     
    然后在initializeGL()函数中,将代码修改如下:
     1 void MyGLWidget::initializeGL()                         //此处开始对OpenGL进行所以设置
     2 {
     3     m_Texture = bindTexture(QPixmap(m_FileName));       //载入位图并转换成纹理
     4     glEnable(GL_TEXTURE_2D);                            //启用纹理映射
     5     buildLists();                                       //创建显示列表
     6  
     7     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);               //黑色背景
     8     glShadeModel(GL_SMOOTH);                            //启用阴影平滑
     9     glClearDepth(1.0);                                  //设置深度缓存
    10     glEnable(GL_DEPTH_TEST);                            //启用深度测试
    11     glDepthFunc(GL_LEQUAL);                             //所作深度测试的类型
    12     glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);  //告诉系统对透视进行修正
    13 <pre name="code" class="cpp">
    glEnable(GL_LIGHT0); //使用默认的0号灯
    glEnable(GL_LIGHTING); //使用灯光
    glEnable(GL_COLOR_MATERIAL); //使用颜色材质}
    
    我们在启用纹理之后调用了buildLists()函数,创建了显示列表,注意在构造函数中调用buildLists()函数时无法生效的,Qt中使用OpenGL的时候,与内存使用相关的OpenGL函数都需要在initialize()、resize()、paintGL()中直接调用或间接调用,否则无法成功地申请空间。
    最后三行使的灯光有效,LIGHT一般是显卡中预先定义过的。最后一行的GL_COLOR_MATERIAL使得我们可以用颜色来贴纹理。如果没有这行代码,纹理将始终保持原来的颜色,glColor3f(r, g,b)就没有用了。
    还有就是paintGL()函数,这次看起来就简单许多了(麻烦的我们已经通过参数列表搞定了),具体代码如下:
     1 void MyGLWidget::paintGL()                              //从这里开始进行所以的绘制
     2 {
     3     static const GLfloat boxColor[5][3] =               //盒子的颜色数组
     4     {
     5         //亮:红、橙、黄、绿、蓝
     6         {1.0f, 0.0f, 0.0f}, {1.0f, 0.5f, 0.0f}, {1.0f, 1.0f, 0.0f},
     7         {0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 1.0f}
     8     };
     9     static const GLfloat topColor[5][3] =               //顶部的颜色数组
    10     {
    11         //暗:红、橙、黄、绿、蓝
    12         {0.5f, 0.0f, 0.0f}, {0.5f, 0.25f, 0.0f}, {0.5f, 0.5f, 0.0f},
    13         {0.0f, 0.5f, 0.0f}, {0.0f, 0.5f, 0.5f}
    14     };
    15  
    16     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存
    17     glBindTexture(GL_TEXTURE_2D, m_Texture);            //选择纹理
    18  
    19     for (int y=1; y<6; y++)                             //循环来控制画盒子
    20     {
    21         for (int x=0; x<y; x++)
    22         {
    23             glLoadIdentity();
    24             //设置盒子的位置
    25             glTranslatef(1.4f+(float(x)*2.8f)-(float(y)*1.4f),
    26                          ((6.0f-float(y))*2.4f)-7.0f, -20.0f);
    27             glRotatef(45.0f+m_xRot, 1.0f, 0.0f, 0.0f);
    28             glRotatef(45.0f+m_yRot, 0.0f, 1.0f, 0.0f);
    29             glColor3fv(boxColor[y-1]);                  //选择盒子颜色
    30             glCallList(m_Box);                          //绘制盒子
    31             glColor3fv(topColor[y-1]);                  //选择顶部颜色
    32             glCallList(m_Top);                          //绘制顶部
    33         }
    34     }
    35 }
    我们一开始就定义了储存盒子和顶部颜色的数组,接着我们用双重循环来画出10个立方体,并在每次画时重置模型观察矩阵,平移和旋转到需要画出立方体位置的中心,具体如何算的我看太懂NeHe的用意就不解释了,反正这并不是重点,我们完全可以根据自己的喜好来摆放这些立方体。
    然后我们选择颜色,接着按前面所讲,调用glCallList()就可以画出我们要的立方体了。相比于之前的几课,显示列表让我们的paintGL()函数看起来简单了许多。
     
    最后是键盘控制的,比较简单我就不过多解释了,具体代码如下:
     1 void MyGLWidget::keyPressEvent(QKeyEvent *event)
     2 {
     3     switch (event->key())
     4     {
     5     case Qt::Key_F1:                                    //F1为全屏和普通屏的切换键
     6         fullscreen = !fullscreen;
     7         if (fullscreen)
     8         {
     9             showFullScreen();
    10         }
    11         else
    12         {
    13             showNormal();
    14         }
    15         updateGL();
    16         break;
    17     case Qt::Key_Escape:                                //ESC为退出键
    18         close();
    19         break;
    20     case Qt::Key_Left:                                  //Left按下向左旋转
    21         m_yRot -= 1.0f;
    22         break;
    23     case Qt::Key_Right:                                 //Right按下向右旋转
    24         m_yRot += 1.0f;
    25         break;
    26     case Qt::Key_Up:                                    //Up按下向上旋转
    27         m_xRot -= 1.0f;
    28         break;
    29     case Qt::Key_Down:                                  //Down按下向下旋转
    30         m_xRot += 1.0f;
    31         break;
    32     }
    33 }

    现在就可以运行程序查看效果了!

  • 相关阅读:
    27. Remove Element
    26. Remove Duplicates from Sorted Array
    643. Maximum Average Subarray I
    674. Longest Continuous Increasing Subsequence
    1. Two Sum
    217. Contains Duplicate
    448. Find All Numbers Disappeared in an Array
    566. Reshape the Matrix
    628. Maximum Product of Three Numbers
    UVa 1349 Optimal Bus Route Design (最佳完美匹配)
  • 原文地址:https://www.cnblogs.com/ybqjymy/p/14048247.html
Copyright © 2020-2023  润新知