| name | threejs-procedural-materials |
| description | Procedural materials for coherent NodeMaterial PBR systems in Three.js WebGPU/TSL. Use when a task needs shared identities across PBR channels, must select UV, array, atlas, or projected mapping, needs footprint-filtered normals and specular AA, or needs dynamic per-instance material state with explicit output ownership. |
Procedural Materials
Build a cause graph, not a collection of channel effects:
stable coordinates -> structural causes -> identity weights
-> causal modifiers -> filtered microstructure
-> NodeMaterial PBR slots -> final-image owner
Use MeshStandardNodeMaterial for standard opaque/emissive PBR and
MeshPhysicalNodeMaterial only when its extra lobes or transport properties are
part of the surface. Keep Three.js lighting, environment, shadows, and energy
handling by assigning node slots instead of replacing the whole fragment output.
For multi-system scenes, $threejs-choose-skills is an optional coordinator.
Fields own shared scalar/vector causes, geometry owns silhouettes, water owns
surface/transport, and the final-image pipeline owns tone mapping and display
conversion. Consume each dynamic or cross-system cause with declared units,
coordinate/frame convention, producer and consumer owners, revision,
spatial/temporal support and filter, validity domain, error bound, update
cadence, and staleness rule. A visual material state does not become another
simulation owner.
Initialize and gate the renderer before allocating storage, dispatching
compute-written cause maps, or compiling storage-backed material branches:
await renderer.init();
if (renderer.backend.isWebGPUBackend !== true) {
throw new Error('Native WebGPU is required for procedural material GPU branches.');
}
const { features, limits } = renderer.backend.device;
Select sampled/storage bindings, writable formats, and filtering from those
initialized features and limits.
1. Freeze coordinates, causes, and identities
Choose UV, object, world, radial, or generated-texture space from the physical
or authored cause. Record scale in meters or declared art units. Mixing spaces
is valid only for distinct causes, such as world-height wetness over object-space
grain.
Define normalized material identity weights and a response bundle per identity:
linear base reflectance
roughness alpha and metal/dielectric endpoint
resolved height/normal spectrum plus removed variance
causal modifiers such as wetness, wear, heat, or coverage
semantic/material ID and diagnostic color
All channels use the same filtered weights. Hard semantic eligibility remains
separate from visible blend width. Blend alpha=roughness^2, then recover
roughness with sqrt(alpha) when one GGX lobe approximates a subpixel mixture.
When terrain or seabed is involved, read
substrate and water ownership
before wiring coast distance, wetness, shallow-water, foam, or caustics.
This step is complete when every PBR channel traces to named causes and identity
weights in one coordinate/scale contract, with a hard semantic fallback for any
zero-weight region.
2. Select the mapping from defect and cost
Choose in this order:
- UVs when seams and texel-density distortion pass.
- Texture arrays when layers share dimensions, format, mips, color semantics,
and sampler policy.
- Atlas when array constraints fail and mip-safe gutters are owned.
- One-/two-axis projection for a dominant direction or top/side split.
- Triplanar only when no cheaper parameterization preserves scale.
- Stochastic tiling only when repetition, rather than parameterization, is the
observed defect.
Count bindings and executed samples separately. Installed r185
triplanarTexture() performs three projected samples per bound texture and
uses absolute-normal weights; it does not reorient tangent-space normals or
provide stochastic tiling. Arrays/atlases reduce bindings, not sample count.
Read
mapping and filtering
before using an atlas, texture array, triplanar mapping, or stochastic tiling.
This step is complete when the selected mapping solves the observed defect,
every texture's color/data semantics and mip policy are explicit, and compiled
binding/sample counts fit the target pipeline with measured value for added taps.
3. Wire shared causes into NodeMaterial slots
Own only the slots required by the material:
colorNode linear identity color and causal shifts
roughnessNode identity alpha plus filtered modifiers
metalnessNode conductor/dielectric identity
normalNode normalMap, texture bump, or shared-height gradient
aoNode local cavity/contact term
opacity/mask/alphaTest cutout or dissolve
emissiveNode actual scene-linear emitted radiance
positionNode visible local displacement
castShadowPositionNode matching caster displacement
castShadow/maskShadow matching cutout or dissolve
In r185, receivedShadowPositionNode is a separate world-space receiver
override; leave it unset unless that replacement is explicitly derived and
validated. Visible and caster displacement share one local-space cause. Emission
feeds the HDR material result; bloom and tone mapping remain downstream owners.
Data textures use NoColorSpace; authored color textures use
SRGBColorSpace; scene-linear generated color remains linear. Keep one tone-map
and output-conversion owner through RenderPipeline.outputColorTransform or one
renderOutput().
This step is complete when every owned slot has one cause, visible/shadow masks
and displacement agree, color/data encodings are correct, and no material node
duplicates final output conversion.
4. Filter microstructure and specular response
Filter height/normal bands before perturbing the normal. Fragment surface
gradients run in derivative-uniform control flow; vertex/compute displacement
uses analytic or stored gradients. Preserve normal mean plus variance across
mips instead of normalizing the mean and discarding its length.
Three.js r185 already adds geometric-normal variation in getRoughness().
Custom specular AA adds only unresolved material-detail variance. Read
derivative normals and specular AA
before adding procedural bump, custom normal filtering, or roughness variance.
When a scalar procedural height supplies both normal detail and specular AA,
read the filtered-height normal example.
Supply its dimensionless footprintCyclesPerSample from the full post-warp
Jacobian spectral norm, physical support frequency in cycles per length unit,
and positive band half-range around the supplied mean. heightHalfAmplitude
uses the same length unit as the surface. It keeps attenuation, removed slope
variance, double-sided surface-gradient normal handling, and roughness^2
widening in one causal path.
This step is complete when all retained bands pass the projected footprint,
removed slope/normal variance is accounted for exactly once, and a no-post
close/mid/far comparison rejects distance-dependent sharpening and grazing
sparkle.
5. Own dynamic and per-instance state
One material graph serves a batch. Static instance variation uses attributes;
hot GPU-owned variation uses storage-backed instance data only when its measured
update/access pattern wins. Dissolve, wetness, variant, or lifetime fields drive
visible and shadow masks from the same stable object/world cause.
Cause maps use the procedural-fields direct-versus-bake gate. Static maps build
once; dynamic maps update at their owner cadence and invalidate dependent mips.
Compute submission precedes the consuming render. In r185, computeAsync() is
not a GPU-completion fence.
State generations remain immutable while referenced by a frame. Resize,
material identity changes, coordinate changes, map encoding changes, and device
loss invalidate their dependent history, bindings, and diagnostics. Every
material, texture, storage resource, and generated variant has a disposal owner.
This step is complete when one owner advances each dynamic cause, each instance
record has stable identity and reset behavior, and no per-object material clone
or frame-critical CPU readback is needed.
6. Diagnose and verify
Expose coordinate mode/scale, identity weights and fallback, each structural
cause, mapping weights/mip/gutter, binding and sample counts, roughness before
and after AA, normal mean/variance, visible/caster parity, raw emission,
no-post beauty, and final post result.
Verify close/mid/far and motion views; conductor/dielectric endpoints; atlas
gutters under selected filtering; compiled stage limits; color-space flags;
stable instance/shadow behavior; and target whole-frame timing/bytes when
performance is claimed.
This step is complete when every PBR identity, mapping branch, filtered-normal
branch, dynamic state, shadow path, color conversion, resource lifetime, and
claimed target budget has direct diagnostic evidence.
Completion
The material system is complete when one shared cause/identity graph feeds all
owned PBR slots; mapping and filtering decisions are evidence-backed; material
detail is stable across footprint and motion; dynamic state has one owner and
reset path; and raw material output, shadows, post, and resource lifetimes agree.