featured image
Tutorials

Reverse Particles and Fading Away

   - 

I need to build a sort of vacuum cleaner effect for a little game I’m working on. This means the particles should be distributed randomly in an area then come back to a center point to show they are being ‘sucked into’ my vacuum cleaner object. I could do this by calculating the exact velocity and rotation for every particle so that they all move towards the same center point. But I’m lazy, so let’s just reverse time instead.

This article is part of my ongoing series of medium difficulty ThreeJS tutorials. I’ve long wanted something in between the intro “How to draw a cube” and “Let’s fill the screen with shader madness” levels. So here it is.

The timeElapsed

One of the advantages of GPU particles is that the path of a particle is computed entirely from input values set in the attribute buffer combined with the time uniform. If we just run the time uniform backwards then time itself will be reversed and particles will go from their endpoints back to a central origin. However there will be some strange behavior when some particles have lifetimes different than others. Instead let’s have the time uniform move forward but create an intermediate variable in the shader called timeElapsed that all calculations will be based on.

To make this work I added a new config variable to my GPUParticleSystem class called reverseTime. This is passed into the vertex shader as a uniform. Now I can create timeElapsed in the shader. It represents the time that has passed since this particle was created (based on the world time and the start time of the particle).

Everything else that depends on time — the alpha, position, color, etc — will use timeElapsed. If time is reversed then timeElapsed will be the time left until death instead of time since birth. Now all effects will be reversed.

Below is a subset of the full shader code

uniform bool reverseTime;
…
void main() {
   vColor = vec4( color, 1.0 );
   vEndColor = vec4( endColor, 1.0);
   vec3 newPosition;
   float timeElapsed = uTime — startTime;
   if(reverseTime) timeElapsed = lifeTime — timeElapsed;
 
   lifeLeft = 1.0 — ( timeElapsed / lifeTime );
   gl_PointSize = ( uScale * size ) * lifeLeft;
   newPosition = positionStart 
       + (velocity * timeElapsed)
       + (acceleration * 0.5 * timeElapsed * timeElapsed)
       ;
   if (lifeLeft < 0.0) { 
       lifeLeft = 0.0; 
       gl_PointSize = 0.;
   }
   //while active use the new position
   if( timeElapsed > 0.0 ) {
       gl_Position = projectionMatrix * modelViewMatrix 
           * vec4( newPosition, 1.0 );
   } else {
       //if dead use the initial position and set point size to 0
       gl_Position = projectionMatrix * modelViewMatrix 
           * vec4( position, 1.0 );
       lifeLeft = 0.0;
       gl_PointSize = 0.;
   }
}

Fade Control

The other feature I need to make this a killer particle system is better control over the alpha value. Currently the alpha is 1 at birth and decays out to 0. In most cases this is not what we actually want. Instead we want particles to fade in, exist at full alpha for a while, then fade out just before they die. We could set fade attributes for each particle, but in most effects the particles fade at the same rate, so I made fadeIn and fadeOut be uniforms.

Add new uniforms when creating the shader:

this.material = new THREE.ShaderMaterial( {
   transparent: true,
   depthWrite: false,
   uniforms: {
     ‘uTime’: {
       value: 0.0
      },
…
     reverseTime: {
         value: this.reverseTime
     },
     fadeIn: {
         value: this.fadeIn
     },
     fadeOut: {
         value: this.fadeOut,
     }
   },
   blending: this.blending,
   vertexShader: GPUParticleShader.vertexShader,
   fragmentShader: GPUParticleShader.fragmentShader
});

And the vertex shader changes:

uniform float fadeIn;
uniform float fadeOut;
…
void main() {
…
if(timeElapsed < fadeIn) {
   alpha = timeElapsed/fadeIn;
}
if(timeElapsed >= fadeIn && timeElapsed <= (lifeTime — fadeOut)) {
   alpha = 1.0;
}
if(timeElapsed > (lifeTime — fadeOut)) {
   alpha = 1.0 — (timeElapsed — (lifeTime-fadeOut))/fadeOut;
}

Let me explain what the code above is doing. To calculate the alpha from the fade values we can divide the particles lifetime into three sections.

  1. from birth through fadeIn, alpha goes from 0 to 1
  2. from fadeIn through fadeOut, alpha is 1
  3. from fadeOut through death: alpha goes from 1 to 0

The three conditionals above implement those three cases.

You can check out the reverse time and alpha effects in this new demo of sucking a skeleton into a monster trap.

Skeleton in Trap

Particles in Reverse

As always the source for everything is on github.

Until Next Time

Remember, if you are working on a cool WebVR experience that you’d like to have showcased right in Firefox Reality, let us know.