这次教程中,我将介绍二次几何体。利用二次几何体,我们可以很容易创建球、圆盘、圆柱和圆锥。
我们先介绍一下二次几何体GLUquadric(NeHe教程用的是GLUquadricObj,源代码中GLUquadricObj是GLUquadric的别名),其实它本质上是一个二次方程,即a1x^2 + a2y^2 + a3z^2 + a4xy + a5yz + a6zx + a7x + a8y + a9z + a10 = 0。要知道,任何一个空间规则曲面(包括平面)都是可以用二次方程表示出来的,因此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 }
现在就可以运行程序查看效果了!