22

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:

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).

Edric
  • 24,639
  • 13
  • 81
  • 91
Harold
  • 1,584
  • 1
  • 12
  • 20
  • 2
    Uhm, that sounds like a "bug". If you can reproduce the issue with r54 would be great if you could report it in the issues section on github. – mrdoob Jan 05 '13 at 00:40
  • Thanks @mrdoob. I've added an issue: https://github.com/mrdoob/three.js/issues/2916 – Harold Jan 10 '13 at 21:07

2 Answers2

10

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.

Engineer
  • 8,529
  • 7
  • 65
  • 105
  • Can you elaborate on "clone your material"? Is this different from constructing a new `THREE.MeshPhongMaterial` for each object? If so, how? – Harold Apr 14 '14 at 15:24
  • @Harold `material.clone()`. It's in the API docs for the base `Material` class. And it exists for most other types of objects in three.js. Since I have no idea how you went about constructing a separate material object previously, I could not tell you if it's the same or different. All I can tell you is that most of the data is shared by reference. – Engineer Apr 14 '14 at 17:35
  • If I get a chance I will experiment with this; we're not using `.clone()` presently. Thanks for the thought. – Harold Apr 14 '14 at 23:37
  • 1
    Finally got a chance to check this out today. It turns out `.clone()` does the same thing I was doing by hand: https://github.com/mrdoob/three.js/blob/master/src/materials/MeshPhongMaterial.js#L103 ... So, the performance is the same. Interestingly, no additional shaders get compiled. That is, `renderer.info.memory.programs` does not change. I will have to investigate the perf hit more to figure out what's really costing me. Thanks again for your idea. – Harold Jul 03 '14 at 18:58
  • @Engineer It's nice to know materials re-use the same program. So other than storing different values and using some more memory, the GPU call time stays the same I suppose? If we call dispose on the material, is the renderer smart enough not to unload the GPU material data because it will be used by the same program by another material? Is there anything I should keep in ind when disposing materials? – trusktr Feb 10 '19 at 19:07
  • 1
    @trusktr A material (not a GL concept) consists of some specific set of (a) shader programs (b) data - i.e. uniforms and vertex attributes. Calls to upload/unload buffer (vertex) data _are_ a distinct GL concept and do not tie directly to any concept of a "material"; and uniforms are never explicitly unloaded. So I would be surprised if disposing Three.js materials implied any unloading whatsoever, as also the GL coding paradigm generally does not make such implications - it is flat and assumes very little (interlinkage). Having said that, I cannot speak to Three's implementation these days. – Engineer Feb 11 '19 at 06:53
0

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
Alex North-Keys
  • 4,200
  • 1
  • 20
  • 22
  • Naturally. (: However, I'd like to be able to do this with the built-in materials without the performance degradation. (i.e., only pay for the uniform set as in your scheme, which should be super-fast) – Harold Aug 04 '13 at 18:09
  • My impression is that most shaders that support vertex color (not by material colors) would tend to expect a array to be handed end for the vertices - an indexed array might be the closest. The vec4 hue idea seems fairly unusual as a single component, but should be pretty easy to add to a vertex shader's vertex color computation. If you're only planned to worth with predefined shaders, however, there's still a chance a thorough web search might turn one up. – Alex North-Keys Aug 05 '13 at 00:57
  • Thanks again for your answer. I was asking specifically about the three.js WebGL library. Your answer pertains to using OpenGL directly. (which could be helpful to someone else) – Harold Jul 03 '14 at 19:02