Skip to main content
This tutorial shows how to create custom materials using Filament’s material definition language. You’ll learn the material syntax, how to define parameters, write shader code, and compile materials for use in your applications.

Overview

Filament materials are defined in .mat files using a domain-specific language that combines:
  • Material properties (shading model, blending, etc.)
  • Parameter definitions
  • Vertex and fragment shader code
The matc compiler transforms .mat files into binary material packages that can be loaded at runtime.

Material Structure

A material file has two main sections:
material {
    // Material properties and parameters
}

vertex {
    // Vertex shader (optional)
}

fragment {
    // Fragment shader (required)
}

Basic Examples

1

Unlit Color Material

The simplest material - displays vertex colors without lighting:
material {
    name : BakedColor,
    requires : [
        color
    ],
    shadingModel : unlit,
    culling : none,
    featureLevel : 0
}

fragment {
    void material(inout MaterialInputs material) {
        prepareMaterial(material);
        material.baseColor = getColor();
    }
}
Key properties:
  • name: Material identifier
  • requires: Vertex attributes needed (color, uv0, tangents, etc.)
  • shadingModel: unlit (no lighting) or lit (with lighting)
  • culling: none, back, or front
  • featureLevel: Minimum feature level (0, 1, 2, or 3)
The getColor() function retrieves interpolated vertex colors.
2

PBR Material with Parameters

A physically-based material with configurable properties:
material {
    name : AssimpDefaultMat,
    shadingModel : lit,
    parameters : [
        {
            type : float3,
            name : baseColor
        },
        {
            type : float,
            name : metallic
        },
        {
            type : float,
            name : roughness
        },
        {
            type : float,
            name : reflectance
        }
    ],
}

fragment {
    void material(inout MaterialInputs material) {
        prepareMaterial(material);
        material.baseColor.rgb = materialParams.baseColor;
        material.metallic = materialParams.metallic;
        material.roughness = materialParams.roughness;
        material.reflectance = materialParams.reflectance;
    }
}
Parameters are accessed via materialParams in shader code. Set them in C++:
materialInstance->setParameter("baseColor", RgbType::LINEAR, float3{0.8});
materialInstance->setParameter("metallic", 1.0f);
materialInstance->setParameter("roughness", 0.4f);
3

Textured Material

A material using texture samplers:
material {
    name : TexturedPBR,
    shadingModel : lit,
    parameters : [
        {
            type : sampler2d,
            name : albedo
        },
        {
            type : sampler2d,
            name : normalMap
        },
        {
            type : sampler2d,
            name : roughnessMetallic
        },
        {
            type : float,
            name : normalScale,
            default : 1.0
        }
    ],
    requires : [ uv0, tangents ]
}

fragment {
    void material(inout MaterialInputs material) {
        prepareMaterial(material);
        
        vec2 uv = getUV0();
        
        // Sample textures
        vec4 albedo = texture(materialParams_albedo, uv);
        vec3 normal = texture(materialParams_normalMap, uv).xyz;
        vec4 rm = texture(materialParams_roughnessMetallic, uv);
        
        // Apply to material
        material.baseColor = albedo;
        material.normal = normal * 2.0 - 1.0;  // Convert from [0,1] to [-1,1]
        material.normal.xy *= materialParams.normalScale;
        material.roughness = rm.g;
        material.metallic = rm.b;
    }
}
Set textures in C++:
materialInstance->setParameter("albedo", albedoTexture, sampler);
materialInstance->setParameter("normalMap", normalTexture, sampler);
materialInstance->setParameter("roughnessMetallic", rmTexture, sampler);
materialInstance->setParameter("normalScale", 1.5f);
4

Custom Vertex Shader

Materials can include custom vertex processing:
material {
    name : WavingFlag,
    shadingModel : lit,
    parameters : [
        {
            type : float,
            name : time
        },
        {
            type : float,
            name : amplitude,
            default : 0.1
        },
        {
            type : float,
            name : frequency,
            default : 2.0
        }
    ],
    requires : [ uv0 ]
}

