Material (asset type)
A material is a named, configured instance of a .shader. The shader defines the surface look (lighting model, available property fields, texture slots); the material picks values for those fields and…
New to how a material relates to its shader, its textures, and the live GPU material behind it? Read
core/resource-modelfirst. It also covers the difference between an authored.materialasset (this type) and a runtime GPU material made withrenderer.material.create.
When to use one
- You want a named surface configuration an entity wears by referencing it
from its
Modelcomponent (material = "@.../<Name>"). - You want runtime property tweaks (
asset.resolve(ref):setProperty(...)) to affect every entity using the material at once. - You want to publish a curated look (
Gold,RedGlow,BrickWall) other authors can reuse without re-typing shader bindings.
If you need a brand-new lighting model or a custom visual effect, you
need a .shader first; the material wraps it.
Where it lives
- Source:
/zero/source/.../<Name>.material/ - Identity:
<Name>(the.materialsuffix strips). - Folder shape:
mat.yaml— canonical material spec (shader + property values). Required.preview.png— the material's rendered shader-ball still, its persisted visual description. Required. Regenerated on everymat.yamlwrite; it syncs, publishes, feeds image-based search, and gives browsers a thumbnail.README.md/.metadata— optional prose + tags, indexed when an author writes real ones.textures/— optional folder for embedded textures. Validator allows the folder; its interior is unrestricted.shaders/— optional folder for a co-located custom shader. Same rule.
How to create one
asset.create("material", "<Name>")
-- Creates: /zero/source/materials/<Name>.material/
-- mat.yaml (shader binding + property scaffold)
-- preview.png (rendered moments later from the mat.yaml write)
Pass the shader and initial values through asset.create, then tune with
setProperty:
local mat = asset.create("material", "<Name>", { shader = "@builtin::shaders.pbr" })
mat:setProperty("base_color", { 0.8, 0.6, 0.1, 1 })
mat:setProperty("roughness", 0.3)
This mints the .material asset and returns a handle you tune with
setProperty. Written to the world's source (the normal edit-mode authoring
path) it persists; a purely runtime asset.create lives only for the session.
For an ephemeral, runtime-only GPU material that is not an asset at all, use
renderer.material.create instead (see core/resource-model).
How it operates
- Property reflection. The bound shader's uniform struct fields
become the material's configurable properties — no manual property
declarations needed.
nagaintrospects the shader at load. - Property values.
mat.yamllistsfloats:,colors:,textures:keyed by the shader's field names. Omitted fields take the shader's default. Acolors:entry is an{ r, g, b, a }map (base_color: { r: 1, g: 1, b: 1, a: 1 });shader:is the shader's identity string. (setPropertyat runtime also accepts a{ r, g, b, a }array — that is the live-write API, notmat.yaml.) - Application. An entity wears a material by referencing it from its
Modelcomponent (material = "@.../<Name>"). Materials affect every entity that references them; runtimeasset.resolve(ref):setProperty(...)changes propagate to all of them immediately. - Texture binding. Texture slots accept
color:r,g,b,afor solid colors,@builtin::textures.<name>for builtins,path/to/image.pngfor file textures, or a render-target handle for camera renders. - Hot reload. Editing
mat.yamlrepacks the GPU uniform buffer on the next frame; visible immediately.
Discovery
asset.list("material")— every registered material.asset.inspect("<name>")— shader binding, property values, source path, this type README.cat /zero/source/<Name>.material— same summary.asset.resolve("<name>"):getProperties()— the property fields exposed by the bound shader, with their current values. Read them back rather than guessing names; which fields exist comes from the shader.asset.resolve("<name>"):getShader()— the shader the material binds.
Authoring conventions
- Use lowercase, descriptive names (
brushed_steel,red_glow,tarmac_wet). Avoid shader-name suffixes in the material name — the binding lives inmat.yaml::shader. - Drive every property the shader exposes; defaults are a fallback, not a contract for which fields exist.
- Embed textures alongside the material (
textures/subfolder) when they're used only by this material; reference shared textures from@builtin::textures.*or a library.
Common pitfalls
- Property names must match the shader's uniform struct exactly (case-sensitive). Misnamed properties silently get the shader's default.
- Broken shaders break the material. A material whose shader has a parse error renders as the magenta checkerboard. Inspect the shader first if the material looks wrong.
setPropertytargets the material, not the entity. Changes affect every entity that references the material.- Renaming the folder changes the identity. Update every
Modelcomponent andasset.createcallsite that references it.
Related types
.shader— defines what properties exist; required before a material can bind to it..preset— captures a component configuration; conceptually similar to a material, but for components instead of shaders.