Realistic Shading in Babylon.js: PBR Materials with HDRI Environment Maps
Learn how to achieve photorealistic shading in Babylon.js using PBRMaterial with HDRI environment maps, including a ready‑to‑run code example, WebGPU/WebGL2 parity, and practical limits to watch.
05 Sept 2025, 18:27 UTC

Why HDRI‑driven PBR matters
When you need photorealistic results in a browser‑based 3D app, the combination of physically based rendering (PBR) and image‑based lighting (IBL) is the shortest path. Babylon.js ships a PBRMaterial that follows the glTF 2.0 metal‑rough workflow and an automatic environment‑map convolution pipeline. The same material definition runs on WebGL 2 and WebGPU without code changes, which makes it a practical choice for projects that must support both back‑ends today.
Setting up the scene and the environment map
The first step is to create a Babylon.js Engine (optionally forcing WebGPU) and load an HDR or pre‑filtered .env file as the scene’s environmentTexture. The engine will generate the irradiance (diffuse) and specular pre‑filtered mipmaps internally.
// index.html – minimal host page
<canvas id="renderCanvas"></canvas>
<script type="module">
import { Engine, Scene, Vector3, HemisphericLight, MeshBuilder, PBRMaterial, TextureTools } from "@babylonjs/core";
const canvas = document.getElementById("renderCanvas");
// Enable WebGPU when available; falls back to WebGL2 automatically
const engine = new Engine(canvas, true, { adaptToDeviceRatio: true, webGPU: true });
const scene = new Scene(engine);
// Basic camera & light (light is optional when using IBL)
const camera = new ArcRotateCamera("cam", Math.PI / 2, Math.PI / 3, 5, Vector3.Zero(), scene);
camera.attachControl(canvas, true);
new HemisphericLight("hemi", new Vector3(0, 1, 0), scene);
// Load an equirectangular HDR and assign it as the environment texture
TextureTools.CreateEquirectangularCubeTexture(
"assets/environment.hdr", // ← replace with your HDR path
scene,
256, // target cube map resolution (power of two)
false,
(texture) => {
scene.environmentTexture = texture;
scene.environmentIntensity = 1.0; // global IBL strength
}
);
// Create a test mesh – a sphere shows both diffuse and specular response
const sphere = MeshBuilder.CreateSphere("sphere", { diameter: 2 }, scene);
// PBR material with metal‑rough parameters
const pbr = new PBRMaterial("pbr", scene);
pbr.albedoColor = new Color3(0.9, 0.85, 0.8); // warm base color
pbr.metallic = 0.8; // 0 = dielectric, 1 = metal
pbr.roughness = 0.25; // low roughness = sharp reflections
pbr.environmentIntensity = 1.2; // per‑material IBL boost
sphere.material = pbr;
engine.runRenderLoop(() => scene.render());
window.addEventListener("resize", () => engine.resize());
</script>
Where to run: any modern browser with a local dev server (e.g., npx serve or VS Code Live Server). Permissions: none beyond standard file access. Placeholders: replace assets/environment.hdr with a valid HDR file (or a .env pre‑filtered cube map). Expected check: the sphere should reflect the surrounding environment with sharp highlights where roughness is low and a softer glow where roughness is higher. Risk: large HDR files (>4 K) can exceed mobile GPU memory; consider a 1 K or 2 K cube map or a KTX2‑compressed version.
Key PBRMaterial knobs you’ll actually use
albedoColor/albedoTexture– base color or map.metallic(0‑1) – switches between dielectric and conductor response.roughness(0‑1) – controls micro‑facet distribution; lower = glossier.bumpTexture/normalTexture– surface detail without geometry.clearCoat+clearCoatRoughness– adds a thin transparent layer (car paint, varnish).sheen+sheenColor– simulates fabric‑like fuzz.environmentIntensity– per‑material IBL scaling, useful for artistic overrides.
All of these map directly to the glTF 2.0 PBR extension, so assets exported from Blender, Substance Painter, or the Babylon.js Editor will render identically.
WebGPU vs. WebGL 2 – no code changes required
When the Engine is constructed with { webGPU: true }, Babylon.js selects the WebGPU backend if the browser supports it. The same PBRMaterial instance compiles a WebGPU shader variant automatically. In practice you’ll see:
- Identical visual output (within floating‑point precision).
- Reduced draw‑call overhead on complex scenes because WebGPU’s pipeline state objects are leaner.
- Potentially higher frame‑rates on desktop GPUs; on mobile the difference is often negligible.
To verify, open the Babylon.js Inspector (scene.debugLayer.show()) and switch the “Engine” dropdown between “WebGL2” and “WebGPU”. The material parameters panel updates in real time, confirming the same values drive both back‑ends.
Limitations you should plan for
| Limitation | Impact | Mitigation |
|---|---|---|
| High‑resolution HDR environment maps | GPU memory pressure; possible frame‑rate drop on low‑end devices. | Down‑sample to 1 K–2 K cube faces, or use KTX2 with BasisU compression. |
| No native double‑sided PBR lighting | Back‑faces render with the same normal orientation, causing incorrect shading on thin geometry. | Set material.backFaceCulling = false and flip normals in the fragment shader (or duplicate geometry with reversed winding). |
| Clear coat / sheen not supported in WebGL 1 fallback | Features silently ignored on very old browsers. | Graceful degradation: provide a fallback material without those layers. |
Quick verification checklist
- Load the scene in a browser; confirm the sphere reflects the HDR environment.
- Open the Inspector (
scene.debugLayer.show()) and tweakmetallic,roughness,environmentIntensity– observe immediate visual changes. - Switch the engine to WebGPU via the Inspector’s Engine selector; ensure no console errors and performance is comparable.
- Profile GPU memory (Chrome DevTools → Performance → GPU) to verify the environment texture size matches expectations.
Takeaway
Babylon.js gives you a production‑ready PBR + IBL pipeline with a single material class and automatic environment‑map convolution. By keeping the HDR resolution modest and being aware of the double‑sided shading gap, you can ship realistic WebGL/WebGPU experiences today without maintaining separate shader code paths.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.