Tutorial #8: Particle Systems
Many things that you might want to place on your page might be difficult or impossible to render using models like we have done in many of the other tutorials. Things like snow, bubbles and sparks consist of many smaller objects and it would be an incredible drain on the graphics card to try to render a separate mesh for each of them. Particle systems work by treating the collection of smaller things as a whole, for example, all the rain, instead of individual drops. The collection then defines a set of rules the individual pieces must abide by. So ‘the rain’ tells each raindrop how to behave, and instead of rendering a full 3D model for each raindrop, we render a much simpler object, often simply a quadrilateral upon which we place a texture to make it look like a raindrop (or spark or snowflake). Lets start with a basic particle system of sparks to demonstrate what we mean.
First, you’ll need a browser that is WebGL capable (See tutorial 1 for instructions on obtaining one), and an image to use for our particles. You’ll need an image to use for particles, in this tutorial we’ll use a flare. To start off, we won’t need the duck model and texture and teapot model and texture, but we’ll be adding them to our scene later, so you may as well get them now.
A first particle system
The html page
<html>
<head>
<title>Canvas3D Tutorial #8: Particle Systems</title>
<script type="application/javascript" src="../canvas3dapi/c3dapi.js" ></script>
<script type="application/javascript" src="tutorial8.js"></script>
</head>
<body>
<canvas id="tutorial" style="border: 2px solid blue" width="500" height="500"></canvas>
</body>
</html>
The javascript
Place the following code in a file called tutorial8.js in the same folder as the html page (should be mydemo, if you’ve been following the other tutorials).
//Particle Systems Tutorial
var psys;
var cam;
c3dl.addMainCallBack(canvasMain, "tutorial");
// The program main
function canvasMain(canvasName)
{
// Create new c3dl.Scene object
scn = new c3dl.Scene();
scn.setCanvasTag(canvasName);
// Create GL context
renderer = new c3dl.WebGL();
//set the background of the scene
scn.setBackgroundColor([0,0,0]);
// Attach renderer to the scene
scn.setRenderer(renderer);
scn.init();
//create a camera
cam = new c3dl.FreeCamera();
// Place the camera.
cam.setPosition([0, 0, 0]);
// Point the camera.
// Here it is pointed directly down the Z-axis..
cam.setLookAtPoint([0, 0, -1]);
// Add the camera to the scene
scn.setCamera(cam);
// Start the scene
scn.startScene();
//This is the new code for the particle system
//add the particle system
psys = new c3dl.ParticleSystem();
//position it in the scene, in full view of the camera
psys.setPosition([0,0,-20]);
//set the initial range of velocities possible for each particle
psys.setMinVelocity([-4,-4,-4]);
psys.setMaxVelocity([-2,2,2]);
//set the range of time (in seconds) that a particle can last
psys.setMinLifetime(0.5);
psys.setMaxLifetime(3);
//Set the range of colours for particles
psys.setMinColor([0.8,0.4,0.4,0.5]);
psys.setMaxColor([1,0.6,0.6,1]);
//set range of sizes for particle
psys.setMinSize(0.1);
psys.setMaxSize(0.5);
//specify how overlapping particles will be rendered
psys.setSrcBlend(c3dl.ONE);
psys.setDstBlend(c3dl.DST_ALPHA);
//set the texture the particles will use
psys.setTexture("flare.png");
//Set the acceleration that will be applied to every particle
psys.setAcceleration([0,-5,0]);
//Set how many particles the system will emit every second
psys.setEmitRate(5);
//Set the total number of particles available to the system
psys.init(50);
//add the particle system to the scene
scn.addObjectToScene(psys);
//END OF NEW CODE
}
When you view the page you’ll see something like this:
, with particles appearing in front of the camera and falling away to the left. Now lets look at the code that got us here:
- psys = new c3dl.ParticleSystem();
Creates a new particle system to work with. - psys.setPosition([0,0,-20]);
setPosition works just like every other time we’ve seen it. We’re just specifying where the particle system will be. This is the point in 3D space from which all the particles are going to be emitted. - psys.setMinVelocity([-4,-4,-4]);
This sets the minimum (that is, the most negative) possible velocity for any particle emitted by this system. Each particle will be randomly assigned a velocity between this and the maximum velocity for the system. - psys.setMaxVelocity([-2,2,2]);
This sets the maximum (most positive) possible velocity for a particle emitted by this system. When a particle is emitted it will be assigned a random value between this and the minimum velocity for the system. - psys.setMinLifetime(0.5);
This sets the shortest duration (in seconds) that a particle emitted by this system will last. When emitted, each particle will be given a random value between this and the maximum duration. Must be greater than 0. - psys.setMaxLifetime(3);
This sets the Maximum duration for a particle to last (in seconds). Each particle emitted by this system will be given a random value between this and the minimum. - psys.setMinColor([0.8,0.4,0.4,0.5]);
Sets the minimum colour values (Red, Green, Blue, Alpha) for any particle emitted by this system. - psys.setMaxColor([1,0.6,0.6,1]);
Sets the maximum RGBA colour values for any particle emitted by this system. - psys.setMinSize(0.1);
Sets the minimum size of any particle. Must be greater than 0. - psys.setMaxSize(0.5);
Sets the maximum size of any particle. Must be greater than 0. Each particle emitted by the system will be randomly scaled between the minimum and maximum size. - psys.setSrcBlend(c3dl.ONE);
This and setDstBlend will determine how overlapping particles will display. More on this later. - psys.setDstBlend(c3dl.DST_ALPHA);
This and setSrcBlend will determine how overlapping particles will display. More on this later. - psys.setTexture(“flare.jpg”);
Sets the texture to be used by every particle. - psys.setAcceleration([0,-5,0]);
Sets the acceleration that will be applied to each particle in this system. Note that this is one value that is not randomized per particle, as every particle should be under the same acceleration. - psys.setEmitRate(5);
Sets how many particles the system will try to emit every second. Note that these are spread throughout the second, not released as a clump. - psys.init(50);
Sets the maximum number of particles this system can have.
Modifying these values will change the way your particles behave. So that each particle will move and look slightly different, but still act as part of a whole, many of these values are randomly determined. A narrow range for any value will mean all particles are very similar (or identical) in that respect (size, for example), while a wide range will allow each one to be distinct. Set too many aspects too wide and you end up with a bunch of seemingly unrelated particles, but set them too restrictive and just get the same particle again and again. There is an important difference between understanding each of these settings and knowing how to use them to achieve the desired effect. They will almost always require some tweaking to get the particles acting the way you want.
Each of these options has a type and range of acceptable values. For example, setPosition, setMinVelocity, setMaxVelocity and setAcceleration all require a valid 3D Vector, while setMinLifetime, setMaxLifetime, setMinSize, setMaxSize, setEmitRate and init all require a positive whole number (a few of those will also accept 0) and setTexture requires an image file. This leaves setSrcBlend and setDstBlend, which only allow certain predefined values. They are: c3dl.ZERO, c3dl.ONE, c3dl.SRC_COLOR, c3dl.ONE_MINUS_SRC_COLOR, c3dl.SRC_ALPHA, c3dl.ONE_MINUS_SRC_ALPHA, c3dl.DST_ALPHA, c3dl.ONE_MINUS_DST_ALPHA, c3dl.DST_COLOR, c3dl.ONE_MINUS_DST_COLOR or c3dl.SRC_ALPHA_SATURATE
The different blend factors will interact with each other differently depending on which ones you pair up, as well as the colour you set, the image you use, etc.
Also note that while you must call new c3dl.ParticleSystem() first, the other functions may be called in any order. You may even add the particle system to the scene and then change the values later. Any values you have not set will use a default, though this will often result in a particle system that is not visible, or does nothing. For example, the emit rate defaults to 0, meaning no particles get emitted. This is done so that it is very clear when you’ve forgotten to set something.
On its own a particle system doesn’t make much of a scene, but if we combine it with several other elements, it can really add to the environment. So now we’re going to integrate three particle systems into a scene with two objects. If you did not download the duck and teapot earlier in the tutorial, do so now.
We’ll use the same html file, but modify this line:
<script type="application/javascript" src="tutorial8.js"></script>
to use tutorial8b.js
<script type="application/javascript" src="tutorial8b.js"></script>
This means we need a tutorial8b.js, and here it is:
//Particle Systems Tutorial part 2
var flames;
var fire;
var bubbles;
var cam;
c3dl.addMainCallBack(canvasMain, "tutorial");
c3dl.addModel("duck.dae");
c3dl.addModel("teapot.dae");
// The program main
function canvasMain(canvasName)
{
// Create new c3dl.Scene object
scn = new c3dl.Scene();
scn.setCanvasTag(canvasName);
// Create GL context
renderer = new c3dl.WebGL();
//set the background of the scene
scn.setBackgroundColor([0,0,0]);
// Attach renderer to the scene
scn.setRenderer(renderer);
scn.init(canvasName);
//create a camera
cam = new c3dl.FreeCamera();
// Place the camera.
cam.setPosition([0, 0, 0]);
// Point the camera.
// Here it is pointed directly down the Z-axis..
cam.setLookAtPoint([0, 0, -1]);
// Add the camera to the scene
scn.setCamera(cam);
// Start the scene
scn.startScene();
//add a light
var sun = new c3dl.DirectionalLight();
sun.setName('mr. sun');
sun.setDirection([-5,-200,-1]);
sun.setDiffuse([1,0.8,0.6]);
sun.setOn(true);
scn.addLight(sun);
//add a duck in the lower right portion of the screen, facing left.
var thing = new c3dl.Collada();
thing.init("duck.dae");
thing.scale([0.05,0.05,0.05]);
thing.setPosition([10,-10,-50]);
thing.yaw(Math.PI);//rotate the duck to face the other way (pi radians = 10 degrees)
scn.addObjectToScene(thing);
//create a particle system of flames from the duck's mouth
flames = new c3dl.ParticleSystem();
flames.setPosition([6.5,-4,-50]);
//make the coming out move rapidly left (-x) in a cone
flames.setMinVelocity([-15,-2,-2]);
flames.setMaxVelocity([-12,4,2]);
//keep the lifespan of the particles short without too much variety
flames.setMinLifetime(0.5);
flames.setMaxLifetime(0.6);
//trying to make bluish flame colour...
flames.setMinColor([0.4,0.4,0.8,0.8]);
flames.setMaxColor([0.6,0.6,1.0,1]);
//keep the particles small
flames.setMinSize(0.5);
flames.setMaxSize(0.8);
flames.setSrcBlend(c3dl.ONE);
flames.setDstBlend(c3dl.DST_ALPHA);
flames.setTexture("flare.png");
//make the flames slow down, and start to rise
flames.setAcceleration([10,5,0]);
//many particles per second
flames.setEmitRate(500);
flames.init(300);
scn.addObjectToScene(flames);
//create a particle system of the flame body heating the teapot.
//because of the difference between the flames the duck is breathing and
// the body of the flames beneath the teapot (we want a distinct change in the way
// the flames act), we need a second system.
fire = new c3dl.ParticleSystem();
//position in front of the duck
fire.setPosition([2,-4,-50]);
//low velocity, rising
fire.setMinVelocity([-2,2,-2]);
fire.setMaxVelocity([2,4,2]);
//short duration
fire.setMinLifetime(0.5);
fire.setMaxLifetime(0.8);
// again, bluish-white colour for fire
fire.setMinColor([0.4,0.4,0.8,0.5]);
fire.setMaxColor([0.6,0.6,1.0,1]);
//small particles
fire.setMinSize(0.1);
fire.setMaxSize(0.5);
fire.setSrcBlend(c3dl.ONE);
fire.setDstBlend(c3dl.DST_ALPHA);
fire.setTexture("flare.png");
//make them go up
fire.setAcceleration([0,5,0]);
//many particles per second
fire.setEmitRate(500);
fire.init(300);
scn.addObjectToScene(fire);
//add a teapot in front of and above the duck (and above the fire)
var thing = new c3dl.Collada();
thing.init("teapot.dae");
thing.setTexture("teapot.png");
thing.setPosition([2,4,-50]);
thing.scale([0.5,0.5,0.5]);
scn.addObjectToScene(thing);
//add a particle system of steam at the teapot's spout
bubbles = new c3dl.ParticleSystem();
bubbles.setPosition([9.5,6.5,-50]);
//allow a little variety in the x and z axes, but make the particles go up (+y)
bubbles.setMinVelocity([-1,1,-2]);
bubbles.setMaxVelocity([2,5,2]);
//allow a wider range of time
bubbles.setMinLifetime(1);
bubbles.setMaxLifetime(2);
//set to a fairly dark gray colour
bubbles.setMinColor([0.2,0.2,0.2,0.3]);
bubbles.setMaxColor([0.2,0.2,0.2,0.5]);
//allow larger particles
bubbles.setMinSize(1);
bubbles.setMaxSize(2);
bubbles.setSrcBlend(c3dl.ONE);
bubbles.setDstBlend(c3dl.DST_ALPHA);
bubbles.setTexture("flare.png");
//accelerate the particles upwards
bubbles.setAcceleration([0,5,0]);
//less particles per second
bubbles.setEmitRate(50);
bubbles.init(300);
scn.addObjectToScene(bubbles);
}
If you’d like you could then modify this even more, to use a timer to make the flames from the duck turn on/off periodically, then have the steam turn on/off with it (but maybe a little behind to simulate the fact that the teapot will take a bit to heat up/cool down).
