Skip to main content
IndirectLight is used to simulate environment lighting, a form of global illumination. Environment lighting has two components:
  1. Irradiance - Light that shines on object surfaces from the environment
  2. Reflections - Specular component for reflective surfaces

Overview

Environments are usually captured as high-resolution HDR equirectangular images and processed by the cmgen tool to generate the data needed by IndirectLight.
Currently IndirectLight is intended for “distant probes” - global illumination from a distant environment at infinity, such as the sky or distant mountains. Only a single IndirectLight can be used in a Scene.

Creation and Destruction

An IndirectLight object is created using the IndirectLight::Builder and destroyed by calling Engine::destroy(const IndirectLight*).
filament::Engine* engine = filament::Engine::create();

filament::IndirectLight* environment = filament::IndirectLight::Builder()
    .reflections(cubemap)
    .build(*engine);

engine->destroy(environment);

Builder Methods

Constructor

Creates an IndirectLight builder.
IndirectLight::Builder builder;

reflections

Set the reflections cubemap mipmap chain.
cubemap
Texture*
required
A mip-mapped cubemap generated by cmgen. Each cubemap level encodes the irradiance for a roughness level
return
Builder&
This Builder, for chaining calls
Texture* cubemap = ...; // Load your cubemap texture
builder.reflections(cubemap);

irradiance (Spherical Harmonics)

Sets the irradiance as Spherical Harmonics.
bands
uint8_t
required
Number of spherical harmonics bands. Must be 1, 2, or 3
sh
math::float3*
required
Array containing the spherical harmonics coefficients. Size must be bands² (1, 4, or 9 coefficients)
return
Builder&
This Builder, for chaining calls
// 3-band spherical harmonics (9 coefficients)
math::float3 sh[9] = {
    {0.282f, 0.282f, 0.282f},  // L00
    {-0.1f, -0.1f, -0.1f},     // L1-1
    {0.2f, 0.2f, 0.2f},        // L10
    // ... remaining coefficients
};
builder.irradiance(3, sh);
The irradiance must be pre-convolved and pre-multiplied by the Lambertian BRDF (1/π). The cmgen tool generates these coefficients automatically.

Spherical Harmonics Indexing

The index in the sh array is given by: index(l, m) = l * (l + 1) + m
IndexlmA_l^mNormalized
0000.2820950.282095
11-1-0.488602-0.325735
2100.4886020.325735
311-0.488602-0.325735
42-21.0925480.273137
52-1-1.092548-0.273137
6200.3153920.078848
721-1.092548-0.273137
8220.5462740.136569

radiance

Sets the irradiance from the radiance expressed as Spherical Harmonics.
bands
uint8_t
required
Number of spherical harmonics bands. Must be 1, 2, or 3
sh
math::float3*
required
Array containing the spherical harmonics coefficients L_l^m. Size must be bands² (1, 4, or 9 coefficients)
return
Builder&
This Builder, for chaining calls
math::float3 radiance[9];
// ... fill with radiance coefficients
builder.radiance(3, radiance);

irradiance (Cubemap)

Sets the irradiance as a cubemap.
cubemap
Texture*
required
Cubemap representing the irradiance pre-convolved by ⟨n·l⟩
return
Builder&
This Builder, for chaining calls
Texture* irradianceCubemap = ...; // Generated by cmgen
builder.irradiance(irradianceCubemap);
This irradiance cubemap can be generated with the cmgen tool. Using a cubemap may be more or less efficient than Spherical Harmonics depending on your hardware (trading ALU for bandwidth).

intensity

Environment intensity.
envIntensity
float
default:"30000"
Scale factor applied to the environment and irradiance such that the result is in lux (lumen/m²)
return
Builder&
This Builder, for chaining calls
builder.intensity(30000.0f); // Default outdoor lighting

rotation

Specifies the rigid-body transformation to apply to the IBL.
rotation
math::mat3f
required
3x3 rotation matrix. Must be a rigid-body transform
return
Builder&
This Builder, for chaining calls
// Rotate environment 45 degrees around Y axis
float angle = 45.0f * (M_PI / 180.0f);
math::mat3f rotation = math::mat3f::rotation(angle, math::float3{0, 1, 0});
builder.rotation(rotation);

build

Creates the IndirectLight object and returns a pointer to it.
engine
Engine&
required
Reference to the filament::Engine to associate this IndirectLight with
return
IndirectLight*
Pointer to the newly created object, or nullptr if an error occurred
IndirectLight* ibl = builder.build(*engine);

Instance Methods

setIntensity

Sets the environment’s intensity.
intensity
float
required
Scale factor applied to the environment and irradiance such that the result is in lux (lumen/m²)
ibl->setIntensity(50000.0f);

getIntensity

Returns the environment’s intensity in lux (lumen/m²).
return
float
Intensity in lux
float intensity = ibl->getIntensity();

setRotation

Sets the rigid-body transformation to apply to the IBL.
rotation
math::mat3f
required
3x3 rotation matrix. Must be a rigid-body transform
math::mat3f rotation = math::mat3f::rotation(angle, axis);
ibl->setRotation(rotation);

getRotation

Returns the rigid-body transformation applied to the IBL.
return
math::mat3f
The rotation matrix
math::mat3f rotation = ibl->getRotation();

getReflectionsTexture

Returns the associated reflection map, or null if it does not exist.
return
Texture*
The reflections texture, or nullptr
Texture* reflections = ibl->getReflectionsTexture();

getIrradianceTexture

Returns the associated irradiance map, or null if it does not exist.
return
Texture*
The irradiance texture, or nullptr
Texture* irradiance = ibl->getIrradianceTexture();

Helper Methods

getDirectionEstimate (Static)

Helper to estimate the direction of the dominant light in the environment.
sh
math::float3[9]
required
3-band spherical harmonics coefficients
return
math::float3
A unit vector representing the direction of the dominant light
math::float3 sh[9] = { /* ... */ };
math::float3 lightDir = IndirectLight::getDirectionEstimate(sh);
This assumes a single dominant light (e.g., sun). With multiple lights or low dynamic range environments, this may return unexpected results.

getColorEstimate (Static)

Helper to estimate the color and relative intensity of the environment in a given direction.
sh
math::float3[9]
required
3-band spherical harmonics coefficients
direction
math::float3
required
A unit vector representing the direction to estimate. Typically from getDirectionEstimate()
return
math::float4
First 3 components are linear color, 4th component is relative intensity
math::float3 sh[9] = { /* ... */ };
math::float3 lightDir = IndirectLight::getDirectionEstimate(sh);
math::float4 colorAndIntensity = IndirectLight::getColorEstimate(sh, lightDir);

// Use for directional light
float intensity = colorAndIntensity.w * ibl->getIntensity();
LightManager::Builder(LightManager::Type::DIRECTIONAL)
    .direction(lightDir)
    .color({colorAndIntensity.x, colorAndIntensity.y, colorAndIntensity.z})
    .intensity(intensity)
    .build(*engine, sunEntity);

getDirectionEstimate (Instance)

Helper to estimate the direction of the dominant light.
return
math::float3
A unit vector representing the direction of the dominant light
math::float3 lightDir = ibl->getDirectionEstimate();
Spherical harmonics must be set in the Builder or the result is undefined.

getColorEstimate (Instance)

Helper to estimate the color and relative intensity in a given direction.
direction
math::float3
required
A unit vector representing the direction to estimate
return
math::float4
First 3 components are linear color, 4th component is relative intensity
math::float3 lightDir = ibl->getDirectionEstimate();
math::float4 colorAndIntensity = ibl->getColorEstimate(lightDir);

Complete Example

#include <filament/Engine.h>
#include <filament/IndirectLight.h>
#include <filament/LightManager.h>
#include <filament/Texture.h>

using namespace filament;
using namespace math;

Engine* engine = Engine::create();

// Load cubemap texture (generated by cmgen)
Texture* reflectionMap = ...; // Your cubemap loading code

// Load spherical harmonics coefficients (from cmgen output)
float3 sh[9] = {
    {0.54f, 0.59f, 0.68f},   // L00
    {-0.09f, -0.09f, -0.10f}, // L1-1
    {0.35f, 0.36f, 0.38f},   // L10
    {-0.09f, -0.09f, -0.10f}, // L11
    {-0.05f, -0.05f, -0.06f}, // L2-2
    {-0.08f, -0.08f, -0.09f}, // L2-1
    {0.03f, 0.03f, 0.03f},   // L20
    {-0.08f, -0.08f, -0.09f}, // L21
    {-0.01f, -0.01f, -0.02f}  // L22
};

// Create IndirectLight
IndirectLight* ibl = IndirectLight::Builder()
    .reflections(reflectionMap)
    .irradiance(3, sh)
    .intensity(30000.0f)
    .build(*engine);

// Add to scene
scene->setIndirectLight(ibl);

// Create matching directional light
utils::Entity sunEntity = utils::EntityManager::get().create();
float3 sunDir = ibl->getDirectionEstimate();
float4 sunColor = ibl->getColorEstimate(sunDir);
float sunIntensity = sunColor.w * ibl->getIntensity();

LightManager::Builder(LightManager::Type::DIRECTIONAL)
    .direction(sunDir)
    .color({sunColor.x, sunColor.y, sunColor.z})
    .intensity(sunIntensity)
    .castShadows(true)
    .build(*engine, sunEntity);

// Don't forget to destroy when done
engine->destroy(ibl);

Using cmgen

The cmgen tool processes HDR environment maps and generates the data needed for IndirectLight:
# Generate IBL data from an HDR environment map
cmgen -x . --format=ktx --size=256 --extract-blur=0.1 environment.hdr

# This generates:
# - environment_ibl.ktx (reflection cubemap)
# - environment_skybox.ktx (skybox cubemap)
# - environment.txt (spherical harmonics coefficients)
Then in your code:
// Load the reflection cubemap
Texture* reflections = loadKtxTexture("environment_ibl.ktx");

// Parse spherical harmonics from .txt file
float3 sh[9];
loadSphericalHarmonics("environment.txt", sh);

// Create IndirectLight
IndirectLight* ibl = IndirectLight::Builder()
    .reflections(reflections)
    .irradiance(3, sh)
    .intensity(30000.0f)
    .build(*engine);

Best Practices

  1. Use cmgen to generate properly formatted IBL data from HDR images
  2. Match directional light to the environment using getDirectionEstimate() and getColorEstimate()
  3. Adjust intensity based on your scene’s lighting requirements (default 30000 lux is for outdoor scenes)
  4. Rotate the environment if needed to align with your scene’s lighting direction
  5. One IndirectLight per Scene - Currently only one is supported

Build docs developers (and LLMs) love