diff --git a/lab/lite/babylon-ref-scene265.html b/lab/lite/babylon-ref-scene265.html
new file mode 100644
index 0000000000..1a50bc9629
--- /dev/null
+++ b/lab/lite/babylon-ref-scene265.html
@@ -0,0 +1,27 @@
+
+
+
+
+ Babylon.js Reference — Scene 265: EnvironmentTest (EXT_lights_image_based)
+
+
+
+
+
+
+
diff --git a/lab/lite/bundle-bjs-scene265.html b/lab/lite/bundle-bjs-scene265.html
new file mode 100644
index 0000000000..65b650293b
--- /dev/null
+++ b/lab/lite/bundle-bjs-scene265.html
@@ -0,0 +1,27 @@
+
+
+
+
+ Babylon.js Reference — Scene 265: EnvironmentTest (EXT_lights_image_based) (Bundle)
+
+
+
+
+
+
+
diff --git a/lab/lite/bundle-scene265.html b/lab/lite/bundle-scene265.html
new file mode 100644
index 0000000000..e567a7b148
--- /dev/null
+++ b/lab/lite/bundle-scene265.html
@@ -0,0 +1,28 @@
+
+
+
+
+ Babylon Lite — Scene 265: EnvironmentTest (EXT_lights_image_based) (Bundle)
+
+
+
+
+
+
+
+
diff --git a/lab/lite/scene265.html b/lab/lite/scene265.html
new file mode 100644
index 0000000000..c08dafd7c4
--- /dev/null
+++ b/lab/lite/scene265.html
@@ -0,0 +1,28 @@
+
+
+
+
+ Babylon Lite — Scene 265: EnvironmentTest (EXT_lights_image_based)
+
+
+
+
+
+
+
+
diff --git a/lab/lite/src/bjs/scene265.ts b/lab/lite/src/bjs/scene265.ts
new file mode 100644
index 0000000000..a9673bc033
--- /dev/null
+++ b/lab/lite/src/bjs/scene265.ts
@@ -0,0 +1,45 @@
+import { ArcRotateCamera } from "@babylonjs/core/Cameras/arcRotateCamera";
+import { Color4 } from "@babylonjs/core/Maths/math.color";
+import { WebGPUEngine } from "@babylonjs/core/Engines/webgpuEngine";
+import "@babylonjs/core/Helpers/sceneHelpers";
+import "@babylonjs/core/Loading/loadingScreen";
+import { SceneLoader } from "@babylonjs/core/Loading/sceneLoader";
+import { Scene } from "@babylonjs/core/scene";
+import "@babylonjs/loaders/glTF";
+
+const MODEL_URL = "https://cx20.github.io/gltf-test/tutorialModels/EnvironmentTest/glTF-IBL/EnvironmentTest.gltf";
+
+(async function () {
+ const __initStart = performance.now();
+ const canvas = document.getElementById("renderCanvas") as HTMLCanvasElement;
+ const engine = new WebGPUEngine(canvas, { antialias: true, adaptToDeviceRatio: true });
+ await engine.initAsync();
+ engine.displayLoadingUI = function () {};
+
+ const scene = new Scene(engine);
+ scene.clearColor = new Color4(0.2, 0.2, 0.3, 1.0);
+
+ // The glTF's EXT_lights_image_based extension installs scene.environmentTexture.
+ await SceneLoader.AppendAsync("", MODEL_URL, scene);
+
+ // Match loadEnvironment's image-processing defaults (the Lite extension sets these).
+ scene.imageProcessingConfiguration.exposure = 0.8;
+ scene.imageProcessingConfiguration.contrast = 1.2;
+ scene.imageProcessingConfiguration.toneMappingEnabled = true;
+
+ // Same auto-framing formula as Lite's createDefaultCamera; override angles to match.
+ scene.createDefaultCamera(true, true, true);
+ const camera = scene.activeCamera as ArcRotateCamera;
+ camera.alpha = Math.PI / 2;
+ camera.beta = Math.PI / 2.5;
+
+ await scene.whenReadyAsync();
+ engine.runRenderLoop(() => scene.render());
+ window.addEventListener("resize", () => engine.resize());
+
+ await new Promise((resolve) => scene.onAfterRenderObservable.addOnce(() => resolve()));
+ canvas.dataset.camAlpha = String(camera.alpha);
+ canvas.dataset.camRadius = String(camera.radius);
+ canvas.dataset.initMs = String(performance.now() - __initStart);
+ canvas.dataset.ready = "true";
+})().catch(console.error);
diff --git a/lab/lite/src/lite/scene265.ts b/lab/lite/src/lite/scene265.ts
new file mode 100644
index 0000000000..c8a2fd7f98
--- /dev/null
+++ b/lab/lite/src/lite/scene265.ts
@@ -0,0 +1,34 @@
+// Scene 265 — EnvironmentTest (cx20 gltf-test parity).
+// Exercises EXT_lights_image_based: the glTF carries its own image-based light
+// (irradiance SH9 + prefiltered specular cubemap), so we do NOT call
+// loadEnvironment — the extension installs the environment onto the scene.
+import { addToScene, startEngine, createEngine, createSceneContext, createDefaultCamera, loadGltf, attachControl, registerScene } from "babylon-lite";
+
+const MODEL_URL = "https://cx20.github.io/gltf-test/tutorialModels/EnvironmentTest/glTF-IBL/EnvironmentTest.gltf";
+
+async function main(): Promise {
+ const __initStart = performance.now();
+ const canvas = document.getElementById("renderCanvas") as HTMLCanvasElement;
+
+ const engine = await createEngine(canvas);
+ const scene = createSceneContext(engine);
+ scene.clearColor = { r: 0.2, g: 0.2, b: 0.3, a: 1.0 };
+
+ const root = await loadGltf(engine, MODEL_URL);
+ addToScene(scene, root);
+
+ const cam = createDefaultCamera(scene);
+ cam.alpha = Math.PI / 2;
+ cam.beta = Math.PI / 2.5;
+ attachControl(cam, canvas, scene);
+
+ await registerScene(scene);
+ await startEngine(engine);
+ (window as any).__scene = scene;
+ canvas.dataset.camAlpha = String(cam.alpha);
+ canvas.dataset.camRadius = String(cam.radius);
+ canvas.dataset.initMs = String(performance.now() - __initStart);
+ canvas.dataset.ready = "true";
+}
+
+main().catch(console.error);
diff --git a/lab/public/bundle/manifest/scene104.json b/lab/public/bundle/manifest/scene104.json
index 500c4ab804..5100e8a395 100644
--- a/lab/public/bundle/manifest/scene104.json
+++ b/lab/public/bundle/manifest/scene104.json
@@ -1,10 +1,10 @@
{
- "rawKB": 101.4,
+ "rawKB": 101.5,
"gzipKB": 39.2,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene104-generate-mipmaps-C2O_HNRi.js",
- "scene104-gltf-feature-registry-BOpBaZNy.js",
+ "scene104-gltf-feature-registry-B_rK3afU.js",
"scene104-gltf-glb-parser-BJfNq64D.js",
"scene104-shader-composer-DishuYLa.js",
"scene104-standard-renderable-CHoHvWO9.js",
diff --git a/lab/public/bundle/manifest/scene105.json b/lab/public/bundle/manifest/scene105.json
index 126d071c1f..6dafbf037f 100644
--- a/lab/public/bundle/manifest/scene105.json
+++ b/lab/public/bundle/manifest/scene105.json
@@ -1,10 +1,10 @@
{
- "rawKB": 102.1,
- "gzipKB": 39.4,
+ "rawKB": 102.2,
+ "gzipKB": 39.5,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene105-generate-mipmaps-q97-3GGe.js",
- "scene105-gltf-feature-registry-GXOsvmf1.js",
+ "scene105-gltf-feature-registry-DKfE0vzV.js",
"scene105-gltf-glb-parser-BsWC1iTB.js",
"scene105-shader-composer-DishuYLa.js",
"scene105-standard-renderable-BXA9dKAx.js",
diff --git a/lab/public/bundle/manifest/scene11.json b/lab/public/bundle/manifest/scene11.json
index 07f2362213..73522f019b 100644
--- a/lab/public/bundle/manifest/scene11.json
+++ b/lab/public/bundle/manifest/scene11.json
@@ -1,6 +1,6 @@
{
- "rawKB": 89,
- "gzipKB": 37.4,
+ "rawKB": 89.1,
+ "gzipKB": 37.5,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene11-create-skeleton-D-3exD5E.js",
@@ -9,7 +9,7 @@
"scene11-gltf-ext-spec-gloss-D60sFZYX.js",
"scene11-gltf-feature-animations-CUKuLYEP.js",
"scene11-gltf-feature-extras-VHPteGay.js",
- "scene11-gltf-feature-registry-HI7s4KMP.js",
+ "scene11-gltf-feature-registry-CoWTXZ-o.js",
"scene11-gltf-feature-skeleton-Djlep9zW.js",
"scene11-gltf-glb-parser-B0CzN8nR.js",
"scene11-gltf-sampler-desc-DP9TFn1W.js",
diff --git a/lab/public/bundle/manifest/scene112.json b/lab/public/bundle/manifest/scene112.json
index c39909d3df..342d7d509d 100644
--- a/lab/public/bundle/manifest/scene112.json
+++ b/lab/public/bundle/manifest/scene112.json
@@ -1,5 +1,5 @@
{
- "rawKB": 120,
+ "rawKB": 120.1,
"gzipKB": 48.8,
"ignoredRawKB": 0,
"runtimeChunks": [
@@ -9,7 +9,7 @@
"scene112-generate-mipmaps-r2o5vX6L.js",
"scene112-gltf-ext-basisu-CRSWpXls.js",
"scene112-gltf-ext-dielectric-C6hvXXiF.js",
- "scene112-gltf-feature-registry-CsinE8Td.js",
+ "scene112-gltf-feature-registry-CBflmSLe.js",
"scene112-gltf-json-asset-Cj8WBr3J.js",
"scene112-ibl-fragment-CeRrvT96.js",
"scene112-pbr-refraction-nAlT2yO6.js",
diff --git a/lab/public/bundle/manifest/scene115.json b/lab/public/bundle/manifest/scene115.json
index ba8bbc07f7..1f1c4d95a4 100644
--- a/lab/public/bundle/manifest/scene115.json
+++ b/lab/public/bundle/manifest/scene115.json
@@ -1,6 +1,6 @@
{
- "rawKB": 126.4,
- "gzipKB": 52.1,
+ "rawKB": 126.5,
+ "gzipKB": 52.2,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene115-create-morph-targets-BxcR5t59.js",
@@ -10,7 +10,7 @@
"scene115-gltf-color-normalize-4-WGzPL2.js",
"scene115-gltf-feature-animations-DOqxEAHR.js",
"scene115-gltf-feature-morph-DLWJRlR1.js",
- "scene115-gltf-feature-registry-v5cVaPih.js",
+ "scene115-gltf-feature-registry-BjqhkauT.js",
"scene115-gltf-feature-skeleton-BktZKWC9.js",
"scene115-gltf-json-asset-DXBgrt5f.js",
"scene115-morph-fragment-BbCT_r5m.js",
diff --git a/lab/public/bundle/manifest/scene12.json b/lab/public/bundle/manifest/scene12.json
index 0cb1cf7646..4cfb345e0b 100644
--- a/lab/public/bundle/manifest/scene12.json
+++ b/lab/public/bundle/manifest/scene12.json
@@ -1,13 +1,13 @@
{
- "rawKB": 103.5,
- "gzipKB": 42.2,
+ "rawKB": 103.6,
+ "gzipKB": 42.3,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene12-create-skeleton-yE34pa8K.js",
"scene12-generate-mipmaps-WJd2dKx2.js",
"scene12-gltf-animation-BQj3RAlu.js",
"scene12-gltf-feature-animations-Db5mzwHK.js",
- "scene12-gltf-feature-registry-BvvuIzLS.js",
+ "scene12-gltf-feature-registry-DvTgP0Jo.js",
"scene12-gltf-feature-skeleton-eatI5LFE.js",
"scene12-gltf-glb-parser-BrsOlymf.js",
"scene12-ibl-fragment-CeRrvT96.js",
diff --git a/lab/public/bundle/manifest/scene127.json b/lab/public/bundle/manifest/scene127.json
index 97abd23de6..aeb5d841d1 100644
--- a/lab/public/bundle/manifest/scene127.json
+++ b/lab/public/bundle/manifest/scene127.json
@@ -1,5 +1,5 @@
{
- "rawKB": 61.3,
+ "rawKB": 61.4,
"gzipKB": 23.5,
"ignoredRawKB": 0,
"runtimeChunks": [
diff --git a/lab/public/bundle/manifest/scene144.json b/lab/public/bundle/manifest/scene144.json
index b3b1c2ef81..1a0eab11fa 100644
--- a/lab/public/bundle/manifest/scene144.json
+++ b/lab/public/bundle/manifest/scene144.json
@@ -1,6 +1,6 @@
{
- "rawKB": 110.5,
- "gzipKB": 44.7,
+ "rawKB": 110.6,
+ "gzipKB": 44.8,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene144-create-skeleton-DalWacs1.js",
@@ -9,7 +9,7 @@
"scene144-gltf-ext-spec-gloss-D60sFZYX.js",
"scene144-gltf-feature-animations-CvLpEQNh.js",
"scene144-gltf-feature-extras-VHPteGay.js",
- "scene144-gltf-feature-registry-ByT8hALD.js",
+ "scene144-gltf-feature-registry-C4zDl2lJ.js",
"scene144-gltf-feature-skeleton-Dvlpv1pW.js",
"scene144-gltf-glb-parser-CXf6hcyj.js",
"scene144-gltf-pbr-builder-ext-g9qXikef.js",
diff --git a/lab/public/bundle/manifest/scene146.json b/lab/public/bundle/manifest/scene146.json
index b46ab447c6..fb0bd845df 100644
--- a/lab/public/bundle/manifest/scene146.json
+++ b/lab/public/bundle/manifest/scene146.json
@@ -1,6 +1,6 @@
{
"rawKB": 111,
- "gzipKB": 45.4,
+ "gzipKB": 45.5,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene146-alpha-test-fragment-D_P8KBFC.js",
diff --git a/lab/public/bundle/manifest/scene147.json b/lab/public/bundle/manifest/scene147.json
index e87fe6e482..3260b1b0e3 100644
--- a/lab/public/bundle/manifest/scene147.json
+++ b/lab/public/bundle/manifest/scene147.json
@@ -1,12 +1,12 @@
{
- "rawKB": 103.7,
+ "rawKB": 103.8,
"gzipKB": 41.6,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene147-directional-light-Bhj3CbsK.js",
"scene147-generate-mipmaps-7FMQd9Gb.js",
"scene147-gltf-feature-lights-punctual-CwjriEB4.js",
- "scene147-gltf-feature-registry-Dj5aS1Qx.js",
+ "scene147-gltf-feature-registry-f8l66LCT.js",
"scene147-gltf-glb-parser-1VrmmUS3.js",
"scene147-gltf-light-pointer-state-B7kFTRxK.js",
"scene147-multilight-wgsl-EMm1KlC7.js",
diff --git a/lab/public/bundle/manifest/scene148.json b/lab/public/bundle/manifest/scene148.json
index fb8cd01768..1b7b4ac7cc 100644
--- a/lab/public/bundle/manifest/scene148.json
+++ b/lab/public/bundle/manifest/scene148.json
@@ -1,12 +1,12 @@
{
- "rawKB": 109.6,
- "gzipKB": 43.1,
+ "rawKB": 109.8,
+ "gzipKB": 43.2,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene148-directional-light-UoLzTD31.js",
"scene148-generate-mipmaps-CfuW6q-h.js",
"scene148-gltf-feature-lights-punctual-BTZaThsN.js",
- "scene148-gltf-feature-registry-BeLwYcCC.js",
+ "scene148-gltf-feature-registry-CG_xHMUj.js",
"scene148-gltf-glb-parser-BR2EHjxU.js",
"scene148-gltf-light-pointer-state-B7kFTRxK.js",
"scene148-pbr-geometry-view-VyZljgCl.js",
diff --git a/lab/public/bundle/manifest/scene149.json b/lab/public/bundle/manifest/scene149.json
index dbfd7589bc..3778b35b6b 100644
--- a/lab/public/bundle/manifest/scene149.json
+++ b/lab/public/bundle/manifest/scene149.json
@@ -1,6 +1,6 @@
{
- "rawKB": 109,
- "gzipKB": 44.5,
+ "rawKB": 109.1,
+ "gzipKB": 44.6,
"ignoredRawKB": 6.8,
"runtimeChunks": [
"scene149-_math-factory-DFVyS8gB.js",
@@ -9,7 +9,7 @@
"scene149-generate-mipmaps-gtcbk4S2.js",
"scene149-geometry-texture-output-BfeQftmX.js",
"scene149-gltf-feature-lights-punctual-D7xjFqdg.js",
- "scene149-gltf-feature-registry-D8cKsNpu.js",
+ "scene149-gltf-feature-registry-ZWJbcsHU.js",
"scene149-gltf-glb-parser-CpRY-0L4.js",
"scene149-gltf-light-pointer-state-B7kFTRxK.js",
"scene149-input-block-BTUtjdzr.js",
diff --git a/lab/public/bundle/manifest/scene152.json b/lab/public/bundle/manifest/scene152.json
index 831b8f48f2..db5717ed7e 100644
--- a/lab/public/bundle/manifest/scene152.json
+++ b/lab/public/bundle/manifest/scene152.json
@@ -1,6 +1,6 @@
{
- "rawKB": 94.1,
- "gzipKB": 39.1,
+ "rawKB": 94.2,
+ "gzipKB": 39.2,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene152-create-skeleton-BvRoBRkx.js",
@@ -9,7 +9,7 @@
"scene152-gltf-ext-spec-gloss-D60sFZYX.js",
"scene152-gltf-feature-animations-B_C5kwG5.js",
"scene152-gltf-feature-extras-VHPteGay.js",
- "scene152-gltf-feature-registry-B7gVMsrH.js",
+ "scene152-gltf-feature-registry-CLQZbp4N.js",
"scene152-gltf-feature-skeleton-CUsQ3cht.js",
"scene152-gltf-glb-parser-Dr_B2hXm.js",
"scene152-gltf-sampler-desc-D0Q2VM93.js",
diff --git a/lab/public/bundle/manifest/scene157.json b/lab/public/bundle/manifest/scene157.json
index f96b7c4294..d317c3e064 100644
--- a/lab/public/bundle/manifest/scene157.json
+++ b/lab/public/bundle/manifest/scene157.json
@@ -1,13 +1,13 @@
{
- "rawKB": 95.5,
- "gzipKB": 38.9,
+ "rawKB": 95.6,
+ "gzipKB": 39,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene157-create-skeleton-CSKHrTlw.js",
"scene157-generate-mipmaps-BYHWBGCp.js",
"scene157-gltf-animation-PnQa26Zh.js",
"scene157-gltf-feature-animations-at7-WE4C.js",
- "scene157-gltf-feature-registry-CUqIEUA4.js",
+ "scene157-gltf-feature-registry-DlyUXqwJ.js",
"scene157-gltf-feature-skeleton-Cxp-WFUs.js",
"scene157-gltf-glb-parser-DZ4Xgxsa.js",
"scene157-multilight-wgsl-BVR6TJvC.js",
diff --git a/lab/public/bundle/manifest/scene158.json b/lab/public/bundle/manifest/scene158.json
index b2a47a12d5..ce7d502a05 100644
--- a/lab/public/bundle/manifest/scene158.json
+++ b/lab/public/bundle/manifest/scene158.json
@@ -1,5 +1,5 @@
{
- "rawKB": 96,
+ "rawKB": 96.1,
"gzipKB": 39.1,
"ignoredRawKB": 0,
"runtimeChunks": [
@@ -7,7 +7,7 @@
"scene158-generate-mipmaps-bNvmU0dI.js",
"scene158-gltf-animation-D4bTYTv1.js",
"scene158-gltf-feature-animations-BPofhcKw.js",
- "scene158-gltf-feature-registry-DT7ENkGn.js",
+ "scene158-gltf-feature-registry-BcxdxuWM.js",
"scene158-gltf-feature-skeleton-D2diH2X_.js",
"scene158-gltf-glb-parser-BhqojsL9.js",
"scene158-multilight-wgsl-DtA6qLNj.js",
diff --git a/lab/public/bundle/manifest/scene164.json b/lab/public/bundle/manifest/scene164.json
index 6adabb8653..82a95a45fc 100644
--- a/lab/public/bundle/manifest/scene164.json
+++ b/lab/public/bundle/manifest/scene164.json
@@ -1,6 +1,6 @@
{
- "rawKB": 98.9,
- "gzipKB": 40.9,
+ "rawKB": 99,
+ "gzipKB": 41,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene164-create-morph-targets-DRqKS4bi.js",
@@ -10,7 +10,7 @@
"scene164-gltf-color-normalize-4-WGzPL2.js",
"scene164-gltf-feature-animations-CoptQtIt.js",
"scene164-gltf-feature-morph-Dcma2yce.js",
- "scene164-gltf-feature-registry-B_YhQvuM.js",
+ "scene164-gltf-feature-registry-C_lojFmB.js",
"scene164-gltf-feature-skeleton-DRjn81A9.js",
"scene164-gltf-json-asset-B2lSB7pA.js",
"scene164-morph-fragment-7aHPckU3.js",
diff --git a/lab/public/bundle/manifest/scene176.json b/lab/public/bundle/manifest/scene176.json
index 74415d7a41..01ec27c8c5 100644
--- a/lab/public/bundle/manifest/scene176.json
+++ b/lab/public/bundle/manifest/scene176.json
@@ -1,12 +1,12 @@
{
- "rawKB": 104,
- "gzipKB": 41.9,
+ "rawKB": 104.1,
+ "gzipKB": 42,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene176-generate-mipmaps-LBrT9Lsp.js",
"scene176-gltf-ext-dielectric-C6hvXXiF.js",
"scene176-gltf-feature-extras-VHPteGay.js",
- "scene176-gltf-feature-registry-C9Nk5MCW.js",
+ "scene176-gltf-feature-registry-vDUh2x2R.js",
"scene176-gltf-json-asset-C-LYB2bT.js",
"scene176-ibl-fragment-CeRrvT96.js",
"scene176-ibl-skybox-wgsl-CFIBOHHx.js",
diff --git a/lab/public/bundle/manifest/scene178.json b/lab/public/bundle/manifest/scene178.json
index 4131a1b938..7c10a991d6 100644
--- a/lab/public/bundle/manifest/scene178.json
+++ b/lab/public/bundle/manifest/scene178.json
@@ -1,11 +1,11 @@
{
- "rawKB": 87.7,
- "gzipKB": 35.7,
+ "rawKB": 87.8,
+ "gzipKB": 35.8,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene178-generate-mipmaps-BGzDQPIy.js",
"scene178-gltf-ext-iridescence-b4LVniAM.js",
- "scene178-gltf-feature-registry-CQxgwmB5.js",
+ "scene178-gltf-feature-registry-2IMHz2-v.js",
"scene178-gltf-glb-parser-d5msxooD.js",
"scene178-ibl-fragment-CeRrvT96.js",
"scene178-ibl-skybox-wgsl-CFIBOHHx.js",
diff --git a/lab/public/bundle/manifest/scene210.json b/lab/public/bundle/manifest/scene210.json
index dc54315ba7..1a6a32a9cd 100644
--- a/lab/public/bundle/manifest/scene210.json
+++ b/lab/public/bundle/manifest/scene210.json
@@ -1,10 +1,10 @@
{
- "rawKB": 76.8,
+ "rawKB": 76.9,
"gzipKB": 31.9,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene210-generate-mipmaps-BeLXoJVY.js",
- "scene210-gltf-feature-registry-CvVtn6EL.js",
+ "scene210-gltf-feature-registry-D0nALpTm.js",
"scene210-gltf-feature-xmp-S8Tl-XNQ.js",
"scene210-gltf-glb-parser-fJOQf1ny.js",
"scene210-gltf-interleave-D_KaKI90.js",
diff --git a/lab/public/bundle/manifest/scene211.json b/lab/public/bundle/manifest/scene211.json
index 7a8c879ae6..21ec38a4f3 100644
--- a/lab/public/bundle/manifest/scene211.json
+++ b/lab/public/bundle/manifest/scene211.json
@@ -1,5 +1,5 @@
{
- "rawKB": 90.7,
+ "rawKB": 90.8,
"gzipKB": 38.3,
"ignoredRawKB": 0,
"runtimeChunks": [
@@ -9,7 +9,7 @@
"scene211-gltf-ext-quantization-DTtElLs7.js",
"scene211-gltf-feature-animations-CsN6DvHw.js",
"scene211-gltf-feature-meshopt-Dt65KW5n.js",
- "scene211-gltf-feature-registry-BBXWh-H3.js",
+ "scene211-gltf-feature-registry-BrpLabr3.js",
"scene211-gltf-feature-skeleton-DpuvZ9VR.js",
"scene211-gltf-json-asset-CHRQZ19d.js",
"scene211-gltf-multi-buffer-Cwm5u1Qq.js",
diff --git a/lab/public/bundle/manifest/scene212.json b/lab/public/bundle/manifest/scene212.json
index 9bb8befb47..9414b9620a 100644
--- a/lab/public/bundle/manifest/scene212.json
+++ b/lab/public/bundle/manifest/scene212.json
@@ -1,11 +1,11 @@
{
- "rawKB": 104.1,
+ "rawKB": 104.2,
"gzipKB": 41.8,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene212-generate-mipmaps-ffZ1zox-.js",
"scene212-gltf-ext-dielectric-C6hvXXiF.js",
- "scene212-gltf-feature-registry-B_jczvrv.js",
+ "scene212-gltf-feature-registry-B1FTzveb.js",
"scene212-gltf-glb-parser-DpbXuAuC.js",
"scene212-ibl-fragment-CeRrvT96.js",
"scene212-ibl-skybox-wgsl-CFIBOHHx.js",
diff --git a/lab/public/bundle/manifest/scene217.json b/lab/public/bundle/manifest/scene217.json
index 31f0a31dac..0cd507d17d 100644
--- a/lab/public/bundle/manifest/scene217.json
+++ b/lab/public/bundle/manifest/scene217.json
@@ -1,6 +1,6 @@
{
"rawKB": 77.9,
- "gzipKB": 31.4,
+ "gzipKB": 31.5,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene217-multilight-wgsl-hb_hGU0D.js",
diff --git a/lab/public/bundle/manifest/scene218.json b/lab/public/bundle/manifest/scene218.json
index edd82bebe7..61953b29e7 100644
--- a/lab/public/bundle/manifest/scene218.json
+++ b/lab/public/bundle/manifest/scene218.json
@@ -1,6 +1,6 @@
{
- "rawKB": 94.8,
- "gzipKB": 39.3,
+ "rawKB": 94.9,
+ "gzipKB": 39.4,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene218-create-skeleton-i4S0gk21.js",
@@ -9,7 +9,7 @@
"scene218-gltf-ext-spec-gloss-D60sFZYX.js",
"scene218-gltf-feature-animations-u2H7ejDl.js",
"scene218-gltf-feature-extras-VHPteGay.js",
- "scene218-gltf-feature-registry-C6aCvKmN.js",
+ "scene218-gltf-feature-registry-wh_ox79H.js",
"scene218-gltf-feature-skeleton-n7Xbyk0V.js",
"scene218-gltf-glb-parser-BXHcqEF1.js",
"scene218-gltf-sampler-desc-CS2bAJuk.js",
diff --git a/lab/public/bundle/manifest/scene219.json b/lab/public/bundle/manifest/scene219.json
index d2bfaa02c4..0d77bfed8d 100644
--- a/lab/public/bundle/manifest/scene219.json
+++ b/lab/public/bundle/manifest/scene219.json
@@ -1,6 +1,6 @@
{
- "rawKB": 98,
- "gzipKB": 40.7,
+ "rawKB": 98.1,
+ "gzipKB": 40.8,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene219-create-skeleton-DIqv-tev.js",
@@ -9,7 +9,7 @@
"scene219-gltf-ext-spec-gloss-D60sFZYX.js",
"scene219-gltf-feature-animations-B_YvITgV.js",
"scene219-gltf-feature-extras-VHPteGay.js",
- "scene219-gltf-feature-registry-Dd1yymM7.js",
+ "scene219-gltf-feature-registry-Dp5eqqoy.js",
"scene219-gltf-feature-skeleton-CpmBaHpL.js",
"scene219-gltf-glb-parser-PDyH09Yg.js",
"scene219-gltf-sampler-desc-CKWNJQQv.js",
diff --git a/lab/public/bundle/manifest/scene240.json b/lab/public/bundle/manifest/scene240.json
index ccdb208255..835b7d3b5b 100644
--- a/lab/public/bundle/manifest/scene240.json
+++ b/lab/public/bundle/manifest/scene240.json
@@ -1,5 +1,5 @@
{
- "rawKB": 91,
+ "rawKB": 91.1,
"gzipKB": 38.1,
"ignoredRawKB": 0,
"runtimeChunks": [
@@ -7,7 +7,7 @@
"scene240-generate-mipmaps-BQZ9ivTX.js",
"scene240-gltf-animation-aBVjSk7N.js",
"scene240-gltf-feature-animations-Cm8MU3RN.js",
- "scene240-gltf-feature-registry-DJN0wxrV.js",
+ "scene240-gltf-feature-registry-1jiIj5sv.js",
"scene240-gltf-json-asset-C-b8cTJb.js",
"scene240-gltf-multi-buffer-Cwm5u1Qq.js",
"scene240-gltf-normals-DYhLwXNX.js",
diff --git a/lab/public/bundle/manifest/scene241.json b/lab/public/bundle/manifest/scene241.json
index 0c963b1dab..7cbaff28bf 100644
--- a/lab/public/bundle/manifest/scene241.json
+++ b/lab/public/bundle/manifest/scene241.json
@@ -1,6 +1,6 @@
{
- "rawKB": 161.8,
- "gzipKB": 66.2,
+ "rawKB": 161.9,
+ "gzipKB": 66.3,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene241-alpha-test-fragment-BpzeKSEk.js",
@@ -21,7 +21,7 @@
"scene241-gltf-feature-animation-pointer-Ch_iHImp.js",
"scene241-gltf-feature-animations-DEv1D7Id.js",
"scene241-gltf-feature-lights-punctual-BYIHIGm7.js",
- "scene241-gltf-feature-registry-CZAnajUH.js",
+ "scene241-gltf-feature-registry-BLRhzZDS.js",
"scene241-gltf-json-asset-Dn-uYlO4.js",
"scene241-gltf-light-pointer-state-B7kFTRxK.js",
"scene241-gltf-pbr-builder-ext-D5jBhNyS.js",
diff --git a/lab/public/bundle/manifest/scene242.json b/lab/public/bundle/manifest/scene242.json
index 9f20380e87..98408e8ef1 100644
--- a/lab/public/bundle/manifest/scene242.json
+++ b/lab/public/bundle/manifest/scene242.json
@@ -1,5 +1,5 @@
{
- "rawKB": 97.2,
+ "rawKB": 97.3,
"gzipKB": 40.3,
"ignoredRawKB": 0,
"runtimeChunks": [
@@ -10,7 +10,7 @@
"scene242-gltf-ext-emissive-strength-CButLZnK.js",
"scene242-gltf-feature-animation-pointer-tuVf2ksY.js",
"scene242-gltf-feature-animations-c59Hsp8m.js",
- "scene242-gltf-feature-registry-B3JqMJCl.js",
+ "scene242-gltf-feature-registry-B-t_oxJU.js",
"scene242-gltf-json-asset-89J8Oj0_.js",
"scene242-ibl-fragment-CeRrvT96.js",
"scene242-pbr-renderable-D53YrDns.js",
diff --git a/lab/public/bundle/manifest/scene243.json b/lab/public/bundle/manifest/scene243.json
index 80271918c7..b8d970bd5e 100644
--- a/lab/public/bundle/manifest/scene243.json
+++ b/lab/public/bundle/manifest/scene243.json
@@ -1,5 +1,5 @@
{
- "rawKB": 97.5,
+ "rawKB": 97.6,
"gzipKB": 41.1,
"ignoredRawKB": 0,
"runtimeChunks": [
@@ -9,7 +9,7 @@
"scene243-gltf-feature-animations-CX3jw94s.js",
"scene243-gltf-feature-extras-VHPteGay.js",
"scene243-gltf-feature-morph-BCUQi4W_.js",
- "scene243-gltf-feature-registry-Cj6WQCqE.js",
+ "scene243-gltf-feature-registry-fDksuXuF.js",
"scene243-gltf-json-asset-Cgf3mpUp.js",
"scene243-gltf-pbr-builder-ext-CVrSOAh6.js",
"scene243-ibl-fragment-CeRrvT96.js",
diff --git a/lab/public/bundle/manifest/scene244.json b/lab/public/bundle/manifest/scene244.json
index 7c70b27d97..2198ca590d 100644
--- a/lab/public/bundle/manifest/scene244.json
+++ b/lab/public/bundle/manifest/scene244.json
@@ -1,5 +1,5 @@
{
- "rawKB": 133.7,
+ "rawKB": 133.8,
"gzipKB": 54.2,
"ignoredRawKB": 0,
"runtimeChunks": [
@@ -12,7 +12,7 @@
"scene244-gltf-ext-uv-transform-YGWG8v_J.js",
"scene244-gltf-feature-animation-pointer-IIOn7FNX.js",
"scene244-gltf-feature-animations-C9Kp7IJh.js",
- "scene244-gltf-feature-registry-D5-R55AG.js",
+ "scene244-gltf-feature-registry-Bnc9qOZU.js",
"scene244-gltf-json-asset-BJhL5QXR.js",
"scene244-gltf-pbr-builder-ext-BI2CisGc.js",
"scene244-ibl-fragment-CeRrvT96.js",
diff --git a/lab/public/bundle/manifest/scene245.json b/lab/public/bundle/manifest/scene245.json
index 3c98b4af28..3e007f4c8d 100644
--- a/lab/public/bundle/manifest/scene245.json
+++ b/lab/public/bundle/manifest/scene245.json
@@ -1,5 +1,5 @@
{
- "rawKB": 100.7,
+ "rawKB": 100.8,
"gzipKB": 42.7,
"ignoredRawKB": 0,
"runtimeChunks": [
@@ -8,7 +8,7 @@
"scene245-generate-mipmaps-WM6bVf2v.js",
"scene245-gltf-animation-l40-wjM0.js",
"scene245-gltf-feature-animations-JDRZSSUs.js",
- "scene245-gltf-feature-registry-gM33Awmp.js",
+ "scene245-gltf-feature-registry-CV2u0xO_.js",
"scene245-gltf-feature-skeleton-D19NTiji.js",
"scene245-gltf-interleave-7NBE5rBz.js",
"scene245-gltf-json-asset-D_ddxWqm.js",
diff --git a/lab/public/bundle/manifest/scene246.json b/lab/public/bundle/manifest/scene246.json
index 486195a816..fe0c264ae1 100644
--- a/lab/public/bundle/manifest/scene246.json
+++ b/lab/public/bundle/manifest/scene246.json
@@ -1,5 +1,5 @@
{
- "rawKB": 99.5,
+ "rawKB": 99.6,
"gzipKB": 42.3,
"ignoredRawKB": 0,
"runtimeChunks": [
@@ -8,7 +8,7 @@
"scene246-generate-mipmaps-DzKvtUax.js",
"scene246-gltf-animation-Bg1KL3K9.js",
"scene246-gltf-feature-animations-DxZ057Zf.js",
- "scene246-gltf-feature-registry-CBbXbqJx.js",
+ "scene246-gltf-feature-registry-BOTdaBTS.js",
"scene246-gltf-feature-skeleton-CD4nSwQl.js",
"scene246-gltf-interleave-ZNL-XZje.js",
"scene246-gltf-json-asset-BLYKbDyg.js",
diff --git a/lab/public/bundle/manifest/scene247.json b/lab/public/bundle/manifest/scene247.json
index 7b8aff3d1f..0db386293a 100644
--- a/lab/public/bundle/manifest/scene247.json
+++ b/lab/public/bundle/manifest/scene247.json
@@ -1,12 +1,12 @@
{
- "rawKB": 84.7,
+ "rawKB": 84.8,
"gzipKB": 35.3,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene247-generate-mipmaps-Crazqgzv.js",
"scene247-gltf-feature-extras-VHPteGay.js",
"scene247-gltf-feature-gpu-instancing-BQnnR0-8.js",
- "scene247-gltf-feature-registry-BfM-Z3oM.js",
+ "scene247-gltf-feature-registry-j6KavomJ.js",
"scene247-gltf-json-asset-CILluwFg.js",
"scene247-gltf-multi-buffer-Cwm5u1Qq.js",
"scene247-ibl-fragment-CeRrvT96.js",
diff --git a/lab/public/bundle/manifest/scene251.json b/lab/public/bundle/manifest/scene251.json
index 08c1152e66..13cbec764b 100644
--- a/lab/public/bundle/manifest/scene251.json
+++ b/lab/public/bundle/manifest/scene251.json
@@ -1,5 +1,5 @@
{
- "rawKB": 89.7,
+ "rawKB": 89.8,
"gzipKB": 37,
"ignoredRawKB": 0,
"runtimeChunks": [
@@ -7,7 +7,7 @@
"scene251-generate-mipmaps-QjbHE23R.js",
"scene251-gltf-animation-Cn2klEOG.js",
"scene251-gltf-feature-animations-BBOyTs9Y.js",
- "scene251-gltf-feature-registry-DXI3KYwX.js",
+ "scene251-gltf-feature-registry-DUX8T-vK.js",
"scene251-gltf-feature-skeleton-C4O8Sov-.js",
"scene251-gltf-glb-parser-8AD91ACR.js",
"scene251-multilight-wgsl-C4_Zdm3l.js",
diff --git a/lab/public/bundle/manifest/scene253.json b/lab/public/bundle/manifest/scene253.json
index e61ae62b87..1a4c5ae6ac 100644
--- a/lab/public/bundle/manifest/scene253.json
+++ b/lab/public/bundle/manifest/scene253.json
@@ -1,5 +1,5 @@
{
- "rawKB": 153,
+ "rawKB": 153.1,
"gzipKB": 64.6,
"ignoredRawKB": 0,
"runtimeChunks": [
@@ -24,7 +24,7 @@
"scene253-gltf-feature-extras-VHPteGay.js",
"scene253-gltf-feature-lights-punctual-BD0mrLuD.js",
"scene253-gltf-feature-morph-OgvZWqfo.js",
- "scene253-gltf-feature-registry-DllfL8b_.js",
+ "scene253-gltf-feature-registry-B2fkk9xC.js",
"scene253-gltf-feature-skeleton-BYmZVC5E.js",
"scene253-gltf-json-asset-CPydyTRL.js",
"scene253-gltf-light-pointer-state-B7kFTRxK.js",
diff --git a/lab/public/bundle/manifest/scene254.json b/lab/public/bundle/manifest/scene254.json
index 2e25b44cec..4da454ab35 100644
--- a/lab/public/bundle/manifest/scene254.json
+++ b/lab/public/bundle/manifest/scene254.json
@@ -1,12 +1,12 @@
{
- "rawKB": 92.4,
- "gzipKB": 38.4,
+ "rawKB": 92.6,
+ "gzipKB": 38.5,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene254-generate-mipmaps-CYwo5pXb.js",
"scene254-gltf-animation-BROLRbEL.js",
"scene254-gltf-feature-animations-XsSEzprH.js",
- "scene254-gltf-feature-registry-swQjGwuy.js",
+ "scene254-gltf-feature-registry-CVmt1bu7.js",
"scene254-gltf-json-asset-CpRpkx7j.js",
"scene254-gltf-sampler-denorm-Bui_WQIw.js",
"scene254-ibl-fragment-CeRrvT96.js",
diff --git a/lab/public/bundle/manifest/scene255.json b/lab/public/bundle/manifest/scene255.json
index cc09d12f0d..cfb573cbb9 100644
--- a/lab/public/bundle/manifest/scene255.json
+++ b/lab/public/bundle/manifest/scene255.json
@@ -1,5 +1,5 @@
{
- "rawKB": 94.1,
+ "rawKB": 94.3,
"gzipKB": 39.5,
"ignoredRawKB": 0,
"runtimeChunks": [
@@ -8,7 +8,7 @@
"scene255-generate-mipmaps-B7LdcXNp.js",
"scene255-gltf-animation-BbNxs5BN.js",
"scene255-gltf-feature-animations-Cd2IBM3n.js",
- "scene255-gltf-feature-registry-BTGiXViM.js",
+ "scene255-gltf-feature-registry-C4D9gW7Q.js",
"scene255-gltf-feature-skeleton-DHaKW6Xm.js",
"scene255-gltf-json-asset-vxujGMAS.js",
"scene255-gltf-normals-DYhLwXNX.js",
diff --git a/lab/public/bundle/manifest/scene257.json b/lab/public/bundle/manifest/scene257.json
index f5dea42556..ac8bda74d4 100644
--- a/lab/public/bundle/manifest/scene257.json
+++ b/lab/public/bundle/manifest/scene257.json
@@ -1,12 +1,12 @@
{
- "rawKB": 80,
+ "rawKB": 80.1,
"gzipKB": 33.1,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene257-flat-normal-wgsl-Cm9mM4tC.js",
"scene257-generate-mipmaps-C2jNwHZ4.js",
"scene257-gltf-feature-primitive-BMqT3MMH.js",
- "scene257-gltf-feature-registry-C3q-THmF.js",
+ "scene257-gltf-feature-registry-BHQCyd-Y.js",
"scene257-gltf-json-asset-DAx_-Tyr.js",
"scene257-gltf-normals-DYhLwXNX.js",
"scene257-ibl-fragment-CeRrvT96.js",
diff --git a/lab/public/bundle/manifest/scene260.json b/lab/public/bundle/manifest/scene260.json
index e34c80e848..6530907d97 100644
--- a/lab/public/bundle/manifest/scene260.json
+++ b/lab/public/bundle/manifest/scene260.json
@@ -1,12 +1,12 @@
{
- "rawKB": 80,
+ "rawKB": 80.1,
"gzipKB": 33.1,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene260-flat-normal-wgsl-Cm9mM4tC.js",
"scene260-generate-mipmaps-BDaYF730.js",
"scene260-gltf-feature-primitive-BMqT3MMH.js",
- "scene260-gltf-feature-registry-zt2COmYK.js",
+ "scene260-gltf-feature-registry-C_jWvWmz.js",
"scene260-gltf-json-asset-B1CNeG6-.js",
"scene260-gltf-normals-DYhLwXNX.js",
"scene260-ibl-fragment-CeRrvT96.js",
diff --git a/lab/public/bundle/manifest/scene265.json b/lab/public/bundle/manifest/scene265.json
new file mode 100644
index 0000000000..7a1c2f9c7c
--- /dev/null
+++ b/lab/public/bundle/manifest/scene265.json
@@ -0,0 +1,16 @@
+{
+ "rawKB": 80,
+ "gzipKB": 33.4,
+ "ignoredRawKB": 0,
+ "runtimeChunks": [
+ "scene265-generate-mipmaps-B94XvMrD.js",
+ "scene265-gltf-ext-lights-image-based-Cy0y6Rcm.js",
+ "scene265-gltf-feature-registry-CBqzfUe8.js",
+ "scene265-gltf-json-asset-D5Ypa449.js",
+ "scene265-ibl-cubemap-upload-aeOmgodq.js",
+ "scene265-ibl-env-assembly-c2UHDMKi.js",
+ "scene265-ibl-fragment-CeRrvT96.js",
+ "scene265-pbr-renderable-BW7BE1kN.js",
+ "scene265.js"
+ ]
+}
diff --git a/lab/public/bundle/manifest/scene27.json b/lab/public/bundle/manifest/scene27.json
index 7b40139d3c..c76dfa8ff7 100644
--- a/lab/public/bundle/manifest/scene27.json
+++ b/lab/public/bundle/manifest/scene27.json
@@ -1,11 +1,11 @@
{
- "rawKB": 82.6,
+ "rawKB": 82.7,
"gzipKB": 33.8,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene27-generate-mipmaps-CaLzDGPp.js",
"scene27-gltf-ext-dielectric-C6hvXXiF.js",
- "scene27-gltf-feature-registry-DpatAEqC.js",
+ "scene27-gltf-feature-registry-SQsTbQJ2.js",
"scene27-gltf-feature-variants-BNhy8bR8.js",
"scene27-gltf-glb-parser-By8noE3G.js",
"scene27-gltf-pbr-builder-ext-OvOYi-mh.js",
diff --git a/lab/public/bundle/manifest/scene28.json b/lab/public/bundle/manifest/scene28.json
index 9a1cc27883..d44d97f384 100644
--- a/lab/public/bundle/manifest/scene28.json
+++ b/lab/public/bundle/manifest/scene28.json
@@ -1,12 +1,12 @@
{
- "rawKB": 86.6,
- "gzipKB": 35.3,
+ "rawKB": 86.7,
+ "gzipKB": 35.4,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene28-clearcoat-fragment-DOT6cYUU.js",
"scene28-generate-mipmaps-7w1hCgCw.js",
"scene28-gltf-ext-clearcoat-BgaLgSgD.js",
- "scene28-gltf-feature-registry-DcWcd3j1.js",
+ "scene28-gltf-feature-registry-C6Rkd9pb.js",
"scene28-gltf-json-asset-BJmxzYNI.js",
"scene28-ibl-fragment-CeRrvT96.js",
"scene28-pbr-renderable-CzHHWfVB.js",
diff --git a/lab/public/bundle/manifest/scene29.json b/lab/public/bundle/manifest/scene29.json
index 0d9a0b2728..9fbb0adf9e 100644
--- a/lab/public/bundle/manifest/scene29.json
+++ b/lab/public/bundle/manifest/scene29.json
@@ -1,12 +1,12 @@
{
- "rawKB": 90.2,
- "gzipKB": 37.3,
+ "rawKB": 90.3,
+ "gzipKB": 37.4,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene29-generate-mipmaps-DJCYSY1_.js",
"scene29-gltf-ext-sheen-Dv23b_3T.js",
"scene29-gltf-ext-uv-transform-tUSEOqA7.js",
- "scene29-gltf-feature-registry-DZ6jUlur.js",
+ "scene29-gltf-feature-registry-CJkVEMgl.js",
"scene29-gltf-json-asset-BCPycsr9.js",
"scene29-gltf-pbr-builder-ext-CpjrBrd7.js",
"scene29-ibl-fragment-CeRrvT96.js",
diff --git a/lab/public/bundle/manifest/scene30.json b/lab/public/bundle/manifest/scene30.json
index 2c68605fa4..05aaa4761b 100644
--- a/lab/public/bundle/manifest/scene30.json
+++ b/lab/public/bundle/manifest/scene30.json
@@ -1,5 +1,5 @@
{
- "rawKB": 103.7,
+ "rawKB": 103.8,
"gzipKB": 42.9,
"ignoredRawKB": 0,
"runtimeChunks": [
@@ -8,7 +8,7 @@
"scene30-gltf-ext-dielectric-C6hvXXiF.js",
"scene30-gltf-ext-uv-transform-DQN5DAiE.js",
"scene30-gltf-feature-draco-BSbw1ZBU.js",
- "scene30-gltf-feature-registry-61PwWdOS.js",
+ "scene30-gltf-feature-registry-DdgFJ0nT.js",
"scene30-gltf-glb-parser-DJ0cTJ-4.js",
"scene30-gltf-pbr-builder-ext-B4o2oNmo.js",
"scene30-ibl-fragment-CeRrvT96.js",
diff --git a/lab/public/bundle/manifest/scene31.json b/lab/public/bundle/manifest/scene31.json
index 1493a08055..3da3066986 100644
--- a/lab/public/bundle/manifest/scene31.json
+++ b/lab/public/bundle/manifest/scene31.json
@@ -1,12 +1,12 @@
{
- "rawKB": 80,
- "gzipKB": 32.9,
+ "rawKB": 80.1,
+ "gzipKB": 33,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene31-emissive-fragment-C1TNSo0w.js",
"scene31-generate-mipmaps-CoCbB-UD.js",
"scene31-gltf-ext-emissive-strength-CButLZnK.js",
- "scene31-gltf-feature-registry-DbXHy-RL.js",
+ "scene31-gltf-feature-registry-COb_OD3O.js",
"scene31-gltf-glb-parser-CzRZVo57.js",
"scene31-ibl-fragment-CeRrvT96.js",
"scene31-pbr-renderable-Du_q1nFe.js",
diff --git a/lab/public/bundle/manifest/scene32.json b/lab/public/bundle/manifest/scene32.json
index 721ef66536..dd44d48165 100644
--- a/lab/public/bundle/manifest/scene32.json
+++ b/lab/public/bundle/manifest/scene32.json
@@ -1,11 +1,11 @@
{
- "rawKB": 79.8,
+ "rawKB": 79.9,
"gzipKB": 32.9,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene32-generate-mipmaps-DdnhjgK3.js",
"scene32-gltf-ext-unlit-1vUYfF7v.js",
- "scene32-gltf-feature-registry-DV4vP3Ve.js",
+ "scene32-gltf-feature-registry-BCljR88z.js",
"scene32-gltf-glb-parser-BX2AS-bF.js",
"scene32-ibl-fragment-CeRrvT96.js",
"scene32-pbr-renderable-BZrTmnOw.js",
diff --git a/lab/public/bundle/manifest/scene33.json b/lab/public/bundle/manifest/scene33.json
index f8bb85ae12..96498aae0c 100644
--- a/lab/public/bundle/manifest/scene33.json
+++ b/lab/public/bundle/manifest/scene33.json
@@ -1,12 +1,12 @@
{
- "rawKB": 103.7,
+ "rawKB": 103.8,
"gzipKB": 42.7,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene33-generate-mipmaps-Z_0Gnbub.js",
"scene33-gltf-ext-dielectric-C6hvXXiF.js",
"scene33-gltf-feature-lights-punctual-Bo3_Vssl.js",
- "scene33-gltf-feature-registry-BJIjfGjc.js",
+ "scene33-gltf-feature-registry-C0aOXsyt.js",
"scene33-gltf-glb-parser-DMBUHtjv.js",
"scene33-gltf-light-pointer-state-B7kFTRxK.js",
"scene33-gltf-sampler-desc-ChMUJ3dG.js",
diff --git a/lab/public/bundle/manifest/scene34.json b/lab/public/bundle/manifest/scene34.json
index cfac1112f3..94108f6768 100644
--- a/lab/public/bundle/manifest/scene34.json
+++ b/lab/public/bundle/manifest/scene34.json
@@ -1,5 +1,5 @@
{
- "rawKB": 96.8,
+ "rawKB": 96.9,
"gzipKB": 40,
"ignoredRawKB": 0,
"runtimeChunks": [
@@ -8,7 +8,7 @@
"scene34-gltf-ext-node-visibility-DWsJZC5Q.js",
"scene34-gltf-feature-animation-pointer-CE1BnN9s.js",
"scene34-gltf-feature-animations-BoKR3Ok5.js",
- "scene34-gltf-feature-registry-CJICailA.js",
+ "scene34-gltf-feature-registry-DJtUXEQr.js",
"scene34-gltf-glb-parser-BxoFTGKn.js",
"scene34-gltf-sampler-denorm-C_vs8OEN.js",
"scene34-ibl-fragment-CeRrvT96.js",
diff --git a/lab/public/bundle/manifest/scene35.json b/lab/public/bundle/manifest/scene35.json
index 6863602671..379b389009 100644
--- a/lab/public/bundle/manifest/scene35.json
+++ b/lab/public/bundle/manifest/scene35.json
@@ -1,11 +1,11 @@
{
- "rawKB": 83.7,
+ "rawKB": 83.8,
"gzipKB": 34.6,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene35-generate-mipmaps-Dw2dswCB.js",
"scene35-gltf-feature-gpu-instancing-D1uNjxTA.js",
- "scene35-gltf-feature-registry-BByeuJvD.js",
+ "scene35-gltf-feature-registry-D5wdCRl6.js",
"scene35-gltf-glb-parser-BJyme4Vk.js",
"scene35-ibl-fragment-CeRrvT96.js",
"scene35-pbr-renderable-BaquMftV.js",
diff --git a/lab/public/bundle/manifest/scene37.json b/lab/public/bundle/manifest/scene37.json
index 7685ccd4bd..9c5a10843e 100644
--- a/lab/public/bundle/manifest/scene37.json
+++ b/lab/public/bundle/manifest/scene37.json
@@ -1,5 +1,5 @@
{
- "rawKB": 97,
+ "rawKB": 97.1,
"gzipKB": 39.8,
"ignoredRawKB": 0,
"runtimeChunks": [
@@ -8,7 +8,7 @@
"scene37-gltf-ext-dielectric-C6hvXXiF.js",
"scene37-gltf-ext-sheen-Dv23b_3T.js",
"scene37-gltf-ext-uv-transform-hVTzf5Q9.js",
- "scene37-gltf-feature-registry-CsR7M1E5.js",
+ "scene37-gltf-feature-registry-tU68dbia.js",
"scene37-gltf-glb-parser-CkbGcOCI.js",
"scene37-gltf-pbr-builder-ext-rpTZqaAk.js",
"scene37-ibl-fragment-CeRrvT96.js",
diff --git a/lab/public/bundle/manifest/scene39.json b/lab/public/bundle/manifest/scene39.json
index 1f2f5f5816..d1171ad283 100644
--- a/lab/public/bundle/manifest/scene39.json
+++ b/lab/public/bundle/manifest/scene39.json
@@ -1,5 +1,5 @@
{
- "rawKB": 108.8,
+ "rawKB": 108.9,
"gzipKB": 46.1,
"ignoredRawKB": 0,
"runtimeChunks": [
@@ -12,7 +12,7 @@
"scene39-gltf-feature-animation-pointer-CLES_-mr.js",
"scene39-gltf-feature-animations-eOJFbLQK.js",
"scene39-gltf-feature-lights-punctual-5HuRS6Ip.js",
- "scene39-gltf-feature-registry-CmrMeHhc.js",
+ "scene39-gltf-feature-registry-DWIeQH-G.js",
"scene39-gltf-json-asset-BoHskl0t.js",
"scene39-gltf-light-pointer-state-B7kFTRxK.js",
"scene39-gltf-pbr-builder-ext-WVY5V9c8.js",
diff --git a/lab/public/bundle/manifest/scene41.json b/lab/public/bundle/manifest/scene41.json
index 8aa2cd343e..c7f40d0d4c 100644
--- a/lab/public/bundle/manifest/scene41.json
+++ b/lab/public/bundle/manifest/scene41.json
@@ -1,11 +1,11 @@
{
- "rawKB": 93,
- "gzipKB": 37,
+ "rawKB": 93.1,
+ "gzipKB": 37.1,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene41-generate-mipmaps-CaoM8To3.js",
"scene41-gltf-ext-spec-gloss-D60sFZYX.js",
- "scene41-gltf-feature-registry-Dy_lcWPj.js",
+ "scene41-gltf-feature-registry-DPchpuak.js",
"scene41-gltf-glb-parser-CkOP9avo.js",
"scene41-gltf-sampler-desc-E_7RaQlV.js",
"scene41-point-light-CnkkP9oB.js",
diff --git a/lab/public/bundle/manifest/scene46.json b/lab/public/bundle/manifest/scene46.json
index 0fcae7f034..fe5c08b798 100644
--- a/lab/public/bundle/manifest/scene46.json
+++ b/lab/public/bundle/manifest/scene46.json
@@ -1,6 +1,6 @@
{
"rawKB": 54.4,
- "gzipKB": 21.2,
+ "gzipKB": 21.3,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene46-standard-renderable-CRXAGvV-.js",
diff --git a/lab/public/bundle/manifest/scene47.json b/lab/public/bundle/manifest/scene47.json
index 1fe6043505..208cbb6d24 100644
--- a/lab/public/bundle/manifest/scene47.json
+++ b/lab/public/bundle/manifest/scene47.json
@@ -1,11 +1,11 @@
{
- "rawKB": 92.4,
- "gzipKB": 36.9,
+ "rawKB": 92.5,
+ "gzipKB": 37,
"ignoredRawKB": 0,
"runtimeChunks": [
"scene47-generate-mipmaps-DOnKpLaE.js",
"scene47-gltf-ext-spec-gloss-D60sFZYX.js",
- "scene47-gltf-feature-registry-CdyWFmG5.js",
+ "scene47-gltf-feature-registry-Cl-zcH-q.js",
"scene47-gltf-glb-parser-6-tsF2C2.js",
"scene47-gltf-sampler-desc-BJMmnJDF.js",
"scene47-shader-composer-DishuYLa.js",
diff --git a/lab/public/bundle/manifest/scene5.json b/lab/public/bundle/manifest/scene5.json
index 3b5ed13b45..d62feafd7c 100644
--- a/lab/public/bundle/manifest/scene5.json
+++ b/lab/public/bundle/manifest/scene5.json
@@ -1,5 +1,5 @@
{
- "rawKB": 91.9,
+ "rawKB": 92,
"gzipKB": 38.9,
"ignoredRawKB": 0,
"runtimeChunks": [
@@ -10,7 +10,7 @@
"scene5-gltf-color-normalize-4-WGzPL2.js",
"scene5-gltf-feature-animations-Db-wL1ps.js",
"scene5-gltf-feature-morph-BNcyLEaQ.js",
- "scene5-gltf-feature-registry-DXE8nKBz.js",
+ "scene5-gltf-feature-registry-CtefggdW.js",
"scene5-gltf-feature-skeleton-BYbOC8HD.js",
"scene5-gltf-json-asset-DXOveXY1.js",
"scene5-morph-fragment-CAPye0MH.js",
diff --git a/lab/public/bundle/manifest/scene7.json b/lab/public/bundle/manifest/scene7.json
index 500b92f3ff..4baa72fd97 100644
--- a/lab/public/bundle/manifest/scene7.json
+++ b/lab/public/bundle/manifest/scene7.json
@@ -1,5 +1,5 @@
{
- "rawKB": 105.3,
+ "rawKB": 105.4,
"gzipKB": 44.6,
"ignoredRawKB": 0,
"runtimeChunks": [
@@ -10,7 +10,7 @@
"scene7-gltf-animation-RP7A9LpW.js",
"scene7-gltf-feature-animations-C9A-KG60.js",
"scene7-gltf-feature-extras-VHPteGay.js",
- "scene7-gltf-feature-registry-pKXLy51C.js",
+ "scene7-gltf-feature-registry-DY1Jo4GK.js",
"scene7-gltf-feature-skeleton-DaFXylO1.js",
"scene7-gltf-json-asset-DelZuh7Y.js",
"scene7-ibl-fragment-CeRrvT96.js",
diff --git a/lab/public/bundle/manifest/scene73.json b/lab/public/bundle/manifest/scene73.json
index 5a0d955c22..dc1cb7e81f 100644
--- a/lab/public/bundle/manifest/scene73.json
+++ b/lab/public/bundle/manifest/scene73.json
@@ -1,5 +1,5 @@
{
- "rawKB": 143.6,
+ "rawKB": 143.7,
"gzipKB": 58.1,
"ignoredRawKB": 3.3,
"runtimeChunks": [
@@ -10,7 +10,7 @@
"scene73-fragment-output-CfRmfbPv.js",
"scene73-generate-mipmaps-DcnO72FN.js",
"scene73-gltf-ext-clearcoat-BgaLgSgD.js",
- "scene73-gltf-feature-registry-BHYeIm-u.js",
+ "scene73-gltf-feature-registry-CSNKAtfS.js",
"scene73-gltf-glb-parser-Bm1IrZjT.js",
"scene73-ibl-fragment-CeRrvT96.js",
"scene73-input-block-lrbZBZJO.js",
diff --git a/lab/public/bundle/manifest/scene99.json b/lab/public/bundle/manifest/scene99.json
index cfab1f7fbb..1b3c672760 100644
--- a/lab/public/bundle/manifest/scene99.json
+++ b/lab/public/bundle/manifest/scene99.json
@@ -1,5 +1,5 @@
{
- "rawKB": 92,
+ "rawKB": 92.1,
"gzipKB": 38.3,
"ignoredRawKB": 0,
"runtimeChunks": [
@@ -8,7 +8,7 @@
"scene99-generate-mipmaps-BFyY2NRa.js",
"scene99-gltf-animation-T1Ovedwq.js",
"scene99-gltf-feature-animations-CIfvZioj.js",
- "scene99-gltf-feature-registry-Cnxn65i3.js",
+ "scene99-gltf-feature-registry-BrSEYdFf.js",
"scene99-gltf-feature-skeleton-BUITWXe0.js",
"scene99-gltf-glb-parser-BtneRupN.js",
"scene99-multilight-wgsl-Cni0HYFi.js",
diff --git a/lab/public/thumbnails/scene265.jpg b/lab/public/thumbnails/scene265.jpg
new file mode 100644
index 0000000000..74b6bec7f7
Binary files /dev/null and b/lab/public/thumbnails/scene265.jpg differ
diff --git a/packages/babylon-lite/src/asset-container.ts b/packages/babylon-lite/src/asset-container.ts
index d2760e610a..524c027299 100644
--- a/packages/babylon-lite/src/asset-container.ts
+++ b/packages/babylon-lite/src/asset-container.ts
@@ -4,6 +4,7 @@ import type { AnimationGroup } from "./animation/animation-group.js";
import type { MaterialVariantData } from "./loader-gltf/material-variants.js";
import type { Mesh } from "./mesh/mesh.js";
import type { Skeleton } from "./skeleton/bone-control.js";
+import type { SceneContext } from "./scene/scene-core.js";
/**
* Result returned by loadGltf / loadBabylon.
@@ -30,6 +31,14 @@ export interface AssetContainer {
* `enableBoneControl()` was called before loading; otherwise `undefined`.
* Drive bones via `getBoneByName()` + the `setBone*` functions. */
skeletons?: Skeleton[];
+ /** Deferred scene-wiring hook contributed by a loader feature that needs the
+ * target `SceneContext` (which the loader itself never sees). `addToScene()`
+ * invokes it once, synchronously, while processing the container. Used by
+ * `EXT_lights_image_based` to install its image-based-light environment
+ * (spherical harmonics + specular cubemap) onto the scene. Lazy features own
+ * the closure so the core loader/scene stay feature-agnostic.
+ * @internal */
+ _sceneSetup?: (scene: SceneContext) => void;
}
/**
diff --git a/packages/babylon-lite/src/loader-gltf/gltf-ext-lights-image-based.ts b/packages/babylon-lite/src/loader-gltf/gltf-ext-lights-image-based.ts
new file mode 100644
index 0000000000..ac9855ecd3
--- /dev/null
+++ b/packages/babylon-lite/src/loader-gltf/gltf-ext-lights-image-based.ts
@@ -0,0 +1,175 @@
+/** EXT_lights_image_based glTF extension.
+ *
+ * Installs a document-level image-based light (diffuse irradiance SH9 + a set of
+ * prefiltered specular mip cubemaps, plus intensity/rotation) referenced by the
+ * active scene, driving the PBR IBL through Lite's existing environment path.
+ *
+ * Reuse (KISS, maximum reuse per GUIDANCE):
+ * - Specular mips are RGBD-encoded PNGs (identical encoding to Babylon `.env`
+ * faces), so they upload through the shared `uploadCubemapRGBD` decoder.
+ * - The BRDF split-sum LUT is generated procedurally on-GPU (`generateBrdfLut`,
+ * shared with the HDR loader) — no external asset URL is needed.
+ * - The final `EnvironmentTextures` are assembled with `assembleEnvironmentTextures`
+ * and wired onto the scene exactly like `loadEnvironment` / `loadHdrEnvironment`
+ * (`scene._envTextures` + `registerEnvSceneUniforms` + tone-mapping defaults).
+ *
+ * The glTF `irradianceCoefficients` are raw SH9 coefficients. Babylon converts
+ * them SH → intensity scale → Lambertian radiance (×1/π) → SphericalPolynomial,
+ * and its shader later expands that polynomial to the pre-scaled harmonics the
+ * scene UBO consumes. We reproduce the SH→polynomial step here so the shared
+ * `polynomialToPreScaledHarmonics` (inside `assembleEnvironmentTextures`) yields
+ * byte-for-byte the same rendering coefficients as Babylon.
+ */
+
+import type { GltfFeature } from "./gltf-feature.js";
+import type { SceneContext } from "../scene/scene-core.js";
+
+// NOTE: this module is a lazily-imported feature chunk (see gltf-feature-registry)
+// and keeps ZERO static runtime imports — every dependency is pulled in via dynamic
+// import inside applyAsset. Scenes that never trigger EXT_lights_image_based never
+// fetch this chunk (or its dependencies) at runtime. The heavy IBL helpers it reuses
+// (cubemap upload, env-texture assembly, BRDF LUT, SH conversion, samplers) are kept
+// as PRIVATE copies (ibl-cubemap-upload.js + ibl-env-assembly.js) rather than shared
+// with loader-env / loader-hdr. This is a deliberate, user-approved exception to the
+// max-reuse mandate: those canonical helpers are single-consumer and thus inlined
+// into every glTF scene's main chunk; importing them here would make this feature a
+// second consumer and force rollup to hoist them into shared chunks that all 44 glTF
+// scenes would pay for. The private copies isolate the feature's byte cost to the one
+// scene that uses it. See ibl-env-assembly.js for the canonical-source mapping.
+
+interface GltfImageBasedLight {
+ name?: string;
+ intensity?: number;
+ rotation?: [number, number, number, number];
+ irradianceCoefficients?: number[][];
+ specularImageSize: number;
+ specularImages: number[][];
+}
+
+/** Convert glTF `EXT_lights_image_based` SH9 irradiance coefficients into the
+ * 27-float spherical-polynomial layout `[x,y,z,xx,yy,zz,yz,zx,xy]` that
+ * `assembleEnvironmentTextures` → `polynomialToPreScaledHarmonics` expects.
+ *
+ * Mirrors Babylon's chain: `SphericalHarmonics.FromArray(coeffs)` →
+ * `scaleInPlace(intensity)` → `convertIrradianceToLambertianRadiance()` (×1/π) →
+ * `SphericalPolynomial.FromHarmonics()` (`updateFromHarmonics`, which folds a
+ * final ×1/π). All band constants are copied from Babylon `sphericalPolynomial`. */
+function irradianceCoefficientsToPolynomial(coeffs: number[][], intensity: number): Float32Array {
+ // Harmonic scale = intensity (radiance) × 1/π (Lambertian normalisation).
+ const s = intensity / Math.PI;
+ const poly = new Float32Array(27);
+ for (let c = 0; c < 3; c++) {
+ const l00 = coeffs[0]![c]! * s;
+ const l1_1 = coeffs[1]![c]! * s;
+ const l10 = coeffs[2]![c]! * s;
+ const l11 = coeffs[3]![c]! * s;
+ const l2_2 = coeffs[4]![c]! * s;
+ const l2_1 = coeffs[5]![c]! * s;
+ const l20 = coeffs[6]![c]! * s;
+ const l21 = coeffs[7]![c]! * s;
+ const l22 = coeffs[8]![c]! * s;
+
+ // updateFromHarmonics(), then the trailing ×(1/π).
+ const k = 1 / Math.PI;
+ poly[0 + c] = -1.02333 * l11 * k; // x
+ poly[3 + c] = -1.02333 * l1_1 * k; // y
+ poly[6 + c] = 1.02333 * l10 * k; // z
+ poly[9 + c] = (0.886277 * l00 - 0.247708 * l20 + 0.429043 * l22) * k; // xx
+ poly[12 + c] = (0.886277 * l00 - 0.247708 * l20 - 0.429043 * l22) * k; // yy
+ poly[15 + c] = (0.886277 * l00 + 0.495417 * l20) * k; // zz
+ poly[18 + c] = -0.858086 * l2_1 * k; // yz
+ poly[21 + c] = -0.858086 * l21 * k; // zx
+ poly[24 + c] = 0.858086 * l2_2 * k; // xy
+ }
+ return poly;
+}
+
+/** Extract the Y-axis rotation angle (radians) from an image-based-light
+ * quaternion. The extension's rotations are pure Y-yaw; Lite drives environment
+ * yaw through `scene.envRotationY`. Babylon inverts the rotation for its
+ * left-handed target scene, so we negate to match its sampling direction. */
+function envYawFromQuaternion(q: [number, number, number, number]): number {
+ return -2 * Math.atan2(q[1], q[3]);
+}
+
+const feature: GltfFeature = {
+ id: "EXT_lights_image_based",
+ async applyAsset(_meshes, _root, ctx) {
+ const json = ctx._json;
+ const lights: GltfImageBasedLight[] | undefined = json.extensions?.EXT_lights_image_based?.lights;
+ const sceneDef = json.scenes?.[json.scene ?? 0];
+ const lightIdx: number | undefined = sceneDef?.extensions?.EXT_lights_image_based?.light;
+ if (!lights?.length || lightIdx === undefined) {
+ return {};
+ }
+ const light = lights[lightIdx];
+ if (!light?.specularImages?.length || !light.irradianceCoefficients) {
+ return {};
+ }
+
+ const engine = ctx._engine;
+ const specularImageSize = light.specularImageSize;
+ const mipCount = light.specularImages.length;
+
+ // Decode every specular face (flat, mip-major face-minor — the order
+ // uploadCubemapRGBD expects; glTF face order +X,-X,+Y,-Y,+Z,-Z matches
+ // WebGPU cube layers).
+ const flatIndices = light.specularImages.flat();
+ const { resolveImage } = await import("./ibl-env-assembly.js");
+ const faceImages = await Promise.all(flatIndices.map((imgIdx) => resolveImage(json, ctx._binChunk, imgIdx, ctx._baseUrl)));
+
+ const { uploadCubemapRGBD } = await import("./ibl-cubemap-upload.js");
+ const specularCube = uploadCubemapRGBD(engine, faceImages, specularImageSize, mipCount);
+ for (const img of faceImages) {
+ img.close();
+ }
+
+ const { generateBrdfLut } = await import("./ibl-env-assembly.js");
+ const brdfLut = generateBrdfLut(engine);
+
+ const intensity = light.intensity ?? 1;
+ const irradianceSH = irradianceCoefficientsToPolynomial(light.irradianceCoefficients, intensity);
+ // Fit the LOD scale to the available mip count exactly as Babylon does.
+ const lodGenerationScale = (mipCount - 1) / Math.log2(specularImageSize);
+
+ const { assembleEnvironmentTextures } = await import("./ibl-env-assembly.js");
+ const textures = assembleEnvironmentTextures(specularCube, brdfLut, irradianceSH, lodGenerationScale, engine);
+
+ const envRotationY = light.rotation ? envYawFromQuaternion(light.rotation) : 0;
+
+ // Pull the scene-wiring helper in now (still lazy — part of this feature
+ // chunk) so the synchronous _sceneSetup closure can use it directly.
+ // registerEnvSceneUniforms is a private copy (see ibl-env-assembly) to
+ // avoid pinning scene-ubo-extras into every glTF scene.
+ const { registerEnvSceneUniforms } = await import("./ibl-env-assembly.js");
+
+ // Defer the scene wiring: applyAsset has no SceneContext, so hand the core
+ // loader a closure that addToScene() runs against the real scene.
+ const _sceneSetup = (scene: SceneContext): void => {
+ scene._envTextures = textures;
+ if (envRotationY) {
+ scene.envRotationY = envRotationY;
+ }
+ registerEnvSceneUniforms(scene);
+
+ // specularCube + brdfLut are created fresh here and owned solely by
+ // this scene (nothing else acquires them), so a direct destroy on
+ // scene teardown is equivalent to the gpu-pool refcount path used by
+ // load-env — and it avoids pinning gpu-pool's exports into every
+ // glTF scene's main chunk, keeping the 43 non-IBL scenes at baseline.
+ scene._disposables.push(() => {
+ specularCube.destroy();
+ brdfLut.destroy();
+ });
+
+ // Match loadEnvironment's image-processing defaults (tone mapping on,
+ // exposure 0.8, contrast 1.2) so IBL-lit output matches Babylon.
+ scene.imageProcessing.toneMappingEnabled = true;
+ scene.imageProcessing.exposure = 0.8;
+ scene.imageProcessing.contrast = 1.2;
+ };
+
+ return { _sceneSetup };
+ },
+};
+export default feature;
diff --git a/packages/babylon-lite/src/loader-gltf/gltf-feature-registry.ts b/packages/babylon-lite/src/loader-gltf/gltf-feature-registry.ts
index fd9165500f..c387dc5c6d 100644
--- a/packages/babylon-lite/src/loader-gltf/gltf-feature-registry.ts
+++ b/packages/babylon-lite/src/loader-gltf/gltf-feature-registry.ts
@@ -59,6 +59,7 @@ const _features: [Trigger, Loader][] = [
// Per-asset features
[hasGltfExtras, () => import("./gltf-feature-extras.js")],
["KHR_lights_punctual", () => import("./gltf-feature-lights-punctual.js")],
+ ["EXT_lights_image_based", () => import("./gltf-ext-lights-image-based.js")],
[(j) => !!j.animations?.length, () => import("./gltf-feature-animations.js")],
// Non-Float32 / normalized animation sampler accessors (e.g. Animation_SamplerType normalized
// BYTE/SHORT rotation) need the lazy denorm converter; plain float samplers never load it.
diff --git a/packages/babylon-lite/src/loader-gltf/ibl-cubemap-upload.ts b/packages/babylon-lite/src/loader-gltf/ibl-cubemap-upload.ts
new file mode 100644
index 0000000000..f3bcc8f401
--- /dev/null
+++ b/packages/babylon-lite/src/loader-gltf/ibl-cubemap-upload.ts
@@ -0,0 +1,100 @@
+/** RGBD cubemap uploader for EXT_lights_image_based.
+ *
+ * A self-contained copy of the RGBD `->` rgba16float cubemap decode used by the
+ * image-based-lights extension. It is intentionally NOT shared with
+ * `loader-env/rgbd-decode.ts`: that module is a static dependency of the `.env`
+ * loader (`load-env.ts`) and is therefore inlined into every environment scene's
+ * chunk graph. Importing `uploadCubemapRGBD` from it would make the extension a
+ * second consumer of that shared module, pinning the (otherwise tree-shaken)
+ * cubemap path into every `.env`/DDS scene's bundle. Keeping a private copy here
+ * — reachable only from this lazily-imported feature chunk — means non-IBL scenes
+ * pay zero bytes for it and only `EXT_lights_image_based` scenes fetch it. */
+
+import { TU } from "../engine/gpu-flags.js";
+import type { EngineContext } from "../engine/engine.js";
+
+const WGSL = `override f:bool=false;@group(0)@binding(0)var t:texture_2d;@group(0)@binding(1)var o:texture_storage_2d;@compute @workgroup_size(8,8)fn main(@builtin(global_invocation_id)g:vec3u){let d=textureDimensions(t);if(any(g.xy>=d)){return;}let c=textureLoad(t,vec2u(g.x,select(g.y,d.y-1u-g.y,f)),0);textureStore(o,g.xy,vec4f(pow(c.rgb,vec3f(2.2))/max(c.a,1.0/255.0),1));}`;
+
+let _device: GPUDevice | null = null;
+let _module: GPUShaderModule | null = null;
+let _pipeline: GPUComputePipeline | null = null;
+
+function getPipeline(device: GPUDevice): GPUComputePipeline {
+ if (device !== _device) {
+ _device = device;
+ _module = device.createShaderModule({ code: WGSL });
+ _pipeline = null;
+ }
+ if (_pipeline) {
+ return _pipeline;
+ }
+ _pipeline = device.createComputePipeline({
+ layout: "auto",
+ compute: { module: _module!, entryPoint: "main", constants: { f: 1 } },
+ });
+ return _pipeline;
+}
+
+/** Decode and upload a RGBD cubemap (6 faces × N mips) → rgba16float cube texture.
+ * Y-flipped on read (BJS uploads cubemap faces with invertY=true). */
+export function uploadCubemapRGBD(engine: EngineContext, images: ImageBitmap[], width: number, mipCount: number): GPUTexture {
+ const device = engine._device;
+ const pipeline = getPipeline(device);
+
+ const texture = device.createTexture({
+ size: { width, height: width, depthOrArrayLayers: 6 },
+ format: "rgba16float",
+ mipLevelCount: mipCount,
+ usage: TU.TEXTURE_BINDING | TU.COPY_DST | TU.COPY_SRC | TU.RENDER_ATTACHMENT,
+ dimension: "2d",
+ });
+
+ for (let mip = 0; mip < mipCount; mip++) {
+ const mipSize = Math.max(1, width >> mip);
+
+ const inputTex = device.createTexture({
+ size: { width: mipSize, height: mipSize },
+ format: "rgba8unorm",
+ usage: TU.TEXTURE_BINDING | TU.COPY_DST | TU.RENDER_ATTACHMENT,
+ });
+
+ const outputTex = device.createTexture({
+ size: { width: mipSize, height: mipSize },
+ format: "rgba16float",
+ usage: TU.STORAGE_BINDING | TU.COPY_SRC,
+ });
+
+ const bindGroup = device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: inputTex.createView() },
+ { binding: 1, resource: outputTex.createView() },
+ ],
+ });
+
+ for (let face = 0; face < 6; face++) {
+ const idx = mip * 6 + face;
+ if (idx >= images.length) {
+ break;
+ }
+
+ device.queue.copyExternalImageToTexture({ source: images[idx]!, flipY: false }, { texture: inputTex, premultipliedAlpha: false }, { width: mipSize, height: mipSize });
+
+ const encoder = device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(Math.ceil(mipSize / 8), Math.ceil(mipSize / 8));
+ pass.end();
+ encoder.copyTextureToTexture({ texture: outputTex }, { texture, origin: { x: 0, y: 0, z: face }, mipLevel: mip }, { width: mipSize, height: mipSize });
+
+ // One submit per face ensures sequential hazards on the reused input/output.
+ device.queue.submit([encoder.finish()]);
+ }
+
+ inputTex.destroy();
+ outputTex.destroy();
+ }
+
+ return texture;
+}
diff --git a/packages/babylon-lite/src/loader-gltf/ibl-env-assembly.ts b/packages/babylon-lite/src/loader-gltf/ibl-env-assembly.ts
new file mode 100644
index 0000000000..bacf93a280
--- /dev/null
+++ b/packages/babylon-lite/src/loader-gltf/ibl-env-assembly.ts
@@ -0,0 +1,180 @@
+/** Private IBL assembly helpers for the EXT_lights_image_based feature.
+ *
+ * ⚠️ DELIBERATE DUPLICATION — user-approved exception to the max-reuse mandate.
+ *
+ * The functions below are byte-for-byte copies of shared environment/HDR IBL
+ * helpers whose canonical homes are:
+ * - sampler descriptors (bilinear/trilinear) → ../resource/samplers.ts
+ * - polynomialToPreScaledHarmonics → ../loader-env/load-env.ts
+ * - assembleEnvironmentTextures → ../loader-env/env-helpers.ts
+ * - generateBrdfLut → ../loader-hdr/hdr-ibl-pipeline.ts
+ * - resolveImage → ./gltf-parser.ts
+ * - registerEnvSceneUniforms (+ its writer) → ../scene/scene-ubo-extras.ts
+ *
+ * WHY the copy exists: those canonical modules are each SINGLE-consumer on
+ * master (env-helpers ← load-env, generateBrdfLut ← load-hdr, samplers ←
+ * generate-mipmaps), so rollup INLINES them into every glTF scene's main chunk.
+ * If the (lazily-imported) EXT_lights_image_based feature imported them directly
+ * it would become a SECOND consumer, forcing rollup to hoist each into a shared
+ * chunk that all 44 glTF scenes then fetch separately (~0.5–1.0 KB of chunk
+ * boilerplate per scene, ~32 KB total across the suite). By duplicating them
+ * here — reachable only from this lazy feature chunk — the 43 non-IBL glTF
+ * scenes keep their original inlined copies untouched (zero bundle delta) and
+ * the feature's cost is isolated to the single scene that uses it
+ * (EnvironmentTest / scene264), whose ceiling absorbs the ~few KB.
+ *
+ * If the canonical helpers change, update these copies to match (they exist
+ * purely to keep the feature self-contained, not to diverge in behaviour).
+ */
+
+import { F32 } from "../engine/typed-arrays.js";
+import { TU } from "../engine/gpu-flags.js";
+import brdfLutWGSL from "../../shaders/hdr-brdf-lut.compute.wgsl?raw";
+import type { EngineContext } from "../engine/engine.js";
+import type { EnvironmentTextures } from "../loader-env/load-env.js";
+import type { SceneContext } from "../scene/scene-core.js";
+
+// Sampler descriptors copied from resource/samplers.ts. Created directly here
+// (device.createSampler) rather than via gpu-pool.getOrCreateSampler so this
+// feature does not add getOrCreateSampler to gpu-pool's retained export set in
+// every glTF scene. The only cost is two extra (immutable, stateless) sampler
+// objects on the single IBL load — negligible and side-effect-free.
+const _bilinearDesc: GPUSamplerDescriptor = { magFilter: "linear", minFilter: "linear" };
+const _trilinearDesc: GPUSamplerDescriptor = { magFilter: "linear", minFilter: "linear", mipmapFilter: "linear" };
+
+/** Copy of loader-env/load-env.ts `polynomialToPreScaledHarmonics`. */
+function polynomialToPreScaledHarmonics(poly: Float32Array): Float32Array {
+ const C00xy = 0.3333338747897695;
+ const C00z = 0.33333298856284405;
+ const C1 = 1.4999984284682104;
+ const C2 = 3.999982863580422;
+ const C20zz = 1.3333326611423701;
+ const C20xy = 0.6666653397393608;
+ const C22 = 1.999991431790211;
+
+ const out = new F32(36);
+ for (let i = 0; i < 3; i++) {
+ const x = poly[i]!;
+ const y = poly[3 + i]!;
+ const z = poly[6 + i]!;
+ const xx = poly[9 + i]!;
+ const yy = poly[12 + i]!;
+ const zz = poly[15 + i]!;
+ const yz = poly[18 + i]!;
+ const zx = poly[21 + i]!;
+ const xy = poly[24 + i]!;
+ out[i] = (xx + yy) * C00xy + zz * C00z; // L00
+ out[4 + i] = y * C1; // L1_1
+ out[8 + i] = z * C1; // L10
+ out[12 + i] = x * C1; // L11
+ out[16 + i] = xy * C2; // L2_2
+ out[20 + i] = yz * C2; // L2_1
+ out[24 + i] = zz * C20zz - (xx + yy) * C20xy; // L20
+ out[28 + i] = zx * C2; // L21
+ out[32 + i] = (xx - yy) * C22; // L22
+ }
+ return out;
+}
+
+/** Copy of loader-env/env-helpers.ts `assembleEnvironmentTextures`. */
+export function assembleEnvironmentTextures(
+ specularCube: GPUTexture,
+ brdfLut: GPUTexture,
+ irradianceSH: Float32Array,
+ lodGenerationScale: number,
+ engine: EngineContext
+): EnvironmentTextures {
+ return {
+ specularCube,
+ specularCubeView: specularCube.createView({ dimension: "cube" }),
+ brdfLut,
+ brdfLutView: brdfLut.createView(),
+ cubeSampler: engine._device.createSampler(_trilinearDesc),
+ brdfSampler: engine._device.createSampler(_bilinearDesc),
+ irradianceSH,
+ sphericalHarmonics: polynomialToPreScaledHarmonics(irradianceSH),
+ lodGenerationScale,
+ };
+}
+
+// Copy of loader-hdr/hdr-ibl-pipeline.ts `generateBrdfLut` (split-sum BRDF LUT).
+let _brdfPipeline: GPUComputePipeline | null = null;
+let _brdfPipelineDevice: GPUDevice | null = null;
+
+/** Copy of loader-hdr/hdr-ibl-pipeline.ts `generateBrdfLut`. */
+export function generateBrdfLut(engine: EngineContext): GPUTexture {
+ const device = engine._device;
+ if (!_brdfPipeline || _brdfPipelineDevice !== device) {
+ _brdfPipeline = device.createComputePipeline({
+ layout: "auto",
+ compute: { module: device.createShaderModule({ code: brdfLutWGSL }), entryPoint: "main" },
+ });
+ _brdfPipelineDevice = device;
+ }
+ const size = 256;
+ const texture = device.createTexture({
+ size: { width: size, height: size },
+ format: "rgba16float",
+ usage: TU.TEXTURE_BINDING | TU.STORAGE_BINDING,
+ });
+ const bindGroup = device.createBindGroup({
+ layout: _brdfPipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: texture.createView() }],
+ });
+ const encoder = device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(_brdfPipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(Math.ceil(size / 8), Math.ceil(size / 8));
+ pass.end();
+ device.queue.submit([encoder.finish()]);
+ return texture;
+}
+
+/** Copy of gltf-parser.ts `resolveImage` — resolves a glTF image (GLB bufferView
+ * or external URI) to an ImageBitmap. Kept private so this feature does not pin
+ * gltf-parser's export surface into every glTF scene's main chunk. */
+export async function resolveImage(json: any, binChunk: DataView, imageIdx: number, baseUrl: string): Promise {
+ const image = json.images[imageIdx];
+
+ if (image.bufferView !== undefined) {
+ const bv = json.bufferViews[image.bufferView];
+ const offset = binChunk.byteOffset + (bv.byteOffset ?? 0);
+ const slice = binChunk.buffer.slice(offset, offset + bv.byteLength);
+ const blob = new Blob([slice as ArrayBuffer], { type: image.mimeType ?? "image/png" });
+ return createImageBitmap(blob, { premultiplyAlpha: "none", colorSpaceConversion: "none" });
+ }
+
+ if (image.uri) {
+ const imageUrl = new URL(image.uri, baseUrl + "x").href;
+ const response = await fetch(imageUrl);
+ if (!response.ok) {
+ throw new Error(`Failed to load image: ${response.status} ${response.statusText}`);
+ }
+ const blob = await response.blob();
+ return createImageBitmap(blob, { premultiplyAlpha: "none", colorSpaceConversion: "none" });
+ }
+
+ throw new Error("Image has neither bufferView nor uri");
+}
+
+/** Copy of scene-ubo-extras.ts `writeEnvShUbo` — writes the environment
+ * spherical-harmonics slice (float offsets 40–75) of the SceneUniforms struct. */
+function writeEnvShUbo(data: Float32Array, scene: SceneContext): void {
+ const sh = scene._envTextures?.sphericalHarmonics;
+ if (sh) {
+ data.set(sh, 40);
+ }
+}
+
+/** Copy of scene-ubo-extras.ts `registerEnvSceneUniforms` (+ its dedup helper) —
+ * registers the env-SH scene-UBO contributor. The render task invokes whatever
+ * contributors are on `scene._sceneUboContributors`, so a private copy of the
+ * writer is functionally identical to the shared one. Kept private so this
+ * feature does not pin scene-ubo-extras into every glTF scene's main chunk. */
+export function registerEnvSceneUniforms(scene: SceneContext): void {
+ const list = (scene._sceneUboContributors ??= []);
+ if (!list.includes(writeEnvShUbo)) {
+ list.push(writeEnvShUbo);
+ }
+}
diff --git a/packages/babylon-lite/src/loader-gltf/load-gltf.ts b/packages/babylon-lite/src/loader-gltf/load-gltf.ts
index 26c654695c..97fd8d340c 100644
--- a/packages/babylon-lite/src/loader-gltf/load-gltf.ts
+++ b/packages/babylon-lite/src/loader-gltf/load-gltf.ts
@@ -180,16 +180,27 @@ export async function loadGltf(engine: EngineContext, source: string | ArrayBuff
// Run every feature's per-asset hook (animations, variants, metadata, …) and
// merge the returned AssetContainer fragments. `entities` is appended (never
// overwritten) so features like KHR_lights_punctual can contribute lights
- // without trampling the root TransformNode.
+ // without trampling the root TransformNode. `_sceneSetup` is composed (chained
+ // in feature order) rather than overwritten, so multiple features can each
+ // contribute deferred scene wiring without the last one winning.
const assetFragments = await Promise.all(features.flatMap((f) => (f.applyAsset ? [f.applyAsset(meshes, root, ctx)] : [])));
const container: AssetContainer = { entities: [root] };
for (const frag of assetFragments) {
if (frag.entities?.length) {
container.entities.push(...frag.entities);
}
- const { entities: _ignored, ...rest } = frag;
+ const { entities: _ignored, _sceneSetup, ...rest } = frag;
void _ignored;
Object.assign(container, rest);
+ if (_sceneSetup) {
+ const prev = container._sceneSetup;
+ container._sceneSetup = prev
+ ? (scene) => {
+ prev(scene);
+ _sceneSetup(scene);
+ }
+ : _sceneSetup;
+ }
}
return container;
}
diff --git a/packages/babylon-lite/src/scene/scene-core.ts b/packages/babylon-lite/src/scene/scene-core.ts
index 6402eeb080..f71e37cfc9 100644
--- a/packages/babylon-lite/src/scene/scene-core.ts
+++ b/packages/babylon-lite/src/scene/scene-core.ts
@@ -332,6 +332,10 @@ export function addToScene(scene: SceneContext, entity: Mesh | LightBase | Camer
}
});
}
+ // Feature-owned scene wiring (e.g. EXT_lights_image_based installs its IBL
+ // environment). Runs synchronously so the environment is registered before
+ // registerScene() builds the scene UBO / PBR renderables.
+ result._sceneSetup?.(ctx);
return;
}
if ("_gpu" in entity && "material" in entity) {
diff --git a/reference/lite/scene265-environment-test/babylon-ref-golden.png b/reference/lite/scene265-environment-test/babylon-ref-golden.png
new file mode 100644
index 0000000000..564e10fe54
Binary files /dev/null and b/reference/lite/scene265-environment-test/babylon-ref-golden.png differ
diff --git a/scene-config.json b/scene-config.json
index 854229b071..6fee3735a9 100644
--- a/scene-config.json
+++ b/scene-config.json
@@ -122,7 +122,7 @@
"slug": "scene12-shader-balls",
"name": "Scene 12 — PBR Shader Balls",
"maxMad": 0.027,
- "maxRawKB": 103.6,
+ "maxRawKB": 103.7,
"description": "3 shader balls with metallic reflectance textures, directional light, env rotation.",
"tags": ["pbr", "gltf"],
"skipPerf": true
@@ -286,7 +286,7 @@
"slug": "scene28-clearcoat-gltf",
"name": "Scene 28 — Clearcoat glTF",
"maxMad": 0.02,
- "maxRawKB": 86.7,
+ "maxRawKB": 86.8,
"description": "ClearCoatTest.gltf (6×3 grid: clearcoat variants) with default environment (IBL only). Matches PG #YG3BBF#33.",
"tags": ["pbr", "gltf", "env", "clearcoat"],
"compatParity": true
@@ -362,7 +362,7 @@
"slug": "scene35-gltf-instancing",
"name": "Scene 35 — EXT_mesh_gpu_instancing",
"maxMad": 0.02,
- "maxRawKB": 83.8,
+ "maxRawKB": 83.9,
"description": "SimpleInstancing.glb (EXT_mesh_gpu_instancing) — 5×5×5 grid of 125 unit cubes driven by per-instance TRS accessors. Default IBL env. Matches PG #YG3BBF#57.",
"tags": ["pbr", "gltf", "env", "gpu-instancing"],
"compatParity": true
@@ -404,7 +404,7 @@
"slug": "scene39-animation-pointer-waterfall",
"name": "Scene 39 — KHR_animation_pointer (Animated Waterfall)",
"maxMad": 0.6,
- "maxRawKB": 108.9,
+ "maxRawKB": 109,
"description": "AnimatedWaterfall.gltf — grass blades rotate via /nodes/{n}/rotation pointers and water/foam surfaces scroll via /materials/{m}/.../KHR_texture_transform offset+scale pointers. Default IBL env, no skybox/ground. Validates KHR_animation_pointer node-TRS + texture-transform handlers.",
"tags": ["pbr", "gltf", "env", "lights-punctual", "texture-transform", "animation-pointer"],
"skipPerf": true,
@@ -436,7 +436,7 @@
"slug": "scene41-physics-shapes",
"name": "Scene 41 — Physics Shape Debug Viewer",
"maxMad": 10,
- "maxRawKB": 93.2,
+ "maxRawKB": 93.4,
"maxGzipKB": 36,
"description": "Havok Physics V2 mesh, convex hull, and aggregate collision shapes with PhysicsViewer wireframes. Loaded skull and seagull port of PG #LKPBW5.",
"tags": ["std", "procedural", "physics"],
@@ -502,7 +502,7 @@
"slug": "scene47-physics-heightfield",
"name": "Scene 47 — Physics Heightfield",
"maxMad": 0.05,
- "maxRawKB": 92.5,
+ "maxRawKB": 92.7,
"maxGzipKB": 35,
"description": "Havok Physics V2 heightfield from heightMap.png with two rows of falling shapes: row 1 PhysicsAggregate all types (box, sphere, capsule, cylinder, plus convex hull and mesh from the seagull glb), row 2 body+shape all types including convex hull and mesh from the seagull glb.",
"tags": ["physics", "gltf", "procedural", "std"],
@@ -1188,7 +1188,7 @@
"slug": "scene115-alien-picking-frame100",
"name": "Scene 115 — Alien Picking Frame 100",
"maxMad": 0.01,
- "maxRawKB": 126.5,
+ "maxRawKB": 126.6,
"description": "Precise picking validation on the animated Alien glTF at frame 100: BJS and Lite CPU PickInfo values drive a surface sphere and normal marker.",
"tags": ["pbr", "gltf", "anim", "skeleton", "picking"],
"skipPerf": true
@@ -1456,7 +1456,7 @@
"slug": "scene152-gltf-manager",
"name": "Scene 152 — Unified AnimationManager",
"maxMad": 0.02,
- "maxRawKB": 94.2,
+ "maxRawKB": 94.3,
"description": "One explicit AnimationManager drives a glTF shark skeleton group and a manual camera orbit property group.",
"tags": ["pbr", "gltf", "anim"],
"skipPerf": true,
@@ -1594,7 +1594,7 @@
"slug": "scene165-shader-material-thin-instances",
"name": "Scene 165 - ShaderMaterial Thin Instances",
"maxMad": 0.01,
- "maxRawKB": 45.2,
+ "maxRawKB": 45.3,
"description": "Custom WGSL ShaderMaterial rendered with thin instances + per-instance color: an 8×8×8 grid of unit cubes with a deterministic color ramp, fixed arc-rotate camera. Validates the auto-injected world0..3/instanceColor vertex attributes and the GPU-culling indirect-draw path.",
"tags": ["shader", "instances", "procedural"],
"skipPerf": true
@@ -1894,7 +1894,7 @@
"slug": "scene215-cascaded-shadows-pbr",
"name": "Scene 215 — Cascaded Shadow Maps (PBR)",
"maxMad": 0.4,
- "maxRawKB": 80.0,
+ "maxRawKB": 80.1,
"description": "PBR counterpart of scene214: a deterministic field of 200 green PBR torus-knot casters drops cascaded shadows onto a large PBR ground receiver, lit by a single DirectionalLight with a 4-cascade CascadedShadowGenerator (PCF5). Proves CSM works on PBR receivers, not just Standard; a seeded PRNG matches the Babylon.js CSM oracle.",
"tags": ["shadows", "csm", "pbr", "directional"],
"skipPerf": true
@@ -1925,7 +1925,7 @@
"slug": "scene218-vat",
"name": "Scene 218 — Vertex Animation Texture (VAT)",
"maxMad": 0.02,
- "maxRawKB": 94.9,
+ "maxRawKB": 95,
"description": "Baked vertex animation: the scene-11 shark's skeletal animation is pre-evaluated into one rgba32float texture (bakeVat) and played through the GPU VAT vertex path (mesh.vat) with NO live CPU skeleton - the vertex shader reads each bone matrix from the baked texture at the current frame row.",
"tags": ["vat", "animation", "skeleton", "instancing", "pbr", "gltf"],
"skipPerf": true,
@@ -1936,7 +1936,7 @@
"slug": "scene219-vat-instanced",
"name": "Scene 219 — Per-instance VAT",
"maxMad": 0.02,
- "maxRawKB": 98.1,
+ "maxRawKB": 98.2,
"description": "Per-instance VAT: the scene-11 shark baked to a VAT texture and rendered through the GPU thin-instance VAT path (one identity-matrix instance) frozen via ?seekTime. finalWorld = instanceMatrix * mesh.world * skin equals the plain skinned pose at identity, so it matches the BJS live-skeleton golden exactly.",
"tags": ["vat", "animation", "skeleton", "instancing", "pbr", "gltf"],
"skipPerf": true,
@@ -2033,7 +2033,7 @@
"slug": "scene240-animated-triangle",
"name": "Scene 240 — AnimatedTriangle",
"maxMad": 0.2,
- "maxRawKB": 91.1,
+ "maxRawKB": 91.2,
"description": "AnimatedTriangle.gltf — cx20 gltf-test. Node-rotation animation; material-less no-NORMAL mesh (smooth normals).",
"tags": ["gltf"],
"skipPerf": true
@@ -2053,7 +2053,7 @@
"slug": "scene242-emissive-fireflies",
"name": "Scene 242 — EmissiveFireflies",
"maxMad": 0.2,
- "maxRawKB": 97.3,
+ "maxRawKB": 97.4,
"description": "EmissiveFireflies.gltf — cx20 gltf-test. KHR_animation_pointer material emissiveFactor/emissiveStrength/baseColorFactor.",
"tags": ["gltf"],
"skipPerf": true
@@ -2063,7 +2063,7 @@
"slug": "scene243-morph-stress-test",
"name": "Scene 243 — MorphStressTest",
"maxMad": 0.2,
- "maxRawKB": 97.6,
+ "maxRawKB": 97.7,
"description": "MorphStressTest.gltf — cx20 gltf-test. Morph-target weight animation.",
"tags": ["gltf"],
"skipPerf": true
@@ -2073,7 +2073,7 @@
"slug": "scene244-pot-of-coals-animation-pointer",
"name": "Scene 244 — PotOfCoalsAnimationPointer",
"maxMad": 0.2,
- "maxRawKB": 133.8,
+ "maxRawKB": 133.9,
"description": "PotOfCoalsAnimationPointer.gltf — cx20 gltf-test. KHR_animation_pointer texture-transform rotation.",
"tags": ["gltf"],
"skipPerf": true
@@ -2083,7 +2083,7 @@
"slug": "scene245-recursive-skeletons",
"name": "Scene 245 — RecursiveSkeletons",
"maxMad": 0.2,
- "maxRawKB": 100.8,
+ "maxRawKB": 100.9,
"description": "RecursiveSkeletons.gltf — cx20 gltf-test. Deep recursive skeletons; interleaved JOINTS/WEIGHTS/COLOR_0.",
"tags": ["gltf"],
"skipPerf": true
@@ -2093,7 +2093,7 @@
"slug": "scene246-simple-skin",
"name": "Scene 246 — SimpleSkin",
"maxMad": 0.2,
- "maxRawKB": 99.6,
+ "maxRawKB": 99.7,
"description": "SimpleSkin.gltf — cx20 gltf-test. Material-less skinned mesh; interleaved JOINTS/WEIGHTS, no-NORMAL smooth normals.",
"tags": ["gltf"],
"skipPerf": true
@@ -2103,7 +2103,7 @@
"slug": "scene247-teapots-galore",
"name": "Scene 247 — TeapotsGalore",
"maxMad": 0.4,
- "maxRawKB": 84.8,
+ "maxRawKB": 84.9,
"description": "TeapotsGalore.gltf — cx20 gltf-test. EXT_mesh_gpu_instancing, top-down view.",
"tags": ["gltf"],
"skipPerf": true
@@ -2154,7 +2154,7 @@
"slug": "scene253-animate-all-the-things",
"name": "Scene 253 — AnimateAllTheThings",
"maxMad": 0.3,
- "maxRawKB": 153.1,
+ "maxRawKB": 153.2,
"description": "cx20 gltf-test AnimateAllTheThings, viewed top-down over the full grid of test objects. Exercises KHR_lights_punctual, KHR_texture_transform, KHR_materials_unlit/ior/transmission/iridescence/volume/emissive_strength and KHR_animation_pointer, plus skinning and morph targets. Frozen at a deterministic frame.",
"tags": ["gltf", "animation", "animation-pointer", "transmission", "iridescence", "skeleton", "morph"],
"skipPerf": true
@@ -2165,7 +2165,7 @@
"name": "Scene 254 — Signed Accessor Animation",
"maxMad": 0.15,
"maxRegionMad": 0.5,
- "maxRawKB": 92.5,
+ "maxRawKB": 92.6,
"description": "glTF Animation_SamplerType_01: rotation animation whose sampler output quaternions are a normalized signed BYTE accessor (componentType 5120). Validates loader support for signed BYTE/SHORT accessor component types (glTF-Asset-Generator Category A). Frozen at seekTime=2.0.",
"tags": ["gltf", "animation", "accessor"],
"skipPerf": true
@@ -2220,7 +2220,7 @@
"name": "Scene 260 — Triangle Strip",
"maxMad": 0.1,
"maxRegionMad": 0.5,
- "maxRawKB": 80,
+ "maxRawKB": 80.2,
"description": "glTF Mesh_PrimitiveMode_11: a quad drawn as a TRIANGLE_STRIP (primitive mode 5) with uint32 indices. Validates non-triangle-list primitive topologies (glTF-Asset-Generator Category C); Lite hardcoded a triangle-list pipeline and rendered POINTS/LINES/strips garbled. The loader now reads the primitive mode and the pipeline honors the matching WebGPU topology.",
"tags": ["gltf", "topology"],
"skipPerf": true
@@ -2268,5 +2268,15 @@
"tags": ["npe", "particles", "billboards"],
"skipPerf": true,
"compatParity": true
+ },
+ {
+ "id": 265,
+ "slug": "scene265-environment-test",
+ "name": "Scene 265 — EnvironmentTest (EXT_lights_image_based)",
+ "maxMad": 0.5,
+ "maxRawKB": 82,
+ "description": "EnvironmentTest.gltf (glTF-IBL) — cx20 gltf-test. Exercises EXT_lights_image_based: the glTF carries its own document-level image-based light (irradiance SH9 + prefiltered specular cubemap mips + intensity/rotation), driving PBR IBL through Lite's existing environment path (EnvironmentTextures + scene UBO spherical harmonics). Grid of metallic-roughness spheres.",
+ "tags": ["gltf", "pbr", "env", "ibl"],
+ "skipPerf": true
}
]
diff --git a/tests/lite/parity/scenes/scene265-environment-test.spec.ts b/tests/lite/parity/scenes/scene265-environment-test.spec.ts
new file mode 100644
index 0000000000..b42a1ea741
--- /dev/null
+++ b/tests/lite/parity/scenes/scene265-environment-test.spec.ts
@@ -0,0 +1,33 @@
+/**
+ * Scene 265 — EnvironmentTest / glTF-IBL (cx20 gltf-test parity).
+ *
+ * Exercises the EXT_lights_image_based glTF extension: the model carries its own
+ * document-level image-based light (irradiance SH9 + prefiltered specular cubemap
+ * mips), which Lite drives through its existing environment/IBL path.
+ */
+import { test, expect } from "../parity-fixtures";
+import * as path from "path";
+import { attachCompareArtifacts, captureGolden, compareImages, getSceneConfig } from "../compare-utils";
+
+const sceneConfig = getSceneConfig(265);
+const REFERENCE_DIR = path.resolve(__dirname, "../../../../reference/lite/scene265-environment-test");
+const GOLDEN_REF = path.join(REFERENCE_DIR, "babylon-ref-golden.png");
+
+test.skip(!!sceneConfig.skipParity, "Scene 265 skipped via skipParity in scene-config.json");
+
+test("Scene 265 — EnvironmentTest matches Babylon.js reference", async ({ page }, testInfo) => {
+ const browser = page.context().browser()!;
+ await captureGolden(browser, { sceneId: 265, timeout: 90_000 });
+
+ await page.goto("/scene265.html");
+ await page.waitForFunction(() => document.querySelector("canvas")?.dataset.ready === "true", { timeout: 60_000 });
+ await page.waitForTimeout(500);
+
+ const screenshotPath = path.join(REFERENCE_DIR, "test-actual.png");
+ await page.locator("canvas").screenshot({ path: screenshotPath });
+
+ const full = compareImages(screenshotPath, GOLDEN_REF);
+ await attachCompareArtifacts(testInfo, screenshotPath, GOLDEN_REF, REFERENCE_DIR);
+ console.log(`Full image MAD: ${full.mad.toFixed(3)} (limit ${sceneConfig.maxMad})`);
+ expect(full.mad, `Full image MAD should be <= ${sceneConfig.maxMad}`).toBeLessThanOrEqual(sceneConfig.maxMad);
+});