Skip to main content
Filament uses physically-based rendering (PBR) to create realistic materials that respond accurately to light. This page explains the theory and implementation of Filament’s shading models.

What is PBR?

Physically-Based Rendering (PBR) is an approach to rendering that models how light interacts with surfaces based on real-world physics. PBR materials:
  • Respond realistically to different lighting conditions
  • Are portable across different engines and tools
  • Produce consistent results with measurable parameters
  • Conserve energy (don’t reflect more light than received)

Standard Lit Shading Model

The lit model is based on a microfacet BRDF (Bidirectional Reflectance Distribution Function) that models surfaces as collections of tiny mirrors.

Dielectrics vs. Conductors

All materials fall into two categories: Dielectrics (Non-metals):
  • Have chromatic diffuse reflection
  • Have achromatic specular reflection (white/gray)
  • Examples: plastic, wood, stone, fabric
Conductors (Metals):
  • Have no diffuse reflection
  • Have chromatic specular reflection (colored)
  • Examples: iron, gold, copper, aluminum
The metallic property controls this:
// Plastic (dielectric)
material.baseColor = vec3(1.0, 0.0, 0.0); // Red diffuse
material.metallic = 0.0; // White specular highlights

// Gold (conductor)
material.baseColor = vec3(1.0, 0.85, 0.57); // Gold specular
material.metallic = 1.0; // No diffuse

Energy Conservation

Filament’s shading models ensure energy conservation:
reflected_light = diffuse_component + specular_component <= incident_light
As metallic increases:
  • Diffuse contribution decreases
  • Specular contribution increases
  • Total energy remains conserved

The BRDF Components

The lit model combines several components: Diffuse Term:
  • Uses Lambertian diffuse with Disney diffuse for improved edge behavior
  • Controlled by baseColor (for dielectrics)
  • Modulated by (1 - metallic)
Specular Term:
  • Uses GGX (Trowbridge-Reitz) microfacet distribution
  • Controlled by roughness
  • Fresnel term uses Schlick approximation
Combined BRDF:
BRDF = (1 - metallic) * diffuse + specular

Roughness and Specular Highlights

Roughness controls how micro-facets are oriented: Smooth surfaces (roughness = 0):
  • Micro-facets aligned with surface
  • Sharp, mirror-like reflections
  • Small, bright specular highlights
Rough surfaces (roughness = 1):
  • Randomly oriented micro-facets
  • Blurred, diffuse reflections
  • Large, dim specular highlights
// Mirror-like
material.roughness = 0.0;

// Matte
material.roughness = 1.0;

// Typical values
material.roughness = 0.3; // Glossy plastic
material.roughness = 0.6; // Semi-rough metal

Perceptual Roughness

Filament uses perceptual roughness, which is more intuitive:
perceptual_roughness = user_input (0..1)
alpha = perceptual_roughness²
This provides better distribution of roughness values in textures and sliders.

Fresnel Effect

The Fresnel effect describes how reflectance changes with viewing angle:
  • At grazing angles (near 90°), all surfaces become highly reflective
  • At normal incidence (0°), reflectance depends on material
// Fresnel is automatic in Filament
// Controlled by:
material.reflectance = 0.5; // For dielectrics
material.metallic = 1.0; // For metals
material.baseColor = goldColor; // Specular color for metals

F0 (Fresnel at Normal Incidence)

F0 is the reflectance at 0° viewing angle: For dielectrics:
F0 = 0.16 * reflectance² 
F0 = ((ior - 1) / (ior + 1))²
For metals:
F0 = baseColor (measured specular color)
Common F0 values:
  • Water: 0.02 (2%)
  • Plastic: 0.04 (4%)
  • Glass: 0.04-0.05 (4-5%)
  • Diamond: 0.17 (17%)

Clear Coat Layer

The clear coat model adds a second specular lobe for multi-layer materials:
final_color = base_layer + clear_coat_layer
The clear coat layer:
  • Is always isotropic (not anisotropic)
  • Is always dielectric (IOR = 1.5)
  • Has independent roughness
  • Can have its own normal map
material.clearCoat = 1.0;
material.clearCoatRoughness = 0.1; // Glossy coat

// Optional: different normal for coat
vec3 coatNormal = texture(materialParams_clearCoatNormal, uv).xyz;
material.clearCoatNormal = coatNormal * 2.0 - 1.0;

Clear Coat IOR Change

When adding a clear coat, Filament accounts for the index of refraction change, which modifies the base layer’s appearance:
// This is handled automatically
// Can be disabled with clearCoatIorChange : false

Anisotropic Reflections

Anisotropic materials have directional micro-facets, like brushed metal:
material.anisotropy = 0.8; // Strength
material.anisotropyDirection = vec3(1.0, 0.0, 0.0); // Tangent direction
The anisotropic BRDF uses:
  • GGX distribution with anisotropic extension
  • Separate roughness along tangent and bitangent
  • Direction map to control highlight orientation
Tangent-space anisotropy:
// Positive = tangent direction
material.anisotropy = 0.8;

// Negative = bitangent direction  
material.anisotropy = -0.8;

Sheen Model

Sheen adds a soft lobe on top of the base layer, useful for fabrics:
material.sheenColor = vec3(0.5, 0.3, 0.1); // Color and intensity
material.sheenRoughness = 0.8; // Typically high for cloth
The sheen BRDF:
  • Uses Charlie sheen distribution (Estevez 2017)
  • Sits below clear coat if present
  • Energy-conserving with base layer

Subsurface Scattering Model

