Three.js, sharing ShaderMaterial between meshes but with different uniform sets - three.js

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

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

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.

Related

dynamic three.js glsl shader texture best practice

I'd like to have a dynamic GLSL shader texture to be used as a reference map (for displacement and other stuff) on multiple, different materials on different Meshes.
My approach would be to do the computation one time, using a THREE.WebGLRenderTarget, setup a ortho cam, a 1X1 plane with a THREE.ShaderMaterial, and access the WebGLRenderTarget.texture, that I'd embed in a "master" object, whenever and wherever I need.
Is there any "official" object I can / may use for this? I seen the postprocessing objects are pretty similar (EG ShaderPass) but I'm unsure if and how to use them.
Thank you.

Three.js ShaderMaterial and multiple different textures

I have some models being loaded in that I want to all render using the same shader.
Since I have 100+ model chunks, each of which has its own texture, I would like to configure things in a way that I can reuse the same material for multiple Meshes. The problem, however, is that by assigning a texture, I have to do it to the material, that is to say the architecture seems to limit me from having a per-mesh texture without completely making a new Material for each of them.
So, everything still works, but the performance of a large scene composed of hundreds of meshes is problematic because of all of the looping state changes and calls being made to switch programs hundreds of times to render each frame. Of course, I should be making one big mesh instead of many little ones, as that'll help reduce actual number of draw calls... but for the time being I'm trying to optimize a little without addressing these issues arising from other parts of the data pipeline. The main issue is that regardless of how many draw calls are involved, all of the shader program changes and uniform assignments (aside from the texture sampler) are unnecessary.
Are there any tricks I can use or ways to easily hack the library to make it recycle the same shader? One of the problems is that due to the way assigning a texture works I do have to create a new ShaderMaterial for each of my Meshes. It's totally unclear how I could avoid doing such a thing and still get the different textures working.

Fixed texture size in Three.js

I am building quite a complex 3D environment in Three.js (FPS-a-like). For this purpose I wanted to structure the loading of textures and materials in an object oriënted way. For example; materials.wood.brownplank is a reusable material with a certain texture and other properties. Below is a simplified visualisation of the process where models uses materials and materials uses textures.
loadTextures();
loadMaterials();
loadModels();
//start doing stuff in the scene
I want to use that material on differently sized objects. However, in Three.js you can't (AFAIK) set a certain texture scale. You will have to set the repeat to scale it appropiate to your object. But I don't want to do that for every plane of every object I use.
Here is how it looks now
As you can see, the textures are not uniform in size.
Is there an easy way achieve this? So cloning the texture and/or material every time and setting the repeat according to the geometry won't do :)
I hope someone can help me.
Conclusion:
There is no real easy way to do this. I ended up changing my loading methods, where things like materials.wood.brownplank are now for example getMaterial('wood', 'brownplank') In the function new objects are instantiated
You should be able to do this by modifying your geometry UV coordinates according to the "real" dimensions of each face.
In Three.js, UV coordinates are relative to the face and texture (as in, 0.0 = one edge, 1.0 = other edge), no matter what the actual size of texture or face is. But by modifying the UVs in geometry (multiply them by some factor based on face physical size), you can use the same material and texture in different sizes (and orientations) per face.
You just need to figure out the mapping between UVs, geometry scale and your desired working units (eg. mm or m). Sorry I don't have, or know a ready algorithm to do it, but that's the approach you probably need to take. Should be quite doable with a bit of experimentation and google-fu.

Set color of entire THREE.Geometry object

Is there a way to set the color of an entire THREE.Geometry object using three.js? There are a few naive ways I could do this, but none of the methods seem ideal.
I could clone a material and set a different color for each geometry. Essentially, each geometry would have a one-to-one relationship with a material. However, this would create many heavyweight material objects and possibly unnecessary extra shaders on the GPU.
I could also use a single white material and color all the faces of the geometry instead. However, there would create much repetition of the same color objects, since each geometry will only have one color but many faces.
Is there a "proper" way of doing this with three.js?
However, this would create many heavyweight material objects and possibly unnecessary extra shaders on the GPU.
It should not. Shaders will be reused if they're the same. So creating as many materials as geometries should be ok.

Many meshes with the same geometry and material, can I change their colors?

I have a large number (~1000) of THREE.Mesh objects that have been constructed from the same THREE.Geometry and THREE.MeshPhongMaterial (which has a map).
I would like to tint (color) these objects individually.
Naïvely, I tried changing the mesh.material.color property, but changing this property on any of the objects changes the color of all the objects at once. This makes sense, since there is only one material that is shared among all the objects.
My next idea was to create a separate THREE.MeshPhongMaterial for each object. So, now I have a large number of THREE.Mesh objects that been constructed from the same THREE.Geometry, but have individual THREE.MeshPhongMaterials (that share the same texture). This allows me to change the colors individually, but the performance is worse. The chrome profilier shows that the app is spending significant time doing material-things like switching textures.
The material color is just a uniform in the shader. So, updating that uniform should be quite quick.
question: Is there a way to override a material color from the mesh level?
If there was, I believe I could share the material among all my objects and get my performance back, while still changing the colors individually.
[I have tested on v49 and v54, they have identical performance and degredation]
update: I have built a test case, and the performance drop due to this is smaller than I thought it was, but is still measurable.
Here are two links:
http://danceliquid.com/docs/threejs/material-test/index.html?many-materials=false
http://danceliquid.com/docs/threejs/material-test/index.html?many-materials=true
In the first case, there are only two materials, in the second case each cube has it's own material. I measure the framerate of the first case to be 53fps on this machine, and the framerate of the second is 46fps. This is about a 15% drop.
In both cases, the color of the material of every cube is changed every frame. In the case with many materials, we actually see each cube getting it's own color, in the case with only two materials, we see them all having the same color (as expected).
Yes. Per object, clone your material using material.clone(), modify its emissive and color, and set the object's material to this clone. Shaders and attributes are copied by reference, so do not worry that you are cloning the entire material each time; in fact the only things that are copied by value are the uniforms (such as emissive and color). So you can change these per individual object.
Personally I store the original material on a separate, custom property of the object so that I can easily switch back to it later; depends what your needs are.
If you're writing your own shaders, you could use a uniform variable for a general tint (not vertex specific) and pass that in to the shader for factoring into the overall color. vec4f_t and vec4f() are not standard in the C portion, but your code probably already has equivalents.
C:
vec4f_t hue = vec4f(....); // fill in as desired
// load the shader so that GLuint shader_id is available.
// "hue" is a uniform var in the vertexshader
GLUint hue_id = glGetUniformLocation(shader_id, "hue");
// later, before rendering the object:
glUniform4fv(hue_id, 1, &hue);
the.vertexshader:
uniform vec4 hue; // add this and use it in the texture's color computation

Resources