Multiple materials with same shader settings

hey team,
I have setup almost 50 identical custom materials, for example one material is like this:

var mat1= new BABYLON.CustomMaterial("mat1", scene);
mat1.diffuseTexture = new BABYLON.Texture("/assets/mat1.png" scene);
mat1.AddAttribute("tileOffset");
mat1.Vertex_Definitions(`
attribute vec4 tileOffset;
varying vec4 vtileOffset;
`);
mat1.Vertex_MainEnd(`
vtileOffset = tileOffset;
`);
mat1.Fragment_Definitions(`
varying vec4 vtileOffset;
`);
mat1.Fragment_Custom_Diffuse(`
float fx = clamp(fract(vDiffuseUV.x), 0., 1.), fy = clamp(fract(vDiffuseUV.y), 0., 1.);
vec2 uvCoord = vec2(vtileOffset.x + fx * vtileOffset.z, vtileOffset.y + fy * vtileOffset.w);
baseColor = texture2D(diffuseSampler, uvCoord);
`);

All of my 50 materials are using same above snippet (for atlas, each diffuseTexture is 1024X1024 and has 64X64 atlas image) except diffuseTexture is different, ie:

mat2.diffuseTexture = new BABYLON.Texture("/assets/mat2.png" scene);
mat3.diffuseTexture = new BABYLON.Texture("/assets/mat3.png" scene);

is there a way I could optimise this? like just same material but with different diffuseTexture?
@Evgeni_Popov any idea for this?
Thanks

It can’t really be optimized as you really need 50 different materials here. Because you are using a CustomMaterial, it will also create 50 shaders (effects), even if the code is the same.

If you wanted to optimize the number of effects created (that would not change the rendering performance, only the memory used and the compilation time), you would need to create your material as a node material instead: @Deltakosh has recently added an optimization where you can reuse the same node material effect for another node material and thus avoid the creation of a new identical effect.

2 Likes

@Evgeni_Popov :bowing_man:

actually, due to lack of my knowledge on this topic, node material is still a dark part which I have not touched yet, and now when seeing to port above shader ( that shader was actually you coded) I feel overwhelmed already, I would be really thankful if you could please show one practical example on how node material work by porting
this customMaterial (named entranceDoorsTextMaterial):
https://playground.babylonjs.com/#RRDCNV
to node material:
https://playground.babylonjs.com/#RRDCNV#1

I just watched every video on YouTube from @PirateJC and @Deltakosh about node materials but still can’t figure out how I would do this, specially the part where I assign tileOffset , I am stuck at this point :frowning: help :cry:

An alternate approach (or one that could use NME) might be to combine all the textures into one atlas texture. Then, you use a single material and just set UVs to the appropriate offset.

A uniform passed into your shader could specify or point to the tile texture coordinates, as one potential thought.

HTH

Thank you @jelster, the issue is they are already combined as per your suggestion :slight_smile:
each texture is 1024x1024 with 64x64 tile uv mapped on each material,
Now I need somehow to optimise this more, I am still trying to figure out how I would set the NME instead custom material as per @Evgeni_Popov suggestion, but seems I can’t figure out how would I make that conversion. :cry:

1 Like

Ah, I saw you mentioned an atlas in your OP, but I must’ve gotten thrown off by this:

You can try this:

https://playground.babylonjs.com/#RRDCNV#14

An attribute block is injected into the node material thanks to the NodeAttributeInjector class. Then this attribute is linked to the VectorSplitter block with this code:

const tileOffsetTextAttribute = new NodeAttributeInjector("MyNode", "tileOffsetText", 4);

const VectorSplitterBlock = entranceDoorsTextMaterial.getBlockByName("VectorSplitter");

tileOffsetTextAttribute.tileOffsetText.connectTo(VectorSplitterBlock.xyzw);

Also, you need to use the Instances block so that instances / thin instances work with a node material.

1 Like

