Image-Based Lighting

Image-Based Lighting from an Environment Texture

Assign scene.environmentTexture and every PBR material uses it twice, as in Image-Based Lighting: diffuse light from the spherical harmonics (nine coefficients per color, read from the .env, not computed) and specular reflections from the mip level matching each surface's roughness. scene.environmentIntensity scales both, material.environmentIntensity one material, and env.rotationY turns the lighting. StandardMaterial ignores the scene environment; give it a reflectionTexture instead:

The scene environment for PBR materials, and a reflection for a StandardMaterialJavaScript
const env = BABYLON.CubeTexture.CreateFromPrefilteredData('booknest-sunrise.env', scene);
scene.environmentTexture = env;                     // every PBR material, from now on
scene.environmentIntensity = 0.35;
const glass = new BABYLON.StandardMaterial('shopWindow', scene);
glass.reflectionTexture = env;                      // StandardMaterial needs it spelled out
glass.alpha = 0.3;

Loaded here, the texture reported 256-pixel faces, gammaSpace false (linear HDR data) and its harmonics present. IBL has no occlusion: the sky lights a surface inside a closed room as brightly as one outdoors. Indoors, keep environmentIntensity low and let lights and shadows carry the interior, bake ambient occlusion into textures, or capture the room with a reflection probe.

PBR balls lit only by an environment texture, from rough to smooth and dielectric to metalHTMLLive
<!doctype html>
<style>
  body { margin: 0; background: #f7f3ec; font: 13px system-ui, sans-serif; color: #333; }
  canvas { width: 100%; max-width: 600px; height: 330px; display: block; touch-action: none; outline: none; }
</style>
<canvas id="view"></canvas>
<div style="max-width:600px;padding:4px 8px">environmentIntensity <input id="intensity" type="range" min="0" max="1.5" step="0.05" value="1"> <span id="value">1.00</span> &nbsp; <label><input id="spin" type="checkbox" checked> rotate env.rotationY</label></div>
<script src="https://cdn.jsdelivr.net/npm/babylonjs@9.28.0/babylon.js"></script>
<script src="https://cdn.jsdelivr.net/npm/babylonjs-gui@9.28.0/babylon.gui.min.js"></script>
<script>
// A procedural HDR sunrise sky, computed texel by texel into six float faces. It stands in for
// an .hdr or .env download, which this sandboxed preview cannot fetch.
function createSkyTexture(scene, size = 128) {
  const sunDir = new BABYLON.Vector3(0.45, 0.28, 0.85).normalize();
  const faceDirection = [                           // (s, t) in -1..1 to a direction, per face
    (s, t) => [1, -t, -s], (s, t) => [-1, -t, s], (s, t) => [s, 1, t],
    (s, t) => [s, -1, -t], (s, t) => [s, -t, 1], (s, t) => [-s, -t, -1]];
  const faces = faceDirection.map((toDirection) => {
    const data = new Float32Array(size * size * 4);
    for (let y = 0; y < size; y++) for (let x = 0; x < size; x++) {
      const [dx, dy, dz] = toDirection((2 * x + 1) / size - 1, (2 * y + 1) / size - 1);
      const len = Math.hypot(dx, dy, dz), up = dy / len;
      const k = Math.sqrt(Math.max(up, 0));            // 0 at the horizon, 1 straight up
      const ground = Math.min(Math.max(-up * 8, 0), 1);   // fade to the ground below the horizon
      const r = (1 - 0.62 * k) * (1 - ground) + 0.36 * ground;    // peach horizon, blue zenith,
      const g = (0.82 - 0.22 * k) * (1 - ground) + 0.3 * ground;  // warm brown ground
      const b = (0.68 + 0.3 * k) * (1 - ground) + 0.24 * ground;
      const cosSun = Math.max((dx * sunDir.x + dy * sunDir.y + dz * sunDir.z) / len, 0);
      const sun = 30 * Math.pow(cosSun, 900) + 0.35 * Math.pow(cosSun, 24);   // disc + glow, HDR
      const i = (y * size + x) * 4;
      data[i] = r + sun; data[i + 1] = g + sun * 0.85; data[i + 2] = b + sun * 0.6; data[i + 3] = 1;
    }
    return data;
  });
  const sky = new BABYLON.RawCubeTexture(scene, faces, size, BABYLON.Constants.TEXTUREFORMAT_RGBA,
    BABYLON.Constants.TEXTURETYPE_FLOAT, true, false, BABYLON.Constants.TEXTURE_TRILINEAR_SAMPLINGMODE);
  sky.gammaSpace = false;                            // linear HDR values, like a loaded .hdr
  return sky;
}

const canvas = document.getElementById('view');
const engine = new BABYLON.Engine(canvas, true);
const scene = new BABYLON.Scene(engine);
const camera = new BABYLON.ArcRotateCamera('cam', -Math.PI / 2, 1.45, 8.5, new BABYLON.Vector3(0, 0.1, 0), scene);
camera.attachControl(canvas, true);
const ui = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI('ui');

// No lights at all: diffuse comes from the texture's spherical harmonics, specular from its
// prefiltered mip levels (the book loads booknest-sunrise.env; this sky is computed in code)
const env = createSkyTexture(scene);
new BABYLON.HDRFiltering(engine).prefilter(env);      // blur a mip chain, as an .env ships it
scene.environmentTexture = env;                       // every PBR material, from now on
scene.createDefaultSkybox(env, true, 100, 0.3);

// Rows: dielectric and metal; columns: roughness 0 to 1
for (let row = 0; row < 2; row++) {
  for (let col = 0; col < 5; col++) {
    const ball = BABYLON.MeshBuilder.CreateSphere(`ball${row}${col}`, { diameter: 1.05, segments: 32 }, scene);
    ball.position.set((col - 2) * 1.3, row ? -0.65 : 0.75, 0);
    const material = new BABYLON.PBRMaterial(`m${row}${col}`, scene);
    material.albedoColor = BABYLON.Color3.FromHexString(row ? '#d8b07a' : '#1f5f8b');
    material.metallic = row;
    material.roughness = col / 4;
    ball.material = material;
  }
}
const note = new BABYLON.GUI.TextBlock('', 'roughness 0 → 1   (top: dielectric blue, bottom: metal gold)');
Object.assign(note, { color: '#333', fontSize: 13, height: '24px', top: '8px',
  verticalAlignment: BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP });
ui.addControl(note);

// StandardMaterial ignores scene.environmentTexture; it needs reflectionTexture spelled out
const glass = new BABYLON.StandardMaterial('shopWindow', scene);
glass.reflectionTexture = env;
glass.alpha = 0.3;
const pane = BABYLON.MeshBuilder.CreatePlane('pane', { width: 7, height: 0.35 }, scene);
pane.position.set(0, -1.55, -0.4);
pane.material = glass;

const slider = document.getElementById('intensity');
slider.addEventListener('input', () => {
  scene.environmentIntensity = +slider.value;         // scales diffuse and specular IBL
  document.getElementById('value').textContent = (+slider.value).toFixed(2);
});
scene.onBeforeRenderObservable.add(() => {
  if (document.getElementById('spin').checked) env.rotationY += engine.getDeltaTime() / 8000;   // turn the lighting
});
engine.runRenderLoop(() => scene.render());
addEventListener('resize', () => engine.resize());
</script>