All examples

Reflection probes

Lighting & GI3D
  • Mouse
  • Touch
  • Gamepad

A ReflectionProbe renders six views from its position into a cube map, and nearby shiny PBR materials reflect it instead of the Environment's .env: the spheres and the floor show the red, green and blue walls of this room. refreshMode says when it captures (once, on demand, every frame), boxProjection lines the reflection up with the room's walls, and size sets the face resolution.

Off airLivePaused

Reflection probes

Lighting & GI · 3D

A room with a red left wall, a blue back wall and a green right wall seen through its open side: a white, a chrome and a gold sphere on a dark glossy floor reflect the coloured walls, and a yellow cube floats in front.
Open standalone

WebGPU: checking…

Try this

  1. Under once, watch the cube's reflection in the chrome stay put, then switch to everyFrame and see it follow.
  2. Turn Box projection off and look at the white band in the floor and spheres: it slides out of line.
  3. Switch the probe off: the spheres go back to reflecting the grey studio the .env was shot in.
Show source code
main.ts
// `ignifx` does not re-export the probe (0.17.0), so it comes from `@ignifx/core`, which does.import { REFLECTION_PROBE_REFRESH_MODES, ReflectionProbe } from "@ignifx/core";import { Camera, Environment } from "ignifx";import { bootExample } from "../_kit/boot.ts";import { degreesPerSecond, twoPlaces } from "../_kit/format.ts";import { attachOrbit } from "../_kit/orbit.ts";import { bind, button, readout, select, slider, toggle } from "../_kit/panel.ts";import { loadEnvironment } from "../_kit/stage.ts";import { createRoom, FOCUS, Orbiter, PROBE_AT, ROOM, SHOT } from "./scene.ts";import type { ReflectionProbeRefreshMode } from "@ignifx/core";/** * A `ReflectionProbe`: a point that renders the scene around it into a cube map, which nearby shiny * PBR materials then reflect instead of the `Environment`'s `.env`. * * The probe sits in the middle of the room. Every lit PBR material reflects the nearest enabled * probe that has captured — one whose `boxProjection` box holds the mesh first, then the closest — * so the three spheres and the floor show the red, green and blue walls rather than the studio the * `.env` was shot in. Diffuse light still comes from the `Environment`, and the probe needs one * loaded: it reuses its lighting tables. * * `refreshMode` is when it captures. `"once"` (the default) captures when the probe is first ready * and again on `refresh()`; `"onDemand"` only on `refresh()`; `"everyFrame"` every frame, which is * six extra scene passes a frame — one probe, on a desktop GPU. Watch the cube's reflection in the * chrome: under `"once"` it stays where the cube was at the last capture, under `"everyFrame"` it * follows. Switching mode keeps the capture the probe already has. * * `boxProjection` is the full size of a box centred on the probe. With it, a reflection is projected * onto the box, so the room's walls and the white band line up where they should (parallax-correct); * `null` draws the cube map as if it were infinitely far away, which is right for a sky and wrong for * a room. `size` is the cube face edge in pixels, rounded down to a power of two from 16 to 1024; * changing it builds a new cube map, which captures at once under `"once"` and `"everyFrame"` and * waits for `refresh()` under `"onDemand"` — until then the materials reflect the `Environment`. *//** The cube face sizes the panel offers, in pixels. */const SIZES = ["32", "64", "128", "256", "512"] as const;/** The face size the example opens on: the engine default. */const START_SIZE = 128;/** The exposure the room is graded at. */const EXPOSURE = 1.1;bootExample({  title: "Reflection probes",  settings: {    rendering: { clearColor: { r: 0.04, g: 0.045, b: 0.055, a: 1 } },  },  async setup({ app, panel }) {    app.registerComponents([Orbiter]);    const eye = app.world.createEntity("Main Camera");    eye.addComponent(Camera, { near: 0.05, far: 100, fov: SHOT.fov });    attachOrbit(app, eye, {      yaw: SHOT.yaw,      pitch: SHOT.pitch,      distance: SHOT.distance,      target: FOCUS,      minDistance: 2,      maxDistance: 12,    });    // The probe needs a loaded `Environment`. The studio probe's skybox is off, so outside the    // room's open side the frame shows the clear colour.    const studio = loadEnvironment(app, "studio");    await studio.promise;    const sky = app.world      .createEntity("Environment")      .addComponent(Environment, { environment: studio, skybox: { enabled: false, size: 20 } });    sky.imageProcessing.exposure = EXPOSURE;    sky.imageProcessing.toneMapping = "agx";    const orbiter = createRoom(app);    const probe = app.world      .createEntity("Room probe", { position: PROBE_AT })      .addComponent(ReflectionProbe, { size: START_SIZE, refreshMode: "once", boxProjection: { ...ROOM } });    panel({      title: "Reflection probes",      groups: [        {          label: "Probe",          controls: [            toggle("Enabled", bind(probe, "enabled")),            select("Refresh mode", REFLECTION_PROBE_REFRESH_MODES, {              value: probe.refreshMode,              change: (mode: string): void => {                probe.refreshMode = mode as ReflectionProbeRefreshMode;              },              read: (): string => probe.refreshMode,            }),            button("Refresh", (): void => {              probe.refresh();            }),            toggle("Box projection", {              value: true,              change: (on: boolean): void => {                probe.boxProjection = on ? { ...ROOM } : null;              },              read: (): boolean => probe.boxProjection !== null,            }),            select("Size", SIZES, {              value: String(START_SIZE),              change: (size: string): void => {                probe.size = Number(size);              },              read: (): string => String(probe.size),            }),          ],        },        {          label: "Scene",          controls: [            slider("Cube orbit", { min: 0, max: 120, step: 5, format: degreesPerSecond }, bind(orbiter, "speed")),            slider("Exposure", { min: 0.3, max: 3, step: 0.05, format: twoPlaces }, bind(sky.imageProcessing, "exposure")),          ],        },        {          label: "Frame",          collapsed: true,          controls: [readout("Draw calls", (): string => String(app.renderer.drawCalls))],        },      ],    });  },});
scene.ts
/** * The room `reflection-probes` looks into: four coloured walls, a glossy floor, three shiny spheres * and a bright cube that circles them. * * @remarks * The colours are the lesson. An `.env` probe is somewhere else — a studio, a street — so a chrome * ball lit by one reflects a place that is not in the scene. The walls here are red, green, blue and * cream, so a reflection that shows them came from the room, and a reflection that shows a grey * softbox came from the `Environment`. The room's open side faces the camera. */import { f32, Light, MeshAsset, Script } from "ignifx";import { paint, place } from "../_kit/props.ts";import type { Point3 } from "../_kit/stage.ts";import type { App, ScriptCallbacks } from "ignifx";/** The room's inner width (X), height (Y) and depth (Z), in metres. */export const ROOM = { x: 6, y: 3, z: 6 } as const;/** How thick the walls are, in metres. */const WALL = 0.2;/** Where the probe sits: the middle of the room, so its box is exactly the room. */export const PROBE_AT: Point3 = { x: 0, y: ROOM.y / 2, z: 0 };/** The point the camera orbits. */export const FOCUS: Point3 = { x: 0, y: 0.8, z: 0.3 };/** The opening shot: outside the open side, looking in and a little down. */export const SHOT = { fov: 46, yaw: 14, pitch: 13, distance: 7.2 } as const;/** Where the orbiting cube circles, and how far out, in metres. */const ORBIT = { centre: { x: 0, y: 0.9, z: 0.4 }, radius: 2 } as const;/** Carries the bright cube around the spheres, so a reflection that is not refreshed falls behind. */export class Orbiter  extends Script.define({    speed: f32(40, { min: 0, tooltip: "Degrees around the circle per second." }),  })  implements ScriptCallbacks{  /** The namespaced registration id. */  static typeId = "reflection-probes/Orbiter";  /** How far round the circle the cube has travelled, in degrees. Starts in front, to the right. */  #angle = 310;  /**   * Advances the orbit. `dt` is scaled, so `?static=1` holds the cube still.   *   * @param dt - Seconds since the previous frame.   */  update(dt: number): void {    this.#angle += this.speed * dt;    const phase = this.#angle * (Math.PI / 180);    this.transform.localPosition.set(      ORBIT.centre.x + Math.cos(phase) * ORBIT.radius,      ORBIT.centre.y,      ORBIT.centre.z + Math.sin(phase) * ORBIT.radius,    );    this.transform.localEulerAngles = { x: 20, y: this.#angle * 2, z: 10 };  }}/** * Builds the room, the spheres, the orbiting cube and the lamp. * * @remarks * `app.registerComponents([Orbiter])` is the caller's job. * * @param app - The app the entities and the assets belong to. * @returns The orbiter, for the panel. */export function createRoom(app: App): Orbiter {  const box = MeshAsset.box(app, { size: 1 });  const half = { x: ROOM.x / 2, z: ROOM.z / 2 };  // A dark, fairly glossy floor: it is a mirror for the room too, and the first place the probe shows.  place(app, "Floor", box, paint(app, "probe/floor", { r: 0.16, g: 0.16, b: 0.18, a: 1 }, { roughness: 0.18 }), {    at: { x: 0, y: -WALL / 2, z: 0 },    scale: { x: ROOM.x + WALL * 2, y: WALL, z: ROOM.z + WALL * 2 },    castShadows: false,  });  place(app, "Back wall", box, paint(app, "probe/blue", { r: 0.16, g: 0.3, b: 0.78, a: 1 }), {    at: { x: 0, y: ROOM.y / 2, z: half.z + WALL / 2 },    scale: { x: ROOM.x + WALL * 2, y: ROOM.y, z: WALL },  });  place(app, "Left wall", box, paint(app, "probe/red", { r: 0.8, g: 0.16, b: 0.12, a: 1 }), {    at: { x: -half.x - WALL / 2, y: ROOM.y / 2, z: 0 },    scale: { x: WALL, y: ROOM.y, z: ROOM.z + WALL * 2 },  });  place(app, "Right wall", box, paint(app, "probe/green", { r: 0.18, g: 0.62, b: 0.24, a: 1 }), {    at: { x: half.x + WALL / 2, y: ROOM.y / 2, z: 0 },    scale: { x: WALL, y: ROOM.y, z: ROOM.z + WALL * 2 },  });  place(app, "Ceiling", box, paint(app, "probe/cream", { r: 0.86, g: 0.82, b: 0.7, a: 1 }), {    at: { x: 0, y: ROOM.y + WALL / 2, z: 0 },    scale: { x: ROOM.x + WALL * 2, y: WALL, z: ROOM.z + WALL * 2 },  });  // A white band along the back wall: a straight line is what shows whether a reflection is  // projected onto the room's box or drawn as if the room were infinitely far away.  place(app, "Band", box, paint(app, "probe/band", { r: 0.95, g: 0.94, b: 0.9, a: 1 }), {    at: { x: 0, y: 1.2, z: half.z - 0.01 },    scale: { x: ROOM.x, y: 0.25, z: 0.02 },    castShadows: false,  });  // The spheres: chrome, glossy white paint, and brushed gold.  const sphere = MeshAsset.sphere(app, { diameter: 1, segments: 48 });  const chrome = paint(app, "probe/chrome", { r: 0.96, g: 0.96, b: 0.97, a: 1 }, { metallic: 1, roughness: 0.04 });  const gloss = paint(app, "probe/gloss", { r: 0.92, g: 0.92, b: 0.9, a: 1 }, { roughness: 0.08 });  const gold = paint(app, "probe/gold", { r: 1, g: 0.78, b: 0.36, a: 1 }, { metallic: 1, roughness: 0.3 });  place(app, "Chrome sphere", sphere, chrome, { at: { x: 0, y: 0.65, z: 0.5 }, scale: { x: 1.3, y: 1.3, z: 1.3 } });  place(app, "Gloss sphere", sphere, gloss, { at: { x: -1.5, y: 0.45, z: 0.2 }, scale: { x: 0.9, y: 0.9, z: 0.9 } });  place(app, "Gold sphere", sphere, gold, { at: { x: 1.5, y: 0.45, z: 0.2 }, scale: { x: 0.9, y: 0.9, z: 0.9 } });  // The cube that moves: bright, so its reflection is easy to find in the chrome.  const cube = place(    app,    "Orbiting cube",    box,    paint(app, "probe/cube", { r: 1, g: 0.75, b: 0.1, a: 1 }, { emissive: { r: 0.9, g: 0.6, b: 0.05, a: 1 } }),    { at: ORBIT.centre, scale: { x: 0.45, y: 0.45, z: 0.45 } },  );  const orbiter = cube.addComponent(Orbiter);  // One lamp under the ceiling lights the room the probe captures.  const lamp = app.world.createEntity("Lamp", { position: { x: 0, y: ROOM.y - 0.4, z: -0.6 } });  lamp.addComponent(Light, { type: "point", intensity: 9, range: 14, color: { r: 1, g: 0.95, b: 0.88, a: 1 } });  return orbiter;}
Uses
ReflectionProbeReflectionProbe.refreshModeReflectionProbe.refreshReflectionProbe.boxProjectionEnvironmentScript
Assets
Studio environment — CC-BY 4.0, Babylon.js contributors