:bowing_man: @Evgeni_Popov thank you so much, I wish I could learn what you know…it has started to make some sense but still in puzzles due to my lack of knowledge on GLSL and how to combine which node, and not much resources I could find to master NME.

And here are couple of issues:

  1. in Safari (WebGL 1) When I open PG I see few errors in console, like:
    WebGL: ERROR: 0:141: 'gl_InstanceID' : undeclared identifier, at first I thought it is not supported in Safari but from same Safari (WebGL 1) I am able to view the shader working fine in preview at URL https://nme.babylonjs.com/#V77XYQ#16 , if it display correctly in nme preview window it should also work in PG?
  2. I have updated PG with 2 materials https://playground.babylonjs.com/#RRDCNV#15. (For demo I just used same mesh with a different texture, which would be different in real scene)
    How can I reuse effects from material 1 on material 2 as you mentioned above:
    reuse the same node material effect for another node material and thus avoid the creation of a new identical effect.?

<3

Regarding 1/, I think you are using 4.2: there’s a bug that has been corrected in 5.0. If you want to fix it for 4.2, see Compiling Node Materials with Instances on Safari - #18 by Evgeni_Popov

For 2/, you need to use clone instead of reloading the 2nd material, and pass true for the 2nd parameter to reuse the same effect. There’s also some code that needs to be added to make NodeAttributeInjector serializable (else clone won’t work):

https://playground.babylonjs.com/#RRDCNV#20

Thanks :relaxed:

For 1. The error is showing on Babylon.js v5.0.0-alpha.21 - WebGL1 - Parallel shader compilation, I am not using 4.2 but for testing I just set to 4.2 and encountered a different error: input left from block Multiply[MultiplyBlock] is not connected and is not optional. Than I copied above overload method which didn’t help. https://playground.babylonjs.com/#RRDCNV#22

It seems NodeAttributeInjector is not compatible with 4.2, so you should stick with 5.0.

Try to add engine.getCaps().canUseGLInstanceID = false; after creating the engine and see if that helps.

sticking with 5.0 :relaxed:

and yes when I included engine.getCaps().canUseGLInstanceID = false; it worked
https://playground.babylonjs.com/#RRDCNV#23
this is confusing though, it works normally in nme editor but not in the PG and both uses same Babylon.js v5.0.0-alpha.21 - WebGL1 - Parallel shader compilation

please could you put some light on these:

  1. are there any drawbacks if I use engine.getCaps().canUseGLInstanceID = false; ?
  2. if I would like to not use alpha should I just remove alpha value and nodes from above nodes like I removed alpha node from here, is that correct https://nme.babylonjs.com/#V77XYQ#17 ? or better if there is a way I could remove alpha in my code so I could use same material for my jpeg textures?
  3. how would I add an ambient texture to it?

