openGL粒子系统实现(面向对象风格)

使用基本openGL(非GLSL)实现一个面向对象风格粒子系统。

粒子系统基本有两部分构成

  • 粒子
  • 粒子发生器
一个粒子发生器控制一群粒子的行为,比如一条流水,一团火。
而本文的粒子系统在发生器上多封装一层
  • 发生器集合
也就是把多个发生器集合起来,方便同时加入多个相同的粒子系统(比如同时需要很多团火焰)。
下面开始逐层介绍。


粒子类

粒子类代表每个粒子,只有属性没有方法,如下:

class Particle {  
public:

    GLfloat life; //剩余生命
    GLfloat fade; //衰减速度
      
    //颜色
    GLfloat r;
    GLfloat g;
    GLfloat b;
      
    //位置
    GLfloat x;  
    GLfloat y;  
    GLfloat z;  
      
    //速度
    GLfloat xVel;  
    GLfloat yVel;  
    GLfloat zVel;  
      
    //加速度
    GLfloat xAcc;  
    GLfloat yAcc;  
    GLfloat zAcc;  

};

这就是本粒子系统中粒子的全部属性。而粒子发生器通过控制每个例子的以上属性来控制整个粒子群。

发生器是一个基类,而通过其不同的继承类实现不同效果的粒子系统。基类如下:

class Generator{
public:
	//粒子初始化与再造函数
	virtual void particleInit(int i)=0;	

	//粒子更新函数
	virtual void particleUpdate(int i)=0;

	//粒子渲染函数
	void particleDisplay(int i){
		glColor4f(
			particles[i].r, 
			particles[i].g, 
			particles[i].b,
			particles[i].life/particleLife
			);  

		glPointSize(particleSize);  

		glBegin(GL_POINTS);  
		glVertex3f(
			particles[i].x, 
			particles[i].y, 
			particles[i].z);   
		glEnd(); 
	}

	//发生器循环函数
	void particleMainLoop(){
		for(int i = 0; i < particleCount; i++){  
			particleDisplay(i);  
			particleUpdate(i);  
		}  
	}

protected:

	//粒子数量与对象数组
	GLuint particleCount;	
	Particle* particles;

	//粒子颜色组长度与颜色数组
	GLuint colorCount;
	GLuint (*colorList)[3]; 

	//粒子系统偏移位置 (以(0,0,0)为参照)
	GLfloat baseX, baseY, baseZ;
	//缺省生命
	GLfloat particleLife;
	//缺省大小
	GLfloat particleSize;
};

其中两个虚函数分别为粒子的初始化与更新函数。一般情况下,只需要定义这两个函数,就可以实现不同效果粒子系统。

下面以下雨系统做示范,即构建一个 RainGenerator

首先需要一个颜色数组,用以粒子运动过程中的颜色变化。假设预设10中颜色,列出数组,粒子由生到死的过程就在这个数组从头到尾过度。

const int colorCount_rain = 10;
GLuint colorArray0 [colorCount_rain][3] = {
	0,0,100, //r,g,b
	0,0,255, 
	0,10,255, 
	0,20,255,  
	0,35,255,  
	0,60,255,  
	0,80,255,  
	0,100,255,  
	0,110,255,  
	0,120,255
};

RainGenerator 定义如下

class RainGenerator: public Generator{
public:

	RainGenerator(GLfloat baseX,  GLfloat baseY, GLfloat baseZ){

		//为位置偏移量赋值
		this->baseX = baseX;
		this->baseY = baseY;
		this->baseZ = baseZ;
	
		//定义粒子数量
		//根据整体效果以及系统性能选取
		particleCount=1000;
		particles = new Particle[particleCount];

		//为颜色数组赋值
		colorList = colorArray_rain;  
		colorCount = colorCount_rain; 
				
		particleSize = 6;
		particleLife = 10;

	}

	~RainGenerator(){
		delete [] particles;
	}


	virtual void particleInit(int i){

		//雨水降落的范围
		//定为以基本位置为中心的 40*20 区域降落
		float xRange = 40;
		float zRange = 20;

		//初始化各项属性
		particles[i].life = particleLife*(rand()%100) * 0.01f; 
		particles[i].fade = 0.006f;  
		particles[i].r = colorList[colorCount-1][0]/255.0f;  
		particles[i].g = colorList[colorCount-1][1]/255.0f;  
		particles[i].b = colorList[colorCount-1][2]/255.0f;  

		particles[i].x = (float)(xRange/2 - xRange*(rand()%100)*0.01); 
		particles[i].y = 0;  
		particles[i].z = (float)(zRange/2 - zRange*(rand()%100)*0.01); 

		particles[i].xVel = 0;
		particles[i].yVel = -0.05;  
		particles[i].zVel = 0;  
		particles[i].xAcc = 0;  
		particles[i].yAcc = 0;  
		particles[i].zAcc = 0;  

		//加上偏移量
		particles[i].x += baseX;
		particles[i].y += baseY;
		particles[i].z += baseZ;
	}



	virtual void particleUpdate(int i){

		if(particles[i].life < 0.0f){  
			particleInit(i);
		}
		else {   
			//更新位置
			particles[i].x += particles[i].xVel;  
			particles[i].y += particles[i].yVel;  
			particles[i].z += particles[i].zVel;  

			//更新速度(若不需要加速度可以注释掉这几句)
			particles[i].xVel += particles[i].xAcc;  
			particles[i].yVel += particles[i].yAcc;  
			particles[i].zVel += particles[i].zAcc;  

			//更新生命
			particles[i].life -= particles[i].fade;  

			//更新颜色
			//这种方法下颜色显示的顺序是从颜色数组的最后到开头
			GLuint colorPos = (GLuint)(particles[i].life/particleLife * (colorCount -1));  
			particles[i].r = colorList[colorPos][0]/255.0f;  
			particles[i].g = colorList[colorPos][1]/255.0f;  
			particles[i].b = colorList[colorPos][2]/255.0f;  
		}  
	}
};

这里有一个细节问题:在初始化粒子位置的时候并没有在第一时间上加上整体偏移量。这是因为粒子初始化时默认以(0,0,0)为基准。如果要制造发散效果时,则需要用到这时候以原点为中心的位置,如

particles[i].x = (float)(1 - rand()%20*0.1); 
particles[i].xVel = particles[i].x /10; //制造向四周发散效果
particles[i].x += baseX; //再加上偏移量

这样的话,发生器就写好了。只需要初始化并周期性调用 mainloop 函数就可以运作这个粒子系统了。

最后再加上一个集成器,方便同时初始化多个相同的粒子系统。

class ParticleSystem{
public:
	ParticleSystem(int numOfGener, int *xpos, int *ypos, int *zpos, int SYSTYPE){
		generCount = numOfGener;
		switch(SYSTYPE){
		case RAINTYPE:
			geners = (Generator**) new RainGenerator* [n];
			for(int i = 0; i<n; i++){
				geners[i] = new RainGenerator((float)xpos[i],(float)ypos[i],(float)zpos[i]);
			}
			break;

		default:
			geners = (Generator**) new RainGenerator* [n];
			for(int i = 0; i<n; i++){
				geners[i] = new RainGenerator((float)xpos[i],(float)ypos[i],(float)zpos[i]);
			}
			break;
		}

	}

	~ParticleSystem(){
		for(int i = 0; i<generCount; i++)
			delete geners[i];
		delete [] geners;
	}

	void particleSystemMainLoop(){
		for(int i = 0; i<generCount; i++)
			geners[i]->particleMainLoop();
		//并且在适当的地方flush
	}

private:
	int generCount;
	Generator ** geners;
};
需要定义可选的系统类型

#define RAINTYPE 0
#define FIRETYPE 1
#define SNOWTYPE 2


这样一来就可以很方便使用了!在主函数里面如下初始化

int xpos[] = {70,10,-80,16};
int ypos[] = {20,20,20,40};
int zpos[] = {10,10,30,-10};

ParticleSystem * rain;
rain = new ParticleSystem(4, xpso, ypos, zpos, RAINTYPE);
就在 (70,20,10) 等四个地方加上了落雨的系统。

最后,只需要在 display 函数里面调用主循环函数

rain->particleSystemMainLoop();
并且 flush 就可以了。

是不是很方便?

给出效果图,是之前做作业时实现的几种系统,献丑。


雨落远看



雨落近看



太阳(由一个火球和一个放射系统构成)



银河



感觉实现的这几个效果还是比较可观。粒子系统能干很多事情,只受想象限制。

另外此处不用GLSL完全是为了方便。有条件还是用GLSL会快一些。

本文有些代码是写博客时即时写的,不敢确保一定没有编译错误和笔误,还望指正。


参考:dengchao66的专栏

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页