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.
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. */
A MaterialInstance is a material with specific parameter values:
Created from a Material
Has unique parameter values
Can be modified at runtime
Assigned to individual renderables
Material.h:273-281
/** * Creates a new instance of this material. Material instances should be freed using * Engine::destroy(const MaterialInstance*). * * @param name Optional name to associate with the given material instance. * * @return A pointer to the new instance. */MaterialInstance* createInstance(const char* name = nullptr) const noexcept;
Think of Material as a class and MaterialInstance as an object instantiated from that class.
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};
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.
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.
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 smoothmi->setParameter("reflectance", 0.5f);
Create one Material and multiple MaterialInstances rather than creating multiple Materials:
// Good: One material, many instancesMaterial* mat = Material::Builder().package(...).build(engine);MaterialInstance* red = mat->createInstance();MaterialInstance* blue = mat->createInstance();// Bad: Creating separate materials for each colorMaterial* redMat = Material::Builder().package(...).build(engine);Material* blueMat = Material::Builder().package(...).build(engine);
Pre-compile Variants
Use asynchronous compilation during loading screens:
material->compile(HIGH, neededVariants, nullptr, [](Material* m) { // Material ready});engine->flush();
Check Parameter Existence
Before setting parameters:
if (material->hasParameter("roughness")) { instance->setParameter("roughness", 0.5f);}
Name Your Instances
Use meaningful names for debugging:
MaterialInstance* mi = material->createInstance("car_paint_red");
Validate Material/Instance
Check validity before use:
if (engine->isValid(material, instance)) { // Safe to use}