Sunday, June 8, 2014

WebGL GPU Particles

Weekend project I started about a month ago, a WebGL GPU particle simulator, simulating 1 million particles at 60 fps!

Live code:
Click to set the gravity point. Hold Alt for Maya camera controls.
(Requires webgl_draw_buffers extension. More info on how to enable.)



Technical Details
This demo can be broken into two parts: 1) the compute and 2) the view.

The compute part is where most of the work happens to simulate the particles. There are three textures representing each of the particles states: position, velocity, and color. Each particle is then represented by a UV coordinate into these textures, essentially a pixel, where its states are stored. A point to note here is that the position and velocity textures have to be float textures in order to represent the large range of numbers. At first I tried scaling up the range of a regular byte texture but there just wasn't enough resolution for the simulation.

The heavy lifting is done through a GLSL shader which I use as a compute shader. It reads in the three state textures (nearest filtering of course, wouldn't make sense otherwise) and performs a simulation step. Then it writes the results back to the corresponding framebuffers, all at once thanks to the webgl_draw_buffers extension. Because OpenGL doesn't allow writing to the same textures that you are reading from, I needed a duplicate set of framebuffers/textures to swap between every frame.

The view part is responsible for the actual drawing of the particles onto the screen. The number of particles is determined by the size of the compute textures (e.g., 1024x1024 textures would give us ~1 million particles!) As mentioned earlier, each particle is represented only by a pixel location in the state textures, so all I had to do for each vertex is to submit a UV coordinate. Then in the vertex shader, I simply use the UV to look up its position from the position texture and its color from the color texture. (The velocity texture is only used by the compute shader to run the simulation and isn't used here.)

In order to ensure smoothness, the simulation runs on a different loop than the rendering. The simulation step has to run at a fixed time step, independent of the frame rate, otherwise a variable time step would introduce irregularities in the simulation, which becomes quite apparent when there are a million particles. Putting the simulation on a fixed update step not only produces smooth results but also allows for consistent results no matter the frame rate.

This is a work in progress, and I'm planning to add more features. Most important is to add support for not having the webgl_draw_buffers extension so any up-to-date browser can run this. Then I'm planning to add a few post-processing effects like ghost trails and bloom to make it look even more awesome. Finally, I'll need better UI to expose more settings to play with!

Note on Live Demo!
It is not guaranteed to work on all machines! The demo is using a draft extension which might not be supported on your hardware. Here's how to try to get it to work:
  1. Get the latest version of Chrome
  2. Go to chrome://flags/
  3. Enable "WebGL Draft Extensions" (chrome://flags/#enable-webgl-draft-extensions)
  4. For Windows, you might also need to enable D3D11 (chrome://flags/#enable-d3d11)
If it doesn't work, try again with Firefox? I'm sorry but I promise I will add support for no extension soon!


  1. Nice! but actually the link to the live demo is broken right now.

    What about flexibility of the system? What if I would like to create another effect? Then probably whole shader would have to be rewritten?

    1. Just updated the post with how to get the demo to run. Make sure you have "WebGL Draft Extensions" flag enabled.

      The system is pretty flexible. The particle behavior is completely defined in particleCompute.fs. Just modify the shader and recompile it and you'd have a new effect. I am in fact thinking about implementing a live shader editor for this as well. It is definitely possible.

  2. Works perfectly on XP Sp3 chromium 33 d3d9. Very beautiful.

  3. this demo doesn't work on Firefox even I enable the extension config. It seems that we can't get the position from the compute buffer. Do u know why or if firefox doesn't support the extensions

  4. Hello there, is this Blog stil alive? Is there a possibility to trigger a click via script on a random position in the webgl-container? Thanks in advance