【Qt OpenGL教程】19:粒子系统
来源:互联网 发布:金盏花洁面啫喱知乎 编辑:程序博客网 时间:2024/05/16 05:26
第19课:粒子系统 (参照NeHe)
这次教程中,我们将创建一个简单的粒子系统,并用它来创建一种喷射效果。利用粒子系统,我们可以实现爆炸、喷泉、流星之类的效果,听起来是不是很棒呢!
我们还会讲到一个新东西,三角形带(我的理解就是画很多三角形来组合成我们要的形状),它非常容易使用,而且当需要画很多三角形的时候,它能加快你程序的运行速度。这次教程中,我将教你该如何做一个简单的微粒程序,一旦你了解微粒程序的原理后,再创建例如:火、烟、喷泉等效果将是很轻松的事情。
程序运行时效果如下:
下面进入教程:
我们这次将在第06课代码的基础上修改代码,这次需要修改的代码量不少,希望大家耐心跟着我一步步来完成这个程序。首先打开myglwidget.h文件,将类声明更改如下:
#ifndef MYGLWIDGET_H#define MYGLWIDGET_H#include <QWidget>#include <QGLWidget>class MyGLWidget : public QGLWidget{ Q_OBJECTpublic: explicit MyGLWidget(QWidget *parent = 0); ~MyGLWidget();protected: //对3个纯虚函数的重定义 void initializeGL(); void resizeGL(int w, int h); void paintGL(); void keyPressEvent(QKeyEvent *event); //处理键盘按下事件private: bool fullscreen; //是否全屏显示 QString m_FileName; //图片的路径及文件名 GLuint m_Texture; //储存一个纹理 static const int MAX_PARTICLES = 1000; //最大粒子数 static const GLfloat COLORS[12][3]; //彩虹的颜色 bool m_Rainbow; //是否为彩虹模式 GLuint m_Color; //当前的颜色 float m_Slowdown; //减速粒子 float m_xSpeed; //x方向的速度 float m_ySpeed; //y方向的速度 float m_Deep; //移入屏幕的距离 struct Particle //创建粒子结构体 { bool active; //是否激活 float life; //粒子生命 float fade; //衰减速度 float r, g, b; //粒子颜色 float x, y, z; //位置坐标 float xi, yi, zi; //各方向速度 float xg, yg, zg; //各方向加速度 } m_Particles[MAX_PARTICLES]; //存放1000个粒子的数组};#endif // MYGLWIDGET_H首先我们定义了一个静态整形常量MAX_PARTICLES来存放粒子的最大数目,和一个静态GLfloat常量数组来存放彩虹的颜色。接着是一个布尔变量m_Rainbow来表示当前模式是否为彩虹模式,然后是GLuint变量m_Color来表示当前的粒子的颜色,它将在控制粒子颜色在彩虹颜色数组中切换。粒子颜色会与纹理融合,我们用纹理而不用电的重要原因是,点的速度慢,而且挺麻烦的,其次纹理很酷,也好控制。
下面四行是定义了四个浮点变量。m_Slowdown控制粒子移动的快慢,数值越高移动越快,数值越低移动越慢,粒子的速度将影响它们在屏幕上移动的距离,要注意速度慢的粒子不会移动很远就会消失。m_xSpeed和m_ySpeed控制尾部的方向,m_xSpeed为正时粒子将会向右移动,负时则向左移动,m_ySpeed为正时粒子将会向上移动,负时则向下移动,m_xSpeed和m_ySpeed有助于在我们想要的方向上移动粒子。最后是变量m_Deep,我们用该变量移入移除我们的屏幕,在粒子系统中,有时当接近你时,可以看见更多美妙的图像。
最后我们定义了结构体Particle,用来描述某一粒子的状态属性。我们用布尔变量active开始,如果为true,我们的粒子为活跃的;如果为false则粒子为死的,此时我们就不绘制它。变量life和fade来控制粒子显示多久以及显示时候的亮度,随着life数值的降低fade的数值也相应减低,这将导致一些粒子比其他粒子燃烧的时间长。后面是记录粒子颜色,位置,速度,加速度等状态属性的变量,作用我想大家会点高中物理都能明白的,最后我们创建一个长度为MAX_PARTICLES的结构体数组。
接下来,我们打开myglwidget.cpp,在构造函数中对新增变量进行初始化,具体代码如下:
const GLfloat MyGLWidget::COLORS[][3] = //彩虹的颜色{ {1.0f, 0.5f, 0.5f}, {1.0f, 0.75f, 0.5f}, {1.0f, 1.0f, 0.5f}, {0.75f, 1.0f, 0.5f}, {0.5f, 1.0f, 0.5f}, {0.5f, 1.0f, 0.75f}, {0.5f, 1.0f, 1.0f}, {0.5f, 0.75f, 1.0f}, {0.5f, 0.5f, 1.0f}, {0.75f, 0.5f, 1.0f}, {1.0f, 0.5f, 1.0f}, {1.0f, 0.5f, 0.75f}};MyGLWidget::MyGLWidget(QWidget *parent) : QGLWidget(parent){ fullscreen = false; m_FileName = "D:/QtOpenGL/QtImage/Particle.bmp"; //应根据实际存放图片的路径进行修改 m_Rainbow = true; m_Color = 0; m_Slowdown = 2.0f; m_xSpeed = 0.0f; m_ySpeed = 0.0f; m_Deep = -40.0f; for (int i=0; i<MAX_PARTICLES; i++) //循环初始化所以粒子 { m_Particles[i].active = true; //使所有粒子为激活状态 m_Particles[i].life = 1.0f; //所有粒子生命值为最大 //随机生成衰减速率 m_Particles[i].fade = float(rand()%100)/1000.0f+0.001; //粒子的颜色 m_Particles[i].r = COLORS[int(i*(12.0f/MAX_PARTICLES))][0]; m_Particles[i].g = COLORS[int(i*(12.0f/MAX_PARTICLES))][1]; m_Particles[i].b = COLORS[int(i*(12.0f/MAX_PARTICLES))][2]; //粒子的初始位置 m_Particles[i].x = 0.0f; m_Particles[i].y = 0.0f; m_Particles[i].z = 0.0f; //随机生成x、y、z轴方向速度 m_Particles[i].xi = float((rand()%50)-26.0f)*10.0f; m_Particles[i].yi = float((rand()%50)-25.0f)*10.0f; m_Particles[i].zi = float((rand()%50)-25.0f)*10.0f; m_Particles[i].xg = 0.0f; //设置x方向加速度为0 m_Particles[i].yg = -0.8f; //设置y方向加速度为-0.8 m_Particles[i].zg = 0.0f; //设置z方向加速度为0 } QTimer *timer = new QTimer(this); //创建一个定时器 //将定时器的计时信号与updateGL()绑定 connect(timer, SIGNAL(timeout()), this, SLOT(updateGL())); timer->start(10); //以10ms为一个计时周期}注意到我们在构造函数之前对定义的静态常量数组COLORS进行初始化,一共包含12种渐变颜色,从红色到紫罗兰。进入构造函数一开始是更换纹理图片以及增加变量的初始化,这些没什么好解释的,下面我们重点看循环部分。我们利用循环来初始化每个粒子,我们让粒子变活跃(不活跃的粒子在屏幕上是不会显示的)之后,我们给它lfie。life满值是1.0f,这也给粒子完整的光亮。值得一提,把粒子的生命衰退和颜色渐暗绑到一起,效果真的很不错!
我们通过随机数来设置粒子退色的快慢,我们取0~99的随机数,然后平分1000份来得到一个很小的浮点数,最后结果加上0.001f来使fade速度值不为0。我们既然给了粒子生命,我们当然要给它其他的属性状态附上值,为了使粒子有不同的颜色,我们用i 变量乘以数组中颜色的数目(12)与MAX_PARTICLES的商,再转换成整数,利用得到的整数取对应的颜色就可以了。然后让粒子从(0, 0, 0)出发,在设定速度时,我们通过将结果乘上10.0f来创造开始时的爆炸效果,加速度就由我们统一指定初始值了。
然后,我们来略微修改initializeGL()函数,代码如下:
void MyGLWidget::initializeGL() //此处开始对OpenGL进行所以设置{ m_Texture = bindTexture(QPixmap(m_FileName)); //载入位图并转换成纹理 glEnable(GL_TEXTURE_2D); //启用纹理映射 glClearColor(0.0f, 0.0f, 0.0f, 0.0f); //黑色背景 glShadeModel(GL_SMOOTH); //启用阴影平滑 glClearDepth(1.0); //设置深度缓存 glDisable(GL_DEPTH_TEST); //禁止深度测试 glEnable(GL_BLEND); //启用融合 glBlendFunc(GL_SRC_ALPHA, GL_ONE); //设置融合因子 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); //告诉系统对透视进行修正 glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);}我们在中间启用了融合并设置了融合因子,这是为了我们的粒子能有不同颜色。然后我们禁用了深度测试,因为如果启用深度测试的话,纹理之间会出现覆盖现象,那样画面简直一团糟。
还有,我们要进入有趣的paintGL()函数了,具体代码如下:
void MyGLWidget::paintGL() //从这里开始进行所以的绘制{ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存 glLoadIdentity(); //重置模型观察矩阵 glBindTexture(GL_TEXTURE_2D, m_Texture); for (int i=0; i<MAX_PARTICLES; i++) //循环所以的粒子 { if (m_Particles[i].active) //如果粒子为激活的 { float x = m_Particles[i].x; //x轴位置 float y = m_Particles[i].y; //y轴位置 float z = m_Particles[i].z + m_Deep; //z轴位置 //设置粒子颜色 glColor4f(m_Particles[i].r, m_Particles[i].g, m_Particles[i].b, m_Particles[i].life); glBegin(GL_TRIANGLE_STRIP); //绘制三角形带 glTexCoord2d(1, 1);glVertex3f(x+0.5f, y+0.5f, z); glTexCoord2d(0, 1);glVertex3f(x-0.5f, y+0.5f, z); glTexCoord2d(1, 0);glVertex3f(x+0.5f, y-0.5f, z); glTexCoord2d(0, 0);glVertex3f(x-0.5f, y-0.5f, z); glEnd(); //更新各方向坐标及速度 m_Particles[i].x += m_Particles[i].xi/(m_Slowdown*1000); m_Particles[i].y += m_Particles[i].yi/(m_Slowdown*1000); m_Particles[i].z += m_Particles[i].zi/(m_Slowdown*1000); m_Particles[i].xi += m_Particles[i].xg; m_Particles[i].yi += m_Particles[i].yg; m_Particles[i].zi += m_Particles[i].zg; m_Particles[i].life -= m_Particles[i].fade; //减少粒子的生命值 if (m_Particles[i].life < 0.0f) //如果粒子生命值小于0 { m_Particles[i].life = 1.0f; //产生一个新粒子 m_Particles[i].fade = float(rand()%100)/1000.0f+0.003f; m_Particles[i].r = colors[m_Color][0]; //设置颜色 m_Particles[i].g = colors[m_Color][1]; m_Particles[i].b = colors[m_Color][2]; m_Particles[i].x = 0.0f; //粒子出现在屏幕中央 m_Particles[i].y = 0.0f; m_Particles[i].z = 0.0f; //随机生成粒子速度 m_Particles[i].xi = m_xSpeed + float((rand()%60)-32.0f); m_Particles[i].yi = m_ySpeed + float((rand()%60)-30.0f); m_Particles[i].zi = float((rand()%60)-30.0f); } } } if (m_Rainbow) //如果为彩虹模式 { m_Color++; //进行颜色的变换 if (m_Color > 11) { m_Color = 0; } }}paintGL()函数中,我们在循环中没有重置模型观察矩阵,因为我们并没有使用过glRotate和glTranslate函数,我们在画粒子位置的时候,计算出相应坐标,用glVertex3f()函数来代替glTranslate函数,这样在我们画粒子的时候就不会改变模型观察矩阵了。
然后我们建立一个循环,在循环中更新绘制每一个粒子。首先检查粒子是否活跃,如果不活跃则不被更新(在这个程序中,它们始终都是活跃的)。接着定义三个临时变量存放粒子的x、y、z值,设置粒子颜色,然后就来绘制它了,我们用一个三角形带来代替四边形这样使程序运行快一点(一般情况是这样,关于三角形带点此有相关文章)。
接下来我们来移动粒子。首先我们取得当前粒子的x位置,然后把x运动速度加上粒子被减速1000倍后的值。所以如果粒子在x轴(0)上屏幕中心的位置,x轴速度(xi)为+10,而m_Slowdown为1,我们可以以10/(1*1000)或0.01f速度移向右边。如果,m_slowDown值到2我们的速度就只有0.005f了。这也是为什么yong10.0f乘开始值来叫像素移动快速,制造一个爆发效果。然后我们要根据加速度更新我们粒子的速度,根据衰退速度更新我们粒子的生命。
最后我们检查粒子是否还活着(生命值大于0),如果粒子烧尽,我们会使它恢复,我们给它满值生命和新的衰退速度。当然我们也重新设定粒子回到屏幕中心,然后重新随机生成速度。要注意,我们没有将移动速度乘10,我们这次不想要一个爆发效果,而要比较慢地移动粒子;然后我们要相应的加上m_xSpeed和m_ySpeed,这个控制了粒子大体得移动方向。最后我们给粒子分配当前的颜色就搞定循环了。
函数最后,我们判断是否为彩虹模式,如果是就改变当前的颜色,这样不同时间“重生”后的粒子就可能得到不同的颜色,从而出现彩虹效果。
最后就是键盘控制了,由于为了增加点趣味性,这次键盘控制比较“麻烦”,但是调理很清晰,具体代码如下:
void MyGLWidget::keyPressEvent(QKeyEvent *event){ switch (event->key()) { case Qt::Key_F1: //F1为全屏和普通屏的切换键 fullscreen = !fullscreen; if (fullscreen) { showFullScreen(); } else { showNormal(); } updateGL(); break; case Qt::Key_Escape: //ESC为退出键 close(); break; case Qt::Key_Tab: //Tab按下使粒子回到原点,产生爆炸 for (int i=0; i<MAX_PARTICLES; i++) { m_Particles[i].x = 0.0f; m_Particles[i].y = 0.0f; m_Particles[i].z = 0.0f; //随机生成速度 m_Particles[i].xi = float((rand()%50)-26.0f)*10.0f; m_Particles[i].yi = float((rand()%50)-25.0f)*10.0f; m_Particles[i].zi = float((rand()%50)-25.0f)*10.0f; } break; case Qt::Key_8: //按下8增加y方向加速度 for (int i=0; i<MAX_PARTICLES; i++) { if (m_Particles[i].yg < 3.0f) { m_Particles[i].yg += 0.05f; } } break; case Qt::Key_2: //按下2减少y方向加速度 for (int i=0; i<MAX_PARTICLES; i++) { if (m_Particles[i].yg > -3.0f) { m_Particles[i].yg -= 0.05f; } } break; case Qt::Key_6: //按下6增加x方向加速度 for (int i=0; i<MAX_PARTICLES; i++) { if (m_Particles[i].xg < 3.0f) { m_Particles[i].xg += 0.05f; } } break; case Qt::Key_4: //按下4减少x方向加速度 for (int i=0; i<MAX_PARTICLES; i++) { if (m_Particles[i].xg > -3.0f) { m_Particles[i].xg -= 0.05f; } } break; case Qt::Key_Plus: //+ 号按下加速粒子 if (m_Slowdown > 1.0f) { m_Slowdown -= 0.05f; } break; case Qt::Key_Minus: //- 号按下减速粒子 if (m_Slowdown < 3.0f) { m_Slowdown += 0.05f; } break; case Qt::Key_PageUp: //PageUp按下使粒子靠近屏幕 m_Deep += 0.5f; break; case Qt::Key_PageDown: //PageDown按下使粒子远离屏幕 m_Deep -= 0.5f; break; case Qt::Key_Return: //回车键为是否彩虹模式的切换键 m_Rainbow = !m_Rainbow; break; case Qt::Key_Space: //空格键为颜色切换键 m_Rainbow = false; m_Color++; if (m_Color > 11) { m_Color = 0; } break; case Qt::Key_Up: //Up按下增加粒子y轴正方向的速度 if (m_ySpeed < 400.0f) { m_ySpeed += 5.0f; } break; case Qt::Key_Down: //Down按下减少粒子y轴正方向的速度 if (m_ySpeed > -400.0f) { m_ySpeed -= 5.0f; } break; case Qt::Key_Right: //Right按下增加粒子x轴正方向的速度 if (m_xSpeed < 400.0f) { m_xSpeed += 5.0f; } break; case Qt::Key_Left: //Left按下减少粒子x轴正方向的速度 if (m_xSpeed > -400.0f) { m_xSpeed -= 5.0f; } break; }}我感觉注释已经写得比较清楚了,就不解释太多了,具体里面的值是怎么得到的,其实就是一点点尝试,感觉效果好久用了,就这么简单!大家注意一下Tab键按下后,全部粒子会回到原点,重新从原点出发,并且我们给它们重新生成速度,方式和初始化时是相同的,这样就又产生了爆炸效果。
现在就可以运行程序查看效果了!
全部教程中需要的资源文件点此下载
- 【Qt OpenGL教程】19:粒子系统
- OpenGL粒子系统模型
- OpenGL--粒子系统
- OpenGL粒子系统
- 30.OpenGL--粒子系统
- OpenGL--粒子系统
- UIKit粒子系统教程
- android opengl es 粒子系统
- OpenGL进阶(六)-粒子系统
- OpenGL进阶(六)-粒子系统
- OpenGL——粒子系统
- OpenGL系统设计-粒子系统(1)
- OpenGL系统设计-粒子系统(2)
- OpenGL系统设计-粒子系统(3)
- OpenGL系统设计-粒子系统(4)
- ActionScript 3 粒子系统教程
- Qt OpenGL教程
- Qt OpenGL教程
- app后端搭建聊天服务器的经历
- 好的博客链接
- Android的屏幕适配
- windows下基于虚拟机的Ubuntu安装
- NDK crash栈信息的错误定位
- 【Qt OpenGL教程】19:粒子系统
- CSS3变形与动画下
- 通知
- aws 代码扫描所有dynamoDB数据返回ScanItemResult格式
- UVALive - 4043 Ants (KM裸题)
- 手机号码匹配
- 杭电 1016 Prime Ring Problem【DFS】
- android 语音识别
- Java--内部类,局部类与匿名类