Skip to main content

Lighting & Shadows

Filament’s lighting system supports physically-based lighting with image-based lighting (IBL), punctual lights, and realistic shadow rendering.

Shadow Rendering

The shadowtest.cpp sample demonstrates directional light shadows with ground plane receivers.

Key Features

  • Directional (sun) light with shadows
  • Shadow casting and receiving configuration
  • Ground plane shadow receiver
  • Sun angular radius for soft shadows

Setting Up Shadow Casting Light

app.light = em.create();
LightManager::Builder(LightManager::Type::SUN)
    .color(Color::toLinear<ACCURATE>(sRGBColor(0.98f, 0.92f, 0.89f)))
    .intensity(110000)
    .direction({ 0.7, -1, -0.8 })
    .sunAngularRadius(1.9f)
    .castShadows(ENABLE_SHADOWS)
    .build(*engine, app.light);
scene->addEntity(app.light);
Source: samples/shadowtest.cpp:97-105 The sun angular radius controls the softness of shadow edges. Larger values create softer, more realistic shadows but may impact performance.

Configuring Shadow Receivers

for (auto renderable : app.meshes->getRenderables()) {
    auto instance = rcm.getInstance(renderable);
    if (rcm.hasComponent(renderable)) {
        rcm.setCastShadows(instance, ENABLE_SHADOWS);
        rcm.setReceiveShadows(instance, false);  // Model doesn't receive shadows
        scene->addEntity(renderable);
    }
}
Source: samples/shadowtest.cpp:87-94

Ground Plane Material

Material* shadowMaterial = Material::Builder()
    .package(RESOURCES_GROUNDSHADOW_DATA, RESOURCES_GROUNDSHADOW_SIZE)
    .build(*engine);
shadowMaterial->setDefaultParameter("strength", 0.7f);

RenderableManager::Builder(1)
    .boundingBox({{ 0, 0, 0 }, { 10, 1e-4f, 10 }})
    .material(0, shadowMaterial->getDefaultInstance())
    .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vertexBuffer, indexBuffer, 0, 6)
    .culling(false)
    .receiveShadows(ENABLE_SHADOWS)
    .castShadows(false)
    .build(*engine, renderable);
Source: samples/shadowtest.cpp:139-181 The ground plane uses a special shadow material that composites shadows onto a simple quad, creating realistic contact shadows.

PBR Lighting

The hellopbr.cpp and sample_full_pbr.cpp samples demonstrate physically-based rendering with IBL and direct lighting.

Basic PBR Setup

auto mi = app.materialInstance = app.material->createInstance();
mi->setParameter("baseColor", RgbType::LINEAR, float3{0.8});
mi->setParameter("metallic", 1.0f);
mi->setParameter("roughness", 0.4f);
mi->setParameter("reflectance", 0.5f);
Source: samples/hellopbr.cpp:119-123

Sun Light Configuration

LightManager::Builder(LightManager::Type::SUN)
    .color(Color::toLinear<ACCURATE>(sRGBColor(0.98f, 0.92f, 0.89f)))
    .intensity(110000)  // Lux
    .direction({ 0.7, -1, -0.8 })
    .sunAngularRadius(1.9f)  // Degrees
    .castShadows(false)
    .build(*engine, app.light);
Source: samples/hellopbr.cpp:134-140

Light Parameters

  • Color: Linear RGB color, use Color::toLinear<ACCURATE>() for sRGB input
  • Intensity: In lux for directional lights (typical sun: 110,000 lux)
  • Direction: Normalized direction vector pointing from the light
  • Sun Angular Radius: Angular size in degrees (sun ~0.545°, larger for softer shadows)

Advanced PBR with Texture Maps

The sample_full_pbr.cpp demonstrates a complete PBR workflow with multiple texture maps.

Supported Texture Maps

