一杯清酒邀明月
天下本无事,庸人扰之而烦耳。

想知道如何在3D空间中移动物体,想知道如何在屏幕上绘制一个图像,而让图像的背景色变为透明,希望有一个简单的动画。这次教程中将教会你所以的一切。当然,这一课是在前面几课知识的基础上创建的,请确保你已经掌握了前面几课的知识,再进入本课教程。

欢迎进入这次教程,这一课将是前面几课的综合。前面的学习中,我们学会了设置一个OpenGL窗口的每个细节,学会在旋转的物体上贴图并打上光线以及混色(透明)处理。这一课中,我们将在3D场景中移动位图,并去除位图上的黑色像素(使用混色)。接着为黑白纹理上色,最后我们将学会创建丰富的色彩,并把混合了不同色彩的纹理相互混合,得到简单的动画效果。

程序运行时效果如下:

下面进入教程:

我们这次将在第01课的基础上修改代码,其中一些与前几课重复的地方我不作过多解释。首先打开myglwdiget.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     bool fullscreen;                                //是否全屏显示
24  
25     QString m_FileName;                             //图片的路径及文件名
26     GLuint m_Texture;                               //储存一个纹理
27     bool m_Twinkle;                                 //星星是否闪烁
28  
29     static const int num = 50;                      //星星的数目
30     struct star{                                    //为星星创建的结构体
31         int r, g, b;                                //星星的颜色
32         GLfloat dist;                               //星星距离中心的距离
33         GLfloat angle;                              //当前星星所处的角度
34     } m_stars[num];
35  
36     GLfloat m_Deep;                                 //星星离观察者的距离
37     GLfloat m_Tilt;                                 //星星的倾角
38     GLfloat m_Spin;                                 //星星的自转
39 };
40  
41 #endif // MYGLWIDGET_H

首先是一个布尔变量m_Twinkle用来表示星星是否闪烁。然后我们创建了一个星星的结构体,结构体包含星星的颜色,离中心距离以及所处角度,并创建了一个大小为50的星星数组。最后三个浮点变量依次表示星星离观察者距离,星星的倾角,星星的自转,这三个浮点变量用于对整体视图的控制。

接下来,我们还是打开myglwidget.cpp,加上声明#include <QTimer>,在构造函数中对新增变量进行初始化,只解释小部分,希望大家结合注释可以理解,代码如下:

 1 MyGLWidget::MyGLWidget(QWidget *parent) :
 2     QGLWidget(parent)
 3 {
 4     fullscreen = false;
 5     m_FileName = "D:/QtOpenGL/QtImage/Star.bmp";        //应根据实际存放图片的路径进行修改
 6     m_Twinkle = false;                                  //默认初始状态为不闪烁
 7  
 8     for (int i=0; i<num; i++)                           //循环初始化所有的星星
 9     {
10         //随机获得星星颜色
11         m_stars[i].r = rand() % 256;
12         m_stars[i].g = rand() % 256;
13         m_stars[i].b = rand() % 256;
14  
15         m_stars[i].dist = ((float)i / num) * 5.0f;      //计算星星离中心的距离,最大半径为5.0
16         m_stars[i].angle = 0.0f;                        //所以星星都从0度开始旋转
17     }
18  
19     m_Deep = -15.0f;                                    //深入屏幕15.0单位
20     m_Tilt = 90.0f;                                     //初始倾角为90度(面对观察者)
21     m_Spin = 0.0f;                                      //从0度开始自转
22  
23     QTimer *timer = new QTimer(this);                   //创建一个定时器
24     //将定时器的计时信号与updateGL()绑定
25     connect(timer, SIGNAL(timeout()), this, SLOT(updateGL()));
26     timer->start(10);                                   //以10ms为一个计时周期
27 }

利用循环对星星的数据进行初始化,第i 颗星星离中心的距离是将i 的值除以星星的总数,然后乘上5.0f。基本上这样使得后一颗星星比前一颗星星离中心更远一点,这样当i = 50时,就刚好达到最大半径5.0f了。然后我们选择颜色都是从0~255之间取一个随机数,为何这里不是通常的0.0f~1.0f呢?这里我们使用的颜色设置函数时glColor4ub,而不是之前的glColor4f,ub意味着参数是Unsigned Byte型的,同时这里去随机数整数似乎要比取一个浮点的随机数更容易一些。

