Warning: (too) long post!
Regarding your PG, it does not work because onBeforeRender
doesn’t exist on Scene
, you should use onBeforeRenderObservable
.
However, it still won’t work because you disabled the cloned meshes for the rendering (onBeforeRenderObservable
is called just before calling render
for the RTT), meaning the RTT will be empty because it does not take into account meshes that are disabled. So you should do as you did in the first place by using renderTarget.onBeforeRenderObservable
to enable the clones / renderTarget.onAfterRenderObservable
to disable them (why did you change to use scene.onXXX
over renderTarget.onXXX
?).
In fact, that won’t still work because enabling the clones in renderTarget.onBeforeRenderObservable
is too late in the processing, the collect of meshes to display by the RTT is done before. You should use onBeforeBindObservable
and onAfterUnbindObservable
instead.
Here’s the PG:
https://www.babylonjs-playground.com/#BYYJ4A#19 (I disabled the depth render because it is not used and it unnecessarily cluttered Spector).
There’s still a problem as you can see, which is that the robot clone (in the RTT) does not animate. It seems that when a mesh is disabled while hitting Scene.render()
(which is what happens in the PG, the clones are enabled only during the RTT rendering), it won’t be animated. I don’t know the workaround for this, but it must be possible to animate the clones “by hand”.
However, I didn’t try hard to find the answer because I don’t think it’s the right solution, cloning all meshes only for RTT rendering seems overkill to me. Also, as you said, keeping in-sync animated meshes may need some thinking, and is also a performance hit: the engine has now double animations to process. You may work around this problem by reusing the animations from the original mesh for the “cloned” one, but it’s yet some more work to do (if at all possible?).
If you need performance, I think you should instead:
- clone the shader material and set a cloned material on each mesh before RTT rendering / reset the original material back after rendering as we did previously
- freeze the materials as much as possible: call
.freeze()
on the material
Last point is the one trying to avoid running a full check in isReady
/ isReadyForSubMesh
each time. Try to do it on as many materials as possible.
At the start of StandardMaterial.isReadyForSubMesh
you have:
public isReadyForSubMesh(mesh: AbstractMesh, subMesh: SubMesh, useInstances: boolean = false): boolean {
if (subMesh.effect && this.isFrozen) {
if (this._wasPreviouslyReady) {
return true;
}
}
So, when the material is frozen and the effect has already been generated previously, it returns early and does not perform any check. Of course, use it wisely: if anything that should trigger a material recompile ever change (lights, some defines, …) unfreeze the material and refreeze later when the material (effect) is updated.
You have the same early return in the PBR material, however you don’t have it for ShaderMaterial
and I don’t know why: @Deltakosh Would it be a problem to add those 5 lines at the beginning of ShaderMaterial.isReady
? It could be a small performance improvement for people wanting to fine manage their custom materials.
Here’s the PG with .freeze
called on all materials:
https://www.babylonjs-playground.com/#BYYJ4A#20
However, you won’t see much (any) improvement…
That’s because we are flipping between materials all the time, and when the material changes, the previous effect used for a mesh is nullified and a new effect must be created with the flipped material (subMesh.effect
is null
in the above code and so this.isFrozen
is of no effect)…
What we would need for maximum efficiency is that when flipping material, we also set the effect to use so that it is not recreated. I don’t think it is currently possible, when you do mesh.material = ...
the engine nullifies the effect for all sub meshes of the mesh (which will trigger a recreation of the effect later on in isReadyForSubMesh
).
[…] Ok, it’s possible as we can get / set the effect used by a submesh ourselves. So, save the effects created for the original / RTT passes and reuse them later just after flipping materials:
https://www.babylonjs-playground.com/#BYYJ4A#21
As expected, we now see (in Chrome performance tab and I also checked directly on my local copy of Babylon repo) that a full run of isReadyForSubMesh
occurs only a single time for each of the materials (except for ShaderMaterial
, as explained above, but it does not take a lot of time anyway).
As you can see, however, it requires some work and that we know what we are doing. But performances can be greatly enhanced when playing at low level with effects.
Note: I needed to access the defines associated with the effect, but as it is a private variable not visible from the outside I had to read it with submesh._materialDefines
. I don’t know if exposing this variable would be something sensible or not (@Deltakosh ?)
WARNING: I’m no expert of the inner working of the engine and so take all I said / did with a big grain of salt…
It’s 6 AM, time to go to sleep (I struggled to make this work, until I realized I needed to set the materialDefines
when calling setEffect
)…