Cost of not reusing materials?

Hi there,

I’m working on a pretty complex application using hundreds of materials simultaneously - many of them being identical. We found that the initial render was quite slow so we tried to reuse materials by a caching strategy. Since then we have seen noticeable improvements on the time taken to render the first frame.

I have a suspicion that the improved performance could actually be the cost of instancing/object creation in JS rather than the materials being shared in Three.

Could someone explain how this works? I have seen that there’s only one WebGL program per material type so does the amount of materials of the same type actually influence Three performance? Is it worth reusing Three materials besides the JS cost of recreating them?

Thanks a bunch
Best regards

The renderer can automatically detect based on material properties (and other factors) whether shader programs can be shared or not. However, if you know in advance that material objects can be shared then do so. Object creation in JavaScript (and the respective GC overhead) tends to be expensive and you should at least avoid it in code which runs in your animation loop.

2 Likes

Thanks for your fast reply.

Of course no object creation or such in the render loop, it’s just purely rendering. Could you perhaps explain why the first render is way faster (execution time) if sharing materials? Is the first render where the renderer actually have to sort all of this out? Our goal is to cut down on the initial render time.

Thanks

Take a look at this example:

Notice the logged time taken for the first initial render.
Now, move out the material creation from the for-loop to only have one material being reused. The first initial render time is now reduced to less than half.

This behavior is what I’m trying to figure out.

Thanks!

When the renderer encounters a material for the first time, it’s necessary to assign a shader program to it. When using just a single material, this is done once. Since the renderer has to do this for all materials in your demo, you have 500 calls to initMaterial() in the first frame and the respective overhead.

The overhead is manageable since only a single shader program has to be compiled. However, the engine has to do some work to figure out if programs can be shared. You can save this work by using as less materials as possible.

Thanks for clarifying.

The hard part for us is that we are creating everything for the scene dynamically based on fetched data. I have implemented a cache where we reuse materials if there has already been one material crated with the same type and options before. Then we simply return that material instead of creating a new one. This increases performance for both the JS part and the first render.

The obvious issue with this approach is that any change to a single meshes material affects all other that share the same material. This can be solved by creating a new material at the time needed, but that will get out of hands quickly.

Is there any other kind of optimization you could recommend me to try out? The most important thing for us is to decrease the initial render.

Thanks!

Not that I’m aware of, sorry.

Okay thanks anyway.

Will there be a positive difference if I would render after adding each material, so that there is already a program for the material being added? :thinking: If the renderer overhead would go away or just become even worse.

Will try it out and report back!

WebGLRenderer.compile() could be useful.

Many thanks. It definitely decreases the execuction time of the initial render but increases execution in setting up the scene (and then running .compile). Not solving it in our case as we need to render ASAP (which is directly after setting up the scene) but for other scenarios it will make big improvements.

In this case you have to wait until the engine makes use of KHR_parallel_shader_compile.