Setting Up PBR Materials with Image-Based Lighting in Babylon.js
A practical guide to setting up Babylon.js PBRMaterial with an HDR environment, metalness-roughness maps, and the checks that catch black meshes and blown‑out highlights.
29 Sept 2025, 04:49 UTC

If your meshes look flat and plasticky under a StandardMaterial, the usual fix is not more lights — it is switching to PBRMaterial with a proper environment map. Physically Based Rendering in Babylon.js models how light actually interacts with a surface, but it only looks right when the scene provides ambient light through an HDR environment. This guide walks through assigning a PBRMaterial, wiring up the core texture maps, and verifying the result behaves correctly.
What you need before starting
This assumes Babylon.js 5.x or later (the API shown is stable across 5.x and 6.x/7.x), a scene with at least one loaded mesh, and the ability to serve texture files over HTTP — loading textures from file:// will fail due to browser security rules. You also need an HDR or DDS environment texture. Babylon's asset library hosts a commonly used one (environment.env), but any prefiltered .env or .dds file works.
Be aware of the cost: PBRMaterial runs a more complex shader than StandardMaterial. On low-end mobile GPUs, many PBR meshes in one scene can drop frame rates noticeably. If you target mobile, profile early and consider reducing environment resolution.
Step 1: Load the environment texture (IBL)
PBR relies on Image-Based Lighting (IBL): the environment texture provides both ambient light and reflections. Without it, metallic surfaces render almost black, because a fully metallic surface has no diffuse response — it only reflects what is around it.
// Run in your scene setup code, after engine and scene creation.
// No special permissions needed; the URL must be reachable by the browser.
const envTexture = BABYLON.CubeTexture.CreateFromPrefilteredData(
"https://assets.babylonjs.com/environments/environment.env",
scene
);
scene.environmentTexture = envTexture;
Step 2: Create and configure the PBRMaterial
PBRMaterial uses the metalness-roughness workflow. The two scalar properties that define the surface are metallic (0 = dielectric like plastic or wood, 1 = metal) and roughness (0 = mirror-smooth, 1 = fully diffuse).
const pbr = new BABYLON.PBRMaterial("pbr", scene);
// Base color texture (sRGB) and scalar factors
pbr.albedoTexture = new BABYLON.Texture("textures/albedo.jpg", scene);
pbr.metallic = 1.0;
pbr.roughness = 0.35;
myMesh.material = pbr;
A quick sanity configuration: pure metal with moderate roughness should show a clear, slightly blurred reflection of the environment. If the surface renders black at this point, the environment texture almost certainly failed to load — check the browser's network tab for a 404 or CORS error.
Step 3: Add normal, metallic-roughness, and occlusion maps
Normal maps fake high-frequency geometric detail (scratches, grooves) on low-poly meshes. Occlusion maps darken crevices where ambient light would naturally be blocked, adding depth without extra lights.
pbr.bumpTexture = new BABYLON.Texture("textures/normal.png", scene);
// glTF-style packed map: roughness in G, metallic in B
pbr.metallicTexture = new BABYLON.Texture("textures/metallicRoughness.png", scene);
pbr.useRoughnessFromMetallicTextureGreen = true;
pbr.useMetallicFromMetallicTextureBlue = true;
pbr.ambientTexture = new BABYLON.Texture("textures/ao.jpg", scene);
When a metallic-roughness texture is present, it multiplies with the scalar metallic/roughness values. If your map already encodes the final values, leave the scalars at their defaults (metallic 1.0 behavior with a map differs by version — when in doubt, set pbr.metallic = 1.0 and pbr.roughness = 1.0 so the map alone controls the result, and verify visually).
Verifying the result
- Material assignment: confirm in code or the Babylon.js Inspector that
myMesh.materialis your PBRMaterial instance, not a leftover standard material. - IBL active: rotate the camera around the mesh. Reflections on metallic areas must shift with the camera. Static highlights mean you are seeing only a point light, not the environment.
- Exposure: if highlights are blown out to pure white or the whole mesh looks gray and dark, adjust
scene.environmentIntensitybefore touching the material — incorrect HDR intensity is the most common cause. - Texture loading: watch the browser console for texture errors. Formats like WebP or very large PNGs can fail on older WebGL implementations; JPG and PNG at power-of-two sizes are the safe baseline.
Limitations and recovery
If performance drops after converting a scene to PBR, the practical levers are: lower the environment texture resolution, reduce the number of simultaneous lights (each light adds cost per PBR pixel), or keep background/static meshes on StandardMaterial. If a mesh renders black, work backwards in this order: environment texture loaded? material actually assigned? metallic texture accidentally all-white with no IBL? Each of those failure modes looks similar but has a different fix, and checking the network tab plus the Inspector resolves most of them in minutes.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.