MultiMaterial Solid Particle System

Now solid particles can be given different materials from the other ones.
Like for standard meshes, first create your MultiMaterial :

    var mat1 = new BABYLON.StandardMaterial("m1", scene);
    var mat2 = new BABYLON.StandardMaterial("m2", scene);
    var mat3 = new BABYLON.StandardMaterial("m3", scene);
    mat2.diffuseColor = BABYLON.Color3.Red();
    mat1.diffuseColor = BABYLON.Color3.Green();
    mat3.diffuseColor = BABYLON.Color3.Yellow();

    var mat = new BABYLON.MultiMaterial("mm", scene);
    mat.subMaterials.push(mat1);
    mat.subMaterials.push(mat2);
    mat.subMaterials.push(mat3);

Then create a SPS with the parameter enableMultiMaterial set to true and assign this multimaterial to its mesh like you would do for any other BJS mesh :

    var sps = new BABYLON.SolidParticleSystem("sps", scene, { enableMultiMaterial: true});
    var model = BABYLON.MeshBuilder.CreateBox("m", {}, scene);
    sps.addShape(model, 1000);
    sps.buildMesh();
    var particles = sps.mesh;
    particles.material = mat;

Now, you can assign a different material from the MultiMaterial object to each particle with the particle property materialIndex.
Then instead of manually creating the submeshes, just call computeSubMeshes() once the particle values are set with setParticles() :

    // particle initialization function
    var initParticle = function(particle) {      
        particle.position.x = areaSize * (Math.random() - 0.5);
        particle.position.y = areaSize * (Math.random() - 0.5);
        particle.position.z = areaSize * (Math.random() - 0.5);

        particle.materialIndex = (Math.random() * mat.subMaterials.length)|0;
    };
    // init particles
    sps.updateParticle = initParticle;
    sps.setParticles();      // sets the particle locations
    sps.computeSubMeshes();  // creates the submeshes from the particle materialIndex values

The SPS geometry is then recomputed so particle vertices and indices are grouped by materials in order to reduce the number of draw calls to its possible minimum (one by used material generally).

This feature also works with an expandable SPS, so you can add, remove or store particles with materials.
It doesn’t work (doesn’t crash but leads to weird visible results) with the particle depth sort when you use transparent materials because particles can’t be sorted in the same time by material AND by the distance to the camera.

You can change the materials, the multimaterial object (example : add some other new materials) or each particle materialIndex value at any time. Just call sps.computeSubMeshes() to actually update the particle materials on the screen.
As this method sorts the all the particles, recompute the whole mesh geometry and allocate new arrays and new buffers each call, it’s probably not a good idea to call it each frame.

PG Examples and documentation to come soon, once the PG engine is upgraded.

6 Likes

http://jerome.bousquie.fr/BJS/test/spsMultiMaterials.html

In this example, 1000 solid particles are created and animated.
Some are given randomly a standard emissive green material, some others are given a textured + a normal map (bump) material, the stones, and some others are given a standard diffuse yellow material.
A glow layer is added in the scene and applies only on the emissive material (so the green one).

So 3 different effects from 3 different materials within the same SPS.

5 Likes

Thanks @jerome,

Fascinating THREADS.

  • add, remove, multi-material SPS.

For anyone here by Google Search:

Cool things happenin in babylon.

:eagle: : )

1 Like

I just added the support for MultiMaterials to immutable SPS also.

This could be an good tool to easily “build” a mesh with multimaterials for people knowning nothing about vertices nor indices by simply assembling particles with different materials.

