Simple texture swap at runtime seems not to work

On my model I can replace a specific texture in Sandbox via Inspector / Load Texture From File with a variant, and all looks fine. But when I try the same in code, the texture renders wrong.

What is different between the Inspector / Load Texture from File, and simply writing:

mesh.material.albedoTexture = new Texture( "texture.png" );

It seems working fine for me. Left ball texture is updated after 2 seconds. https://playground.babylonjs.com/#20OAV9#7284

1 Like

When you say the texture renders wrong, do you mean that it’s upside down? If so try passing false for the invertY option, e.g. for gltf models.

mesh.material.albedoTexture = new Texture("texture.png", scene, {invertY: false});

Blake,
Sorry, guess I should have shown the result. It’s not an inversion.

The model in its native state, with the texture exported inside the GLB and not altered in code:
image

The model with the same texture but loaded as an external PNG, then applied via mesh.material.albedoTexture. The result should be identical.
image

The normal and metallic/roughness channels appear unaffected as expected, but the diffuse channel is lost.

Here is the texture itself, which swaps in fine via Inspector manipulation:

FWIW this is a Webpack app.

Also, Ive confirmed at runtime that the mesh’s imported material is a PBRMaterial instance as expected.


make sure that the texture buffer has loaded otherwise it will fallback to another texture.

Pryme8,

Thanks for the suggestion, actually tried that one already too. No change, apparently the engine handles the texture.isReady state for us.

Funny thing is I have two similar variant textures, one predominantly light and one dark. The lightness and darkness are apparent in the result, which tells me the specific texture is driving the material in some way, just not the intended way.

Here’s the result from the dark version swapped in at runtime:
image

And here’s the light version:
image

I know it looks like the textures themselves must be wrong, but both swap fine in the Sandbox Inspector.

dark texture:
image

light texture:
image

Hmm, it looks like the inspector changes the texture by calling updateURL() on the existing texture (rather than creating a new one). So I guess calling it like below would work like the inspector, by preserving the existing texture’s settings (disclaimer I’ve never actually used/noticed that function before thou). :slight_smile:

mesh.material.albedoTexture.updateURL("texture.png");
1 Like

Blake,

According to pbrMaterial.d.ts (Im using Typescript), albedoTexture is an instance of BaseTexture, not of Texture. Meanwhile updateURL() is a method on Texture, which extends BaseTexture, and so not available on the PBRMaterial.albedoTexture

But trying it anyway, because who knows…

HEY, whadayaknow! Success! Apparently SceneLoader has performed some kind of setup in the initialization of the embedded texture, which I dont/cant know to replicate on my own new Texture(). But at least now I dont need to.

Also, it smells like a minor bug that I should have to do a runtime type check of the albedoTexture to confirm it’s a Texture not a BaseTexture, in order to access a critical method. Maybe Texture.updateURL() should be promoted into the BaseTexture class?

Either way, thank you! New trick learned, if not fully understood. Live texture swap now behaves as intended.

image

3 Likes

BaseTexture doesn’t know anything about a URL, so promoting that function would be weird.

Fair enough. But then should PBRMaterial:albedoTexture be promoted to an instance of Texture instead of BaseTexture ?

Otherwise, there’s no safe way to invoke updateURL() without engaging in run-time type inspection which seems hackish to me.

Maybe BaseTexture needs a generic update() method, which Texture could overload to implement URL awareness?

But I admit this is an academic issue now, not at all critical.

No, that doesn’t make sense either. The albedoTexture can be other textures that derive from BaseTexture also.

If you want to invoke updateURL without casting, you can store the original texture as a Texture.

const texture = new Texture(...);
material.texture = texture;
texture.updateURL(...);
1 Like

Yes that’s the method I know.
Besides, you can also make clones of textures (not instances) and assign and update them on your materials (whatever material)… and then, to be honest I don’t see the ‘academic part’ issue missing in this aspect in BJS. For me, it all works fine.

@bghgary Except that these are instantiated via SceneLoader.LoadAssetContainer() so I’ve no control over the instantiation of the given Texture.

So to summarize:

SceneLoader instantiates a PBRMaterial with a Texture in albedoTexture (BaseTexture),
requiring runtime explicit type checking to avoid making an otherwise dangerous assumption in calling albedoTexture.updateURL() when I need to do a texture swap.

I mean if runtime type inspection is considered a best practice, okay, it just feels a little wonky to me.

Either make this assumption (and it is okay if loading a glTF) or check the type. I don’t see another way around it.