Another thing , as all above looks so complex I wanted to do some basic learning by using a standard material,

  1. could you please show a simple example of nodes (only at nme) for standardMaterial to node material nodes for:
    var standardMaterial = new BABYLON.StandardMaterial(“standardMaterial”, scene);
    standardMaterial.diffuseColor = new BABYLON.Color3(2.7,2.7, 2.7);
    standardMaterial l.diffuseTexture = new BABYLON.Texture(‘https//…’);
    I actually couldn’t understand if I need light or not, and how will I increase diffuseColor https://nme.babylonjs.com/#RSPIRY#1
  2. comparing with above standard material, which is more performant? a standard material or a node material port of above snippet?

Thanks a million :bowing_man:

What do you get if you do console.log(engine.getCaps().canUseGLInstanceID) before setting it to false?

Yes, just don’t connect the a input of the FragmentOutput block if you don’t want to support alpha. If you want to have two materials with and without alpha, you should do two node materials (or clone the node material that has alpha connected and remove programmatically the connection to the FragmentOutput.a input) because meshes with alpha materials are handled specifically, they don’t write to the zbuffer and they are sorted/rendered last, and it’s not something you want for materials that don’t have alpha: if the FragmentOutput.a input is connected with something, the material is considered transparent even if it occurs that alpha is always 1.

See Node Material | Babylon.js Documentation

It’s the best way to learn, this node material supports everything the StandardMaterial supports and is (or at least should be) cleanly split into building blocks. There are also some explanations on how it has been built in the doc linked above.

It should be more or less the same perf as the code will do the same thing.

1 Like

Thank you @Evgeni_Popov a lot,

It outputs true

please how would I use ambientTexture in same material? should I copy https://nme.babylonjs.com/#V77XYQ#19 then how to link and use in code
something like this?

const entranceDoors2TextMaterial = entranceDoorsTextMaterial.clone("mat2", true);
const diffuseTexture2Block = entranceDoors2TextMaterial.getBlockByName("DiffuseTexture");
diffuseTexture2Block.texture = new BABYLON.Texture("https://raw.githubusercontent.com/RazaGR/babylon/master/B8F.jpg", scene);
diffuseTexture2Block.texture.hasAlpha = true;
const ambientTexture2Block = entranceDoors2TextMaterial.getBlockByName("AmbientTexture");
ambientTexture2Block.texture = new BABYLON.Texture("https://raw.githubusercontent.com/RazaGR/babylon/master/B8F.jpg", scene);

if so how to connect in https://nme.babylonjs.com/#V77XYQ#19 ?

The PR that fixed the problem was bugged, here’s another one that should be ok:

As a workaround, you can use:

engine.getCaps().canUseGLInstanceID = engine.webGLVersion > 1;

(that’s what the fix is doing)

You should simply multiply the Lights.diffuse output with the AmbientTexture.rgb output before linking to the Add block.

1 Like

Thank you so much for all @Evgeni_Popov , It has been a year I learned so much from you <3

I was able to port all 50 custom materials to node materials, but result is ,node materials are decreasing my performance, with custom materials I was around 250 but with node materials around 350.

My real issue is I have a huge CPU load, specially on mobile devices its heating up a lot, even though my FPS is at 60 ( draw calls are lower in this screen cause I removed some meshes for testing, otherwise drawcanllss are between 250-350) .

second screen when I have various animated objects in scene:

making it run on some mobile devices seems so complex, I have followed various techniques to optimise my scene, I have combined as much materials as possible by using atlas, used thin instances for every duplicated meshes, almost all objects I am optimising with:

        obj.freezeWorldMatrix();
        obj.cullingStrategy = BABYLON.AbstractMesh.CULLINGSTRATEGY_OPTIMISTIC_INCLUSION_THEN_BSPHERE_ONLY;
        obj.doNotSyncBoundingInfo = true;
        obj.freezeNormals();

freezing materials ,ktx2 textures etc and scene:

scene.autoClear = false; // Color buffer
scene.autoClearDepthAndStencil = false; // Depth and stencil, obviously

My most big issue is I am getting huge CPU usage, at first I thought it was due to so many custom material but after trying node material it seems not the issue, and I am not able to understand how could I find what is causing this kind of load on CPU, is there any way I could find which function or material is using too much CPU?

p.s: can I send you link in private message to check please? and of course I would be very glad to show you what I have built :smiley:
Thanks <3

There is extremely high ratio (10:1) between faces numbers and vertices numbers in your model, is it possible to optimize its geometry? Without this it will be hard for mobiles.

1 Like

That’s a nice cache @labris, my actual scene in blender displays 53k vertices and in babylon so many, let me see what is wrong here, I actually have few meshes which I just hide on mobile.

1 Like

ok I found those faces and vertices are being generated from thinInstances, is that expected behaviour or a bug? I used to believe thininstance do not create any vertices @Evgeni_Popov ? if you change totalObjs line#66 value in PG
https://playground.babylonjs.com/#RRDCNV#24
you will notice indices/face count increase with it, but it doesn’t change Total vertices value, strange hm