这次教程中,我将介绍二次几何体。利用二次几何体,我们可以很容易创建球、圆盘、圆柱和圆锥。

我们先介绍一下二次几何体GLUquadric(NeHe教程用的是GLUquadricObj,源代码中GLUquadricObj是GLUquadric的别名),其实它本质上是一个二次方程,即a1x^2 + a2y^2 + a3z^2 + a4xy + a5yz + a6zx + a7x + a8y + a9z + a10 = 0。要知道,任何一个空间规则曲面(包括平面)都是可以用二次方程表示出来的,因此OpenGL利用二次几何体来实现一些函数,帮助用户更简单的绘画出常用的空间曲面。

程序运行时效果如下:

Qt OpenGL 二次几何体_环境光

下面进入教程:

我们将在第07课的基础上修改代码,我只会对新增代码作解释,首先打开myglwidget.h文件,将类声明更改如下:

 1 #ifndef MYGLWIDGET_H
 2 #define MYGLWIDGET_H
 3  
 4 #include <QWidget>
 5 #include <QGLWidget>
 6  
 7 class GLUquadric;
 8 class MyGLWidget : public QGLWidget
 9 {
10     Q_OBJECT
11 public:
12     explicit MyGLWidget(QWidget *parent = 0);
13     ~MyGLWidget();
14  
15 protected:
16     //对3个纯虚函数的重定义
17     void initializeGL();
18     void resizeGL(int w, int h);
19     void paintGL();
20  
21     void keyPressEvent(QKeyEvent *event);           //处理键盘按下事件
22  
23 private:
24     void glDrawCube();                              //绘制立方体
25  
26 private:
27     bool fullscreen;                                //是否全屏显示
28  
29     QString m_FileName;                             //图片的路径及文件名
30     GLuint m_Texture;                               //储存一个纹理
31  
32     bool m_Light;                                   //光源的开/关
33     GLfloat m_xRot;                                 //x旋转角度
34     GLfloat m_yRot;                                 //y旋转角度
35     GLfloat m_xSpeed;                               //x旋转速度
36     GLfloat m_ySpeed;                               //y旋转速度
37     GLfloat m_Deep;                                 //深入屏幕的距离
38  
39     int m_Part1;                                    //圆盘的起始角度
40     int m_Part2;                                    //圆盘的结束角度
41     int m_P1;                                       //增量1
42     int m_P2;                                       //增量2
43     GLUquadric *m_Quadratic;                        //二次几何体
44     GLuint m_Object;                                //绘制对象标示符
45 };
46  
47 #endif // MYGLWIDGET_H

首先我们在类前面增加了GLUquadric的类声明。接着我们增加了6个变量,前4个变量用于控制绘制“部分圆盘”的,下面会解释。然后我们定义一个二次几何体对象指针和一个GLuint变量,二次几何体就不解释了,m_Object是配合键盘控制来完成图形之间切换的。最后我们增加了一个函数声明glDrawCube(),这个函数是用来绘制立方体的。

接下来,我们需要打开myglwidget.cpp,在构造函数中初始化新增变量(除了m_Quadratic)并修改析构函数(删除掉创建的二次几何体),很简单不多解释,具体代码如下:

 1 MyGLWidget::MyGLWidget(QWidget *parent) :
 2     QGLWidget(parent)
 3 {
 4     fullscreen = false;
 5     m_FileName = "D:/QtOpenGL/QtImage/Wall1.bmp";       //应根据实际存放图片的路径进行修改
 6     m_Light = false;
 7  
 8     m_xRot = 0.0f;
 9     m_yRot = 0.0f;
10     m_xSpeed = 0.0f;
11     m_ySpeed = 0.0f;
12     m_Deep = -5.0f;
13  
14     m_Part1 = 0;
15     m_Part2 = 0;
16     m_P1 = 0;
17     m_P2 = 1;
18     m_Object = 0;
19  
20     QTimer *timer = new QTimer(this);                   //创建一个定时器
21     //将定时器的计时信号与updateGL()绑定
22     connect(timer, SIGNAL(timeout()), this, SLOT(updateGL()));
23     timer->start(10);                                   //以10ms为一个计时周期
24 }
1 MyGLWidget::~MyGLWidget()
2 {
3     gluDeleteQuadric(m_Quadratic);
4 }

