• 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 }

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

  • 相关阅读:
    Sum Root to Leaf Numbers [LeetCode]
    Symmetric Tree [LeetCode]
    Combination Sum II [LeetCode]
    Maximal Rectangle [LeetCode]
    Trapping Rain Water [LeetCode]
    Combination Sum [LeetCode]
    05 如何“响铃”
    04 八进制
    03 关键字?保留字?预留字?
    020 函数之变量的作用域
  • 原文地址:https://www.cnblogs.com/ybqjymy/p/14048247.html
Copyright © 2020-2023  润新知