Advanced Particle System Effects
To go over the basics of a particle system, see the Introduction to Particle Systems tutorial.
Weather
Setup
To make a snow effect, start by adding snowflake images for each particle and define the movement behavior and other dynamic elements of the particles in the updateParticle
function.
The images
The following three images are used in this tutorial. On the left is the rain particle; the center image is a snow particle; and the right image is used for fire effects.
The update function
The update function is used to define movement, arrangement, and visualization of the particles. Modify things like the particle’s color
, imageSize
, and particleLife
. We can even modify the particles based on their distance to the camera (as done below), to an imported model, or to the earth itself.
Here is our update function for snow:
// snow
var snowGravityVector = new Cesium.Cartesian3();
var snowUpdate = function(particle, dt) {
Cesium.Cartesian3.normalize(particle.position, snowGravityVector);
Cesium.Cartesian3.multiplyByScalar(snowGravityVector,
Cesium.Math.randomBetween(-30.0, -300.0),
snowGravityVector);
particle.velocity = Cesium.Cartesian3.add(particle.velocity, snowGravityVector, particle.velocity);
var distance = Cesium.Cartesian3.distance(scene.camera.position, particle.position);
if (distance > (snowRadius)) {
particle.endColor.alpha = 0.0;
} else {
particle.endColor.alpha = snowSystem.endColor.alpha / (distance / snowRadius + 0.1);
}
};
The first part of the function makes the particles fall downward as if by gravity. The update function also includes a distance check so particles disappear when they are far away from the camera.
Additional weather effects
Use fog and atmosphere effects to enhance the visualization and match the type of weather we’re trying to replicate.
hueShift
changes the color along the color spectrum, saturationShift
changes how much color versus black and white the visual actually entails, and brightnessShift
changes how vivid the colors are.
Fog density changes how opaque the overcover on the earth is with the fog’s color. The fog minimumBrightness
is used to darken the fog.
// snow
scene.skyAtmosphere.hueShift = -0.8;
scene.skyAtmosphere.saturationShift = -0.7;
scene.skyAtmosphere.brightnessShift = -0.33;
scene.fog.density = 0.001;
scene.fog.minimumBrightness = 0.8;
The systems
Snow
The snow system uses the snowflake_particle
image and uses a minimumImageSize
and maximumImageSize
to create snowflakes at random sizes in that range.
var snowParticleSize = scene.drawingBufferWidth / 100.0;
var snowRadius = 100000.0;
var snowSystem = new Cesium.ParticleSystem({
modelMatrix : new Cesium.Matrix4.fromTranslation(scene.camera.position),
minimumSpeed : -1.0,
maximumSpeed : 0.0,
lifetime : 15.0,
emitter : new Cesium.SphereEmitter(snowRadius),
startScale : 0.5,
endScale : 1.0,
image : "../../SampleData/snowflake_particle.png",
emissionRate : 7000.0,
startColor : Cesium.Color.WHITE.withAlpha(0.0),
endColor : Cesium.Color.WHITE.withAlpha(1.0),
minimumImageSize : new Cartesian2(snowParticleSize, snowParticleSize),
maximumImageSize : new Cartesian2(snowParticleSize * 2.0, snowParticleSize * 2.0),
updateCallback : snowUpdate
});
scene.primitives.add(snowSystem);
Rain
The rain system uses circular_particle.png
for the rain drops. imageSize
is used to stretch the image vertically to give the rain an elongated look.
rainSystem = new Cesium.ParticleSystem({
modelMatrix : new Cesium.Matrix4.fromTranslation(scene.camera.position),
speed : -1.0,
lifetime : 15.0,
emitter : new Cesium.SphereEmitter(rainRadius),
startScale : 1.0,
endScale : 0.0,
image : "../../SampleData/circular_particle.png",
emissionRate : 9000.0,
startColor :new Cesium.Color(0.27, 0.5, 0.70, 0.0),
endColor : new Cesium.Color(0.27, 0.5, 0.70, 0.98),
imageSize : new Cesium.Cartesian2(rainParticleSize, rainParticleSize * 2),
updateCallback : rainUpdate
});
scene.primitives.add(rainSystem);
The rain update function is slightly different because rain falls much faster than snow.
// rain
rainGravityScratch = Cesium.Cartesian3.normalize(particle.position, rainGravityScratch);
rainGravityScratch = Cesium.Cartesian3.multiplyByScalar(rainGravityScratch,
-1050.0,
rainGravityScratch);
particle.position = Cesium.Cartesian3.add(particle.position, rainGravityScratch, particle.position);
To make the environment match the mood of the scene, modify the atmosphere and fog to match the rain. The below code makes a dark blue sky with a thin fog cover.
// rain
scene.skyAtmosphere.hueShift = -0.97;
scene.skyAtmosphere.saturationShift = 0.25;
scene.skyAtmosphere.brightnessShift = -0.4;
scene.fog.density = 0.00025;
scene.fog.minimumBrightness = 0.01;
For additional help, see the Sandcastle example for both snow and rain.
Comet and rocket tails
Comet tail.
Rocket tail.
Using multiple particle systems
To create comet and rocket trails, we will need multiple particle systems. Each location on a ring of particles created by the example is a completely separate particle system. This allows us to more uniformly control the direction of the systems’ movement. An easy way to visualize this effect is to limit cometOptions.numberOfSystems
to 2 and cometOptions.colorOptions
to include just two colors, as shown in the image below.
To streamline the different sets of systems, create arrays to carry the separate systems associated with the comet versus those associated with the rocket example.
var rocketSystems = [];
var cometSystems = [];
Create two different options for objects; one for the comet version and one for the rocket version. This allows for a varied look between the two with different initial number of systems, offset values, etc.
var cometOptions = {
numberOfSystems : 100.0,
iterationOffset : 0.003,
cartographicStep : 0.0000001,
baseRadius : 0.0005,
colorOptions : [{
red : 0.6,
green : 0.6,
blue : 0.6,
alpha : 1.0
}, {
red : 0.6,
green : 0.6,
blue : 0.9,
alpha : 0.9
}, {
red : 0.5,
green : 0.5,
blue : 0.7,
alpha : 0.5
}]
};
var rocketOptions = {
numberOfSystems : 50.0,
iterationOffset : 0.1,
cartographicStep : 0.000001,
baseRadius : 0.0005,
colorOptions : [{
minimumRed : 1.0,
green : 0.5,
minimumBlue : 0.05,
alpha : 1.0
}, {
red : 0.9,
minimumGreen : 0.6,
minimumBlue : 0.01,
alpha : 1.0
}, {
red : 0.8,
green : 0.05,
minimumBlue : 0.09,
alpha : 1.0
}, {
minimumRed : 1,
minimumGreen : 0.05,
blue : 0.09,
alpha : 1.0
}]
};
colorOptions
is an array of colors used for a randomized visual. Rather than having a set color, each system starts with one specific color dependent on the current system being created. In the below example, i
represents the current iteration.
var color = Cesium.Color.fromRandom(options.colorOptions[i % options.colorOptions.length]);
Setup
Use the below function as the initializer for each system:
function createParticleSystems(options, systemsArray) {
var length = options.numberOfSystems;
for (var i = 0; i < length; ++i) {
scratchAngleForOffset = Math.PI * 2.0 * i / options.numberOfSystems;
scratchOffset.x += options.baseRadius * Math.cos(scratchAngleForOffset);
scratchOffset.y += options.baseRadius * Math.sin(scratchAngleForOffset);
var emitterModelMatrix = Cesium.Matrix4.fromTranslation(scratchOffset, matrix4Scratch);
var color = Cesium.Color.fromRandom(options.colorOptions[i % options.colorOptions.length]);
var force = forceFunction(options, i);
var item = viewer.scene.primitives.add(new Cesium.ParticleSystem({
image : getImage(),
startColor : color,
endColor : color.withAlpha(0.0),
particleLife : 3.5,
speed : 0.00005,
imageSize : new Cesium.Cartesian2(15.0, 15.0),
emissionRate : 30.0,
emitter : new Cesium.CircleEmitter(0.1),
bursts : [ ],
lifetime : 0.1,
forces : force,
modelMatrix : particlesModelMatrix,
emitterModelMatrix : emitterModelMatrix
}));
systemsArray.push(item);
}
}
Since both tail versions are similar, the same createPrarticleSystems
function can be used to create either. Pass in cometOptions
or rocketOptions
for the options
parameter to create the different effects.
Create the particle image from scratch
Instead of loading an image from a URL, the getImage
function creates an image using an HTML canvas. This makes the image creation more flexible.
var particleCanvas;
function getImage() {
if (!Cesium.defined(particleCanvas)) {
particleCanvas = document.createElement('canvas');
particleCanvas.width = 20;
particleCanvas.height = 20;
var context2D = particleCanvas.getContext('2d');
context2D.beginPath();
context2D.arc(8, 8, 8, 0, Cesium.Math.TWO_PI, true);
context2D.closePath();
context2D.fillStyle = 'rgb(255, 255, 255)';
context2D.fill();
}
return particleCanvas;
}
The force function
Here is our updateCallback
function:
var scratchCartesian3 = new Cesium.Cartesian3();
var scratchCartographic = new Cesium.Cartographic();
var forceFunction = function(options, iteration) {
var iterationOffset = iteration;
var func = function(particle) {
scratchCartesian3 = Cesium.Cartesian3.normalize(particle.position, new Cesium.Cartesian3());
scratchCartesian3 = Cesium.Cartesian3.multiplyByScalar(scratchCartesian3, -1.0, scratchCartesian3);
particle.position = Cesium.Cartesian3.add(particle.position, scratchCartesian3, particle.position);
scratchCartographic = Cesium.Cartographic.fromCartesian(particle.position,
Cesium.Ellipsoid.WGS84,
scratchCartographic);
var angle = Cesium.Math.PI * 2.0 * iterationOffset / options.numberOfSystems;
iterationOffset += options.iterationOffset;
scratchCartographic.longitude += Math.cos(angle) * options.cartographicStep;
scratchCartographic.latitude += Math.sin(angle) * options.cartographicStep;
particle.position = Cesium.Cartographic.toCartesian(scratchCartographic);
};
return func;
};
Notice forceFunction
is returning a function
. The returned func
is the actual updateCallback
function. For each iteration, the update function creates a different spinning offset based on the angle
and iterationOffset
. Smaller iteration offsets adjust the angle only a little, allowing the radius to grow steadily larger as the system continues, as shown in the comet example. Larger iteration offsets will change the angle much faster; this will make a much tighter, jittery, cylindrical output as in the rocket example.
This tutorial uses sine and cosine functions for circular effects. For other effects, try making shapes such as the Lissajous curve, the Gibbs phenomenon, or a square wave.
Relative positioning
Use a modelMatrix
to position the particle systems in the proper location behind the plane. Since these systems are vertical, we need to do a slight offset by using our particleOffset
value. As shown in the createParticleSystems
function, emitterModelMatrix
is calculated for each system with the offset depending on the iteration.
// positioning the plane
var planePosition = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883, 800.0);
var particlesOffset = new Cesium.Cartesian3(-8.950115473940969, 34.852766731753945, -30.235411095432937);
// creating the particles model matrix
var transl = Cesium.Matrix4.fromTranslation(particlesOffset, new Cesium.Matrix4());
var translPosition = Cesium.Matrix4.fromTranslation(planePosition, new Cesium.Matrix4());
var particlesModelMatrix = Cesium.Matrix4.multiplyTransformation(translPosition, transl, new Cesium.Matrix4());
Resources
For additional help see the Sandcastle example for both tails examples.
For more example code, see: