Material
What a 3D surface looks like. The built-in looks are static factories — Material.lit (PBR),
Material.unlit (flat), Material.video, Material.shadow — and custom compiled shaders load as
materials too. Uniforms are set through .set() / the uniforms proxy, which lowers each JS
value to the right native uniform call. Assign to a mesh via mesh.material — see
Mesh.
At a glance
const tex = await Texture.load(asset('./crate.jpg'))
const crate = Mesh.box({ material: Material.lit({ color: '#ffffff', map: tex }) })
const marker = Mesh.sphere({ material: Material.unlit({ color: '#0f0' }) })
scene.add(crate, marker)
crate.material.set('roughness', 0.3).set('metallic', 1)Factories
Material.lit(options?: { color?: ColorInput, map?: Texture | Canvas | null,
roughness?: number, metallic?: number }): Material // PBR lit
Material.unlit(options?: { color?: ColorInput, map?: Texture | Canvas | null }): Material // flat, ignores lights
Material.video(map?: Texture): Material // samples a VideoPlayer texture
Material.shadow(color = '#000000aa'): Material // shadow-catcher: invisible except where shadows fall
Material.particles(options?: { map?: Texture | null, sheet?: number | [cols, rows],
emissive?: number, blend?: 'alpha' | 'add', soft?: boolean,
render?: 'point' | 'quad' | 'stretch' | 'ribbon', stretch?: number }): Material
// sprites/quads/ribbons for Particles + Trail (their default)
Material.load(url: string): Promise<Material> // custom compiled shader (.mat / filamat)litneeds light to be visible — IBL and/or aLight.sun(). Its PBR scalars are 0–1:roughness0 = mirror, 1 = matte;metallic0 = dielectric, 1 = metal (both are plain uniforms — unset means the shader's default, and.set('roughness', v)changes them later).videotakes the texture fromVideoPlayer.texture.shadowis the AR staple: a ground plane that shows only the shadows cast on it.particlesis what aParticlessystem (andTrail) draws with when you don't pass a custom material — you rarely construct it yourself, since theParticles/Trailoptions forward to it. Per-particle tint/size/rotation/opacity/frame come from the particle curves; the uniforms (sheetSize,emissive,additive,softness, plusmode/stretchon the quad variant) set the per-system look, and the factory options map onto them.render: 'point'usesparticles.filamat; the other modes shareparticles-quad.filamat.
Color & map
material.color = '#ff8800' // any ColorInput
material.map = tex // Texture, or a Canvas (baked to a texture on assign)
material.map = null // remove the mapcolor and map are conveniences over the underlying uniforms (baseColor / baseColorMap on
lit/unlit, color on shadow). On lit/unlit the shader multiplies them — baseColor × baseColorMap, both defaulting to white (an unset map is backed by a 1×1 white texture) — so a
color alone or a map alone shows through unchanged, a color set alongside a map tints the
map, and map = null resets the texture back to white. On a custom material they don't know
the uniform names — color is a silent no-op and map writes baseColorMap; set your own
uniforms directly instead.
Custom shaders & uniforms
const material = await Material.load(asset('./hologram.mat'))
// or, from a response you already have:
const material = new Material(await fetch(asset('./hologram.mat'), { useOnce: true }))
material.set(key: string, value): this // chainable
material.uniforms[key] = value // same thing, property-style
material.uniforms.speed // reads return the last value YOU set (no native read)Each value type lowers to a specific uniform call:
| JS value | Lowered to |
|---|---|
'#rrggbb' string |
RGB color |
'#rrggbbaa' string |
RGBA color |
number |
float |
boolean |
bool |
Texture |
sampler (honors the texture's wrapS/wrapT) |
number[] / Float32Array |
float array / vector |
null |
clears the uniform (a texture resets to the 1×1 white) |
color strings must be #-hex here — the wider ColorInput forms (rgba(…), packed
ints) are only understood by the color setter, not by set(). And a Vec3/Quat wrapper is
not lowered — spread it to a plain array first: material.set('dir', [...v]).
The scene occlusion material
scene.occlusionMaterial hands back the scene's occlusion material as a regular Material, so
you can set uniforms on it — see Scene (host-gated: not available on the web viewer).
Pitfalls
// ✗ expecting material.color to work on a custom shader
customMat.color = '#f00' // no-op: the SDK doesn't know your uniform's name
// ✓ address the uniform directly
customMat.set('baseColor', '#ff0000')
// ✗ passing a Vec3 as a uniform value
mat.set('lightDir', dir) // not an Array — lowers to NULL, not a vec3
// ✓
mat.set('lightDir', [...dir])