constexpr int MAP_COLOR       = 0;  // Base color (sRGB)
constexpr int MAP_AO          = 1;  // Ambient occlusion (linear)
constexpr int MAP_ROUGHNESS   = 2;  // Roughness (linear)
constexpr int MAP_METALLIC    = 3;  // Metallic (linear)
constexpr int MAP_NORMAL      = 4;  // Normal map (linear)
constexpr int MAP_BENT_NORMAL = 5;  // Bent normals for AO (linear)
constexpr int MAP_HEIGHT      = 6;  // Height map for parallax (linear)
Source: samples/sample_full_pbr.cpp:65-72

Dynamic Material Generation

The sample dynamically generates a material shader based on available textures:
MaterialBuilder builder;
builder
    .name("DefaultMaterial")
    .material(shader.c_str())
    .multiBounceAmbientOcclusion(true)
    .specularAmbientOcclusion(MaterialBuilder::SpecularAmbientOcclusion::BENT_NORMALS)
    .shading(Shading::LIT);

if (hasUV) {
    builder.require(VertexAttribute::UV0);
}

for (auto& map: g_maps) {
    if (map.texture != nullptr) {
        builder.parameter(map.parameterName, MaterialBuilder::SamplerType::SAMPLER_2D);
    }
}
Source: samples/sample_full_pbr.cpp:384-406

Parallax Occlusion Mapping

When a height map is present, the sample implements parallax occlusion mapping:
vec3 v = tangentFromWorld * getWorldViewVector();

float minLayers = 8.0;
float maxLayers = 48.0;
float numLayers = mix(maxLayers, minLayers,
        dot(getWorldGeometricNormalVector(), getWorldViewVector()));
float heightScale = 0.05;

float layerDepth = 1.0 / numLayers;
vec2 deltaUV = v.xy * heightScale / (v.z * numLayers);
vec2 currUV = uv0;
float height = 1.0 - textureGrad(materialParams_heightMap, currUV, uvDx, uvDy).r;

for (int i = 0; i < int(numLayers); i++) {
    currLayerDepth += layerDepth;
    currUV -= deltaUV;
    height = 1.0 - textureGrad(materialParams_heightMap, currUV, uvDx, uvDy).r;
    if (height < currLayerDepth) break;
}
Source: samples/sample_full_pbr.cpp:280-308

Image-Based Lighting (IBL)

IBL is configured through the FilamentApp configuration:
Config config;
config.iblDirectory = FilamentApp::getRootAssetsPath() + "assets/ibl/lightroom_14b";
Screenshot Description: A reflective metallic sphere showing IBL reflections from a lightroom environment, with warm indirect lighting illuminating the diffuse surfaces.

IBL Best Practices

  1. Generate with cmgen: Use Filament’s cmgen tool to preprocess HDR environment maps
  2. Resolution: 256x256 or 512x512 is sufficient for most use cases
  3. Intensity: Adjust IBL intensity to balance with direct lighting
  4. Rotation: Rotate IBL to align with scene lighting direction

Ambient Occlusion

AO can be baked into textures or calculated with SSAO:
view->setAmbientOcclusionOptions({
    .radius = 0.01f,
    .bilateralThreshold = 0.005f,
    .quality = View::QualityLevel::ULTRA,
    .lowPassFilter = View::QualityLevel::MEDIUM,
    .upsampling = View::QualityLevel::HIGH,
    .enabled = true
});
Source: samples/sample_full_pbr.cpp:243-250

Multi-Bounce Ambient Occlusion

For more realistic results, enable multi-bounce AO:
builder.multiBounceAmbientOcclusion(true)
       .specularAmbientOcclusion(MaterialBuilder::SpecularAmbientOcclusion::BENT_NORMALS);
Source: samples/sample_full_pbr.cpp:394-395 Bent normal maps improve specular AO by providing the least-occluded direction for each pixel.

Light Types & Intensities

Light TypeUnitTypical Range
SUN (directional)lux100,000 - 120,000
Point/Spotlumens100 - 10,000
Indoor ambientlux100 - 1,000
  • lightbulb.cpp - Point light demonstration
  • gltf_viewer.cpp - Complete lighting pipeline with GUI controls
  • material_sandbox.cpp - Interactive lighting and material editor

Build docs developers (and LLMs) love