Skip to main content

Overview

Filament’s material system provides a flexible way to define surface appearance. Materials are compiled offline and used at runtime to create material instances that can be assigned to renderables.

Material Architecture

Material vs Material Instance

A Material is a template compiled from shader code:
  • Created from binary packages (compiled by matc)
  • Defines shading model and parameters
  • Immutable at runtime
  • Shared across multiple instances
Material.h:54-60
/**
 * A Material defines the visual appearance of a surface.
 *
 * Materials are created from a binary blob generated by the material compiler (matc).
 * A Material is a template from which MaterialInstance objects can be created.
 */
Think of Material as a class and MaterialInstance as an object instantiated from that class.

Creating Materials

From Compiled Package

Materials must be built from pre-compiled binary packages:
hellotriangle.cpp:145-147
app.mat = Material::Builder()
        .package(RESOURCES_BAKEDCOLOR_DATA, RESOURCES_BAKEDCOLOR_SIZE)
        .build(*engine);

Material Builder Options

The Material::Builder provides several configuration options:
Material* material = Material::Builder()
    .package(packageData, packageSize)
    .build(*engine);

Material Properties

Materials expose numerous read-only properties:

Shading Properties

Material.h:286-310
Shading getShading() const noexcept;                    // Lit, unlit, subsurface, etc.
Interpolation getInterpolation() const noexcept;        // Smooth or flat shading
BlendingMode getBlendingMode() const noexcept;          // Opaque, transparent, add, etc.
VertexDomain getVertexDomain() const noexcept;          // Object, world, view, device
MaterialDomain getMaterialDomain() const noexcept;      // Surface, post-process, compute
CullingMode getCullingMode() const noexcept;            // None, front, back
TransparencyMode getTransparencyMode() const noexcept;  // Default or two-pass

Rendering States

Material.h:312-328
bool isColorWriteEnabled() const noexcept;
bool isDepthWriteEnabled() const noexcept;
bool isDepthCullingEnabled() const noexcept;
bool isDoubleSided() const noexcept;
bool isAlphaToCoverageEnabled() const noexcept;
bool hasShadowMultiplier() const noexcept;
bool hasSpecularAntiAliasing() const noexcept;

float getMaskThreshold() const noexcept;
float getSpecularAntiAliasingVariance() const noexcept;
float getSpecularAntiAliasingThreshold() const noexcept;

Required Attributes

Query which vertex attributes the material needs:
Material.h:343-344
AttributeBitset getRequiredAttributes() const noexcept;

Working with Material Instances

Creating Instances

hellopbr.cpp:117-123
app.material = Material::Builder()
    .package(RESOURCES_AIDEFAULTMAT_DATA, RESOURCES_AIDEFAULTMAT_SIZE).build(*engine);
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);

Default Instance

Every material has a default instance:
Material.h:452-456
MaterialInstance* getDefaultInstance() noexcept;
MaterialInstance const* getDefaultInstance() const noexcept;
You can set parameters on the default instance:
Material.h:400-410
template <typename T>
void setDefaultParameter(const char* name, T value) noexcept {
    getDefaultInstance()->setParameter(name, value);
}

Assigning to Renderables

Material instances are assigned during renderable creation:
hellotriangle.cpp:149-152
RenderableManager::Builder(1)
        .boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }})
        .material(0, app.mat->getDefaultInstance())
        .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, app.vb, app.ib, 0, 3)

Material Parameters

Parameter Types

Materials support various parameter types:
Material.h:94-115
struct ParameterInfo {
    const char* name;           // Parameter name
    bool isSampler;             // Is it a texture?
    bool isSubpass;             // Is it a subpass?
    union {
        ParameterType type;     // Scalar/vector type
        SamplerType samplerType; // Texture type
        SubpassType subpassType; // Subpass type
    };
    uint32_t count;             // Array size
    Precision precision;        // low, medium, high
};

Querying Parameters

Material.h:358-376
size_t getParameterCount() const noexcept;

size_t getParameters(ParameterInfo* parameters, size_t count) const noexcept;

bool hasParameter(const char* name) const noexcept;

bool isSampler(const char* name) const noexcept;

Setting Parameters

Common parameter types:
materialInstance->setParameter("roughness", 0.5f);
materialInstance->setParameter("metallic", 1.0f);

Material Compilation

Asynchronous Compilation

Materials can be pre-compiled asynchronously to avoid runtime stutters:
Material.h:216-255
void compile(CompilerPriorityQueue priority,
        UserVariantFilterMask variants,
        backend::CallbackHandler* handler = nullptr,
        utils::Invocable<void(Material*)>&& callback = {}) noexcept;
Usage:
material->compile(
    Material::CompilerPriorityQueue::HIGH,
    UserVariantFilterBit::DIRECTIONAL_LIGHTING | UserVariantFilterBit::DYNAMIC_LIGHTING,
    nullptr,
    [](Material* m) {
        // Material variants are now compiled
    }
);

engine->flush();  // Start compilation work
Only compile the variants your application actually needs. Compiling all variants can be expensive.

Variant Filtering

From Material.md.html:241-244:
UserVariantFilterMask::ALL should be used with caution. Only variants that an application needs should be included in the variants argument. For example, the STE variant is only used for stereoscopic rendering.

Material Domains

Materials can serve different purposes:
  • Surface - Standard 3D surfaces (most common)
  • Post-process - Screen-space effects
  • Compute - Compute shader materials

