As you can find in Babylon101 there are many properties of the particle system that can be tweaked to control the look of a particle system. Babylon.js allows you even more customization to obtain the system you want. These can be split into into two types custom functions and custom effects.
There are three methods you can customize:
You can directly attach all these functions to the particleSystem.
Since Babylon.js V3.2 you can use the first two, startDirectionFunction
and startPositionFunction
, in creating a new particle emitter type as was done with the createBoxEmitter, createSphereEmitter and createConeEmitter.
The start direction function has the default form
particleSystem.startDirectionFunction = (emitPower: number, worldMatrix: Matrix, directionToUpdate: Vector3) {
var randX = randomNumber(this.direction1.x, this.direction2.x);
var randY = randomNumber(this.direction1.y, this.direction2.y);
var randZ = randomNumber(this.direction1.z, this.direction2.z);
Vector3.TransformNormalFromFloatsToRef(randX * emitPower, randY * emitPower, randZ * emitPower, worldMatrix, directionToUpdate);
}
The start position function has the default form
particleSystem.startPositionFunction = (worldMatrix: Matrix, positionToUpdate: Vector3): void => {
var randX = randomNumber(this.minEmitBox.x, this.maxEmitBox.x);
var randY = randomNumber(this.minEmitBox.y, this.maxEmitBox.y);
var randZ = randomNumber(this.minEmitBox.z, this.maxEmitBox.z);
Vector3.TransformCoordinatesFromFloatsToRef(randX, randY, randZ, worldMatrix, positionToUpdate);
}
The update function has the default form
updateFunction = function(particles) {
for (var index = 0; index < particles.length; index++) {
var particle = particles[index];
particle.age += this._scaledUpdateSpeed;
if (particle.age >= particle.lifeTime) { // Recycle
particles.splice(index, 1);
this._stockParticles.push(particle);
index--;
continue;
}
else {
particle.colorStep.scaleToRef(this._scaledUpdateSpeed, this._scaledColorStep);
particle.color.addInPlace(this._scaledColorStep);
if (particle.color.a < 0)
particle.color.a = 0;
particle.angle += particle.angularSpeed * this._scaledUpdateSpeed;
particle.direction.scaleToRef(this._scaledUpdateSpeed, this._scaledDirection);
particle.position.addInPlace(this._scaledDirection);
this.gravity.scaleToRef(this._scaledUpdateSpeed, this._scaledGravity);
particle.direction.addInPlace(this._scaledGravity);
}
}
}
Two simple examples of customising the update function
Randomise the Particle's Color per Frame
Add the line in the else
section
particle.color = new BABYLON.Color4(Math.random(), Math.random(), Math.random(), 1)
Grow Particles from Size 0 to a Final Size
A little trickier since the particles are to start from size 0 both min and max sizes must be 0 and a new property must be added to give the final size.
Add in main body of code
particleSystem.minSize = 0;
particleSystem.maxSize = 0;
particleSystem.finalSize = 1;
And the in the else
section, to get the particle to final size by 35% of life time add
if (particle.age < particle.lifeTime * .35) {
particle.size = particleSystem.finalSize * particle.age / (particle.lifeTime * 0.35);
}
Starting from Babylon.js V3.2 you can create a new object of type IParticleEmitterType
into the particle system. Examples of this type of object are sphereParticleEmitter
and coneParticleEmitter
which are produced by using createSphereEmitter and createConeEmitter.
These objects are assigned to a new property particleEmitterType
of the particleSystem.
You use the startDirectionFunction
and startPositionFunction
as methods for objects of this type to determine the region of space that the particles are emitted from and their direction of travel.
You can create your own ParticleEmitterType by extending IParticleEmitterType and assigning it to particleEmitterType
, overriding the default methods startDirectionFunction
and startPositionFunction
.
Below is an example to create a new spray emitter which will send streams of particles out of the top, bottom and sides of a cylindrical region.
In order to determine where a particle is emitted from the cylinder is divided into two regions as in the diagram below.
Any particle with a start position inside the red region is emitted in the direction from the center to the particle. Any particle with a start position inside the blue region is emitted horizontally.
The createSprayEmitter
method sets the radius and height of the cylinder, creates a new SprayParticleEmitter
object which is assigned to the 'particleEmitterType` property.
BABYLON.ParticleSystem.prototype.createSprayEmitter = function (radius, height) {
if (radius === void 0) {
radius = 0.5;
}
if (height === void 0) {
height = 1;
}
var particleEmitter = new BABYLON.SprayParticleEmitter(radius, height);
this.particleEmitterType = particleEmitter;
return particleEmitter;
}
The SprayParticleEmitter
class is formed with two methods startDirectionFunction
and startPositionFunction
and Babylon.js takes care of the rest.
var SprayParticleEmitter = (function () {
function SprayParticleEmitter(radius, height, directionRandomizer) {
if (radius === void 0) { radius = 0.5; }
if (height === void 0) { height = 1; }
if (directionRandomizer === void 0) { directionRandomizer = 0; }
this.height = height;
this.directionRandomizer = directionRandomizer;
this.radius = radius;
}
SprayParticleEmitter.prototype.startDirectionFunction = function (worldMatrix, directionToUpdate, particle) {
var direction = particle.position.subtract(worldMatrix.getTranslation()).normalize();
var randX = BABYLON.Scalar.RandomRange(0, this.directionRandomizer);
var randY = BABYLON.Scalar.RandomRange(0, this.directionRandomizer);
var randZ = BABYLON.Scalar.RandomRange(0, this.directionRandomizer);
if(direction.x * direction.x + direction.z * direction.z > 0.1 * this.radius && Math.abs(direction.y) > 0.1 * this.height / 2) {
direction.x += randX;
direction.y = randY;
direction.z += randZ;
}
else {
direction.x += randX;
direction.y += randY;
direction.z += randZ;
}
direction.normalize();
BABYLON.Vector3.TransformNormalFromFloatsToRef(direction.x, direction.y, direction.z, worldMatrix, directionToUpdate);
};
SprayParticleEmitter.prototype.startPositionFunction = function (worldMatrix, positionToUpdate, particle) {
var s = BABYLON.Scalar.RandomRange(0, Math.PI * 2);
var h = BABYLON.Scalar.RandomRange(-0.5, 0.5);
var radius = BABYLON.Scalar.RandomRange(0, this.radius);
var randX = radius * Math.sin(s);
var randZ = radius * Math.cos(s);
var randY = h * this.height;
BABYLON.Vector3.TransformCoordinatesFromFloatsToRef(randX, randY, randZ, worldMatrix, positionToUpdate);
};
return SprayParticleEmitter;
}());
BABYLON.SprayParticleEmitter = SprayParticleEmitter;
A custom effect is achieved via a fourth parameter when creating a new particle system
var particleSystem = new BABYLON.ParticleSystem("particles", 2000, scene, customEffect);
The customEffect is a type of BABYLON.Effect used to target a valid shader program and is created with the createEffectForParticles
method of the engine
object.
This method takes three parameters
var customEffect = engine.createEffectForParticles(fragment, uniforms, samplers);
When assigning a fragment shader to the shader store the name should have FragmentShader
appended. So for example the creation of a custom effect using fragment name myParticle
would require a myParticleFragmentShader
added to the shader store
BABYLON.Effect.ShadersStore["myParticleFragmentShader"] = [...]
var customEffect = engine.createEffectForParticles("myParticle", [...]);
By default Babylon.js will give you a vUV and a vColor varying parameter. It will also transmit you the particle texture.
You can add further uniform variables, for example to pass a uniform
variable such as time, put it into the uniforms array
var customEffect = engine.createEffectForParticles("myParticle", [time]);
then pass it using setFloat
with an onBind
method for customEffect
.
var time = 0;
var order = 0.1;
customEffect.onBind = function () {
customEffect.setFloat("time", time);
time += order;
if (time > 100 || time < 0) {
order *= -1;
}
};
you can see an example of the above in this playground
The particle effect object is a slightly-modified Babylon Effect Object. Also notice that the ShadersStore is a namespace upon this special effect-object.