注意:这是一个十分基础的粒子系统,没有billboard没有几何着色器没有光照没有噪声处理,算是粒子系统的一个小基础
上周通过OpenGL编程指南用TransFormFeedback技术按照书上那不完全的代码补全了粒子碰撞效果,然后我本意是想做一个火球效果。。。。。结果我觉得我还是先一步步来做一个基础的粒子系统。
粒子系统的主要成分就是一个粒子发射器类,我们写好该类后,可以通过调整参数实现各种不同的效果。
首先是粒子结构体:
struct Particle
{
vec3 Position, Velocity;//定义粒子的位置和速度
vec4 Color;//定义粒子颜色
GLfloat Life;//定义粒子生命周期
Particle() : Position(0.0f), Velocity(0.0f), Color(1.0f), Life(0.0f) { }
};
然后是粒子发射器类:
class ParticleGenerator
{
public:
ParticleGenerator(Shader shader, GLuint texture, GLuint amount, vec3 gravity);
void Update(GLfloat dt, vec3 position, vec3 velocity, GLuint newParticles, vec3 offset = vec3(0.0f, 0.0f, 0.0f));//更新粒子状态
void Draw(mat4 projection, mat4 view, mat4 model);//绘制粒子
private:
vector<Particle> particles;//粒子数组,保存每个粒子的信息
GLuint amount;
Shader shader;
GLuint texture;
vec3 gravity;
GLuint VAO;
void init();//将粒子进行初始化
GLuint firstUnusedParticle();//查询死亡的粒子
void respawnParticle(Particle &particle, vec3 position,vec3 velocity, vec3 offset = vec3(0.0f, 0.0f, 0.0f));//将死亡的粒子重新初始化
};
定义完后,我们接下来就开始写各个函数:
1、粒子发射器的构造函数
ParticleGenerator::ParticleGenerator(Shader shader, GLuint texture, GLuint amount, vec3 gravity): shader(shader), texture(texture), amount(amount),gravity(gravity)
{
this->init();
}
2、init函数,该函数用于具体设置粒子的形状
GLuint VBO;
GLfloat particle_quad[] =
{
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f
};//小立方体的顶点数据
glGenVertexArrays(1, &this->VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(this->VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(particle_quad), particle_quad, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
glBindVertexArray(0);
for (GLuint i = 0; i < this->amount; ++i)
{
this->particles.push_back(Particle());
}
3、Update函数
void ParticleGenerator::Update(GLfloat dt, vec3 position, vec3 velocity, GLuint newParticles, vec3 offset)
{
//我当前想要生成newParticles个新粒子,我首先要查询是否有旧粒子死亡,为了防止生成大量数据
for (GLuint i = 0; i < newParticles; ++i)
{
int unusedParticle = this->firstUnusedParticle();//获取已经死亡的旧粒子的下标
if (unusedParticle != -1)
{
this->respawnParticle(this->particles[unusedParticle], position, velocity, offset);//重新生成该粒子
}
}
for (GLuint i = 0; i < this->amount; ++i)//更新每个粒子的状态
{
Particle &p = this->particles[i];
p.Life -= dt * 0.05; //减掉寿命
if (p.Life > 0.0f)//没死的话
{
p.Velocity = p.Velocity + dt * this->gravity;//更新速度
p.Position += p.Velocity * dt;//更新位置
p.Color.a = p.Life;//更新颜色
}
}
}
4、Draw函数
void ParticleGenerator::Draw(mat4 projection, mat4 view, mat4 model)
{
glBlendFunc(GL_SRC_ALPHA, GL_ONE);//开启融混
this->shader.Use();
for (vector<Particle>::iterator it = this->particles.begin(); it != this->particles.end(); it++)
{
if (it->Life > 0.0f)
{
glUniform3f(glGetUniformLocation(this->shader.Program, "offset"), it->Position.x, it->Position.y, it->Position.z);
//传递坐标更改值,因为我暂时没用Transformfeedback,所以用了累计函数的形式进行更新
glUniform4f(glGetUniformLocation(this->shader.Program, "color"), it->Color.x, it->Color.y, it->Color.z, it->Color.w);
//传递颜色更改值
glUniformMatrix4fv(glGetUniformLocation(this->shader.Program, "projection"), 1, GL_FALSE, value_ptr(projection));
glUniformMatrix4fv(glGetUniformLocation(this->shader.Program, "view"), 1, GL_FALSE, value_ptr(view));
glUniformMatrix4fv(glGetUniformLocation(this->shader.Program, "model"), 1, GL_FALSE, value_ptr(model));
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, this->texture);
glBindVertexArray(this->VAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
}
}
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);//将融混模式调回默认状态
}
5、firstUnusedParticle函数,获取最先死亡的粒子
GLuint ParticleGenerator::firstUnusedParticle()
{
//因为粒子随着时间生成,所以我们可以用一个lastUsedParticle记录上一个死亡的粒子的下标,因为下一个死亡的粒子的下标往往在上一个死亡粒子的后面
for (GLuint i = lastUsedParticle; i < this->amount; ++i)
{
if (this->particles[i].Life <= 0.0f)
{
lastUsedParticle = i;
return i;
}
}
//如果没有找到的话,我们再找前面是否有粒子死亡
for (GLuint i = 0; i < lastUsedParticle; ++i)
{
if (this->particles[i].Life <= 0.0f)
{
lastUsedParticle = i;
return i;
}
}
lastUsedParticle = 0;
return -1;
}
6、respawnParticle函数
void ParticleGenerator::respawnParticle(Particle &particle, vec3 position,vec3 velocity, vec3 offset)
{
GLfloat random1 = ((rand() % 100) - 50) / 10.0f;
GLfloat random2 = ((rand() % 100) - 50) / 10.0f;
GLfloat rColor = 0.5 + ((rand() % 100) / 100.0f);
particle.Position = position;//定义初始位置
particle.Color = vec4(rColor, rColor, rColor, 1.0f);//定义初始颜色
particle.Life = 1.0f;//定义生命值大小
vec3 direction = normalize(vec3(random1, 0.0f, random2));
particle.Velocity = (velocity + direction * 100.0f + offset) * 0.1f;//定义初始速度
}
最后我们需要在main函数中调用:
ParticleGenerator *particle = new ParticleGenerator(particleshader, textureofparticle, 10000, vec3(0.0f, -9.8f, 0.0f));
particle->Update(deltaTime, vec3(0.0f, 0.0f, 0.0f), vec3(0.0f, 500.0f, 0.0f), 2, vec3(0.0f));
//参数分别为距离绘制上一帧的时间差,发射器初始位置,粒子初始速度,更新粒子的数量,发射器范围大小(类比于枪械的口径)
particle->Draw(projection, camera.GetViewMatrix(), model);