然后我们要对initializeGL()函数作一定的修改,修改后代码如下:

 1 void MyGLWidget::initializeGL()                         //此处开始对OpenGL进行所以设置
 2 {
 3     m_Texture = bindTexture(QPixmap(m_FileName));
 4     glEnable(GL_TEXTURE_2D);
 5  
 6     glClearColor(0.0, 0.0, 0.0, 0.0);                   //黑色背景
 7     glShadeModel(GL_SMOOTH);                            //启用阴影平滑
 8     glClearDepth(1.0);                                  //设置深度缓存
 9     glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);  //告诉系统对透视进行修正
10  
11     glBlendFunc(GL_SRC_ALPHA, GL_ONE);                  //设置混色函数取得半透明效果
12     glEnable(GL_BLEND);                                 //开启混合(混色)
13 }

这里我们不打算使用深度测试,如果你使用第01课的代码的话,请确认是否已经去掉了glDepthFunc(GL_LEQUAL);和glEnable(GL_DEPTH_TEST);两行。否则,你所见到的最终效果会一团糟。这里我们使用了纹理映射,因此请你确认你已经加入了这些这一课中所没有的代码。同样要注意的是我们也开启了混合(混色),这是为了给纹理上色,产生不同颜色的星星。

还有就是最重点的paintGL()函数,我会一一作出解释,具体代码如下:

 1 void MyGLWidget::paintGL()                              //从这里开始进行所以的绘制
 2 {
 3     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存
 4     glBindTexture(GL_TEXTURE_2D, m_Texture);            //选择纹理
 5  
 6     for (int i=0; i<num; i++)
 7     {
 8         glLoadIdentity();                               //绘制每颗星星之前,重置模型观察矩阵
 9         glTranslatef(0.0f, 0.0f, m_Deep);               //深入屏幕里面
10         glRotatef(m_Tilt, 1.0f, 0.0f, 0.0f);            //倾斜视角
11         glRotatef(m_stars[i].angle, 0.0f, 1.0f, 0.0f);  //旋转至当前所画星星的角度
12         glTranslatef(m_stars[i].dist, 0.0f, 0.0f);      //沿x轴正向移动
13         glRotatef(-m_stars[i].angle, 0.0f, 1.0f, 0.0f); //取消当前星星的角度
14         glRotatef(-m_Tilt, 1.0f, 0.0f, 0.0f);           //取消视角倾斜
15  
16         if (m_Twinkle)                                  //启动闪烁效果
17         {
18             //使用byte型数据值指定一个颜色
19             glColor4ub(m_stars[num-i-1].r, m_stars[num-i-1].g, m_stars[num-i-1].b, 255);
20             glBegin(GL_QUADS);                          //开始绘制纹理映射过的四边形
21                 glTexCoord2f(0.0f, 0.0f);
22                 glVertex3f(-1.0f, -1.0f, 0.0f);
23                 glTexCoord2f(1.0f, 0.0f);
24                 glVertex3f(1.0f, -1.0f, 0.0f);
25                 glTexCoord2f(1.0f, 1.0f);
26                 glVertex3f(1.0f, 1.0f, 0.0f);
27                 glTexCoord2f(0.0f, 1.0f);
28                 glVertex3f(-1.0f, 1.0f, 0.0f);
29             glEnd();                                    //四边形绘制结束
30         }
31  
32         glRotatef(m_Spin, 0.0f, 0.0f, 1.0f);            //绕z轴旋转星星
33         //使用byte型数据值指定一个颜色
34         glColor4ub(m_stars[i].r, m_stars[i].g, m_stars[i].b, 255);
35         glBegin(GL_QUADS);                          //开始绘制纹理映射过的四边形
36             glTexCoord2f(0.0f, 0.0f);
37             glVertex3f(-1.0f, -1.0f, 0.0f);
38             glTexCoord2f(1.0f, 0.0f);
39             glVertex3f(1.0f, -1.0f, 0.0f);
40             glTexCoord2f(1.0f, 1.0f);
41             glVertex3f(1.0f, 1.0f, 0.0f);
42             glTexCoord2f(0.0f, 1.0f);
43             glVertex3f(-1.0f, 1.0f, 0.0f);
44         glEnd();                                    //四边形绘制结束
45  
46         m_Spin += 0.01f;                            //星星的自转
47         m_stars[i].angle += (float)i / num;         //改变星星的公转角度
48         m_stars[i].dist -= 0.01f;                   //改变星星离中心的距离
49         if (m_stars[i].dist < 0.0f)                 //星星到达中心了么
50         {
51             m_stars[i].dist += 5.0f;                //往外移5.0单位
52             m_stars[i].r = rand() % 256;
53             m_stars[i].g = rand() % 256;
54             m_stars[i].b = rand() % 256;
55         }
56     }
57 }

