Skip to main content

Overview

Filament uses image-based lighting (IBL) for realistic environmental reflections and indirect lighting. Environment maps are typically stored as cubemaps and processed using the cmgen tool to generate the necessary data for physically-based rendering.

The cmgen Tool

cmgen is a command-line tool that processes environment maps and generates:
  • Prefiltered cubemap for specular reflections
  • Spherical harmonics for diffuse irradiance
  • DFG lookup tables for split-sum approximation
  • KTX container files for efficient loading

Basic Usage

# Process an equirectangular HDR image
cmgen -x . --format=ktx --size=256 --extract-blur=0.1 lightroom_14b.hdr

# This generates:
# - lightroom_14b_ibl.ktx (prefiltered cubemap)
# - lightroom_14b_skybox.ktx (blurred skybox)
# - lightroom_14b_sh.txt (spherical harmonics coefficients)

Common Options

cmgen [options] <input>

Input formats:
  .hdr, .exr          Equirectangular HDR images
  .png, .jpg          Equirectangular LDR images (converted to HDR)
  Directory           6 cubemap face images

Key options:
  -x <dir>            Output directory
  --format=<fmt>      Output format: ktx, png, rgbm, hdr, exr, dds
  --size=<size>       Output cubemap size (default: 256)
  --extract-blur=<b>  Skybox blur amount 0-1 (default: 0.0)
  --sh=<bands>        Spherical harmonics bands (default: 3)
  --no-mirror         Don't mirror the environment

Advanced Options

# Generate DFG lookup table
cmgen --ibl-dfg=dfg.ktx --dfg-multiscatter --dfg-cloth

# Custom mipmap levels
cmgen --size=512 --deploy=./ibl --extract=./skybox \
      --extract-blur=0.1 environment.hdr

# Irradiance only (for baked lighting)
cmgen --sh=3 --sh-irradiance --format=text environment.hdr

Loading IBL in Filament

#include <filamentapp/IBL.h>

// Load IBL from directory containing cmgen output
auto ibl = FilamentApp::get().getIBL();
if (ibl) {
    view->setIndirectLight(ibl->getIndirectLight());
    scene->setSkybox(ibl->getSkybox());
}

Manual Loading

#include <filament/IndirectLight.h>
#include <filament/Skybox.h>
#include <image/Ktx1Bundle.h>

// Load KTX cubemap
std::ifstream iblStream("ibl.ktx", std::ios::binary);
std::vector<uint8_t> iblData(
    (std::istreambuf_iterator<char>(iblStream)),
    std::istreambuf_iterator<char>());

image::Ktx1Bundle iblBundle(iblData.data(), iblData.size());

// Create cubemap texture
Texture* iblTexture = Texture::Builder()
    .width(iblBundle.info().pixelWidth)
    .height(iblBundle.info().pixelHeight)
    .levels(iblBundle.getNumMipLevels())
    .format(Texture::InternalFormat::RGB16F)
    .sampler(Texture::Sampler::SAMPLER_CUBEMAP)
    .build(*engine);

// Upload mipmap levels
for (uint32_t level = 0; level < iblBundle.getNumMipLevels(); level++) {
    for (uint32_t face = 0; face < 6; face++) {
        auto [data, size] = iblBundle.getBlob({level, 0, face});
        Texture::PixelBufferDescriptor buffer(
            data, size,
            Texture::Format::RGB,
            Texture::Type::HALF);
        
        iblTexture->setImage(*engine, level,
            0, 0, face,
            iblTexture->getWidth(level),
            iblTexture->getHeight(level), 1,
            std::move(buffer));
    }
}

// Create indirect light
IndirectLight* ibl = IndirectLight::Builder()
    .reflections(iblTexture)
    .intensity(30000.0f)  // Adjust based on your scene
    .build(*engine);

view->setIndirectLight(ibl);

Spherical Harmonics

Load spherical harmonics for diffuse irradiance:
// Read SH coefficients from cmgen output
std::ifstream shFile("environment_sh.txt");
float3 shCoefficients[9];
for (int i = 0; i < 9; i++) {
    shFile >> shCoefficients[i].r 
           >> shCoefficients[i].g 
           >> shCoefficients[i].b;
}

