How to Use Three.js Light Probes for Irradiance

This article explains how the light probe system in Three.js captures and applies diffuse indirect lighting (irradiance) to 3D materials. You will learn how Spherical Harmonics (SH) are used to store light data, how to generate light probes from environment maps, and how Three.js materials process this data in the shader pipeline to achieve realistic, high-performance ambient lighting.

Understanding Light Probes and Irradiance

In 3D graphics, irradiance represents the total amount of light falling onto a surface from all directions. While direct light sources (like directional or point lights) handle immediate light rays, indirect lighting (global illumination) simulates how light bounces off surrounding environments.

A LightProbe in Three.js is an invisible object placed in a scene that captures this indirect diffuse light. Unlike lightmaps, which are baked directly onto 3D meshes, light probes store light information at a specific point in space. This allows dynamic objects moving through the scene to receive realistic, location-specific ambient lighting.

How Three.js Captures Irradiance

Three.js captures irradiance using a mathematical technique called Spherical Harmonics (SH). Spherical Harmonics project 3D light data onto a sphere using a series of mathematical coefficients, similar to how Fourier transforms compress audio signals.

By default, Three.js uses a third-order Spherical Harmonics system, which requires only 9 color coefficients (vectors of Red, Green, and Blue) to represent an entire 360-degree light profile. This compression makes light probes incredibly lightweight and efficient for real-time rendering.

To capture irradiance, Three.js provides a utility called LightProbeGenerator. This helper extracts light data from an environment map (such as a cube texture or an equirectangular image) and calculates the correct SH coefficients:

import { LightProbeGenerator } from 'three/examples/jsm/lights/LightProbeGenerator.js';

// Load an environment cube map
const cubeTextureLoader = new THREE.CubeTextureLoader();
cubeTextureLoader.load([ 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' ], (cubeTexture) => {

    // Generate the light probe from the cube texture
    const lightProbe = LightProbeGenerator.fromCubeTexture(cubeTexture);
    
    // Add the probe to the scene
    scene.add(lightProbe);
});

During this generation process, the LightProbeGenerator scans the pixels of the cube map, computes their brightness and color distribution, and populates the lightProbe.sh property with the resulting 9 coefficients.

How Three.js Applies Irradiance to Materials

Once a LightProbe is added to the scene, Three.js automatically incorporates its data into the rendering pipeline for standard materials, such as MeshStandardMaterial and MeshPhysicalMaterial.

The application of irradiance to a material occurs in the WebGL shader during the fragment rendering stage:

  1. Reading the Normal Vector: The shader identifies the surface normal (the direction the surface is facing) of the fragment currently being rendered.
  2. Reconstructing the Light: The shader passes this normal vector into a Spherical Harmonics mathematical function alongside the 9 stored coefficients from the light probe.
  3. Calculating Diffuse Shading: This function outputs the exact color and intensity of the ambient light coming from the direction of the surface normal.
  4. Blending with Albedo: The calculated irradiance is multiplied by the material’s base diffuse color (albedo) and added to the final lighting equation of the pixel.

Because Spherical Harmonics only capture low-frequency (blurry) lighting detail, they are ideal for diffuse reflections but cannot handle sharp, mirror-like specular reflections. For specular reflections, Three.js materials rely on PMREM (Prefiltered Mipmapped Radiance Environment Map) textures rather than light probes.