Skip to main content
Filament provides advanced shadow mapping with multiple techniques and extensive customization options. Shadows add depth and realism to 3D scenes by simulating how objects block light.

Shadow Types

Filament supports several shadow mapping techniques, each with different quality and performance characteristics.

PCF (Percentage Closer Filtering)

The default shadow technique providing good quality at reasonable performance.
view->setShadowType(ShadowType::PCF);
Characteristics:
  • Fast and efficient
  • Soft shadow edges through filtering
  • Good for most use cases
  • Default shadow type

VSM (Variance Shadow Maps)

Pre-filtered shadow maps using variance to determine shadowing.
view->setShadowType(ShadowType::VSM);

// Configure VSM options
VsmShadowOptions vsmOptions;
vsmOptions.anisotropy = 2;              // 2^2 = 4 anisotropic samples
vsmOptions.mipmapping = true;           // Enable mipmaps
vsmOptions.msaaSamples = 4;             // MSAA sample count
vsmOptions.highPrecision = false;       // Use 16-bit (32-bit if true)
vsmOptions.lightBleedReduction = 0.15f; // Reduce light bleeding

view->setVsmShadowOptions(vsmOptions);
Characteristics:
  • Supports pre-filtering and blurring
  • Can produce soft shadows efficiently
  • May exhibit light bleeding artifacts
  • All shadow participants must be both casters and receivers
VSM-specific light options:
ShadowOptions shadowOpts;
shadowOpts.vsm.elvsm = true;      // Enable ELVSM to reduce light leaks
shadowOpts.vsm.blurWidth = 10.0f; // Blur width (0-125)

LightManager::Builder(LightManager::Type::SUN)
    .shadowOptions(shadowOpts)
    .build(*engine, sun);

DPCF (Directional PCF)

PCF with contact hardening simulation for more realistic soft shadows.
view->setShadowType(ShadowType::DPCF);

SoftShadowOptions softOptions;
softOptions.penumbraScale = 1.0f;       // Global penumbra scale
softOptions.penumbraRatioScale = 1.0f;  // Contact hardening strength

view->setSoftShadowOptions(softOptions);
Characteristics:
  • Simulates contact hardening
  • Shadows softer farther from contact point
  • Higher quality than basic PCF
  • Moderate performance cost

PCSS (Percentage Closer Soft Shadows)

Physically-based soft shadows with contact hardening.
view->setShadowType(ShadowType::PCSS);

SoftShadowOptions softOptions;
softOptions.penumbraScale = 1.0f;
softOptions.penumbraRatioScale = 1.5f;  // Artistic control

view->setSoftShadowOptions(softOptions);
Characteristics:
  • Most realistic soft shadows
  • Proper contact hardening
  • Highest performance cost
  • Recommended for high-quality rendering

Shadow Configuration

Basic Shadow Setup

Enable shadows on lights and configure shadow options:
ShadowOptions shadowOptions;
shadowOptions.mapSize = 2048;              // Shadow map resolution
shadowOptions.shadowCascades = 4;          // Number of cascades
shadowOptions.constantBias = 0.001f;       // Bias in world units
shadowOptions.normalBias = 1.0f;           // Normal offset scale
shadowOptions.shadowFar = 100.0f;          // Shadow distance
shadowOptions.shadowNearHint = 1.0f;       // Optimize from this distance
shadowOptions.shadowFarHint = 100.0f;      // Optimize up to this distance

LightManager::Builder(LightManager::Type::SUN)
    .castShadows(true)
    .shadowOptions(shadowOptions)
    .build(*engine, sun);

Cascaded Shadow Maps (CSM)

Directional lights support cascaded shadow maps to improve shadow quality across view distances:
shadowOptions.shadowCascades = 4;  // 1-4 cascades

// Uniform split scheme
LightManager::ShadowCascades::computeUniformSplits(
    shadowOptions.cascadeSplitPositions, 4);

// Logarithmic split scheme
LightManager::ShadowCascades::computeLogSplits(
    shadowOptions.cascadeSplitPositions, 4, nearPlane, farPlane);

// Practical split scheme (blend of uniform and log)
LightManager::ShadowCascades::computePracticalSplits(
    shadowOptions.cascadeSplitPositions, 4, nearPlane, farPlane, 0.5f);
Split positions:
  • For N cascades, need N-1 split positions
  • Values range from 0.0 (camera near) to 1.0 (camera far)
  • Default: for 4 cascades

Shadow Map Resolution

Higher resolution improves quality but uses more memory:
shadowOptions.mapSize = 1024;  // Low quality / mobile
shadowOptions.mapSize = 2048;  // Medium quality (default)
shadowOptions.mapSize = 4096;  // High quality / desktop
Shadow map size must be a power of 2 and at least 8 pixels. Each cascade uses the same resolution.

Shadow Bias