// Apply to indirect light
IndirectLight* ibl = IndirectLight::Builder()
    .reflections(iblTexture)
    .irradiance(3, shCoefficients)  // 3 bands = 9 coefficients
    .intensity(30000.0f)
    .build(*engine);

Creating a Skybox

// Load skybox texture (often same as IBL or blurred version)
Texture* skyboxTexture = loadKtxCubemap("skybox.ktx");

Skybox* skybox = Skybox::Builder()
    .environment(skyboxTexture)
    .build(*engine);

scene->setSkybox(skybox);

Intensity and Exposure

Adjust IBL brightness:
// Set IBL intensity (in lux for outdoor, varies for indoor)
IndirectLight* ibl = IndirectLight::Builder()
    .reflections(iblTexture)
    .intensity(30000.0f)  // Bright outdoor: 20000-50000
    .build(*engine);

// Or use camera exposure
Camera& camera = view->getCamera();
camera.setExposure(16.0f, 1.0f / 125.0f, 100.0f);  // f/16, 1/125s, ISO 100

Custom Environment Setup

From the gltf_viewer sample:
auto setupIBL = [&app]() {
    auto ibl = FilamentApp::get().getIBL();
    if (ibl) {
        app.viewer->setIndirectLight(
            ibl->getIndirectLight(),
            ibl->getSphericalHarmonics());
        
        // Optional: Use IBL texture for fog
        app.viewer->getSettings().view.fogSettings.fogColorTexture = 
            ibl->getFogTexture();
    }
};

// Load custom IBL
app.config.iblDirectory = "assets/ibl/lightroom_14b";

Environment Map Workflow

1. Capture or Find HDR Environment

  • Photograph with 360° camera
  • Use HDR panoramas from HDRI Haven, Poly Haven, etc.
  • Create synthetic environments in 3D software

2. Process with cmgen

# Basic processing
cmgen -x ./output \
      --format=ktx \
      --size=256 \
      --extract-blur=0.1 \
      environment.hdr

# High quality
cmgen -x ./output \
      --format=ktx \
      --size=512 \
      --extract-blur=0.05 \
      --sh=3 \
      environment.exr

3. Load in Filament

// Use generated files
Texture* iblTexture = loadKtx("output/environment_ibl.ktx");
Texture* skyboxTexture = loadKtx("output/environment_skybox.ktx");
float3 sh[9] = loadSphericalHarmonics("output/environment_sh.txt");

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

Skybox* skybox = Skybox::Builder()
    .environment(skyboxTexture)
    .build(*engine);

view->setIndirectLight(ibl);
scene->setSkybox(skybox);

Multiple Environments

Switch between different IBL setups:
struct Environment {
    IndirectLight* ibl;
    Skybox* skybox;
    float intensity;
};

std::vector<Environment> environments = {
    loadEnvironment("studio"),
    loadEnvironment("outdoor"),
    loadEnvironment("night")
};

// Switch environment
void setEnvironment(int index) {
    auto& env = environments[index];
    view->setIndirectLight(env.ibl);
    scene->setSkybox(env.skybox);
    // Adjust other settings as needed
}

Performance Considerations

  1. Resolution: 256x256 is good for most scenes, 512x512 for high-quality
  2. Format: KTX with RGB16F for HDR, RGBM for LDR compatibility
  3. Mipmaps: cmgen generates optimal mipmap chain for specular
  4. Sharing: Reuse IBL textures across multiple views/scenes

Common IBL Settings

Scene TypeIntensity (lux)SizeBlur
Indoor studio5,000 - 10,0002560.1
Outdoor day20,000 - 50,000256-5120.05
Outdoor overcast10,000 - 20,0002560.1
Night scene500 - 2,000128-2560.0
Product render15,000 - 30,0005120.05

Debugging IBL

// Visualize reflection probes
auto& debug = engine->getDebugRegistry();
bool showProbes = false;
debug.setProperty("d.lighting.debug_ibl", showProbes);

// Check SH coefficients
for (int i = 0; i < 9; i++) {
    std::cout << "SH[" << i << "] = " 
              << sh[i].r << ", " << sh[i].g << ", " << sh[i].b 
              << std::endl;
}

See Also

Build docs developers (and LLMs) love