Atmosphere addon attaches its PBR material plugin to materials in other engines/scenes

Category: Bugs · Babylon.js: 9.27.1 (also 9.17.1) · Package: @babylonjs/addons / ADDONS.Atmosphere

Playground https://playground.babylonjs.com/#GAUAPX#2 ← (select the WebGPU engine, then Run)

What happens

new Atmosphere(...) registers AtmospherePBRMaterialPlugin through the global RegisterMaterialPlugin, and the factory only checks the material’s class:

// packages/dev/addons/src/atmosphere/atmosphere.ts (constructor)
UnregisterMaterialPlugin(MaterialPlugin);
RegisterMaterialPlugin(MaterialPlugin, (material) => {
    if (material.getClassName() === "PBRMaterial") {
        return new AtmospherePBRMaterialPlugin(material, this, this.depthTexture === null);
    }
    return null;
});

Because the registry is page-wide, every PBRMaterial created afterwards gets a plugin bound to this atmosphere, including materials in a different Scene or a different Engine. The plugin then binds the atmosphere’s LUTs and UBO while rendering the foreign material.

Repro

The Playground has an atmosphere in the main engine and a second, unrelated Engine (WebGL2) on a small canvas in the top right. That second engine renders a PBR sphere and has no atmosphere.

Main engine Second engine (no atmosphere)
WebGPU bindTexture: parameter 2 is not of type 'WebGLTexture' is thrown on almost every frame (111 of 120). The sphere never renders.
WebGL2 No exception, but bindTexture/bindBufferBase: object does not belong to this context, followed by hundreds of GL_INVALID_OPERATION: used but unbound uniform buffer. The sphere never renders.

The console logs make the cause explicit:

[repro] previewMat is in scene2: true
[repro] previewMat has AtmospherePBRMaterialPlugin: true
[repro] ...bound to an atmosphere whose scene is scene2? false

We hit this in production with an avatar-preview carousel that runs its own small Engine next to the main WebGPU world. Its preview went blank as soon as we turned the atmosphere on.

Expected

Only materials in atmosphere.scene get the plugin.

Suggested fix

Add a scene check to the factory. This matches how scene-bound plugins usually guard themselves:

RegisterMaterialPlugin(MaterialPlugin, (material) => {
    if (material.getClassName() === "PBRMaterial" && material.getScene() === this.scene) {
        return new AtmospherePBRMaterialPlugin(material, this, this.depthTexture === null);
    }
    return null;
});

The Playground has an APPLY_PROPOSED_FIX flag that re-registers the factory with exactly this check. With it on, the main scene’s materials still get the plugin, the preview material gets none, and the preview renders with 0 errors on both WebGPU and WebGL2.

Related (from reading the source, not separately repro’d): the registration uses one fixed key ("atmo-pbr"). A second Atmosphere in another scene therefore replaces the first one’s factory, and dispose() on either removes the factory for both. Using a per-instance key, for example one suffixed with scene.uid, together with the scene check would let atmospheres in two scenes coexist. Happy to open a PR for either version.

Thanks for the atmosphere addon, by the way. It looks fantastic in our world.

Gentle nudge bc i have no clue if addons are meant to go via some alternate pipe - or if it’s just not something you want to look at. Or if it’s just that I dropped it on a weekend. :wink:

Happy to open a PR for it, but i also respect that it might be out of scope or covered elsewhere.

–Allen

Sorry if we aren’t very responsive over the next few days—we have our in-person meeting this week!

I think you can create a PR for this problem, we will happily review it.

Sweet. and enjoy your face to face. wondering if you’re all in Redmond or Paris. Either way - ignore us and enjoy the team time. It is such a great opportunity.

I’ll file the PR.

Allen