Strange Animation Behavior On First Play

In our project, we hide meshes that have a bounding sphere radius under a specific size. We check this using the onAfterWorldMatrixUpdate observable.

I’ve found that when this is in place, when playing an animation that includes meshes that are initially hidden because of this check, during the first time playing the animation, there seems to be a brief stutter or skip on the animation. Even in cases where the animation is reset and the meshes are hidden again, this does not reoccur, meaning it ONLY happens during the first time playing an animation.

Is this expected behavior, is something wrong with how Babylon is functioning, is there a better way to get this same functionality that I’m not seeing?

Playground: https://playground.babylonjs.com/#MH5MCV#13

Edit: For a bit of context, we’re doing this as a method of reducing unnecessary draws.

I think what’s happening is when the animation first starts, the hidden mesh becomes visible, which causes its material to trigger a shader compile.

I’m not sure what’s the best way to avoid it. I tried using mesh.visibility in https://playground.babylonjs.com/#MH5MCV#24 and it seemed to help on Edge and Chrome, but it made almost no difference on Firefox.

@Evgeni_Popov might know a better solution.

1 Like

Is there a functional difference between mesh.visibility and mesh.isVisible?

From my memory of why we’re doing this, we’re trying to reduce our draw calls and saw that mesh.isVisible was a way to accomplish that.

Was working with one of my friends, we came up with another solution that addresses what @docEdub brought up regarding shader compiling.

Ultimately, creating a new mesh with the material of a mesh that would be hidden and adding that to the scene so we could force the shader to be compiled no matter what.

for (const mesh of assetContainer.meshes) {
  const materialBox = BABYLON.CreateBox('materialBox', {
      size: 0.2,
  }, scene);

  materialBox.material = mesh.material;
  materialBox.visibility = false;
}

If anyone can think of a better solution than this, please let me know.

I believe you may try to dispose materialBox after compiling.

There should not be any difference between isVisible = false and visibility = 0, because if any condition is true, the mesh won’t be considered as visible:

Instead of creating a mesh, at start time, you can call Material.forceCompilation or forceCompilationAsync and wait for the callback / promise to kick in before going on.

Note that visibility is not a boolean, it is a value between 0 and 1 and acts as an alpha value.

@Evgeni_Popov Using forceCompilation and forceCompilationAsync seems like a much better approach, thank you. Unfortunately, I am running into the following error while using it:

pbrBaseMaterial.ts:1885 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading ‘hasThinInstances’)

Not sure if that’s expected?

This error means you are passing undefined for the first parameter of forceCompilation: you should pass the mesh for which the material must be compiled.

@Evgeni_Popov Tried doing a playground with forceCompilation, seems like the issue still happens.

Am I just not doing something right or is an alternative method needed for this?

You should wait for the compilation to finish: you should pass a parameter for onCompiled and continue only when it has been called.

It’s probably easier to call forceCompilationAsync for all your materials and wait for all the promises to resolve by using await Promise.all(array_of_promises).

1 Like

Hey @Evgeni_Popov , thanks for the suggestion.
Unfortunately, I’m still getting the same issue while using that approach.

This is with calling forceCompilationAsync on each material and using await Promise.allSettled to wait for all of them to finish.

That’s not an easy one, because it uses the KHR material transmission extension, which triggers the creation of a render target texture under the hood…

You can do something like this to wait for this RTT to be ready for rendering:

const opaqueTextureReady = () => {
    new Promise((resolve) => {
        const check = () => {
            if (scene._transmissionHelper.getOpaqueTarget().isReadyForRendering()) {
                resolve();
            } else {
                setTimeout(() => {
                    check();
                }, 100);
            }
        };

        check();
    });
};

await opaqueTextureReady();

However, this transmission helper uses a setTimeout call (with a 0 timeout) to setup the meshes for this RTT, and you will have to make sure these calls have been handled before calling opaqueTextureReady. The easiest way is to call setTimeout with a small timeout before doing await opaqueTextureReady():

    await new Promise((resolve) => {
        setTimeout(() => {
            resolve();
        }, 10);
    });

Putting it all together:

1 Like

Hey @Evgeni_Popov , thank you for that info.

We did successfully get that implemented and resolved the issue, but created another issue at the same time.

There are some instances where scene._transmissionHelper is undefined indefinitely, causing some of our models to never actually render. Any ideas on what could be happening there or how to work past it?

scene._transmissionHelper will only exist if you load a glb file with a material that uses the KHR_material_transmission extension.