继续,我们需要定义我们新增的glDrawCube()函数了,其实就是画一个纹理立方体,完全可以从第07课的paintGL()函数中复制过来,不再多作解释,代码如下:

 1 void MyGLWidget::glDrawCube()
 2 {
 3     glBegin(GL_QUADS);                                  //开始绘制立方体
 4         glNormal3f(0.0f, 1.0f, 0.0f);
 5         glTexCoord2f(1.0f, 1.0f);
 6         glVertex3f(1.0f, 1.0f, -1.0f);                  //右上(顶面)
 7         glTexCoord2f(0.0f, 1.0f);
 8         glVertex3f(-1.0f, 1.0f, -1.0f);                 //左上(顶面)
 9         glTexCoord2f(0.0f, 0.0f);
10         glVertex3f(-1.0f, 1.0f, 1.0f);                  //左下(顶面)
11         glTexCoord2f(1.0f, 0.0f);
12         glVertex3f(1.0f, 1.0f, 1.0f);                   //右下(顶面)
13  
14         glNormal3f(0.0f, -1.0f, 0.0f);
15         glTexCoord2f(0.0f, 0.0f);
16         glVertex3f(1.0f, -1.0f, 1.0f);                  //右上(底面)
17         glTexCoord2f(1.0f, 0.0f);
18         glVertex3f(-1.0f, -1.0f, 1.0f);                 //左上(底面)
19         glTexCoord2f(1.0f, 1.0f);
20         glVertex3f(-1.0f, -1.0f, -1.0f);                //左下(底面)
21         glTexCoord2f(0.0f, 1.0f);
22         glVertex3f(1.0f, -1.0f, -1.0f);                 //右下(底面)
23  
24         glNormal3f(0.0f, 0.0f, 1.0f);
25         glTexCoord2f(1.0f, 1.0f);
26         glVertex3f(1.0f, 1.0f, 1.0f);                   //右上(前面)
27         glTexCoord2f(0.0f, 1.0f);
28         glVertex3f(-1.0f, 1.0f, 1.0f);                  //左上(前面)
29         glTexCoord2f(0.0f, 0.0f);
30         glVertex3f(-1.0f, -1.0f, 1.0f);                 //左下(前面)
31         glTexCoord2f(1.0f, 0.0f);
32         glVertex3f(1.0f, -1.0f, 1.0f);                  //右下(前面)
33  
34         glNormal3f(0.0f, 0.0f, -1.0f);
35         glTexCoord2f(0.0f, 0.0f);
36         glVertex3f(1.0f, -1.0f, -1.0f);                 //右上(后面)
37         glTexCoord2f(1.0f, 0.0f);
38         glVertex3f(-1.0f, -1.0f, -1.0f);                //左上(后面)
39         glTexCoord2f(1.0f, 1.0f);
40         glVertex3f(-1.0f, 1.0f, -1.0f);                 //左下(后面)
41         glTexCoord2f(0.0f, 1.0f);
42         glVertex3f(1.0f, 1.0f, -1.0f);                  //右下(后面)
43  
44         glNormal3f(-1.0f, 0.0f, 0.0f);
45         glTexCoord2f(1.0f, 1.0f);
46         glVertex3f(-1.0f, 1.0f, 1.0f);                  //右上(左面)
47         glTexCoord2f(0.0f, 1.0f);
48         glVertex3f(-1.0f, 1.0f, -1.0f);                 //左上(左面)
49         glTexCoord2f(0.0f, 0.0f);
50         glVertex3f(-1.0f, -1.0f, -1.0f);                //左下(左面)
51         glTexCoord2f(1.0f, 0.0f);
52         glVertex3f(-1.0f, -1.0f, 1.0f);                 //右下(左面)
53  
54         glNormal3f(1.0f, 0.0f, 0.0f);
55         glTexCoord2f(1.0f, 1.0f);
56         glVertex3f(1.0f, 1.0f, -1.0f);                  //右上(右面)
57         glTexCoord2f(0.0f, 1.0f);
58         glVertex3f(1.0f, 1.0f, 1.0f);                   //左上(右面)
59         glTexCoord2f(0.0f, 0.0f);
60         glVertex3f(1.0f, -1.0f, 1.0f);                  //左下(右面)
61         glTexCoord2f(1.0f, 0.0f);
62         glVertex3f(1.0f, -1.0f, -1.0f);                 //右下(右面)
63     glEnd();                                            //立方体绘制结束
64 }

