Hello !
I have an issue with freezing materials.
For example I want to freeze some materials when I don’t alternate them and don’t change the meshes informations.
However sometimes I want to add some transparency temporarly so I have to unfreeze the material do my changes and when I remove the transparency freeze it again.
But after that when I take a screenshot it seems that the material is not rendered correctly like the freeze function was called to quickly.
Here’s a playground example where you can toggle transparency and freeze with the “k” key and the GUI button enables to take a screenshot :
https://www.babylonjs-playground.com/#3GPEIL#22
You can see that if we call the freeze function after a timeOut it works. But I prefer to avoid to use this as a solution.
Thank you
The problem is that the uniform buffer of the material couldn’t be updated with the latest changes when you set mat.freeze()
right away. This update is done in mat.bindForSubMesh
and is guarded by:
if (!ubo.useUbo || !this.isFrozen || !ubo.isSync)
It evaluates to false because every component are false:
-
!ubo.useUbo = false
because we are in WebGL2. Note that in WebGL1 everything would work as expected because !ubo.useUbo
would evaluate to true
and so the update code would be executed
-
!this.isFrozen = false
as the material is frozen
-
!ubo.isSync = false
because the GPU uniform buffer is in sync with the current material data
There are several solutions but none are really satisfactory, I think @sebavan or @Deltakosh will have to say what is the right way to handle this use case.
- use a timeout as you did, to let time for the system to run the
mat.bindForSubMesh
with this.isFrozen
still equal to false
- call yourself
mat.bindForSubMesh
before calling mat.freeze
: https://www.babylonjs-playground.com/#3GPEIL#23:
- you will need to call
scene.resetCachedMaterial();
to make sure the check of the cache fail
- you can pass any mesh / submesh that has this material as its material
- make sure
ubo.isSync
will evaluate to false
by setting mat._uniformBuffer._needSync = true
: https://www.babylonjs-playground.com/#3GPEIL#24
- this is the easiest solution but probably the worst because it needs some knowledge of the underlying implementation and access some private properties
Even if 3. is bad in itself, I think in the spirit it is the best because it acts only on the material, whereas 2. needs you to pass a mesh/submesh that is in reality not used for the buffer update.
Maybe a material.forceRefreshData
property (or any suitable name) could be added that would essentially do this._uniformBuffer._needSync = true
?
However, as it is a specific use case, I’m not sure a new property should be created for that…
Thinking out loud, the easiest thing would be to add this line in the material.freeze
method itself:
public freeze(): void {
this.markDirty();
this.checkReadyOnlyOnce = true;
this._uniformBuffer._needSync = true;
}
https://www.babylonjs-playground.com/#3GPEIL#25
1 Like
@Evgeni_Popov Thank you !
It’s a bit clearer but what about calling scene.render
before freezing the material ? In this example it looks like it solves the problem but maybe because it let the time to bindForSubMesh
to be called ?
https://www.babylonjs-playground.com/#3GPEIL#26
I think that adding a flag or a method that makes us sure that the material will be refreshed after the instruction would be a great thing.
I will probably go with the solution 3 because I’m not really fan of calling something to get get all the meshes that uses the material then make a loop to call bindForSubMesh
for each one of them.
Yes, calling scene.render()
will work too, as (among other things) it’s looping over all meshes and do all the necessary stuff to display them. It may be a bit overkill in this circumstance, however.
Note that you don’t need to call bindForSubMesh
for all meshes that use this material, you need to call it only for one (anyone), as the call is meant to refresh the material, not the mesh.