首先是清屏和绑定纹理,接着进入循环,画每颗星星前当然要重置模型观察矩阵并进行视图的移动旋转,然后我们来移动星星。我们要做的第一件事是把场景沿y轴旋转。如果我们旋转90度的话,x轴就不再是从左到右的了,它将从里到外穿出屏幕。第二行代码沿x轴移动一个正值,通常这样代表移向了屏幕的右侧,但由于我们绕y轴旋转了坐标系,x轴的正向可以使任意方向。因此,当我们沿x轴正向移动时,可能向左、向右、向前、向后。

接着的代码带一点小技巧。我们绘制的星星实际上是一个平面的纹理,现在我们在屏幕中心画了个平面的四边形然后贴上纹理,这看起来很不错。但是当我们绕着y轴转上个90度的话,纹理在屏幕上就只剩下右侧和左侧的两条边朝着我们了,看起来就是一条细线,这不并不是我们所想要的,我们希望星星永远正面朝着我们。因此,在绘制星星之前,我们通过逆序旋转来抵消之前对星星所作的任何旋转,当然旋转的角度就要加上- 号了。

然后到了if 条件从句,如果m_Twinkle为TRUE,我们在屏幕上先画一次不旋转的星星,当我们画第i颗星星时,将采用第num-i-1颗星星的颜色使得颜色不同。由于开启了m_Twinkle,每颗星星最后会被绘制两遍,两遍绘制的星星颜色相互融合,会产生很棒的效果,看起来比原来亮了许多。值得注意的是,给纹理上色是件很容易的事,尽管纹理本身是黑白的,纹理将变成我们在绘制它之前选定的任意颜色。if 条件从句后,我们要绘制第二遍的星星,和前面不同的是,这一遍的星星肯定会被绘制,并且这次的星星绕着z轴旋转(星星的自转)。

后面的代码代表星星的运动,我们增加m_Spin的值来控制星星自转,然后将每颗星星的公转角度增加 i/num这使得离中心更远的星星转得更快,最后减少每颗星星离屏幕中心的距离,这样看起来星星们好像被不断地吸入屏幕的中心。

最后几行是检查星星是否已经碰到了屏幕中心。当星星碰到屏幕中心时,我们为它赋上新颜色,然后往外移5.0单位,这颗星星将重新踏上回归屏幕中心的旅程。

最后就是键盘控制部分了,具体代码如下(相信大家结合注释可以很容易看懂):

 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_T:                                 //T为星星开启关闭闪烁的切换键
21         m_Twinkle = !m_Twinkle;
22         break;
23     case Qt::Key_Up:                                //Up按下屏幕向上倾斜
24         m_Tilt -= 0.5f;
25         break;
26     case Qt::Key_Down:                              //Down按下屏幕向下倾斜
27         m_Tilt += 0.5f;
28         break;
29     case Qt::Key_PageUp:                            //PageUp按下缩小
30         m_Deep -= 0.1f;
31         break;
32     case Qt::Key_PageDown:                          //PageDown按下放大
33         m_Deep += 0.1f;
34         break;
35     }
36 }

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

posted on 2020-11-27 15:08  一杯清酒邀明月  阅读(443)  评论(0编辑  收藏  举报