这次教程中,我将教大家如何创建一个飘动的旗帜。我们所要创建的旗帜,说白了就是一个以正弦波方式运动的纹理映射图像。虽然不会很难,但效果确实很不错,希望大家能喜欢。当然这次教程是基于第06课的,希望大家确保已经掌握了前6课再进入本次教程。
程序运行时效果如下:
下面进入教程:
我们这次将在第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 bool fullscreen; //是否全屏显示
24
25 GLfloat m_xRot; //绕x轴旋转的角度
26 GLfloat m_yRot; //绕y轴旋转的角度
27 GLfloat m_zRot; //绕z轴旋转的角度
28 QString m_FileName; //图片的路径及文件名
29 GLuint m_Texture; //储存一个纹理
30
31 float m_Points[45][45][3]; //储存网格顶点的数组
32 int m_WiggleCount; //用于控制旗帜波浪运动动画
33 };
34
35 #endif // MYGLWIDGET_H
我们增加了m_Points三维数组来存放网格各顶点独立的x、y、z坐标,这里网格由45×45点形成,换句话说也就是由44格×44格的小方格子组成的。另一个新增变量m_WiggleCount用来使产生纹理波浪运动动画,每2帧一次变换波动形状看起来很不错。
接下来,我们需要打开myglwidget.cpp,加上声明#include <QtMath>,在构造函数对新增变量数据进行初始化,具体代码如下:
1 MyGLWidget::MyGLWidget(QWidget *parent) :
2 QGLWidget(parent)
3 {
4 fullscreen = false;
5 m_xRot = 0.0f;
6 m_yRot = 0.0f;
7 m_zRot = 0.0f;
8 m_FileName = "D:/QtOpenGL/QtImage/Tim.bmp"; //应根据实际存放图片的路径进行修改
9
10 for (int x=0; x<45; x++) //初始化数组产生波浪效果(静止)
11 {
12 for (int y=0; y<45; y++)
13 {
14 m_Points[x][y][0] = float((x / 5.0f) - 4.5f);
15 m_Points[x][y][1] = float((y / 5.0f) - 4.5f);
16 m_Points[x][y][2] = float(sin((((x/5.0f)*40.0f)/360.0f)*3.141592654*2.0f));
17 }
18 }
19 m_WiggleCount = 0;
20
21 QTimer *timer = new QTimer(this); //创建一个定时器
22 //将定时器的计时信号与updateGL()绑定
23 connect(timer, SIGNAL(timeout()), this, SLOT(updateGL()));
24 timer->start(10); //以10ms为一个计时周期
25 }
增加的代码就是一个循环,利用循环来添加波浪效果(只是让旗帜看起来有起伏效果,还不能达到波动动画的目的)。值得注意的是,我们在求m_Points[x][y][0]和m_Points[x][y][1]时,都是用x、y除以5.0f,如果除以5的话,由于整数除法取整,会导致画面出现锯齿效果,这显然不是我们想要的。最后减去4.5f这样使得计算结果落在区间[-4.5, 4.5],也就让我们的波浪可以“居中”了。点m_Points[x][y][2]最后的值就是一个sin()函数计算的结果(因为我们模拟的是正弦波运动),×8.0f是求相应角度(360度平分到45个点就是8度一个点了),最后角度转换到弧度制我就不多做解释了。
然后在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 glEnable(GL_DEPTH_TEST); //启用深度测试
10 glDepthFunc(GL_LEQUAL); //所作深度测试的类型
11 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); //告诉系统对透视进行修正
12
13 glPolygonMode(GL_BACK, GL_FILL); //后表面完全填充
14 glPolygonMode(GL_FRONT, GL_LINE); //前表面使用线条绘制
15 }
最后加了两行代码,用来指定使用完全填充模式来填充多边形区域的后表面,而多边形的前表面则使用轮廓线填充,这些方式完全取决于你的个人喜好,这里我们只是为了区分前后表面罢了。
最后,我们将重写整个paintGL()函数,当然这依旧是重点,代码如下:
1 void MyGLWidget::paintGL() //从这里开始进行所以的绘制
2 {
3 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存
4 glLoadIdentity(); //重置当前的模型观察矩阵
5
6 glTranslatef(0.0f, 0.0f, -15.0f); //移入屏幕15.0单位
7 glRotatef(m_xRot, 1.0f, 0.0f, 0.0f); //绕x旋转
8 glRotatef(m_yRot, 0.0f, 1.0f, 0.0f); //绕y旋转
9 glRotatef(m_zRot, 0.0f, 0.0f, 1.0f); //绕z旋转
10
11 glBindTexture(GL_TEXTURE_2D, m_Texture); //旋转纹理
12 float flag_x1, flag_y1, flag_x2, flag_y2; //用来将纹理分割成小的四边形方便纹理映射
13 glBegin(GL_QUADS);
14 for (int x=0; x<44; x++)
15 {
16 for (int y=0; y<44; y++)
17 {
18 //分割纹理
19 flag_x1 = float(x) / 44.0f;
20 flag_y1 = float(y) / 44.0f;
21 flag_x2 = float(x+1) / 44.0f;
22 flag_y2 = float(y+1) / 44.0f;
23
24 //绘制一个小的四边形
25 glTexCoord2f(flag_x1, flag_y1);
26 glVertex3f(m_Points[x][y][0], m_Points[x][y][1], m_Points[x][y][2]);
27 glTexCoord2f(flag_x1, flag_y2);
28 glVertex3f(m_Points[x][y+1][0], m_Points[x][y+1][1], m_Points[x][y+1][2]);
29 glTexCoord2f(flag_x2, flag_y2);
30 glVertex3f(m_Points[x+1][y+1][0], m_Points[x+1][y+1][1], m_Points[x+1][y+1][2]);
31 glTexCoord2f(flag_x2, flag_y1);
32 glVertex3f(m_Points[x+1][y][0], m_Points[x+1][y][1], m_Points[x+1][y][2]);
33 }
34 }
35 glEnd();
36
37 if (m_WiggleCount == 3) //用来变换波浪形状(每2帧一次)产生波浪动画
38 {
39 //利用循环使波浪值集体左移,最左侧波浪值到了最右侧
40 for (int y=0; y<45; y++)
41 {
42 float temp = m_Points[0][y][2];
43 for (int x=0; x<44; x++)
44 {
45 m_Points[x][y][2] = m_Points[x+1][y][2];
46 }
47 m_Points[44][y][2] = temp;
48 }
49 m_WiggleCount = 0; //计数器清零
50 }
51 m_WiggleCount++; //计数器加一
52
53 m_xRot += 0.3f;
54 m_yRot += 0.2f;
55 m_zRot += 0.4f;
56 }
我们创建了四个浮点临时变量并利用循环和除法,将纹理分割成小的四边形,使得我们能准确的对应进行纹理映射,然后画出全部的四边形拼到一起就是一个波动状态的旗帜了。
接着我们判断一下m_WiggleCount是否为2,如果是,就将波浪值m_Points[x][y][2]集体循环左移(最左侧波浪值会到最右侧)。这样我们相当于每2帧一次变化了旗帜的波动状态,看起来就是一个飘动的旗帜,不是静止的了(大家可以尝试着注释掉某一部分代码看看发生什么改变)。然后计数器清零加一什么的就不过多解释了!
现在就可以运行程序查看效果了!