然后我们需要修改一下initializeGL()函数,在其中完成对m_Quadratic的初始化,具体代码如下:

 1 void MyGLWidget::initializeGL()                         //此处开始对OpenGL进行所以设置
 2 {
 3     m_Texture = bindTexture(QPixmap(m_FileName));       //载入位图并转换成纹理
 4     glEnable(GL_TEXTURE_2D);                            //启用纹理映射
 5  
 6     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);               //黑色背景
 7     glShadeModel(GL_SMOOTH);                            //启用阴影平滑
 8  
 9     glClearDepth(1.0);                                  //设置深度缓存
10     glEnable(GL_DEPTH_TEST);                            //启用深度测试
11     glDepthFunc(GL_LEQUAL);                             //所作深度测试的类型
12     glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);  //告诉系统对透视进行修正
13  
14     m_Quadratic = gluNewQuadric();                        //创建二次几何体
15     gluQuadricNormals(m_Quadratic, GLU_SMOOTH);           //使用平滑法线
16     gluQuadricTexture(m_Quadratic, GL_TRUE);              //使用纹理
17  
18     //光源部分
19     GLfloat LightAmbient[] = {0.5f, 0.5f, 0.5f, 1.0f};  //环境光参数
20     GLfloat LightDiffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};  //漫散光参数
21     GLfloat LightPosition[] = {0.0f, 0.0f, 2.0f, 1.0f}; //光源位置
22     glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);     //设置环境光
23     glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);     //设置漫射光
24     glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);   //设置光源位置
25     glEnable(GL_LIGHT1);                                //启动一号光源
26 }

注意到我们增加了三行代码,首先调用gluNewQuadric()创建了一个二次几何体对象,并让m_Quadratic指向这个二次几何体。然后第二行代码将在二次曲面的表面创建平滑的法向量,这样当灯光照上去的时候将会好看些。最后我们使在二次曲面表面的纹理映射有效。

还有就是paintGL()函数了,最近几课,我们通过分过程渐渐让paintGL()函数看起来趋于简化,具体代码如下:

 1 void MyGLWidget::paintGL()                              //从这里开始进行所以的绘制
 2 {
 3     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存
 4     glLoadIdentity();                                   //重置模型观察矩阵
 5     glTranslatef(0.0f, 0.0f, m_Deep);                    //移入屏幕5.0单位
 6     glRotatef(m_xRot, 1.0f, 0.0f, 0.0f);                //绕x轴旋转
 7     glRotatef(m_yRot, 0.0f, 1.0f, 0.0f);                //绕y轴旋转
 8  
 9     glBindTexture(GL_TEXTURE_2D, m_Texture);            //选择纹理
10     switch(m_Object)
11     {
12     case 0:                                             //绘制立方体
13         glDrawCube();
14         break;
15     case 1:                                             //绘制圆柱体
16         glTranslatef(0.0f, 0.0f, -1.5f);
17         gluCylinder(m_Quadratic, 1.0f, 1.0f, 3.0f, 64, 64);
18         break;
19     case 2:                                             //绘制圆盘
20         gluDisk(m_Quadratic, 0.5f, 1.5f, 64, 64);
21         break;
22     case 3:                                             //绘制球
23         gluSphere(m_Quadratic, 1.3f, 64, 64);
24         break;
25     case 4:                                             //绘制圆锥
26         glTranslatef(0.0f, 0.0f, -1.5f);
27         gluCylinder(m_Quadratic, 1.0f, 0.0f, 3.0f, 64, 64);
28         break;
29     case 5:                                             //绘制部分圆盘
30         m_Part1 += m_P1;
31         m_Part2 += m_P2;
32  
33         if (m_Part1 > 359)
34         {
35             m_P1 = 0;
36             m_Part1 = 0;
37             m_P2 = 1;
38             m_Part2 = 0;
39         }
40         if (m_Part2 > 359)
41         {
42             m_P1 = 1;
43             m_P2 = 0;
44         }
45  
46         gluPartialDisk(m_Quadratic, 0.5f, 1.5f, 64, 64, m_Part1, m_Part2-m_Part1);
47         break;
48     }
49  
50     m_xRot += m_xSpeed;                                 //x轴旋转
51     m_yRot += m_ySpeed;                                 //y轴旋转
52 }

