想知道如何在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 }
现在就可以运行程序查看效果了!