Immutable Solid Particle Systems
An immutable SPS is one whose particle properties do not change. This is one way to optimize your scene when it needs many similar objects that will not change afterward, such as buildings in the distance or asteroids. Placing multiple copies of a model into one mesh means only one draw call per frame. Besides SPS, other methods of forming a single mesh from multiple copies of one mesh include merge meshes, instances, and thin instances.
You can create an immutable SPS in two different ways.
- Build your SPS as explained earlier in this section, and call initParticles and setParticles() just once, before and outside the render loop, to set your particles where and how you need them. This method is quite simple. However, the SPS mesh is still built as updatable so that particle properties can be initialized, and because of this, particle data is cached while waiting for a hypothetical further change. This is the only way to do it when using billboard mode, and in this case you still need to call setParticles in the render loop even if the particles do not move.
- Set the SPS to be non-updatable on construction by using a third options parameter: SolidParticleSystem(name, scene, { updatable: false }).
It is the second method that we describe here.
To construct an immutable SPS, you use:
import { SolidParticleSystem } from "@babylonjs/core/Particles/solidParticleSystem";var SPS = new SolidParticleSystem(name, scene, { updatable: false });As the mesh cannot be updated, you cannot initialize the particles with initParticles(), and calling setParticles() has no effect; do not call it, and you will spare some CPU. For now, the particles array is not even populated. In fact, no particle management function called after SPS.buildMesh() will have any effect.
How do you set the initial particle properties when the SPS mesh cannot be updated? You pass a custom function to an options object with the property positionFunction when you add a model as a shape, like this:
SPS.addShape(model, number_of_particles, { positionFunction: myCustomFunction });Your custom function will be called as many times as the number_of_particles for the added shape. It takes up to three parameters: a particle object; an index, i, which is a counter for the total number of shapes added by all calls to addShape; and s, which is a counter for the number of models added by calling addShape.
For example, a custom function has this form:
const myBuilder = function (particle, i, s) { // set properties of particle};When you use it on two models:
SPS.addShape(model1, 1000, { positionFunction: myBuilder });SPS.addShape(model2, 500, { positionFunction: myBuilder });While model1 is added, i runs from 0 to 999 and s runs from 0 to 999.
When model2 is added, i runs from 1000 to 1499 and s runs from 0 to 499.
The particle object has the following properties that you can set:
| property | type | default |
|---|---|---|
| position | Vector3 | (0,0,0) |
| rotation | Vector3 | (0,0,0) |
| rotationQuaternion | Quaternion | null, if rotationQuaternion is set, rotation is ignored |
| scaling | Vector3 | (1,1,1) |
| pivot | Vector3 | (0,0,0) |
| color | Color4 | null |
| uvs | Vector4 | (0,0,1,1) |
An example that builds an immutable mesh:
import { Vector4 } from "@babylonjs/core/Maths/math.vector";import { MeshBuilder } from "@babylonjs/core/Meshes/meshBuilder";import { SolidParticleSystem } from "@babylonjs/core/Particles/solidParticleSystem";const myBuilder = (particle, i, s) => { // particle is the current particle // i is its global index in the SPS // s is its index in its shape, so here from 0 to 149 particle.rotation.y = s / 150; particle.position.x = s - 150; particle.uvs = new Vector4(0, 0, 0.33, 0.33); // first image from an atlas particle.scaling.y = Math.random() + 1;};const box = MeshBuilder.CreateBox("b", {}, scene);const SPS = new SolidParticleSystem("SPS", scene, { updatable: false });SPS.addShape(box, 150, { positionFunction: myBuilder }); // myBuilder will be called for each of the 150 boxesconst mesh = SPS.buildMesh();Immutable color cube of triangle particles: Immutable Color Cube of Triangle Particles Immutable town with 80,000 buildings: Immutable Town With 80,000 Buildings
Note that although the particles cannot move, you can still move, scale, or rotate the whole SPS mesh. When there is no need for your SPS mesh to move, scale, or rotate, you can use the standard Babylon.js mesh freezeXXX() methods for further performance gains.
SPS.mesh.freezeWorldMatrix(); // prevents from re-computing the World Matrix each frameSPS.mesh.freezeNormals(); // prevents from re-computing the normals each frameYou are not restricted to using positionFunction only with an SPS created with updatable = true, although using initParticles() and setParticles() is probably easier and gives you a few more particle properties.
Solid Particle Vertex Position
In addition to positionFunction for setting a particle's properties, there is also a vertexFunction option to modify the shape of each particle by changing the positions of the vertices of the model that forms the particle. The custom function you assign to the vertexFunction is called once by SPS.buildMesh() for each vertex of a particle.
var myVertexFunction = function (particle, vertex, i) { // particle : the current particle // vertex : the current vertex position within the particle // i : index of the vertex in the particle shape vertex.x *= Math.random() + 1;};SPS.addShape(box, 150, { vertexFunction: myVertexFunction }); // the 150 boxes will have their vertices moved randomlySPS.buildMesh();Of course, you can use both properties together:
SPS.addShape(box, 150, { vertexFunction: myVertexFunction, positionFunction: myPositionFunction,});Example with asteroids: Solid Particle Vertex Position Example
Making your SPS immutable is one way to optimize your scene. Next we will consider ways to optimize when using an updatable SPS.
Rebuild the mesh
If a mesh changed at creation time with positionFunction or vertexFunction has then been modified with setParticles(), it can be rebuilt by reapplying the internally stored positionFunction or vertexFunction functions.
Note that only the functions are stored, not their results. This means that if one of your functions produces different results on each call (using Math.random(), for instance), you will not get back the same SPS mesh shape but another computed shape.
SPS.rebuildMesh();Except in some very specific cases, you might not need to use this function.