WebGL and Three.js: Particles
Written by Greg Stier
What is WebGL / Three.js?
WebGL is a Javascript API used to render 3D graphics to the screen in a browser. The WebGL API can be complicated and messy, but lucky for us there are libraries that simplify this. One such library is Three.js.
Three.js
Three.js is a lightweight 3D library with a rich feature set that hides the complexities of WebGL and makes it very simple to get started with 3D programming on the web.
Three.js can be downloaded from github or the three.js website, where you will also find links to documentation and examples.
Introduction
This is the sixth tutorial in a series I’ve written about programming 3D graphics in the browser using Three.js. In this tutorial we are going to cover the steps needed to create a particle system using Three.js.
A particle system is a loosely defined term, but in general, it is a rendering technique that uses many small sprites or 3D meshes to create certain types of effects that are hard or inefficient to create with traditional rendering techniques. Examples of effects created with particles would include: fire, smoke, rain, snow, clouds, dust, muzzle flashes, and explosions. I thought that since we are in the middle of the holiday season, it would be appropriate (at least where I live) to create a particle system that simulates a snowfall.
To view this demonstration, you will need a WebGL compatible browser. If you have a recent version of any major browser, you should be able to view it. If you are running an older version of Internet Explorer or a mobile device, you may be out of luck. You can view the result of this tutorial here, and the source code for this tutorial can be found here.
If you would like to learn some more basics of Three.js programming, I encourage you to take a look at my previous tutorials:
Getting Started
In this tutorial I’m going to begin with a simple HTML file similar to the ones I’ve used in my previous tutorials:
<!DOCTYPE HTML>
<html>
<head>
<title>WebGL/Three.js: Particles</title>
<style>
body {
margin: 0px;
background-color: #fff;
overflow: hidden;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r72/three.min.js"></script>
<script src="js/particles.js"></script>
</body>
</html>
The HTML assumes that you have a folder named js at the same level as your html file. You can see from the code above that I’m loading the Three.js library from a cdn, and for this tutorial I’m using version r72. The code for this application is in particles.js located in the js folder.
The scene I’m going to create is a very simple scene. It will contain one rotating cube as our main scene element and we will then add the snowfall to give it a nice backdrop. The cube isn’t necessary, but since we are creating a snowfall backdrop, having something in the scene helps to show the volume and depth of our particle system. Below is the starting code for our application:
var camera;
var scene;
var renderer;
var cubeMesh;var clock;
var deltaTime;var particleSystem;
init();
animate();function init() {
clock = new THREE.Clock(true);
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 50;var light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 1, -1, 1 ).normalize();
scene.add(light);var geometry = new THREE.CubeGeometry( 10, 10, 10);
var material = new THREE.MeshPhongMaterial( { color: 0x0033ff, specular: 0x555555, shininess: 30 } );cubeMesh = new THREE.Mesh(geometry, material );
cubeMesh.position.z = -30;
scene.add( cubeMesh );renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );window.addEventListener( 'resize', onWindowResize, false );
render();
}function animate() {
deltaTime = clock.getDelta();cubeMesh.rotation.x += 1 * deltaTime;
cubeMesh.rotation.y += 2 * deltaTime;render();
requestAnimationFrame( animate );
}function render() {
renderer.render( scene, camera );
}function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
render();
}
If any of the code above is new to you, see my previous post: Getting started with WebGL and Three.js.
If you run the application now, you should see our simple scene:
A blue cube rotating in space. Aside from the fact that it’s 3D graphics running in the browser (which is pretty cool), it’s kind of boring. Let’s add some particles.
Creating a Particle System in Three.js
In Three.js, creating a particle system consists of three steps:
- Create a generic
Geometry
object, adding a set of vertices to that geometry. Each vertex in the geometry will represent one particle in the system. - Create a special material,
PointsMaterial
,to give the particles a desired look. - Create a
Points
mesh with our geometry and material. This is the object that represents the particle system as a whole.
This implementation has some pros and cons. On the pro side, since all the particles are contained in one mesh and share the same material, the particles are rendered in one draw call, so it is very efficient on the GPU. Also, this allows us to move and rotate the entire particle system by modifying one mesh. On the con side, this limits the amount of flexibility we have when it comes to giving each particle an individual look, or changing the orientation of individual particles.
I’m going to start by adding a new function just below the init
function. This function is responsible for initializing our particle system. Add the following code to the application:
function createParticleSystem() {// The number of particles in a particle system is not easily changed.
var particleCount = 2000;// Particles are just individual vertices in a geometry
// Create the geometry that will hold all of the vertices
var particles = new THREE.Geometry();// Create the vertices and add them to the particles geometry
for (var p = 0; p < particleCount; p++) {// This will create all the vertices in a range of -200 to 200 in all directions
var x = Math.random() * 400 - 200;
var y = Math.random() * 400 - 200;
var z = Math.random() * 400 - 200;// Create the vertex
var particle = new THREE.Vector3(x, y, z);// Add the vertex to the geometry
particles.vertices.push(particle);
}// Create the material that will be used to render each vertex of the geometry
var particleMaterial = new THREE.PointsMaterial(
{color: 0xffffff,
size: 4,
map: THREE.ImageUtils.loadTexture("images/snowflake.png"),
blending: THREE.AdditiveBlending,
transparent: true,
});// Create the particle system
particleSystem = new THREE.Points(particles, particleMaterial);return particleSystem;
}
In our new function, you can see I’m creating the geometry with a set of 2000 vertices. This is done using the Vector3
class. The location of each vertex is placed randomly between -200 and 200 in all directions.
Next, I create the PointsMaterial
with the color set to white, a size of 4, and I’m using a texture to give my particles their shape. This texture is assumed to live in a folder named images and is adjacent to the js folder. It’s best to use a texture that has a transparent background, so only the shape of the texture is rendered. Because of this, I’m also setting the blending mode to AdditiveBlending
, and I’m setting transparent to be true. If we didn’t set these last two properties, each particle would render the entire texture with a black background. This would look very bad when our particles are in between the camera and the other scene object.
The final step is to update the init
function to add the particle system to the scene. I’m going to add the following two lines of code just after the code that adds the cubeMesh
to the scene:
particleSystem = createParticleSystem();
scene.add(particleSystem);
If you refresh your browser you should see something like this:
As you can see, we now have a spinning cube in space surrounded by a backdrop of snowflakes. Actually, it looks more like a cube floating in space with a backdrop of stars. In fact, if that’s the look you are going for, then you’re done. That’s not the look I’m going for, so to give it a more convincing snowfall look, I’m going to set the particles in motion.
Animating Particles in Three.js
There are two ways in which we can animate particles in Three.js. I briefly mentioned the first method earlier, that is, we can change the position and orientation of the entire particle system. The second method is to change the position of each individual vertex in the geometry used to create the particle system. This is the method we are going to use first.
I’m going to create a new function that will handle all of the details of animating the particles. Add the following function to the application:
function animateParticles() {
var verts = particleSystem.geometry.vertices;
for(var i = 0; i < verts.length; i++) {
var vert = verts[i];
if (vert.y < -200) {
vert.y = Math.random() * 400 - 200;
}
vert.y = vert.y - (10 * deltaTime);
}
particleSystem.geometry.verticesNeedUpdate = true;}
In this function, I’m iterating over all of the vertices contained in the geometry of the particle system and adjust the vertical position of each one to give the effect that the particles are falling. The line of code that does this is the following:
vert.y = vert.y - (10 * deltaTime);
Here you can see we are decreasing the vertical position by 10 * deltaTime
. Since different devices have different rendering speeds, we multiply the decrease in vertical position by deltaTime
to ensure the falling speed is the same across devices. The deltaTime
variable is the amount of time that passed since the last time we called getDelta()
, and since we are calling that function once per frame in the animate()
function, the deltaTime
is the amount of time that has passed since the last frame. So by multiplying the decrease amount by deltaTime
we are saying that we want the particle to fall at a rate of 10 units per second regardless of the frame rate.
You can also see that if the vertical position of the particle falls below -200, we reset its position to a random vertical position.
Finally, we have one more line of code needed to tell Three.js that the vertices of the geometry have changed:
particleSystem.geometry.verticesNeedUpdate = true;
This line tells Three.js the geometry is dirty and needs to be refreshed before rendering the next frame. To actually view the animation, we need to call our newly created function. In the animate()
function, add the following line just before the call to render()
:
animateParticles();
Refresh your browser, and you should see snow falling around the cube.
So far it looks good, but if you’ve ever seen snow fall, you know that snow rarely falls straight down like we see here. Snowfall is almost always accompanied by some wind. Let’s add one final touch to give our snowfall an even more convincing look by giving it a little bit of vertical movement.
To give our snowfall vertical movement we could come up with an algorithm to modify the x and z position of each particle, but coming up with an algorithm that gives us smooth movement can be very cumbersome. Instead, we’re going to use the other method I’ve discussed for animating particles. We’re going to simply modify the rotation of the entire particle system to give the illusion of vertical movement.
To modify the rotation of the particle system, we need to add just one line of code to our animateParticles()
function. Add the following line to the bottom of the animateParticles()
function:
particleSystem.rotation.y -= .1 * deltaTime;
In the line above we are modifying the rotation around the y axis of the entire particle system. Again we are multiplying the speed of rotation by deltaTime
to ensure a constant speed across all devices. The number .1 is the number of degrees in radians.
Refresh the browser, and now you should see the rotating cube with a (somewhat) convincing snowfall backdrop.
Conclusion
Particle systems are a great way to add character to otherwise boring scenes without adding a lot of overhead. They can provide a sense of depth and volume to your scene that is really hard to accomplish with conventional rendering techniques.
Creating a particle system in Three.js is a straightforward three step process:
- Create a geometry with a set of vertices, each vertex is a particle.
- Create a material to give the particles the desired look.
- Create the particle system mesh with the previously created geometry and material.
Once your particle system is created you have two options to animate it:
- Modify the position of each particle.
- Modify the position/rotation of the particle system as a whole.
Even with this simple example you have lots of options to try to create different effects. Try making some of the following changes to achieve a different effect:
- Change the size of the particles in the material.
- Change the texture used for the particles.
- Change the opacity of the material.
- Change the color of the material.
- Change the animation speed of the particles or the particle system.
- Combine multiple particle systems to create more complex systems like fire and smoke.
Image courtesy of Unsplash.