The subsurface model approximates light scattering beneath the surface:
material.thickness = 2.0; // Material thickness
material.subsurfacePower = 12.0; // Scattering falloff
material.subsurfaceColor = vec3(1.0, 0.8, 0.7); // Scattering color

Approximation Method

Filament uses a fast approximation:
  1. Wrap lighting: Light wraps around to back side
  2. Thickness-based attenuation: Uses thickness parameter
  3. Color scattering: Modulated by subsurfaceColor
Formula:
subsurface = subsurfaceColor * pow(saturate(wrapNdotL), subsurfacePower)
This is cheaper than full subsurface scattering but effective for many materials.

Cloth Shading Model

The cloth model is optimized for fibrous materials:

Key Differences from Lit Model

  1. Softer specular lobe - Better matches fabric appearance
  2. Forward/backward scattering - For velvet-like materials
  3. Two-tone specular - Via sheenColor
  4. No metallic - Fabrics are always dielectric
// Velvet-like
material.baseColor.rgb = vec3(0.1); // Dark base
material.sheenColor = vec3(0.8, 0.2, 0.3); // Bright rim

// Denim-like
material.baseColor.rgb = vec3(0.2, 0.3, 0.5);
material.sheenColor = vec3(luminance(baseColor)); // Natural

Ashikhmin-Shirley BRDF

Cloth uses a modified Ashikhmin-Shirley model:
  • Softer highlights than GGX
  • Better edge scattering
  • Optimized for cloth appearance

Image-Based Lighting (IBL)

Filament uses pre-filtered environment maps for efficient IBL:

Split-Sum Approximation

specular_IBL ≈ pre_filtered_environment × BRDF_LUT
Pre-filtered environment:
  • Stored in cubemap with mip levels
  • Each mip represents different roughness
  • Generated offline with cmgen tool
BRDF LUT:
  • 2D lookup table (NdotV, roughness)
  • Pre-computed Fresnel integral
  • Shared across all materials

Diffuse IBL

Diffuse IBL uses spherical harmonics:
// Automatic in Filament
// Controlled by indirect light intensity
Quality levels:
  • 1 band (1 SH coefficient) - Fastest
  • 2 bands (4 SH coefficients) - Good
  • 3 bands (9 SH coefficients) - Best (default)

Advanced Lighting Features

Specular Ambient Occlusion

AO for specular/reflections, derived from roughness:
material {
    specularAmbientOcclusion : simple // or bentNormals
}
Simple mode:
  • Fast approximation
  • Based on horizon occlusion
Bent normals mode:
  • More accurate
  • Requires bent normal map
  • Higher cost

Multi-bounce Ambient Occlusion

Accounts for multiple light bounces in occluded areas:
material {
    multiBounceAmbientOcclusion : true
}
Effect:
  • Reduces over-darkening
  • Adds color to AO (based on baseColor)
  • More realistic occlusion

Refraction

Filament supports screen-space and cubemap refraction:

Solid Refraction

material.ior = 1.5; // Glass
material.transmission = 1.0; // Fully transmissive
material.absorption = vec3(0.5, 0.8, 0.5); // Green tint
material.thickness = texture(materialParams_thickness, uv).r;

Thin Refraction

material.ior = 1.5;
material.transmission = 1.0;
material.microThickness = 0.001; // 1mm shell

Dispersion

Chromatic aberration for refractions:
material.dispersion = 0.36; // Diamond-like
Causes RGB channels to refract at different angles, creating rainbow effects.

Specular Anti-Aliasing

Reduces specular aliasing on glossy materials:
material {
    specularAntiAliasing : true,
    specularAntiAliasingVariance : 0.15,
    specularAntiAliasingThreshold : 0.2
}
How it works:
  • Analyzes normal map variance
  • Increases roughness where needed
  • Preserves highlight shape at distance

Custom Surface Shading

For advanced users, implement custom lighting:
material {
    customSurfaceShading : true
}

fragment {
    vec3 surfaceShading(
        const MaterialInputs materialInputs,
        const ShadingData shadingData,
        const LightData lightData
    ) {
        // Custom lighting implementation
        float NdotL = lightData.NdotL;
        vec3 diffuse = shadingData.diffuseColor * NdotL;
        return diffuse * lightData.colorIntensity.rgb;
    }
}
Provides full control over per-light shading calculations.

Performance Considerations

Material Complexity Costs

FeatureRelative Cost
Base lit model1x
+ Normal mapping1.1x
+ Clear coat2x
+ Anisotropy1.2x
+ Subsurface1.3x
+ Refraction (screen-space)2-3x

Optimization Tips

  1. Don’t set unused properties
    // Bad - costs even at 0
    material.clearCoat = 0.0;
    
    // Good - don't set at all
    // (just omit it)
    
  2. Use appropriate shading models
    • Cloth model for fabrics (not lit + sheen)
    • Unlit for UI elements
    • Subsurface only when needed
  3. Limit variants
    material {
        variantFilter : [skinning, ssr] // Skip unused variants
    }
    
  4. Optimize textures
    • Pack roughness/metallic/AO into single texture
    • Use appropriate precision (medium for mobile)

Validation and Debugging

Common Issues

Too dark:
  • Check ambient occlusion is in [0,1]
  • Verify IBL is set on scene
  • Ensure colors are in linear space
Wrong specular color:
  • For metals, specular = baseColor
  • For non-metals, check reflectance value
  • Verify metallic is 0 or 1 (not in-between)
Energy gain (too bright):
  • Check baseColor is in [0,1]
  • Verify emissive is reasonable (< 10,000 nits)
  • Don’t add to baseColor AND emissive

Further Reading

Build docs developers (and LLMs) love