Builtin Library
readme.md
layeredMaterial
A surface written as a stack of materials that already exist, compiled to one WGSL surface shader.
Rust over painted metal, snow over rock, mud over a vehicle, lacquer over paint: each is one material laid over another. Both materials are usually already authored; what a stack adds is a way to say "this one, over that one, where the mask says", and to move the "where" at runtime.
local layeredMaterial = require("@builtin::systems.layeredMaterial.layeredMaterial")
local mat = layeredMaterial.material({
name = "worn_barrel",
layers = {
{ material = "@builtin::materials.default" },
{
material = "@builtin::materials.gold",
blend = 0.6,
mask = "@builtin::systems.terrainSystem.demo.textures.rock_diff",
maskChannel = "r",
maskContrast = 2,
uvScale = { 4, 4 },
},
},
})
entity.spawn("barrel").component.add("Model", { model = "cube", material = mat })
material compiles the stack, writes it out as a real .shader asset, makes a
material wearing it, and writes every property the stack declares onto it — from
the stack where it says one, from the declared default where it does not — plus
every texture the layers bind. Calling it again with the same names is how a
stack is iterated on: what the second call says is what the material holds.
Driving the blend
Every coverage term is a property of the generated material, so a running scene moves it:
local ref = asset.ref("worn_barrel", "material")
ref:setProperty(layeredMaterial.property(2, "coverage"), 0.9)
property(index, input) names what the stack declared — layer 1 is the bottom
one. The per-instance term is the other half of the same knob: point a layer at
one lane of the entity's own shader data and each entity carries its own amount.
{ material = "@builtin::materials.rust", blend = 1, instanceLane = 0, instanceComponent = 0 }
What a stack costs
| Bound | Value | Why |
|---|---|---|
| Layers | limits().maxLayers | The shading cost of a stack is bounded before it is compiled. |
| Texture slots | limits().textureSlots | WebGPU guarantees 16 sampled textures per fragment stage and the renderer's own bind group holds several of them. |
Slots are spent on the maps the layers actually bind. A layer whose material is uniform values alone costs none; one binding all five glTF maps costs five; a mask costs one more. A stack past the budget is refused, naming what each layer spent.
Energy
The layers are blended as shading inputs and shaded once. Shading each layer separately and adding the results would put more light back out than arrived; a surface half covered by a layer here puts out what half of each layer would, and a fully covered one puts out exactly what the covering layer alone would.
The layer vocabulary
inputs() lists it. A layer carries the metallic-roughness set, the dielectric
parameters, emissive, clearcoat, anisotropy, transmission, thickness and
attenuation, plus a UV transform of its own so an overlay tiles at its own texel
density. Normal scale and occlusion strength are declared by a layer that binds
the map they modulate. Alpha and sidedness belong to the surface
and are read off the bottom layer. A property a layer's source material sets that
a layer does not carry comes back in unsupported.
A layer binding no normal map is flat — its shading normal is the geometric one. The mesh's vertex colour tints the bottom layer, the one the geometry wears.
Reading a material first
local read = layeredMaterial.read("@builtin::materials.gold")
print(read.shader, read.values.metallic, read.textures.base_color_texture)
read is what compile does with a { material = ... } layer, on its own.
Where this sits
@builtin::shaders.pbr_layered is a hand-written two-layer shader: base and top,
fixed at two, with the top layer's maps chosen when it was written. A stack says
the same thing without the shader being written for the pair, and holds more than
two layers as long as their maps fit the budget.
@builtin::systems.materialGraph compiles a node graph to the same WGSL surface
DSL. A graph describes one material's arithmetic; a stack describes how whole
materials sit on each other.