Bias parameters prevent shadow acne and peter-panning artifacts:
// Constant bias - move shadow in world units
shadowOptions.constantBias = 0.001f;  // 1mm (ignored for VSM)

// Normal bias - offset along surface normal  
shadowOptions.normalBias = 1.0f;      // Multiplier (ignored for VSM)

// Depth bias - prevent self-shadowing
shadowOptions.polygonOffsetConstant = 0.5f;  // Depth units
shadowOptions.polygonOffsetSlope = 2.0f;     // Slope-scaled units
VSM ignores constantBias, normalBias, and polygon offset parameters. Use VSM blur to reduce artifacts.

LiSPSM Optimization

Light-space Perspective Shadow Mapping improves resolution:
shadowOptions.lispsm = true;   // Enable (default)
shadowOptions.stable = false;  // Optimize for resolution vs stability
LiSPSM characteristics:
  • Greatly improves effective resolution
  • Similar quality to cascades without extra cost
  • Incompatible with large blur (VSM/PCSS)
  • Disable if blur artifacts occur

Screen-Space Contact Shadows

Enhance shadows with screen-space ray-marching:
shadowOptions.screenSpaceContactShadows = true;
shadowOptions.stepCount = 8;                  // Ray-marching steps
shadowOptions.maxShadowDistance = 0.3f;       // Max distance in world units
Characteristics:
  • Adds fine detail to shadow edges
  • Useful for large scenes
  • Works regardless of shadow casting settings
  • Moderate performance cost

Shadow Bulb Radius

Control soft shadow appearance:
shadowOptions.shadowBulbRadius = 0.02f;  // 2cm (default)
  • Only affects DPCF and PCSS
  • Simulates light source size
  • Larger values = softer shadows
  • Physically represents light bulb radius

Shadow Transform

Artistic control over shadow direction:
// Rotate shadow direction by 30 degrees around Y-axis
float angle = 30.0f * M_PI / 180.0f;
math::quatf rotation = math::quatf::fromAxisAngle({0, 1, 0}, angle);

shadowOptions.transform = rotation;  // Must be unit quaternion
Only applies to directional lights. Use with caution as it may produce unrealistic results.

Renderable Shadow Settings

Control which objects cast and receive shadows:
auto& rcm = engine->getRenderableManager();
auto instance = rcm.getInstance(entity);

// Enable shadow casting
rcm.setCastShadows(instance, true);

// Enable shadow receiving  
rcm.setReceiveShadows(instance, true);

View-Level Shadow Control

Enable or disable all shadows in a view:
// Disable all shadows
view->setShadowingEnabled(false);

// Re-enable shadows
view->setShadowingEnabled(true);

Updating Shadow Options

Shadow options can be updated at runtime:
auto& lcm = engine->getLightManager();
auto instance = lcm.getInstance(sun);

// Get current options
ShadowOptions opts = lcm.getShadowOptions(instance);

// Modify
opts.mapSize = 4096;
opts.shadowCascades = 3;

// Update
lcm.setShadowOptions(instance, opts);

Performance Tips

Optimize Shadow Map Size

  • Use smallest acceptable resolution
  • Mobile: 512-1024
  • Desktop: 2048-4096
  • Reduce for distant/less important lights

Cascade Configuration

  • More cascades = better quality but higher cost
  • Mobile: 1-2 cascades
  • Desktop: 2-4 cascades
  • Use practical split scheme for best quality

Shadow Distance

  • Set shadowFar to limit shadow rendering distance
  • Improves quality and performance
  • Match to visible scene distance

Shadow Hints

  • shadowNearHint: optimize quality from this distance
  • shadowFarHint: optimize quality up to this distance
  • Default: 1m to 100m

Light Configuration

  • Limit number of shadow-casting lights
  • Disable shadows for small/distant lights
  • Use castLight(false) for shadow-only lights

Example: High-Quality Shadows

ShadowOptions options;
options.mapSize = 4096;
options.shadowCascades = 4;
options.constantBias = 0.0005f;
options.normalBias = 1.0f;
options.shadowNearHint = 0.5f;
options.shadowFarHint = 150.0f;
options.lispsm = true;
options.stable = false;
options.screenSpaceContactShadows = true;
options.stepCount = 16;
options.shadowBulbRadius = 0.03f;

LightManager::ShadowCascades::computePracticalSplits(
    options.cascadeSplitPositions, 4, 0.1f, 100.0f, 0.5f);

view->setShadowType(ShadowType::PCSS);

SoftShadowOptions softOpts;
softOpts.penumbraScale = 1.2f;
softOpts.penumbraRatioScale = 1.5f;
view->setSoftShadowOptions(softOpts);

LightManager::Builder(LightManager::Type::SUN)
    .castShadows(true)
    .shadowOptions(options)
    .build(*engine, sun);

Next Steps

Lighting

Learn about light types and configuration

Post-Processing

Add bloom, DOF, and other effects

Build docs developers (and LLMs) love