Materials & surfaces
Words that shade a surface so a shape reads as glass, metal, plastic or stone. A material is
three things: a normal (which way the surface faces at this pixel), a light (where it
comes from) and a response (how the surface answers: diffuse, glint, mirror, tint).
shapedSurface turns a shape prop into a surface you can shade. It hands your surface
function a frame with the shape's distance field, and surfaceField binds what you read from
it: the signed distance, the pixel size, the neighbour taps.
From there a material is a recipe read top to bottom. A normal from geometricNormal, bent
by nudgeNormal or tiltNormal for relief. A light from keyLightAt (an angle) or
pointLightFrom (a position). A response from lambert, sharpGlint, dualLobeGlint,
schlickFresnel, beerLambert. A finish from neutralTone and silhouette, closed with
guarded around insideShape so pixels outside the shape pay nothing. The same words light
anything with a slope, not only shapes: fdSlope of a noise field feeds nudgeNormal, and
keyLightAt with lambert shades it.
Two conventions to hold. The viewer looks along +z, so a normal that faces the viewer has
negative z and a key light on the viewer's side has a negative elevation (−0.6 raking to
−0.9 frontal). And a trailing hint argument only names a value in the compiled shader for
debugging. Leave it out.
import {materials} from 'shaders/std'
Reach for it when
| When you need | Use |
|---|---|
| a shape prop should become a shaded surface | shapedSurface, then surfaceField as the first line |
| the surface normal of whatever shape is active | geometricNormal |
| relief from a height field or noise | fdSlope or perlinSlope into nudgeNormal |
| brushed or woven relief along a direction | tiltNormal with grainNoise |
| a light from an angle prop | keyLightAt |
| a light from a position prop | placementPoint into pointLightFrom, faded by inverseSquare |
| soft diffuse shading | lambert |
| a highlight that follows a sharpness slider | sharpGlint |
| a highlight with a hot core and a wide halo | dualLobeGlint |
| a highlight stretched along a grain | anisoSpecular with wardAlphas |
| brighter or more mirror-like rims | fresnelBoost, schlickFresnel over grazingOf or grazingFlat |
| a studio reflection in a metal | viewRay, reflect, studioSoftboxes, smeared by smearAlong |
| glass, ice or water that darkens with thickness | opticalThickness into beerLambert |
| a rainbow sheen | cosineRainbow |
| stars, glitter or dust | pointStars with twinkle |
| a soft edge and nothing drawn outside the shape | silhouette inside guarded on insideShape |
| bright light brought back into range | neutralTone for a surface, exposureTone for emitted light |
| film grain or per-pixel jitter | sensorGrain, hashNoise, interleavedNoise |
| a rim glow or contact shadow outside the shape | nearestEdge, continuedField |
Example
import {defineShader, p, uniformOf, paint, frames, materials, compose, math, transformColor, transformColorSpace, colorSpaceOptions} from 'shaders/std'
const {add, mul, dot, vec3, vec4} = math
const {noiseField, warped, scaledVolume, tone, pair} = paint
const {surfaceOf, direction} = frames
const {fdSlope, nudgeNormal, keyLightAt, lambert, dualLobeGlint, neutralTone} = materials
// One height field drives everything: warped noise on a zoomable slab.
const height = scaledVolume(
warped(noiseField('mx3'), {amount: p('warp'), amountScale: 4, timeScale: 0.12}),
{scale: p('scale')},
)
// Silk: the field's slope becomes a normal, a key light shades it, a two-lobe sheen catches the folds.
export const SilkTide = defineShader({
name: 'SilkTide',
animatedTime: {speed: 'speed'},
props: {
colorA: {default: '#0b1026', transform: transformColor},
colorB: {default: '#ff7e5f', transform: transformColor},
colorSpace: {default: 'oklch', transform: transformColorSpace, compileTime: true, ui: {type: 'select', options: colorSpaceOptions}},
highlight: {default: '#fff3dc', transform: transformColor},
scale: {default: 1.8},
warp: {default: 0.55},
lightAngle: {default: 35},
sheen: {default: 1},
speed: {default: 1},
},
paint: (params) => {
const {uv} = surfaceOf(params)
const u = (name: string) => uniformOf(p(name), params)
// 1 · the field and its slope: three taps of the noise, bound once
const relief = fdSlope((at) => height(params, at), uv, 0.0035, 'tide')
// 2 · color: tone-shape the field and read it through the two-color palette
const shaped = tone(() => relief.value, {glow: 5, contrast: 1.3, balance: 50, invert: true})(params, uv)
const base = pair(p('colorA'), p('colorB'), p('colorSpace'))(shaped, params)
// 3 · light: slope → normal → key light → a wrapped diffuse and a sheen
const tilt = mul(u('sheen'), -0.08)
const n = nudgeNormal(vec3(0, 0, -1), mul(relief.dx, tilt), mul(relief.dy, tilt), 'tideN')
const key = keyLightAt(direction(u('lightAngle'), 'tideDir'), -0.7, 'tideKey')
const diffuse = add(0.55, mul(0.45, lambert(n, key.member('L'), {wrap: 0.5})))
const spec = dualLobeGlint(dot(n, key.member('H')), {core: [140, 1], halo: [22, 0.15], gain: u('sheen')})
const rgb = neutralTone(add(mul(base.member('rgb'), diffuse), mul(u('highlight').member('rgb'), spec)))
return compose.dithered(vec4(rgb, base.member('a')), params)
},
})
Types
| Type | What it is |
|---|---|
ShapedSurfaceEffect | The three fields shapedSurface returns. |
ShapedSurfaceSpec | The options shapedSurface takes. |
SurfaceField | The field values a material reads, bound once: the taps, the signed distance and the pixel size. |
SurfaceFrame | What shapedSurface hands your surface function: the shape's distance field, sampled and ready to shade. |
Words
- shapedSurfaceTurns a shape prop into a surface your material shades.
- surfaceFieldThe first line of a material: the field taps, the signed distance and the pixel size, bound once.
- geometricNormalThe surface normal of the shape, whichever kind of shape is active.
- marchedNormalThe surface normal of a 3D shape, from the marched depth.
- nudgeNormalTilts a normal by a slope in x and y, turning a height field into relief.
- tiltNormalTilts a normal along and across a direction, for brushed or woven relief.
- tiltAlongTilts a normal along one 2D direction by a slope amount.
- fdSlopeThe value and slope of any field at a point:
value,dxanddy. - perlinSlopeThe slope of smooth noise at a point, for wavy or molten relief.
- fieldSlopeThe direction the shape's distance grows in: a 2D vector pointing outward at this pixel.
- surfacePatternCoordinates for a pattern that sticks to the shape's surface.
- keyLightAtA key light from an angle: the light direction
.Land the half vector.Hfor highlights. - keyLightXYA key light from explicit x and y components, for a second or a crossed light.
- lightVec3A 3D light direction from a
direction()vector and an elevation. - placementPointA position prop in the shape's own coordinates, so you can compare it with the field.
- pointLightFromA point light as seen from a point on the shape: the 3D light vector
L, the 2D directiontoLightand thedistance. - inverseSquareHow a point light fades with distance: 1 at the source, half at
range. - lambertHow much a light falls on a surface: 0 facing away, 1 facing it.
- sharpGlintA highlight whose tightness follows a sharpness slider.
- dualLobeGlintA highlight with a tight hot core and a wide dim halo.
- anisoSpecularA highlight stretched along a grain, as on brushed metal or carbon fibre.
- wardAlphasTurns roughness and anisotropy sliders (0–1 each) into the two lobe widths
anisoSpeculartakes. - viewRayThe direction the viewer looks along at this pixel, for reflections that sweep across a face.
- grazingOfHow edge-on the surface is to the viewer: 0 face-on, 1 grazing.
- grazingFlatHow edge-on the surface is to a straight-on viewer: 0 face-on, 1 grazing.
- fresnelBoostA multiplier that brightens grazing edges: 1 face-on, up to
1 + amountat the rim. - schlickFresnelHow mirror-like a surface is at this pixel:
r0face-on, rising tor0 + gainat the rim. - studioSoftboxesThe brightness of a five-light studio seen in a reflected direction.
- smearAlongBlurs any 2D lookup along a direction, for streaked reflections and motion smear.
- opticalThicknessHow much material the view passes through at this pixel: 0 at the edge, up to about 1.2 deep inside.
- beerLambertHow much light survives a thickness of tinted material, per channel.
- cosineRainbowA smooth rainbow color from a value that wraps every 1.
- tintRampMaps a lighting value (0 dark, 1 light) onto two colors.
- neutralToneCompresses bright color back into range without shifting its hue.
- exposureToneTurns light that piles past 1 into a soft burn toward white, like film.
- silhouetteThe shape's coverage at this pixel: 1 inside, 0 outside, soft across the edge.
- insideShapeTrue inside the shape and up to two pixels outside it.
- guardedEvaluates an expression only where a condition holds, and a cheap fallback everywhere else.
- surfaceNoiseSmooth signed noise at a 2D point, roughly −1 to 1, with features about one unit wide.
- surfaceNoiseAt
surfaceNoiseat a 2D point you already hold. - volumeNoiseAtSmooth signed noise at a 3D point, roughly −1 to 1.
- cellNoiseAtCellular noise at a 2D point: 0 at each cell's seed point, rising toward the cell walls.
- valueNoiseSmooth noise 0–1 at a 2D point, softer and blockier than
surfaceNoise. - hashNoiseRandom 0–1 per point, with no smoothness between neighbours.
- interleavedNoiseA 0–1 dither pattern over device pixels where neighbours differ as much as possible.
- grainNoiseOne octave of smooth signed noise along and across a direction, for grain.
- flowWarpA slowly folding 2D offset to add to pattern coordinates, for molten or wind-blown motion.
- sensorGrainAdds a fine per-pixel brightness jitter, so rendered light reads as photographed.
- pointStarsA plane of sparse glinting points: stars, glitter, dust.
- twinkleA brightness factor that shimmers over time, different for each element.
- shellRefractCoordinates for what sits inside a glass shell, bent where the shell curves.
- rotationSensorHow far a 3D shape has rotated, as a 2D pan you can move content by.
- nearestEdgeThe closest point on the shape's edge to this pixel, and the outward normal there.
- continuedFieldThe distance field extended past the edge of an SVG shape's texture.