Skip to main content
This tutorial demonstrates how to create a physically-based rendering (PBR) example with Filament. You’ll learn how to set up PBR materials using the metallic-roughness workflow and configure image-based lighting (IBL) for realistic environment reflections.

Overview

In this tutorial, you’ll learn how to:
  • Create PBR materials with metallic-roughness parameters
  • Load and display 3D meshes
  • Set up image-based lighting (IBL)
  • Configure directional lights
  • Apply material parameters for realistic rendering

Complete Source Code

The complete example is available in samples/hellopbr.cpp.
1

Define the Application Structure

Set up the basic application structure:
struct App {
    Config config;
    utils::Entity light;
    Material* material;
    MaterialInstance* materialInstance;
    MeshReader::Mesh mesh;
    mat4f transform;
};

static const char* IBL_FOLDER = "assets/ibl/lightroom_14b";
The IBL folder contains precomputed environment maps generated by the cmgen tool.
2

Configure Application Settings

Set up the application with IBL support:
int main(int argc, char** argv) {
    App app;
    app.config.title = "hellopbr";
    app.config.iblDirectory = FilamentApp::getRootAssetsPath() + IBL_FOLDER;
    
    FilamentApp::get().run(app.config, setup, cleanup);
    return 0;
}
Setting config.iblDirectory enables FilamentApp to automatically load and configure IBL.
3

Create PBR Material

Build a PBR material with metallic-roughness parameters:
app.material = Material::Builder()
    .package(RESOURCES_AIDEFAULTMAT_DATA, RESOURCES_AIDEFAULTMAT_SIZE)
    .build(*engine);
This loads a pre-compiled PBR material. The material definition (aiDefaultMat.mat) specifies:
material {
    name : AssimpDefaultMat,
    shadingModel : lit,
    parameters : [
        { type : float3, name : baseColor },
        { type : float, name : metallic },
        { type : float, name : roughness },
        { type : float, name : reflectance }
    ],
}
4

Create Material Instance and Set Parameters

Create a material instance and configure PBR properties:
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);
Parameter meanings:
  • baseColor: Surface color in linear RGB space (0.8 = light gray)
  • metallic: 1.0 = fully metallic, 0.0 = non-metallic (dielectric)
  • roughness: 0.0 = mirror-like, 1.0 = completely rough
  • reflectance: Controls reflections for dielectrics (0.5 = 4% reflectance)
5

Load 3D Mesh

Load a mesh from embedded binary data:
app.mesh = MeshReader::loadMeshFromBuffer(engine, 
    MONKEY_SUZANNE_DATA, nullptr, nullptr, mi);
The MeshReader utility:
  • Parses the mesh format
  • Creates vertex and index buffers
  • Builds renderable entities
  • Applies the material instance
6

Position the Mesh

Set up the initial transform:
auto& tcm = engine->getTransformManager();
auto ti = tcm.getInstance(app.mesh.renderable);
app.transform = mat4f{ mat3f(1), float3(0, 0, -4) } * tcm.getWorldTransform(ti);
rcm.setCastShadows(rcm.getInstance(app.mesh.renderable), false);
scene->addEntity(app.mesh.renderable);
This:
  • Positions the mesh at Z = -4 (4 units away from camera)
  • Stores the base transform for animation
  • Disables shadow casting
  • Adds the entity to the scene
7

Create Directional Light

Add a sun light for direct illumination:
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(false)
        .build(*engine, app.light);
scene->addEntity(app.light);
Light properties:
  • SUN: Directional light with parallel rays
  • color: Warm white color (converted from sRGB to linear)
  • intensity: 110,000 lux (bright daylight)
  • direction: Light direction vector (not normalized)
  • sunAngularRadius: 1.9 degrees (realistic sun size)
8

Animate the Mesh

Add rotation animation:
FilamentApp::get().animate([&app](Engine* engine, View* view, double now) {
    auto& tcm = engine->getTransformManager();
    auto ti = tcm.getInstance(app.mesh.renderable);
    tcm.setTransform(ti, app.transform * mat4f::rotation(now, float3{ 0, 1, 0 }));
});
This rotates the mesh around the Y-axis (vertical), showing how light interacts with the surface from different angles.
9

Cleanup Resources

Properly destroy all allocated resources:
auto cleanup = [&app](Engine* engine, View*, Scene*) {
    engine->destroy(app.light);
    engine->destroy(app.mesh.renderable);
    engine->destroy(app.materialInstance);
    engine->destroy(app.material);
};

Understanding PBR Parameters

Base Color

The base color represents:
  • For metals: The color of reflected light (e.g., gold = yellow-orange)
  • For dielectrics: The diffuse surface color
Always use linear RGB values, not sRGB.

Metallic

Controls whether the surface behaves as:
  • 1.0: Metal (reflective, no diffuse)
  • 0.0: Dielectric/non-metal (has diffuse component)
  • Values between 0-1 create blended behavior

Roughness

Controls microsurface detail:
  • 0.0: Perfect mirror reflections
  • 0.5: Moderate roughness (brushed metal)
  • 1.0: Completely diffuse reflections

Reflectance

For dielectrics only, controls the amount of specular reflection at normal incidence:
  • 0.5: Common dielectrics (~4% reflectance)
  • 0.35: Water (~2% reflectance)
  • 0.7: Glass (~8% reflectance)

Image-Based Lighting

Generating IBL

Use the cmgen tool to generate IBL from an environment map:
# From an HDR equirectangular image
cmgen -x ./ibl_output --format=ktx --size=256 --extract-blur=0.1 environment.hdr
This generates:
  • ibl_output_ibl.ktx: Prefiltered environment map
  • ibl_output_skybox.ktx: Skybox cubemap
  • Spherical harmonics coefficients

IBL Components

FilamentApp automatically loads:
  • Indirect Light: Prefiltered environment for reflections
  • Skybox: Background environment
  • Spherical Harmonics: Efficient diffuse irradiance

Light Types

Filament supports several light types:
// Directional/Sun light
LightManager::Builder(LightManager::Type::SUN)
    .direction({0, -1, 0})
    .intensity(100000)
    .build(*engine, entity);

// Point light
LightManager::Builder(LightManager::Type::POINT)
    .position({0, 2, 0})
    .intensity(100000)
    .falloff(10.0f)
    .build(*engine, entity);

// Spot light
LightManager::Builder(LightManager::Type::SPOT)
    .position({0, 2, 0})
    .direction({0, -1, 0})
    .intensity(100000)
    .falloff(10.0f)
    .spotLightCone(15.0f, 20.0f)  // inner, outer angles
    .build(*engine, entity);

Color Spaces

Filament uses linear color space for lighting calculations:
// Convert sRGB to linear
float3 linearColor = Color::toLinear<ACCURATE>(sRGBColor(0.8f, 0.5f, 0.2f));

// Set material parameter with color type
mi->setParameter("baseColor", RgbType::LINEAR, linearColor);

Building and Running

# Build the sample
cmake --build . --target hellopbr

# Run with default IBL
./hellopbr

# Run with custom IBL
./hellopbr --ibl=path/to/ibl/

Next Steps

Build docs developers (and LLMs) love