Is it intentional that morph targets do not work on meshes with frozen materials?

Is it intentional that morph targets do not work on meshes with frozen materials?

From what I’ve researched, this appears to be by design, but is there any official Babylon.js documentation that mentions this?

For someone unfamiliar with the internal architecture of CG, since materials and morphs are distinct concepts, it is not intuitive that morphs would stop working simply because a material is frozen.

I spent a lot of time trying to figure out why the morphs weren’t working, so it would be great if there were documentation explaining this. (My apologies if it already exists.)

I’m attaching a repro just in case:

(However, for some reason, the morphs work randomly in this repro.)

Thank you for this great framework!

For your reference, here is another repro: Babylon.js Playground

In this repro, multiple random books are generated, but because the material is frozen, the morphs do not work, and all the books end up with exactly the same dimensions.
If you comment out L13, it works normally.

cc @Evgeni_Popov

Hi @syuilo — yes, this is by design rather than a documentation gap per se: it’s a consequence of what freeze() does internally, but it’s definitely subtle.

The relevant bit lives in the material bindForSubMesh path. For PBRMaterial (and the same shape applies to StandardMaterial), per-frame uniform binding — including BindMorphTargetParameters — is wrapped in roughly if (mustRebind || !this.isFrozen) { ... }. When the material is frozen, mustRebind becomes false on subsequent frames, so the per-sub-mesh morph influences are no longer uploaded. As long as one mesh uses the material that’s mostly fine — its influences were uploaded on the first frame and stay in the uniform. But as soon as you have several meshes sharing the same frozen material with different per-mesh morph influences (your books case), only the last-bound influences stay in the uniform, and every mesh ends up rendering with those — which is exactly the “all books look identical” symptom you described.

Repro showing the difference (latest, WebGL2):

A few ways to work around it:

  1. Don’t freeze() the material when it’s shared across meshes with different per-mesh morph influences.
  2. If you really want freezing for perf, clone the material per mesh and freeze() each clone — then each mesh-material pair only has one set of influences to bind, and the cached uniforms stay correct.
  3. Call unfreeze() before changing influences / regenerating your clones, then freeze() again — works as long as you don’t render frames between the influence change and the next rebind.

I’ll see about getting this called out in the Material.freeze() API docs so the next person doesn’t have to dig for it. Sorry you spent time on it — and thanks for the clean repro!

Thank you for your detailed explanation and updating the doc!

I’m glad to have learned more about Babylon.