7

As the title says, I would like to reuse a given ShaderMaterial for different meshes, but with a different set of uniforms for each mesh (in fact, some uniforms may vary between meshes, but not necessarily all of them): is it possible ?

It seems a waste of resources to me to have to create a full ShaderMaterial for each mesh in this circumstance, the idea being to have a single vertex/fragment shader program but to configurate it through different uniforms, whose values would change depending on the mesh. If I create a new ShaderMaterial for each mesh, I will end up with a lots of duplications (vertex+fragment programs + all other data members of the Material / ShaderMaterial classes).

If the engine was able to call a callback before drawing a mesh, I could change the uniforms and achieve what I want to do. Another possibility would be to have a "LiteShaderMaterial" which would hold a pointer to the shared ShaderMaterial + only the specific uniforms for my mesh.

Note that my question is related to this one Many meshes with the same geometry and material, can I change their colors? but is still different, as I'm mostly concerned about the waste of resources - performance wise I don't think it would be a lot different between having multiple ShaderMaterial or a single one, as the engine should be smart enough to note that all materials have the same programs and don't resend them to the gfx card.

Thanks

Community
  • 1
  • 1
Popov
  • 560
  • 5
  • 11

2 Answers2

10

When cloning a ShaderMaterial, the attributes and vertex/fragment programs are copied by reference. Only the uniforms are copied by value, which is what you want.

This should work efficiently.

You can prove it to yourself by creating a ShaderMaterial and then using ShaderMaterial.clone() to clone it for each mesh. Then assign each material unique uniform values.

In the console type "render.info". It should show 1 program.

three.js r.64

WestLangley
  • 102,557
  • 10
  • 276
  • 276
  • Thanks for your answer, but as I said above, I'm not really concerned by performance but by resources instead: for eg, the vertex/fragment programs will be copied in each material, as well as all other data members of ShaderMaterial / Material. It may not be a big concern today with all the memory we have, but having to create thousands of ShaderMaterial because I have thousands of meshes feels wrong to me, when I have a single (or only a couple of different) vertex/fragment shaders and all I need is to change the inputs of these programs. – Popov Mar 24 '13 at 18:45
  • 1
    Not true. When cloning, the vertex/fragment programs and the attributes are copied by reference. Only the uniforms are copied by value, which is what you want. – WestLangley Mar 24 '13 at 19:02
  • vertex and fragment shader variables are string in the ShaderMaterial class, so when cloning the material the whole programs are copied each time. Also, as I create a new material, I get all the other data members copied with the same values than my initial shader which I really don't need as those values won't change (and most of them won't be used at all). I guess I need what I have described above, a lite ShaderMaterial that I would instanciate for each mesh and which would hold only the uniforms array + a reference to a "master" Shader. – Popov Mar 24 '13 at 19:31
  • Not true. When cloning the material, the vertex/fragment program strings are not copied each time. Only a reference is copied. – WestLangley Mar 24 '13 at 20:16
  • See line 70/71 in ShaderMaterial.clone() (https://github.com/mrdoob/three.js/blob/master/src/materials/ShaderMaterial.js#LC70), fragmentShader and vertexShader are copied in the corresponding variables in the cloned material. As those variables are "string", the values are copied by value (you can't pass a string by reference in javascript). Or did I miss something ? – Popov Mar 25 '13 at 14:50
  • Ah I understand: all string are in fact immutable in javascript, so as it is simply copied from one variable to another, internally javascript is only copying the reference... Thanks for your input ! So, I think I'm going to mark this question as solved and will clone my materials. – Popov Mar 25 '13 at 14:56
  • @Popov Are you sure if we do `object1.foo = "foo"; object2.foo = object1.foo` that there will then only be a single instance of that string in memory? Where's the proof of that? – trusktr Jan 02 '18 at 08:44
1

You can safely create multiple ShaderMaterial instances with the same parameters, with clone or otherwise. Three.js will do some extra checks as a consequence of material.needsUpdate being initially true for each instance, but then it will be able to reuse the same program for all instances.

In newer releases another option is to use a single ShaderMaterial, but to add changes to uniforms in the objects' onBeforeRender functions. This avoids unnecessary calls to initMaterial in the renderer, but whether or not this makes it a faster solution overall would have to be tested. It may be a risky solution if you push too much what is being modified before the rendering, as in worst case the single material could then have to be recompiled multiple times during the render. I recommend this guide for further tips.

Elias Hasle
  • 637
  • 7
  • 15