vertex {
    void materialVertex(inout MaterialVertexInputs material) {
        vec2 uv = material.uv0;
        float wave = sin(uv.x * materialParams.frequency + materialParams.time) 
                   * materialParams.amplitude;
        material.worldPosition.y += wave;
    }
}

fragment {
    void material(inout MaterialInputs material) {
        prepareMaterial(material);
        material.baseColor = vec4(0.8, 0.2, 0.2, 1.0);
    }
}
Update time parameter each frame:
materialInstance->setParameter("time", (float)now);
5

Transparent Material

Materials with alpha blending:
material {
    name : TransparentColor,
    shadingModel : lit,
    blending : transparent,
    transparency : twoPassesTwoSides,
    parameters : [
        {
            type : float3,
            name : baseColor
        },
        {
            type : float,
            name : opacity
        }
    ]
}

fragment {
    void material(inout MaterialInputs material) {
        prepareMaterial(material);
        material.baseColor.rgb = materialParams.baseColor;
        material.baseColor.a = materialParams.opacity;
    }
}
Blending modes:
  • transparent: Alpha blending
  • add: Additive blending
  • multiply: Multiplicative blending
  • screen: Screen blending
Transparency modes:
  • default: Single-sided, single pass
  • twoPassesOneSide: Two passes, single-sided
  • twoPassesTwoSides: Two passes, double-sided

Material Properties

Shading Models

shadingModel : lit          // Full PBR lighting
shadingModel : unlit        // No lighting (emissive only)
shadingModel : subsurface   // Subsurface scattering
shadingModel : cloth        // Cloth/fabric shading
shadingModel : specularGlossiness  // Legacy specular workflow

Parameter Types

type : bool
type : int
type : float
type : float2
type : float3
type : float4
type : mat3
type : mat4
type : sampler2d
type : samplerExternal
type : samplerCubemap

Required Attributes

requires : [
    color,      // Vertex color
    uv0,        // Primary UV coordinates
    uv1,        // Secondary UV coordinates
    tangents    // Tangent space (for normal mapping)
]

Culling Modes

culling : back       // Cull back faces (default)
culling : front      // Cull front faces
culling : none       // No culling (double-sided)

Depth Testing

depthWrite : true    // Write to depth buffer (default)
depthWrite : false   // Don't write depth

depthCulling : true  // Enable depth test (default)
depthCulling : false // Disable depth test

Material Inputs

The MaterialInputs struct contains all surface properties:
fragment {
    void material(inout MaterialInputs material) {
        prepareMaterial(material);
        
        // PBR inputs
        material.baseColor = vec4(1.0);      // RGBA color
        material.metallic = 0.0;             // 0.0 to 1.0
        material.roughness = 0.5;            // 0.0 to 1.0
        material.reflectance = 0.5;          // 0.0 to 1.0
        
        // Normal mapping
        material.normal = vec3(0, 0, 1);     // Tangent-space normal
        
        // Emissive
        material.emissive = vec4(0.0);       // RGB intensity
        
        // Ambient occlusion
        material.ambientOcclusion = 1.0;     // 0.0 to 1.0
        
        // Clear coat
        material.clearCoat = 0.0;            // 0.0 to 1.0
        material.clearCoatRoughness = 0.0;   // 0.0 to 1.0
        material.clearCoatNormal = vec3(0, 0, 1);
        
        // Subsurface
        material.subsurfaceColor = vec3(1.0);
        material.subsurfacePower = 12.234;
        material.thickness = 1.0;
    }
}

Built-in Functions

Fragment Shader

// Vertex attributes
vec4 getColor()                    // Vertex color
vec2 getUV0()                      // Primary UV
vec2 getUV1()                      // Secondary UV
vec3 getWorldPosition()            // World-space position
vec3 getWorldNormalVector()        // World-space normal
vec3 getWorldTangentFrame()[3]     // Tangent space basis