Advanced Features

UBO Batching

Control uniform buffer object batching:
Material.h:82-91
enum class UboBatchingMode {
    DEFAULT,   // Follow engine settings
    DISABLED,  // Disable for this material
};
Set in builder:
Material::Builder()
    .package(data, size)
    .uboBatching(Material::UboBatchingMode::DISABLED)
    .build(*engine);

Constant Specialization

Specialize material constants at build time:
Material.h:147-167
Builder& constant(const char* name, size_t nameLength, int32_t value);
Builder& constant(const char* name, size_t nameLength, float value);
Builder& constant(const char* name, size_t nameLength, bool value);

// Inline helper
Builder& constant(const char* name, T value) {
    return constant(name, strlen(name), value);
}
Constants are “baked in” and cannot change after the material is built.

Reflection and Refraction

Material.h:346-353
RefractionMode getRefractionMode() const noexcept;
RefractionType getRefractionType() const noexcept;
ReflectionMode getReflectionMode() const noexcept;

Material Lifecycle

Creation

// 1. Build material from package
Material* material = Material::Builder()
    .package(data, size)
    .build(*engine);

// 2. Create instance(s)
MaterialInstance* instance1 = material->createInstance("instance1");
MaterialInstance* instance2 = material->createInstance("instance2");

Usage

// 3. Configure instances
instance1->setParameter("baseColor", RgbType::LINEAR, float3{1, 0, 0});
instance2->setParameter("baseColor", RgbType::LINEAR, float3{0, 1, 0});

// 4. Assign to renderables
RenderableManager::Builder(1)
    .material(0, instance1)
    .build(engine, entity1);

Destruction

All MaterialInstances must be destroyed before destroying their parent Material.
// 1. Destroy instances first
engine->destroy(instance1);
engine->destroy(instance2);

// 2. Then destroy material
engine->destroy(material);

Common Material Workflows

PBR Material Setup

MaterialInstance* mi = material->createInstance();

// Set PBR parameters
mi->setParameter("baseColor", RgbType::LINEAR, float3{0.8f, 0.1f, 0.1f});
mi->setParameter("metallic", 0.0f);      // Non-metallic
mi->setParameter("roughness", 0.5f);     // Medium roughness
mi->setParameter("reflectance", 0.5f);   // Standard 4% reflectance

// Optional: Set textures
mi->setParameter("baseColorMap", albedoTexture, sampler);
mi->setParameter("normalMap", normalTexture, sampler);
mi->setParameter("metallicRoughnessMap", mrTexture, sampler);

Metallic Material

MaterialInstance* mi = material->createInstance();

// Gold material
mi->setParameter("baseColor", RgbType::sRGB, float3{1.0f, 0.85f, 0.57f});
mi->setParameter("metallic", 1.0f);      // Fully metallic
mi->setParameter("roughness", 0.3f);     // Slightly rough
// Note: reflectance is ignored for metals

Transparent Material

MaterialInstance* mi = material->createInstance();

// Glass-like material (material must be compiled with transparent blending)
mi->setParameter("baseColor", RgbType::LINEAR, float4{1.0f, 1.0f, 1.0f, 0.3f});
mi->setParameter("metallic", 0.0f);
mi->setParameter("roughness", 0.0f);     // Very smooth
mi->setParameter("reflectance", 0.5f);

Best Practices

Create one Material and multiple MaterialInstances rather than creating multiple Materials:
// Good: One material, many instances
Material* mat = Material::Builder().package(...).build(engine);
MaterialInstance* red = mat->createInstance();
MaterialInstance* blue = mat->createInstance();

// Bad: Creating separate materials for each color
Material* redMat = Material::Builder().package(...).build(engine);
Material* blueMat = Material::Builder().package(...).build(engine);
Use asynchronous compilation during loading screens:
material->compile(HIGH, neededVariants, nullptr, [](Material* m) {
    // Material ready
});
engine->flush();
Before setting parameters:
if (material->hasParameter("roughness")) {
    instance->setParameter("roughness", 0.5f);
}
Use meaningful names for debugging:
MaterialInstance* mi = material->createInstance("car_paint_red");
Check validity before use:
if (engine->isValid(material, instance)) {
    // Safe to use
}

Debugging Materials

Get Material Information

const char* name = material->getName();
Material::Shading shading = material->getShading();
size_t paramCount = material->getParameterCount();

// Get all parameters
std::vector<Material::ParameterInfo> params(paramCount);
material->getParameters(params.data(), paramCount);

for (const auto& param : params) {
    std::cout << "Parameter: " << param.name;
    if (param.isSampler) {
        std::cout << " (texture)";
    }
    std::cout << std::endl;
}

Get Material Source

For debugging, you can retrieve the original material definition:
Material.h:382-386
std::string_view getSource() const noexcept;

Performance Considerations

Material Switches

Minimize material switches per frame. Filament optimizes rendering by batching objects with the same material.

Parameter Updates

Updating material parameters is relatively cheap, but avoid doing it every frame for many instances.

Texture Bindings

Texture parameter updates can be more expensive than scalar updates due to binding overhead.

Variant Compilation

Pre-compile needed variants during load time to avoid runtime shader compilation stutters.

PBR Theory

Understand the physics behind material parameters

Entity-Component System

Learn how materials are assigned to renderables

Build docs developers (and LLMs) love