The goal of this lab is to get you working with a simple particle system in WebGL via Three.js. Our particules will be handled by the CPU rather than the GPU, which simplifies things because we will not have to write any custom shaders. Otherwise they behave in similar ways.
Getting Started
To begin with, fork a copy of the starter code. This is structured much like our other Three.js programs, with an empty scene, camera, and lighting. It's just waiting to have content added to it.
Your first step is to create a set of particles to work with. They won't look like much at first, and they won't move. But it's a start! Add the following code to the base file inside createWorld. You can alter the color and the number of particles if you want, to see what kind of difference it makes..
// create the particle variables var particleCount = 1800, pgeo = new THREE.Geometry(), pmat = new THREE.PointsMaterial({ color: 'yellow', size: 10 }); // now create the individual particles for (var p = 0; p < particleCount; p++) { // create a particle with random // position values, -250 -> 250 var pX = Math.random() * 500 - 250, pY = Math.random() * 500 - 250, pZ = Math.random() * 500 - 250, particle = new THREE.Vector3(pX, pY, pZ); // add it to the geometry pgeo.vertices.push(particle); } // create the particle system particles = new THREE.Points(pgeo,pmat); // add it to the scene scene.add(particles);
The end result should look something like this:
If you modify the render function to rotate the particle cloud slowly around the y axis, you will see that these square points are distributed in space.
Adding Motion
While a bunch of square particles hanging in space is something, it would be much nicer if they could move around. This means updating the position vector for each of the particles. You can do this in a loop within the render function.
for (var p = 0; p < 1800; p++) { particles.geometry.vertices[p].x += Math.random()-0.5; particles.geometry.vertices[p].y += Math.random()-0.5; particles.geometry.vertices[p].z += Math.random()-0.5; }
Right after this loop, we should also tell the renderer that we made some changes.
particles.geometry.verticesNeedUpdate = true;
If all went well, we should now have some motion! It's a bit jittery, to be sure -- your particles are demonstrating Brownian motion, like the molecules in a room full of air as they are buffeted by their neighbors..
It would be nice to make the particles behave a little more smoothly. We can do this by diving them a persistent velocity. If we add random adjustments to the velocity rather than the position, we'll get the effect we want. You can do this by making a vector of velocity vectors, one for each particle, and then changing render as described. You'll probably want the size of the velocity and its randomness to be much smaller than that of the position -- remember that the velocity is added to the particle 30 times a second! Values around 1/30 the size of the position numbers should do the trick. Adding velocity is an optional change -- only do it if you have time. The Brownian motion will suffice for the rest of the lab, and there are a few toher things we want to get to.
If you let your particles run for a while, you will notice that they eventually thin out and disappear. Like air molecules, particles under Brownian motion are subject to diffusion. Given enough time, they will all wander out of the camera frame. It would be better if we contained them within a finite volume. The easiest way to do this is to set up boundaries as a giant cube from -250 to +250 in each dimension. If a particle wanders out one face of the cube, it is moved so that it enters at the opposite face. This can be managed with a single line per position coordinate:
particles.geometry.vertices[p].x = (particles.geometry.vertices[p].x+750)%500-250;
Once you have done that, it is safe to add some systematic motion to one or more of the components. For example, if you add a small number to the z component each time, you will get a "hyperspace" effect. On the other hand, if you subtract a litle bit you will get something like falling snow.
Textured Particles
Everything we have done so far is all very well, but despite all the motions we're going to be limited in the effects we can create if our particles always look like little squares. The solution is to apply a texture to each point. Instead of a square, the renderer will paint a tiny image at each location. Typical particle textures use transparency to diffuse their boundaries, so that individual particles can merge together seamlessly.
Note that once we do this, the usual restrictions on texture images will apply -- it will work on repl.it but you won't be able to view the result from a local file. (Review the configuration notes if necessary.)
We'll use a small smoke particle for our texture:
Download a copy of the image by right-clicking on it. Then upload it
to your repl, and add a line in the script to load the texture as you
would for any other.
The next step is to make some changes to the texture's material (assuming particleTexture is the name of the variable holding the loaded texture):
pmat = new THREE.PointsMaterial({ map: particleTexture, color: 0xffffff, alphaMap: particleTexture, transparent: true, size: 5, depthWrite: false, blending: THREE.AdditiveBlending });
Notice that we have used the same image as both the main texture and the alpha (transparency) map. This is appropriate for fog, smoke, etc. but won't always be the case. The last two parameters control the mixing when two point textures happen to overlap -- if you have time, you might try removing them to see what happens.
Assuming all went well, your squares should now have been replaced by little white puffs. If you don't see anything, make sure that the texture image is in your project folder, and that you loaded it properly. Now you can play with your particles. What effects can you dream up?
Variations
With small variations to the basic file of this lab, you should be able to achieve any of the following effects. You are not required to do all of these -- see how many you can explore if you have the time!
- Hyperspace drive as described above. If you change the sign of the addition to z, you change the direction of travel.
- Snow and Rain as described above. The main difference between the two is in the speed of the particles' fall.
- Faucet is a variation on the rain model. Restrict the x and z variation of the particles to a narrow range (say, +/- 1) and move the view farther away (say, z = 100). You might give the particles a slight bluish tint. The result should look like a stream of water from a faucet.
- Waterfall is like the faucet except the particles are less constricted in x and y.
- Fireball is the most different from our original example,
but not too hard to implement. To begin, we should change the
particle color to orange. Then we need to introduce the notion of
gravity -- a force that consistently adjusts the velocity in a
particular direction. In this particular case, we want gravity to
attract all the points towards the origin, like a collapsing star.
There are various ways to do this, including some that would earn a
physicist's approval and others that would not. One simple method is
to systematically scale down each vector with every frame.
particles.geometry.vertices[p].multiplyScalar(0.8);
If your particle model includes velocity, you should probably scale that as well, although not by as much. The scaling factors chosen will help determine the final size and compactness of the ball.
Finish Up
You do not have to turn this lab in, but make sure that everyone in your group has a copy by the end of the lab. You may wish to use particle effects in your game.