Example : a SPS created with the parameter updatable to false and a positionFunction

    // particle initialization for immutable SPS 
    // particle material settings
    var initParticle = function(particle) {
        particle.position.x = areaSize * (Math.random() - 0.5);
        particle.position.y = areaSize * (Math.random() - 0.5);
        particle.position.z = areaSize * (Math.random() - 0.5);
        particle.materialIndex = (Math.random() * mat.subMaterials.length)|0;
    };

    var sps = new BABYLON.SolidParticleSystem("sps", scene, { enableMultiMaterial: true, updatable: false });
    sps.addShape(model, 500, {positionFunction: initFunction); // the materials are set at construction time
    sps.buildMesh();  // builds a non-updatable mesh and the subMeshes 
                      // for multimaterials according to the particle materialIndex values
    sps.mesh.material = multimat;

That simple …

I also simplify the use of computeSubMeshes(). It’s no longer needed after calling buildMesh() at construction time (updatable SPS or not), on particle removal or insertion. The method buildMesh() will do it for you when needed.

The call to computeSubMeshes() is now required only when you change the particle materialIndex values (in a call of updateParticle() for instance) or the materials themselves and you don’t call buildMesh() again because it’s not needed.

2 Likes

Very cool. I wonder if it would be a useful feature to automatically create a multimaterial based on the material that is on the meshes that are added to the immutable SPS.

Edit:
That would reduce the code to this:

var sps = new BABYLON.SolidParticleSystem("sps", scene, { enableMultiMaterial: true, updatable: false });
sps.addShape(model, 500, {positionFunction: initFunction); // the materials are set at construction time
sps.buildMesh();  // builds a non-updatable mesh and the subMeshes

The SPS particles don’t know the multimaterial, but only their materialIndex, a bit like subMeshes don’t know the mesh multimaterial itself but only the material indexes.
https://doc.babylonjs.com/how_to/multi_materials

The init function (actually called positionFunction) passed in the method addShape() is required when you want to build an immutable SPS. Indeed, in this case, only the mesh geometry is built and no particle at all is created. It’s another way to build a mesh by assembling subparts (the virtual “particles”).
As there’s no particle in an immutable SPS, you can’t set their materialIndex values afterwards in the code. You have to do it at construction time, that’s why it’s required to do it in the init function.

When you use an updatable SPS (default) with the MultiMaterial support, you can set all the particle values, so their materialIndex values, at any time either in the sps.updateParticle() function that is called then by setParticles() as usual, either in your own logic (say, a loop setting these values when needed, outside the call to setParticles()).
In this case, the mesh is already built so no need for calling again sps.buildMesh(). Just call then sps.computeSubMeshes() once only if you’ve done some updates on the particle materialIndex values.

If you’ve got an expandable mesh and if you add or remove particles, then you have to call sps.buildMesh() to update the geometry. In this case, the geometry will also take in account the recomputation of the subMeshes.

Said simplier, whatever immutable SPS or not, if you set particle materialIndex values at some time, you then need to call sps.computeSubMeshes() after this setting only if no call to sps.buildMesh() was required first.

@adam you mean : to create the multimaterial object and then to assign it to the sps mesh ?
Why not ? but in this case, we will still have to push materials in the multimaterial object at some moment.

I was just thinking that under the hood you could create the multimaterial in the buildMesh function. The SPS will know all the materials to add to the multimaterial and the appropriate indexes for each particle. The user would just need to set the enableMultiMaterial to true when they create the immutable SPS.

Yes, but this step would still be required :

var multimat = new BABYLON.MultiMaterial("multi", scene);
multimat.subMaterials.push(material0);
multimat.subMaterials.push(material1);
multimat.subMaterials.push(material2);

Why couldn’t you do that automatically in the buildMesh function?

You can get the materials from each mesh when they add shapes, right?

ah ok :slight_smile:
I initially didn’t understand that you spoke about automatically copying the materials from the models and then generate the sps multimaterial from them.
Yep, this is doable.

I want also to keep the ability to set a different multimaterial than the model materials. I’ll think about this and do it next week probably.

Yes, that makes sense.

I think that I have a way to do what you mean. Something simple for the final user like

// model1, model2 and model3 are meshes with already set materials at this step
var sps = new BABYLON.SolidParticleSystem('sps', scene, {useModelMaterial: true});
sps.addShape(model1, 300);
sps.addShape(model2, 300)
sps.addShape(model3, 300);
sps.buildMesh();

This would enable the multimaterial support, then copy the model geometries AND create automatically the SPS multimaterial with the model materials, with the following rule :

  • if several models share the same material, this material is used only once in the SPS (particles are sorted in this purpose to minimize the draw call numbers)
  • if a model has no material, a standard material is created
  • if another following model has also no material, the first rule applies : the newly created standard material is shared among the particles depicting all the model with no material.
3 Likes

ok done.
So now, it’s possible to manage the SPS MultiMaterial by three different ways :

  • easy way : set useModelMaterial to true and the SPS will build automatically its own MultiMaterial from the model materials as explained in the former post. This works also for immutable SPS of course, meaning that you can create a non-updatable mesh with multimaterials just by assembling some different shapes. Note that you can overwrite the model material for the wanted particles by setting their own materialIndex values.

  • intermediate way : whatever useModelMaterial or enableMultiMaterial set to true, you can pass the SPS an array of material objects. The material indexes in this array are the materialIndex values that you can give to your particles. Just use sps.setMultiMaterial([mat1, mat2, mat3]) and everything is just done automatically.
    This can even be changed afterwards.

var sps = new BABYLON.SolidParticleSystem('sps', scene, {enableMultiMaterial: true});
sps.addShape(model1, 3000, {positionFunction: initFunction}); // initFunction sets the particle materialIndex values here
sps.buildMesh();
sps.setMultiMaterial([mat1, mat2, mat3]);

// later in the code
sps.setMultiMaterial([mat2, mat1, mat3]); // swaps mat1 and mat2. No need for more.
  • the advanced way : build your own MultiMaterial object by hands like you would do for any other mesh and assign it to the SPS mesh (or use the former method setMultiMaterial()). Then each time that you change the particle materialIndex values (say, in the call to sps.setParticles() or in any other function of your own), just call sps.computeSubMeshes() so that the whole geometry is recomputed under the hood (particles are sorted by materials to minimize the draw call number) and the new material values are then taken in account.
var sps = new BABYLON.SolidParticleSystem('sps', scene, {enableMultiMaterial: true});
sps.addShape(model1, 3000); 
sps.buildMesh();
sps.setMultiMaterial([mat1, mat2, mat3]);

// later again in the code
sps.setParticles();     // if this changes the particle materialIndex values
sps.computeSubMeshes(); // then update the SPS geometry

Documentation and PG examples once the PG engine is upgraded, as usual :wink:

4 Likes

You rock mate!

1 Like

thanks :wink:

Documented : Solid Particle System - Babylon.js Documentation

3 Likes

For those who are wondering, this PG from the documentation is supposed to work, but its feature (autoUpdateSubMeshes) has been merged in the main code just after the documentation edition (yes, documented before coded :smiley: )
So it will be visible, as usual, when the PG engine will be upgraded. Just wait please

1 Like

For the fun, here’s an example of transmutation from stone to gold :smiley:
When the particles rise up from the bottom, they cross the radiative zone and they are then transmuted into gold… then they fall back, crossing again the radiative zone and get back to their initial stone status
https://www.babylonjs-playground.com/#RCUHJA#9

[EDIT] the same with some variable rotations according to the zones so it gives a more dynamic effect
https://www.babylonjs-playground.com/#RCUHJA#10

5 Likes

Still working here at 60 fps in Chrome with 4000 particles instead of 250 … I thought my function computeSubMeshes()would be more CPU intensive and GC teasing. Well, good news then :wink:

https://www.babylonjs-playground.com/#RCUHJA#11

4 Likes