Material graphs
Material graphs — a material as nodes
A surface shader is WGSL. @builtin::systems.materialGraph.materialGraph lets a
material be written as nodes and the links between them instead, and compiles it
to the same two files a hand-written .shader asset holds — the material a graph
produces is an ordinary material, drawn by an ordinary program.
local materialGraph = require("@builtin::systems.materialGraph.materialGraph")
local material = materialGraph.material({
name = "rusty_metal",
properties = {
{ name = "tint", type = "color", default = { 0.8, 0.4, 0.2, 1 } },
{ name = "wear", type = "range", default = 0.5, min = 0, max = 1 },
{ name = "albedo_map", type = "texture", default = "white" },
},
nodes = {
base = { op = "sampleTexture", texture = "albedo_map" },
tint = { op = "property", property = "tint" },
mixed = { op = "multiply", a = { node = "base", swizzle = "rgb" }, b = { node = "tint", swizzle = "rgb" } },
wear = { op = "property", property = "wear" },
},
surface = { albedo = "mixed", roughness = "wear", metallic = 1 },
}, { name = "rusty" })
entity.spawn("pillar").component.add("Model", { model = "cube", material = material })
A port takes a number, an array of 2-4 numbers, the name of another node,
{ node = "<name>", swizzle = "rgb" }, or a node written inline where it is
used. A node's type — float, vec2, vec3, vec4 — comes from its inputs: in
an arithmetic node the widest input decides the result and a float is broadened
to it, while two different vector widths are refused, naming the node.
compile(graph) is the same work with nothing installed — it returns the
shader.wgsl and properties.yaml text, the nodes it emitted, and the nodes the
terminal could not reach. check(graph) hands an error back instead of raising.
nodeTypes() lists every operation with the inputs a node of it is written with
and which of those it cannot be written without; surfaceChannels() lists what
the surface terminal accepts. The package's own readme has the full reference.
Calling material again with the same material name is how a graph is iterated
on: the shader is rewritten from the graph, the material is brought onto that
shader's properties, and opts.values is applied over it. The shader itself
compiles at the next frame boundary, the way any shader edit does —
asset.ref(name, "shader"):compileStatus() reports that outcome.