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?
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.
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.
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.
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.
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? If the renderer overhead would go away or just become even worse.
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.