// Utilities
vec3 inverseTonemapSRGB(vec3 x)   // sRGB to linear
vec3 inverseTonemap(vec3 x)        // Generic inverse tonemap

Vertex Shader

// Access to vertex data
material.worldPosition             // Modify world position
material.uv0, material.uv1        // Modify UVs
material.color                     // Modify vertex color

Compiling Materials

Using matc

Compile a material file:
# Basic compilation
matc -o output.filamat input.mat

# With optimization
matc -a opengl -p mobile -o output.filamat input.mat

# Multiple platforms
matc -a all -o output.filamat input.mat
Options:
  • -a: API (opengl, vulkan, metal, all)
  • -p: Platform (mobile, desktop, all)
  • -g: Generate debug info
  • -O: Optimization level (0-3)

CMake Integration

set(MATERIAL_SRCS
    materials/my_material.mat
    materials/another_material.mat
)

foreach(mat_src ${MATERIAL_SRCS})
    get_filename_component(mat_name ${mat_src} NAME_WE)
    set(mat_output "${CMAKE_CURRENT_BINARY_DIR}/${mat_name}.filamat")
    
    add_custom_command(
        OUTPUT ${mat_output}
        COMMAND matc -a all -o ${mat_output} ${CMAKE_CURRENT_SOURCE_DIR}/${mat_src}
        DEPENDS ${mat_src}
        COMMENT "Compiling material ${mat_src}"
    )
    
    list(APPEND MATERIAL_BINS ${mat_output})
endforeach()

add_custom_target(materials DEPENDS ${MATERIAL_BINS})

Loading in C++

// Load from file
std::ifstream in("material.filamat", std::ios::binary);
std::vector<uint8_t> data((std::istreambuf_iterator<char>(in)),
                           std::istreambuf_iterator<char>());

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

// Create instance
MaterialInstance* instance = material->createInstance();
instance->setParameter("baseColor", float3{1.0, 0.0, 0.0});

Advanced Techniques

Material Domains

Specialize materials for specific use cases:
material {
    name : PostProcessMaterial,
    shadingModel : unlit,
    domain : postprocess
}
Domains:
  • surface (default): Standard surface materials
  • postprocess: Post-processing effects

Custom Interpolators

Pass custom data from vertex to fragment shader:
material {
    name : CustomInterpolator,
    variables : [
        { name : customData, type : float3 }
    ]
}

vertex {
    void materialVertex(inout MaterialVertexInputs material) {
        variable_customData = material.worldPosition * 0.5 + 0.5;
    }
}

fragment {
    void material(inout MaterialInputs material) {
        prepareMaterial(material);
        material.baseColor.rgb = variable_customData;
    }
}

Vertex Attribute Access

Direct access to vertex attributes:
vertex {
    void materialVertex(inout MaterialVertexInputs material) {
        // Access raw attributes
        vec3 pos = getPosition();
        vec4 tangent = getTangent();
        vec3 normal = getNormal();
    }
}

Best Practices

  1. Always call prepareMaterial() at the start of the material function
  2. Use linear color space for all color parameters
  3. Normalize normal maps after unpacking from textures
  4. Clamp PBR values to valid ranges (metallic, roughness, etc.)
  5. Consider mobile performance when writing complex shaders
  6. Use feature levels to provide fallbacks for older hardware

Debugging

Validation

# Check material syntax
matc --analyze input.mat

Visual Debugging

Output intermediate values:
fragment {
    void material(inout MaterialInputs material) {
        prepareMaterial(material);
        
        // Visualize normals
        material.baseColor.rgb = material.normal * 0.5 + 0.5;
        
        // Visualize UVs
        material.baseColor.rgb = vec3(getUV0(), 0.0);
    }
}

Next Steps

Build docs developers (and LLMs) love