Porting from Babylon.js to Babylon Lite
Table Of Contents
This guide shows how to translate a Babylon.js (BJS) scene to Babylon Lite, side by side. Babylon Lite uses factory functions instead of constructors, plain data instead of class instances, and explicit addToScene() instead of auto-registration.
Quick Reference
| Babylon.js | Babylon Lite |
|---|---|
Babylon.js new WebGPUEngine(canvas); await engine.initAsync() | Babylon Lite const engine = await createEngine(canvas) |
Babylon.js new Scene(engine) | Babylon Lite createSceneContext(engine) |
Babylon.js engine.runRenderLoop(() => scene.render()) | Babylon Lite await startEngine(engine) |
Babylon.js new ArcRotateCamera("cam", α, β, r, target, scene) | Babylon Lite createArcRotateCamera(α, β, r, target) |
Babylon.js new FreeCamera("cam", position, scene) | Babylon Lite createFreeCamera(position, target) |
Babylon.js scene.createDefaultCamera(true, true, true) | Babylon Lite createDefaultCamera(scene) |
Babylon.js camera.attachControl(canvas, true) | Babylon Lite attachControl(camera, canvas, scene) (arc-rotate) / attachFreeControl(camera, canvas, scene) (free) |
Babylon.js new HemisphericLight("h", new Vector3(0,1,0), scene) | Babylon Lite createHemisphericLight([0,1,0], 1.0) |
Babylon.js new DirectionalLight("d", new Vector3(0,-1,0), scene) | Babylon Lite createDirectionalLight([0,-1,0]) |
Babylon.js new SpotLight("s", pos, dir, angle, exp, scene) | Babylon Lite createSpotLight(pos, dir, angle, exp) |
Babylon.js MeshBuilder.CreateSphere("s", {}, scene) | Babylon Lite createSphere(engine) |
Babylon.js MeshBuilder.CreateBox("b", {}, scene) | Babylon Lite createBox(engine) |
Babylon.js MeshBuilder.CreateGround("g", {}, scene) | Babylon Lite createGround(engine, opts) |
Babylon.js new StandardMaterial("mat", scene) | Babylon Lite createStandardMaterial() |
Babylon.js new PBRMaterial("pbr", scene) | Babylon Lite createPbrMaterial() |
Babylon.js new GridMaterial("grid", scene) (@babylonjs/materials) | Babylon Lite createGridMaterial(opts) |
Babylon.js SceneLoader.ImportMeshAsync("", url, file, scene) | Babylon Lite addToScene(scene, await loadGltf(engine, url)) |
Babylon.js new CubeTexture(url, scene) + createDefaultEnvironment() | Babylon Lite await loadEnvironment(scene, url, opts) |
Babylon.js new Texture(url, scene) | Babylon Lite await loadTexture2D(engine, url) |
Babylon.js KTX1 compressed 2D texture | Babylon Lite await loadKtxTexture2D(engine, baseUrl, suffixes) |
Babylon.js glTF KTX2 / KHR_texture_basisu texture source | Babylon Lite addToScene(scene, await loadGltf(engine, ktx2GltfUrl)) (auto-detected) |
Babylon.js Basis Universal (.basis) 2D texture | Babylon Lite await loadBasisTexture2D(engine, url) |
Babylon.js new ShadowGenerator(size, light) with a directional light and ESM | Babylon Lite createEsmDirectionalShadowGenerator(engine, light, opts) |
Babylon.js sg.usePercentageCloserFiltering = true with a spotlight | Babylon Lite createPcfSpotlightShadowGenerator(engine, light, opts) |
Babylon.js sg.usePercentageCloserFiltering = true with a directional light | Babylon Lite createPcfDirectionalShadowGenerator(engine, light, opts) |
Babylon.js mesh.thinInstanceSetBuffer("matrix", data, 16) | Babylon Lite setThinInstances(mesh, data, count) |
Babylon.js mesh.thinInstanceSetBuffer("color", data, 4) | Babylon Lite setThinInstanceColors(mesh, data) |
Babylon.js new Vector3(x, y, z) | Babylon Lite { x, y, z } or [x, y, z] |
Babylon.js new Color3(r, g, b) | Babylon Lite [r, g, b] |
Babylon.js Matrix.Identity() | Babylon Lite mat4Identity() |
Babylon.js mesh.dispose() | Babylon Lite removeFromScene(scene, mesh) |
Babylon.js scene.onBeforeRenderObservable.add(fn) | Babylon Lite onBeforeRender(scene, fn) |
Key Differences
1. No Scene in Constructors
BJS objects take scene in their constructor and auto-register. Lite objects are plain data — you create them, then addToScene() them explicitly.
// ❌ Babylon.jsconst light = new HemisphericLight("light", new Vector3(0, 1, 0), scene);
// ✅ Babylon Liteconst light = createHemisphericLight([0, 1, 0], 1.0);addToScene(scene, light);2. Engine & Render Loop
BJS uses runRenderLoop with a callback. Lite uses a single startEngine(engine) that returns a promise resolving after the first frame.
// ❌ Babylon.jsconst engine = new WebGPUEngine(canvas);await engine.initAsync();const scene = new Scene(engine);// ... setup ...engine.runRenderLoop(() => scene.render());
// ✅ Babylon Liteconst engine = await createEngine(canvas);const scene = createSceneContext(engine);// ... setup ...await startEngine(engine);3. Plain Data, Not Classes
Lite uses plain objects, arrays, and Float32Array instead of BJS classes like Vector3, Color3, Matrix.
// ❌ Babylon.jslight.direction = new Vector3(0, -1, 0);light.diffuse = new Color3(1, 0, 0);
// ✅ Babylon Liteconst light = createDirectionalLight([0, -1, 0]);light.diffuse = [1, 0, 0];4. Camera Controls Are Separate
BJS cameras have attachControl as a method. Lite separates camera data from input handling.
// ❌ Babylon.jsconst camera = new ArcRotateCamera("cam", -Math.PI/2, Math.PI/2, 5, Vector3.Zero(), scene);camera.attachControl(canvas, true);
// ✅ Babylon Liteconst camera = createArcRotateCamera(-Math.PI/2, Math.PI/2, 5, { x: 0, y: 0, z: 0 });scene.camera = camera;attachControl(camera, canvas, scene);5. Loaders and Scene Registration
loadEnvironment() adds its environment data/renderables to the scene internally. loadGltf() returns an asset container; pass it to addToScene() so transform-node hierarchies, meshes, and animation groups are registered explicitly.
// ❌ Babylon.jsawait SceneLoader.ImportMeshAsync("", baseUrl, "model.glb", scene);scene.environmentTexture = new CubeTexture(envUrl, scene);scene.createDefaultEnvironment({ createSkybox: true, skyboxSize: 1000 });
// ✅ Babylon LiteaddToScene(scene, await loadGltf(engine, "model.glb"));await loadEnvironment(scene, envUrl, { skyboxUrl: "skybox.dds", skyboxSize: 1000, groundTextureUrl: "ground.png", brdfUrl: "/brdf-lut.png",});6. Shadows Attach to Lights
BJS creates a ShadowGenerator separately. Lite assigns it directly to the light.
// ❌ Babylon.jsconst sg = new ShadowGenerator(1024, light);sg.addShadowCaster(mesh);sg.useBlurExponentialShadowMap = true;ground.receiveShadows = true;
// ✅ Babylon Litelight.shadowGenerator = createEsmDirectionalShadowGenerator(engine, light, { mapSize: 1024, depthScale: 50, blurScale: 2,});setShadowTaskCasterMeshes(light.shadowGenerator, [mesh]);ground.receiveShadows = true;await registerSceneWithShadowSupport(scene);For PCF shadows:
// ❌ Babylon.jsconst sg = new ShadowGenerator(1024, spotLight);sg.usePercentageCloserFiltering = true;
// ✅ Babylon LitespotLight.shadowGenerator = createPcfSpotlightShadowGenerator(engine, spotLight, { mapSize: 1024,});setShadowTaskCasterMeshes(spotLight.shadowGenerator, [mesh]);await registerSceneWithShadowSupport(scene);7. Thin Instances Use Raw Arrays
No Matrix class needed. Pass raw Float32Array with 16 floats per instance.
// ❌ Babylon.jsconst matrices = new Float32Array(count * 16);// ... fill with Matrix values ...mesh.thinInstanceSetBuffer("matrix", matrices, 16);mesh.thinInstanceSetBuffer("color", colors, 4);
// ✅ Babylon Liteconst matrices = new Float32Array(count * 16);// ... fill directly (column-major 4x4) ...setThinInstances(mesh, matrices, count);setThinInstanceColors(mesh, colors);addToScene(scene, mesh);8. Mesh Factories Take Engine, Not Scene
BJS mesh builders take scene. Lite mesh factories take engine (for GPU buffer creation) and return plain mesh data.
// ❌ Babylon.jsconst sphere = MeshBuilder.CreateSphere("sphere", { diameter: 2 }, scene);sphere.material = new StandardMaterial("mat", scene);
// ✅ Babylon Liteconst sphere = createSphere(engine);sphere.material = createStandardMaterial();addToScene(scene, sphere);9. Removing & Disposing Entities
BJS uses mesh.dispose() on individual objects. Lite uses removeFromScene() which removes the mesh from the scene and destroys all its GPU resources (buffers, textures, skeleton data).
// ❌ Babylon.jssphere.dispose();
// ✅ Babylon LiteremoveFromScene(scene, sphere);For full teardown:
// ✅ Babylon Lite — tear down everythingdisposeScene(scene); // releases all meshes, renderables, disposablesdisposeEngine(engine); // destroys GPU device, render targets, swapchainFull Example: Porting a PBR Scene
Babylon.js
const engine = new WebGPUEngine(canvas);await engine.initAsync();const scene = new Scene(engine);scene.clearColor = new Color4(0.2, 0.2, 0.3, 1);
const light = new HemisphericLight("h", new Vector3(0, 1, 0), scene);light.intensity = 1.0;
await SceneLoader.ImportMeshAsync("", baseUrl, "BoomBox.glb", scene);const envTex = new CubeTexture(envUrl, scene);scene.environmentTexture = envTex;scene.createDefaultCamera(true, true, true);scene.createDefaultEnvironment({ skyboxSize: 1000 });
engine.runRenderLoop(() => scene.render());Babylon Lite
const engine = await createEngine(canvas);const scene = createSceneContext(engine);
addToScene(scene, await loadGltf(engine, "BoomBox.glb"));await loadEnvironment(scene, envUrl, { skyboxUrl: "skybox.dds", skyboxSize: 1000, groundTextureUrl: "ground.png", brdfUrl: "/brdf-lut.png",});
const cam = createDefaultCamera(scene);attachControl(cam, canvas, scene);addToScene(scene, createHemisphericLight([0, 1, 0], 1.0));
await startEngine(engine);Gotchas
| Gotcha | Details |
|---|---|
Gotcha No auto-add | Details Meshes, lights, transform nodes, and loadGltf() asset containers must be explicitly added with addToScene(). loadEnvironment() adds its environment data/renderables internally. |
Gotcha No new keyword | Details Everything is created via factory functions, not constructors. |
Gotcha Assign camera explicitly | Details Either use createDefaultCamera(scene) (auto-assigns) or set scene.camera = myCamera manually. |
Gotcha Materials are optional | Details createStandardMaterial() / createPbrMaterial() return props objects. Assign to mesh.material. |
Gotcha WebGPU only | Details No WebGL fallback. createEngine() throws if WebGPU is unavailable. |
Gotcha No dispose() on meshes | Details Use removeFromScene(scene, mesh) to remove a single mesh and destroy its GPU resources. Use disposeScene(scene) + disposeEngine(engine) to tear down everything. |
Gotcha Tree-shakable imports | Details Import only what you use. Unused features are stripped from the bundle. |
Gotcha KTX2 is glTF-scoped | Details KTX1 has a direct loadKtxTexture2D() helper. KTX2/BasisU texture sources are handled through glTF KHR_texture_basisu during loadGltf() so non-KTX2 scenes pay zero runtime bundle cost. |
Gotcha Material property animation | Details Mutating material props at runtime requires marking the material dirty. See Material Animation section below. |
Material Animation
Babylon Lite supports animating material properties at runtime (e.g. changing colors, alpha, anisotropy intensity per frame). Two approaches are available:
Manual (default — zero overhead)
Mutate the property, then call markMaterialUboDirty():
import { markMaterialUboDirty } from "@babylonjs/lite";
onBeforeRender(scene, () => { material.alpha = Math.sin(time) * 0.5 + 0.5; markMaterialUboDirty(material);});This works for both PBR and Standard materials. Zero runtime cost when nothing changes.
Automatic tracking (opt-in)
Call enableMaterialTracking() once on a material to install property setters that auto-detect changes — including in-place array mutations like material.diffuseColor[0] = 0.5:
import { enableMaterialTracking } from "@babylonjs/lite";
const mat = createPbrMaterial({ anisotropy: { isEnabled: true, intensity: 1.0 } });enableMaterialTracking(mat);
// Now mutations auto-mark the material UBO dirty — no manual call needed:onBeforeRender(scene, () => { mat.anisotropy!.intensity = Math.cos(a) * 0.5 + 0.5; // auto-dirty mat.emissiveColor![0] = 0.5; // auto-dirty (index write)});enableMaterialTracking is fully tree-shakable — scenes that don't import it pay zero bundle cost.
| Feature | markMaterialUboDirty | enableMaterialTracking |
|---|---|---|
Feature Bundle cost | markMaterialUboDirty | enableMaterialTracking |
Feature Per-frame cost | markMaterialUboDirty | enableMaterialTracking |
Feature Catches color[0] = x | markMaterialUboDirty | enableMaterialTracking |
Feature Catches mat.alpha = x | markMaterialUboDirty | enableMaterialTracking |
Material Stencil (opt-in)
Babylon Lite supports a per-material stencil test baked into the main color pass — for masking effects like portals and decals (one material writes the stencil buffer where it draws, another discards fragments where the stencil was written). It is an explicit opt-in so stencil-free scenes stay byte-near-identical:
import { createStandardMaterial, enableMaterialStencil, registerScene } from "@babylonjs/lite";
// Writer: stamp the stencil buffer (0 → 1) everywhere it draws.const mask = createStandardMaterial();mask.stencil = { passOp: "increment-clamp" };
// Tester: draw only where the stencil is still the pass's default reference of 0// (i.e. where the writer did NOT draw). No dynamic stencil reference needed.const masked = createStandardMaterial();masked.stencil = { compare: "equal" };
enableMaterialStencil(); // ← opt-in, BEFORE registerSceneawait registerScene(scene);StencilState accepts compare, passOp, failOp, depthFailOp, readMask, and writeMask (all
optional; defaults "always" / "keep" / 0xff). Stencil is applied only on a stencil-capable target (the
main color pass) and ignored on depth-only/shadow passes. Without calling enableMaterialStencil, a
material's stencil field is inert and the pipeline builders carry no stencil code — enableMaterialStencil
is fully tree-shakable, so scenes that don't import it pay no bundle cost.
glTF / PBR Extensions
Babylon Lite's glTF loader + PBR material understand the following extensions. Each feature is tree-shakable: scenes that don't use it pay no bundle cost.
| Extension / Feature | Support | Notes |
|---|---|---|
Extension / Feature KHR_materials_pbrSpecularGlossiness | Support ✅ | Notes Auto-detected by loadGltf() |
Extension / Feature KHR_materials_clearcoat | Support ✅ | Notes Auto-detected; or createPbrMaterial({ clearCoat: { ... } }) |
Extension / Feature KHR_materials_sheen | Support ✅ | Notes Auto-detected (BJS-spec albedo scaling for glTF); or createPbrMaterial({ sheen: { ... } }) |
Extension / Feature KHR_materials_anisotropy | Support ✅ | Notes Auto-detected; or createPbrMaterial({ anisotropy: { ... } }) |
Extension / Feature KHR_materials_variants | Support ✅ | Notes selectVariant(scene, name), getVariantNames(scene), resetVariant(scene) |
Extension / Feature KHR_materials_ior | Support ✅ | Notes Auto-detected; index of refraction for dielectrics (Scene 30) |
Extension / Feature KHR_materials_specular | Support ✅ | Notes Auto-detected; dielectric specular intensity + color (Scene 30) |
Extension / Feature KHR_materials_volume | Support ✅ | Notes Auto-detected; attenuation color/distance + thickness (Scene 30) |
Extension / Feature KHR_materials_transmission | Support ✅ | Notes Frame-graph scene-texture transmission for transmissive glTF materials (Scenes 30/33/112). Screen-space scene-texture refraction; parity is within-5 = 100% of pixels. |
Extension / Feature KHR_texture_transform | Support ✅ | Notes Auto-resolved at load (material-wide UV transform) |
Extension / Feature KHR_texture_basisu | Support ✅ | Notes Auto-detected; dynamically loads KTX2 decoder/upload path only for glTF assets that declare the extension (Scene 112) |
Extension / Feature EXT_texture_webp | Support ✅ | Notes Auto-detected through texture source selection; image decode is browser-native (Scene 37) |
Extension / Feature KHR_draco_mesh_compression | Support ✅ | Notes Auto-detected; loads draco_decoder.js + .wasm on demand from site root (override via setDracoBaseUrl()) |
Extension / Feature KHR_materials_emissive_strength | Support ✅ | Notes Auto-detected; multiplies emissive output (Scene 31) |
Extension / Feature KHR_materials_unlit | Support ✅ | Notes Auto-detected; emits base color directly with no lighting (Scene 32) |
Extension / Feature KHR_lights_punctual | Support ✅ | Notes Auto-detected; point / spot / directional lights baked from glTF nodes (Scene 33) |
Extension / Feature KHR_node_visibility | Support ✅ | Notes Auto-detected; per-node visibility flag honoured at render time (Scene 34) |
Extension / Feature KHR_animation_pointer | Support ✅ | Notes Auto-detected; animates arbitrary JSON pointers (e.g. node visibility, material UBO fields) (Scene 34) |
Extension / Feature EXT_mesh_gpu_instancing | Support ✅ | Notes Auto-detected; per-node TRS accessors expanded into thin instances (Scene 35) |
Extension / Feature EXT_meshopt_compression | Support ✅ | Notes Auto-detected; meshopt-decodes vertex/index buffers via a dynamically-imported decoder (Scene 211) |
Extension / Feature KHR_mesh_quantization | Support ✅ | Notes Auto-detected; normalized/quantized vertex attributes uploaded with native typed formats (Scene 211) |
Extension / Feature KHR_xmp_json_ld | Support ✅ | Notes Auto-detected; JSON-LD metadata packets surfaced on AssetContainer.xmpMetadata with zero render impact (Scene 210) |
Extension / Feature Interleaved vertex buffers | Support ✅ | Notes Genuine GPU-level interleave: a strided bufferView is uploaded once and bound to each attribute slot via arrayStride/offset — no CPU de-interleave or asset rewrite (Scene 210) |
Extension / Feature Subsurface translucency + thickness | Support ✅ | Notes createPbrMaterial({ subsurface: { translucency, thickness } }) |
Extension / Feature Specular anti-aliasing | Support ✅ | Notes Auto-on for glTF; manual: createPbrMaterial({ enableSpecularAA: true }) |
Extension / Feature Morph targets | Support ✅ | Notes PBR meshes only (not StandardMaterial) |
Extension / Feature Skeletal animation (4 or 8 bones) | Support ✅ | Notes Driven by createAnimationController(scene) |
Extension / Feature Animation blending / weights / additive clips | Support ✅ | Notes AnimationManager with setAnimationWeight(), crossFadeAnimationGroups(), and setAnimationAdditive() (Scenes 155-158) |
Extension / Feature ShaderMaterial | Support ✅ | Notes WGSL-only createShaderMaterial() with typed uniforms, samplers, defines, alpha blend/test (Scenes 159-163) |
Extension / Feature GridMaterial | Support ✅ | Notes Procedural unlit object-space grid via createGridMaterial(): mainColor/lineColor, gridRatio, gridOffset, major/minor units, opacity, antialias, useMaxLine, preMultiplyAlpha, opacityTexture, visibility (Scene 213) |
Extension / Feature Node Material | Support ✅ | Notes NME snippet parser covering core, PBR, math, texture, procedural, normal, screen/depth, matrix, loop, and storage blocks (Scenes 60-89) |
Extension / Feature Sprites / billboards | Support ⚡ | Notes 2D layers, depth-hosted sprites, facing/axis-locked/cutout billboards; not the full BJS SpriteManager API (Scenes 50-57) |
Extension / Feature Gaussian splatting | Support ✅ | Notes .ply, .splat, .sog, .spz, bake transforms, material plugin fragments (Scenes 120-126) |
Extension / Feature CSG / CSG2 | Support ✅ | Notes Mesh boolean subtract/intersect/union/add APIs (Scenes 90-91) |
Extension / Feature Physics | Support ⚡ | Notes Havok Physics V2 subset (Scene 40) |
Extension / Feature Navigation / Recast | Support ⚡ | Notes Recast V2 navmesh, crowd pathing, tile-cache obstacles, off-mesh links, raycast (Scenes 170-175) |
Extension / Feature Device-lost recovery | Support ✅ | Notes Opt-in WebGPU device-loss recovery (Scene 164) |
Extension / Feature Screen-space SSS (PrePass) | Support ❌ | Notes Not implemented — only BRDF-layer translucency |
See lab/lite/src/lite/scene*.ts for end-to-end examples of each extension in action.