I have project where the scene is made up of meshes which can change material properties dynamically at runtime (alpha & color + shader specific properties like scrolling uvs etc), often fading in or out, and we use a variety of shaders made via the NME that are implemented as NodeMaterials at runtime.
To get suitable rendering & alpha sorting order we’ve been swapping between 2 material variants (regular and fade) on each mesh, where if the object has a final alpha value less than 1, we switch to the fade material variant for the mesh. And we perform the opposite if the alpha value reaches 1/opaque.
Here’s an example of the material settings we have set differently between the regular and fade variants.
What we’re finding is that we suffer poor FPS drops whenever we trigger the dynamic changes to the scene and material swapping takes place, and then once everything is recalculated/changed the FPS speeds up again.
We already during a loading screen, call material.forceCompilationAsync(...) on each material used in the scene to help avoid run-time stalls. Our meshes and material properties are fairly unique per use case so my understanding is that material and/or mesh instancing isn’t useful. As is material freezing because of the alpha/color property changes.
So my question is, for a fairly dynamic scene with fading in/out of meshes, and glass/transparency usage on various meshes, what would you recommend as the best approach to managing materials and scene state to achieve optimum performance out of BJS?
I did something similar in a project but not with node materials. However, this might apply to you as well.
From the above, I understand that it is when you perform the swap of materials that you get the lag.
What about creating an instance or clone of mesh, and not switch material, simply bringing down one to alpha 0 while the other goes to alpha 1? Of course, by saying this, I don’t know just how many meshes are affected in your project.
In your case, you would need to create two different materials instead of modifying the existing one. Then, you can use AbstractMesh.setMaterialForRenderPass(material, id) to set the right material for the right render pass id (which probably is scene.activeCamera.renderPassId).
@Evgeni_Popov , I read this render pass tech before I asked the initial question and didn’t think it was applicable to my use case, as I wanted to have user controlled fading of some objects in the scene, while keeping other opaque. But after re-reading the docs and playground code I see that when a non-default render pass is in use, and the mesh doesn’t have a material for that render pass, it falls back to the default render passes material. This is a key/important point.
Even though I’m setting/unsetting a material for a render pass on 4000 boxes it’s still pretty quick. But I wonder if this is because there’s only 2 materials in use here.
Also I do see that my PG and even the one in the documentation that I extended from, has a second or two FPS drop when the very first change in render pass takes places
camera.renderPassId = renderPassSand;
and then in subsequent toggling this FPS drop isn’t observed. Why is this?
From further testing, the issue appears directly linked to the number of cubes/objects I have in the viewport when I trigger the initial change to the sand material (mat2). If I have all 8000 cubes, I get the initial FPS drop. But if I have 2000 or less I don’t see the drop in FPS. 8000 cubes is 96000 triangles. Which isn’t what I would thought is a really heavy load in raw triangles alone.
That’s because the first time a material is in use by a mesh it must be compiled for that mesh, there’s no way around that.
What you can try is pre-compiling the materials beforehand (at program starting), so that you don’t experience the hiccup at the time you switch materials. To do that, you should set the current render pass id to the right one (engine.currentRenderPassId = id) and then loop over the meshes and call mat.forceCompilation(mesh). Don’t forget to reset engine.currentRenderPassId to the value it had before you changed it!
Yes that’s what I had in mind but I would do it like this instead, else you may have some code that executes with the wrong engine.renderPassId because forceCompilation() is using a setTimeout to execute some code in case some resources are not available right away:
However, it does not work fully because:
for the PBR materials, the forceCompilation method is not updating the subMeshes with an up to date DrawWrapper, it is simply creating/compiling the effect. So, when rendering for the first time the sub meshes there are still some work to be done, which takes a little time
for the standard materials, the forceCompilation method (in Material.forceCompilation) is creating a temporary sub mesh instead of using the one(s) from the mesh (I don’t know why), so there’s the same problem than with the PBR materials, the DrawWrapper is not created for the sub mesh(es) at that time but when rendering the sub mesh(es) for the first time.
I will let @sebavan / @Deltakosh deal with those ones as it could be a bit involved to fix them (or at least we must be sure of the fixes - and also because I’m in holidays ).
In my project I use the async approach for material forceCompilation (although not with a render pass approach) and I believe I run into the same problems on first time rendering still as in your updated PG there. So It would be great if I could get this issue solved somehow so I can do all my loading/preparing behind a loading screen and then get a nice smooth user experience. I am happy to contribute coding time if I’m pointed at the right place/approach to take for the solution to this problem.
Thank you for this. Using the scene.whenReadyAsync during my scene loading, which runs through the 2 permutations of the regular and fade materials per mesh that we use, has made a great improvement.
So far, I am still just doing regular material swapping on the mesh. Do you think I will see a performance improvement if I change my code around to use the renderPassId and setMaterialForRenderPass approach instead?