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.

