Particle system

A simple particle system I created and a fire effect test scene.

November 24, 2017 - 3 minute read -
opengl cpp mac

It is really a simple one but with a very satisfied effect. :sparkles:

Fire :fire:

For a good result, I create this fire effect with 4 Emitters and 1 light:

  • smoke: 20 particles.
  • spark: 40 particles.
  • drop: 8 particles.
  • fire: 26 particles with uv animation.
  • light: a real light, color of (1, 0.4, 0.1).

So there will be a totally 94 particles for each fire effect.

Component

Like all the other game engines, the whole particle system is simply combined with two parts: Emitter and Particle.

Emitter

Emitter is for randomly generating Particle components. And here are some features currently supporting:

  • uv animation when set to true, play a uv animation.
  • divide texture divide x and y for uv animation.
  • sample time time between each frame for uv animation.
  • max particles max number of particles for Emitter to generate.
  • spawn time particle spawn time.
  • life time defined by max and min.
  • init scale init scale. (And not support randomly generating for now)
  • init velocity defined by horizontal and vertical random range.
  • gravity gravity behavior.

Particle

Particle component is really simple. It will just do a update.

Techniques

Effect render pass

For a classic way to render a particle system, there usually is a sort progress for alpha test, during which all the particles will be sorted by their z position.

render scene -> sort particles -> render particles

But by using OpenGL depth map settings, there is a more efficient way to do a particle rendering. And this is also much more controllable.

render scene -> copy depth -> set depth to read only ->
set blend mode to add or alpha -> render particles

And here is the code:

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, this->fxPass.fbo);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
glClear(GL_COLOR_BUFFER_BIT);
glBlendFunc(GL_ONE, GL_ONE);
glDepthMask(GL_FALSE);
fxLayer->render(camera);

Instance draw

Since there will 94 draw calls for each fire effect, so just use OpenGL instance draw function. And there will be 4 draw calls for each fire after all.

template <typename T> void drawInstance(std::vector<T>* instances) {
  glBindBuffer(GL_ARRAY_BUFFER, this->vboInstance);
  glBufferSubData(GL_ARRAY_BUFFER, 0, instances->size() * sizeof(T), instances->data());
  glDrawElementsInstanced(GL_TRIANGLES, this->count, GL_UNSIGNED_INT, 0, instances->size());
}

Implementation

Scene the whole system is based on a ECS (Entity Component System), to implement this particle system will be something like this:

...
GameObject* fire = new GameObject();
Emitter* fireEmitter = fire->addComponent<Emitter>();
// emitter settings
fireEmitter->material = game->resources->getMaterial("fx_fire");
fireEmitter->animation = true;
...
// create particles
fireEmitter->createParticles("particle_name", scene);
// add to current scene
scene->addGameObject("object_name"), fire);
...

Assets

  • Character and animation from mixamo.