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:
- Reading the Normal Vector: The shader identifies the surface normal (the direction the surface is facing) of the fragment currently being rendered.
- Reconstructing the Light: The shader passes this normal vector into a Spherical Harmonics mathematical function alongside the 9 stored coefficients from the light probe.
- Calculating Diffuse Shading: This function outputs the exact color and intensity of the ambient light coming from the direction of the surface normal.
- 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.