Log inGet started

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-model first. It also covers the difference between an authored .material asset (this type) and a runtime GPU material made with renderer.material.create.

When to use one

  • You want a named surface configuration an entity wears by referencing it from its Model component (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 .material suffix 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 every mat.yaml write; 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

  1. Property reflection. The bound shader's uniform struct fields become the material's configurable properties — no manual property declarations needed. naga introspects the shader at load.
  2. Property values. mat.yaml lists floats:, colors:, textures: keyed by the shader's field names. Omitted fields take the shader's default. A colors: 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. (setProperty at runtime also accepts a { r, g, b, a } array — that is the live-write API, not mat.yaml.)
  3. Application. An entity wears a material by referencing it from its Model component (material = "@.../<Name>"). Materials affect every entity that references them; runtime asset.resolve(ref):setProperty(...) changes propagate to all of them immediately.
  4. Texture binding. Texture slots accept color:r,g,b,a for solid colors, @builtin::textures.<name> for builtins, path/to/image.png for file textures, or a render-target handle for camera renders.
  5. Hot reload. Editing mat.yaml repacks 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 in mat.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.
  • setProperty targets the material, not the entity. Changes affect every entity that references the material.
  • Renaming the folder changes the identity. Update every Model component and asset.create callsite that references it.
  • .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.
  • asset-type
  • reference