我们将原来的绘制立方体部分的代码换成了一个switch()语句,我们利用m_Object来确定画哪一种物体(具体哪个值对应哪个,请大家参照注释)。我们后面讨论绘制这些物体调用的函数时,会忽略第一个参数m_Quadratic,这个参数将被除立方体外的所有对象使用。由于前面已经解释过二次几何体的实质,我们在讨论下面函数的参数时将忽略它。

我们创建的第2个对象是一个圆柱体:参数2是圆柱的底面半径;参数3是圆柱的顶面半径;参数4是圆柱的高度(表面我们也可以绘制圆台的);参数5是纬线(环绕z轴有多少细分);参数6是经线(沿着z轴有多少细分)。细分越多该对象就越细致,其实我们可以用gluCylinder来绘制多棱柱的,只要把参数5和参数6换成对应的棱数就行了。

第3个对象是一个CD一样的盘子:参数2是盘子的内圆半径,该参数可以为0.0,则表示在盘子中间没孔,内圆半径越大孔越大;参数3表示外圆半径,这个参数必须比内圆半径大;参数4是组成该盘子切片的数量;参数5是组成盘子的环的数量,环很像唱片上的轨迹。同样,把参数4改成边数,同样可以得到带孔(不带孔)的多边形。

第4个对象是球:参数2是球的半径;和圆柱一样,参数3是纬线;参数4是经线。细分越多球看起来就越平滑。

第5个对象是圆锥:其实和绘制圆柱是一样的,只是把顶面半径设置为0.0,这样顶面就成了一个点。同样参考上面说的方法可以绘制多棱锥。

第6个对象将被gluPartialDisk()函数创建。相比于gluDisk()函数,gluPartialDisk()多了两个新参数。参数6是我们想要绘制的分部盘子的开始角度,参数6是旋转角,也就是转过的调度。我们将要增加旋转角,这将引起盘子沿顺时针方向缓慢的被绘制在屏幕上。一旦旋转角达到360度,我们将开始增加开始角度,这样盘子看起来就像是被逐渐地抹去一样,我们将重复这两个过程。

最后我们修改一下键盘控制函数,不多解释了,具体代码如下:

 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         break;
16     case Qt::Key_Escape:                                //ESC为退出键
17         close();
18         break;
19     case Qt::Key_L:                                     //L为开启关闭光源的切换键
20         m_Light = !m_Light;
21         if (m_Light)
22         {
23             glEnable(GL_LIGHTING);                      //开启光源
24         }
25         else
26         {
27             glDisable(GL_LIGHTING);                     //关闭光源
28         }
29         break;
30     case Qt::Key_Space:                                 //空格为物体的切换键
31         m_Object++;
32         if (m_Object == 6)
33         {
34             m_Object = 0;
35         }
36         break;
37     case Qt::Key_PageUp:                                //PageUp按下使木箱移向屏幕内部
38         m_Deep -= 0.1f;
39         break;
40     case Qt::Key_PageDown:                              //PageDown按下使木箱移向观察者
41         m_Deep += 0.1f;
42         break;
43     case Qt::Key_Up:                                    //Up按下减少m_xSpeed
44         m_xSpeed -= 0.1f;
45         break;
46     case Qt::Key_Down:                                  //Down按下增加m_xSpeed
47         m_xSpeed += 0.1f;
48         break;
49     case Qt::Key_Right:                                 //Right按下减少m_ySpeed
50         m_ySpeed -= 0.1f;
51         break;
52     case Qt::Key_Left:                                  //Left按下增加m_ySpeed
53         m_ySpeed += 0.1f;
54         break;
55     }
56 }

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