Generated lava-cause asset preview

Procedural Materials

Author workload-selected WebGPU/TSL procedural materials in Three.js. Use for coupled terrain/coast/seabed response bundles, grass, rock, dry/wet sand and reef identities, NodeMaterial PBR fields, atlas and triplanar filtering, footprint filtering, specular AA, terrain wetness, stylized palette/facet policies, emissive or raymarched fields, per-instance dissolve, derivative normals, and explicit physical-response bundles.

$threejs-procedural-materials 1 primary implementation 1 flagship 1 secondary surface native evidence pending Latest skill update commit 9077075 ↗ SKILL.md on GitHub ↗ raw (for agents) ↗

Primary implementation surface

These routes are generated from canonical source. Their exact status remains separate from implementation availability.

The approach, mathematically

Materials are authored as PBR identity fields: albedo, roughness, and normal all derive from shared procedural causes. Surface normals come from height derivatives — screen-space derivatives give filtering for free:

$$\mathbf n = \operatorname{normalize}\!\big(\mathbf n_g - \partial_x h\,\mathbf t - \partial_y h\,\mathbf b\big)$$

Specular antialiasing widens roughness where the normal field varies inside a pixel (Kaplanyan-style variance from derivatives), preventing distant sparkle:

$$\alpha' = \sqrt{\alpha^2 + \operatorname{clamp}\!\big(\|\partial_x \mathbf n\|^2 + \|\partial_y \mathbf n\|^2\big)}$$

Triplanar projection blends three axis-aligned samples with a sharpened weight $w_i = |n_i|^k / \sum |n_j|^k$, and emissive surfaces (lava) map temperature through a blackbody-inspired ramp so brightness lives in scene-relative HDR units, not display units.

Preview and evidence ledger

Every image identifies what it proves. Page screenshots demonstrate the published presentation only; generated inputs demonstrate asset channels only; canonical acceptance still requires render-target readback and a schema-v2 bundle.

Canonical runtime evidence pending4 published images

The full skill

The complete SKILL.md as loaded by agents — verbatim, rendered.

Procedural Materials

Build procedural materials as WebGPURenderer + TSL + NodeMaterial graphs. The canonical physically lit lane is a MeshStandardNodeMaterial or MeshPhysicalNodeMaterial whose node slots preserve Three.js lighting, environment, shadow, transmission, clearcoat, sheen, anisotropy, and output upgrades while replacing only the material causes.

Numerical Provenance

Every numerical claim emitted from this skill carries one label:

  • Derived: follows from a stated equation, format, or byte count.
  • Gated: a capability or acceptance threshold that must pass.
  • Measured: captured on the named browser, GPU, resolution, material, lights, camera coverage, and workload; it does not transfer automatically.
  • Authored: a tunable appearance or planning starting point.

Unlabelled vector widths, channel counts, and API-version digits are structural, not performance or physics claims.

Use $threejs-choose-skills preflight for scenes that also need atmosphere, clouds, oceans, shadows, post, or validation ownership.

When a rendered surface also participates in contact, flow, heat, wetting, or another physical interaction, use the route's physics-domain and interaction contract. Bind its semantic surface explicitly to a PhysicsMaterialId in the shared PhysicsMaterialRegistry. Never infer friction, restitution, density, compliance, permeability, adhesion, or thermal/hydraulic response from base color, roughness, metalness, clearcoat, opacity, a texture name, or visual wetness.

Every dynamic physics/receiver/lighting cause is sampled through its unchanged PhysicsSignalDescriptor at an exact PhysicsInstant or PhysicsTimeInterval, including context, frame/origin/transform revision, requested/actual footprint and filter, validity/error, state/resource generation, residency, and cadence/latency. A rendered dynamic cause resolves through a view-independent candidate pair: its previous/current arms own independent provenance and state handles, CameraViewPublication owns render mapping, ViewPreparationPublication owns per-view caches/shadows/resets, and the sealed snapshot references candidate binding IDs and leases. Convert world coordinates only through the active PhysicsContext boundary and the camera publication's derived render transform; material uniforms do not define another scale, clock, provider, transform, or state owner.

Select Architecture From Invocation Topology

Algorithm class dominates material throughput. Start from one shared TSL cause graph, not from independent texture/noise calls per PBR channel:

stable coordinates
  -> structural fields
  -> material identity weights
  -> causal modifiers
  -> filtered microstructure
  -> derivative normals and specular AA
  -> NodeMaterial PBR slots
  -> node post/output

Texture-space/decoupled shading is a separate architecture, not the default specular-stability path. It requires an explicit radiance-cache owner, update and visibility model, and measured bandwidth/error contract; do not silently replace derivative normals or specular AA with cached radiance.

The target graph writes:

  • colorNode from linear authored identity colors or SRGBColorSpace color textures sampled through TSL.
  • roughnessNode, metalnessNode, aoNode, opacityNode, and physical slots from the same identity weights.
  • normalNode from normalMap(), bumpMap(texture(...)) for texture height maps, or a surface-gradient normal built from the same scalar procedural height. Fragment derivatives must execute in derivative-uniform control flow; vertex/compute displacement uses analytic or stored gradients.
  • emissiveNode only for actual material emission; route glow through $threejs-bloom and BloomNode in the node render pipeline.
  • positionNode and castShadowPositionNode consume the same local-space displacement node when material displacement must match visible and caster geometry. receivedShadowPositionNode is a separate world-space receiver override in r185; leave it null unless a world-space replacement is derived and validated explicitly.
  • maskNode, alphaTestNode, castShadowNode, or maskShadowNode for dissolve and cutout behavior, driven by the same instance fields.

Read references/procedural-pbr-system.md for the WebGPU/TSL material system, quality tiers, budgets, atlas/triplanar costs, derivative normals, specular AA, planet fields, wetness, emissive ownership, per-instance dissolve, and validation.

Canonical walnut, antique-gold, ebony, and lava TSL example: examples/tsl-procedural-pbr/.

The example now enforces dielectric/conductor metalness endpoints, meter-valued height through the route's metersPerWorldUnit, footprint-filtered structural bands, and removed material slope-energy transfer so r185 geometry roughness is not counted twice. Its spectral-support/variance multipliers and identity ranges remain Authored trial values. The Node construction validator is structural evidence, not an energy, visual-reference, timing, or thermal acceptance proof.

Use the sibling examples as domain sources, not implementation recipes:

  • $threejs-procedural-fields for designing shared scalar/vector causes.
  • $threejs-procedural-planets for planet-space coordinates, altitude filtering, and orbit-to-close material/geometry parity.
  • $threejs-water-optics for coupled reflection, refraction, absorption, crest response, and water-surface diagnostics.
  • $threejs-bloom and $threejs-image-pipeline for HDR emissive extraction, tone mapping, output conversion, and node post ownership.
  • $threejs-scalable-real-time-shadows when material displacement, alpha, or projected environmental occlusion must remain shadow-consistent.

Coupled Terrain, Coast, And Seabed Contract

For generated islands, coasts, riverbanks, reefs, or exposed terrain, consume one versioned field contract from $threejs-procedural-fields. Do not recreate coast, slope, moisture, or substrate noise inside each material. At minimum, the contract declares units, sign conventions, coordinate frame, generation revision, filtering policy, and update cadence for:

signed coast distance and nearest-coast tangent/normal
terrain/seabed elevation, water-rest elevation, and water-column depth
geometric slope, curvature/cavity, drainage/moisture, and exposure
substrate/material identity and terrace/cliff/beach semantic masks
salt/spray exposure, run-up or inundation envelope, and persistent wetness
reef/rock/sand/organic-cover eligibility and authored exclusion masks

water-column depth is Derived from compatible elevations; it is not a second painted shallow-water mask. Water owns dynamic free-surface, foam, and optical transport. The route-selected receiver owner alone integrates liquid/ snow storage, precipitation, inundation/wash, infiltration, drainage, evaporation, and melt. This skill owns only the visual dry/wet terrain and submerged-substrate response projected from those immutable signals. A white shoreline stripe painted into terrain albedo is not foam, and cyan seabed emission is not shallow-water transport.

Build normalized identity weights for grass/organic cover, dry rock or cliff, dry sand, wet sand or waterline substrate, submerged sand, reef/rock, and any project-specific identity. Preserve hard semantic exclusions separately, then filter only the visible transition width from the projected footprint. Every identity is a response bundle:

linear base reflectance + roughness-alpha + metalness endpoint
resolved height/normal spectrum + removed-slope variance
porosity/absorption or wet-film approximation
macro color variation + microstructure scale
material-slot/semantic ID + diagnostic color

Blend alpha = roughness^2, not unrelated roughness scalars. Filter the same weights into color, roughness, height/normal, AO/cavity, and wetness; otherwise the grass edge, cliff normal, and sand response detach under motion. Terrain geometry owns silhouettes and intentionally faceted normals. Material normals add only footprint-valid detail and must not smooth away authored cliff facets.

Stylization is an authored identity transform, not license to violate material causality. Quantize a controlled palette, macro-value families, roughness families, and geometric facet normals before lighting while retaining scene-linear PBR and one output transform. Do not bake light-facing highlights, ambient occlusion, foam, or turquoise water into terrain base color. Under shallow water, seabed color and roughness remain substrate properties; the water owner supplies depth-dependent attenuation, refraction, surface Fresnel, caustics, and foam.

The detailed response, filtering, and asset-channel contract is in references/procedural-pbr-system.md.

Physics Material Boundary

Keep MaterialResponseBundleId and PhysicsMaterialId distinct. Bind both to the semantic surface/asset slot that needs them; neither ID is derived from the other, and the mapping need not be one-to-one. A physics record declares its constitutive model, SI units, validity range/state, uncertainty, provenance, version, and solver combine rule. Depending on the selected solver it may contain density; static/dynamic and anisotropic friction; restitution with an impact-speed range; normal/tangential compliance and damping; permeability, porosity, absorption, capillary/contact-angle or hydraulic data; adhesion as a named surface-energy or traction model; and thermal conductivity, specific heat, emissivity spectrum, or phase-change data. Omit unsupported properties explicitly; do not fabricate plausible constants.

Collider and support proxies carry PhysicsMaterialId independently of render material and LOD. Wet, icy, damaged, compacted, or thermally changed states switch to a versioned physics material/state only through the owning physical model; a darker or shinier shader state alone does not change contact response. Material-pair lookup uses a deterministic canonical ID/registry/law-state key unless the constitutive law is explicitly directed. Latch both material IDs and state versions atomically for a solve; select exactly one admissible pair law, reject incompatible restitution-plus-damping energy models, and report dissipated work. Read the detailed registry and validation contract in references/procedural-pbr-system.md.

Capability Gate And Tiers

Initialize the renderer before selecting quality. This skill has one production path: native WebGPU.

await renderer.init();
if (renderer.backend.isWebGPUBackend !== true) {
  throw new Error("threejs-procedural-materials requires native WebGPU.");
}

Call renderer.compute()/computeAsync() only for a cause-map or instance update selected by the procedural-fields amortized cost gate. After initialization, computeAsync() provides no GPU-completion fence in r185; use compute() for submission and an actual readback/map or timestamps for completion evidence.

Quality tiers:

Tier Material architecture Targets
Full TSL fields, cost-gated StorageTexture cause maps, storage-backed instance state, filtered normals/specular AA, projection only where UVs cannot preserve scale Measured close-inspection target inside budget
Budgeted same graph with packed sampled data, single-/two-axis projection where valid, fewer filtered bands, lower update cadence Measured full tier misses the named target's traffic, thermal, or frame gate
Minimum native precomputed generated variants, lower field resolution, filtered UV/array sampling, static instance attributes Gated WebGPU exists; Budgeted still misses target

These rows change presentation work only. A physics-facing cause/provider may change representation, filter, cadence, state, or error only through a coordinator-admitted QualityTransition; material quality selection has no authority to mutate physics or receiver state.

Dynamic cause-map producers are explicit PhysicsGraph stages when they advance physical/receiver state, with declared versions and GPU dependencies. A view-independent render projection publishes each stable binding as a leased PresentedStatePair in PhysicsPresentationCandidate; each pair arm carries its own PresentationSampleProvenance, presentedInstant, state handle, and spatial binding. A projection selected or built for one view belongs instead to ViewPreparationPublication. Materials resolve the sealed snapshot's candidate binding/lease refs plus its CameraViewPublication render mapping; snapshots do not copy pairs or transforms. A new generation never overwrites a leased previous/current resource in place, and multi-target completion/retirement is recorded by lease ID in FrameExecutionRecord.

Performance Budgets

There is no universal desktop/mobile millisecond, map-extent, band-count, or MiB row. The application declares whole-frame Gated GPU p95, CPU p95, peak-live-byte, quality-error, and sustained-thermal limits for named workloads. Select a tier only from Measured evidence for that workload.

Record output extent/DPR, covered fragments, overdraw/MSAA/helper estimate, active procedural operations, tile extent and dirty/update cadence, per-stage bindings, executed texture operations, producer/consumer bytes, mip/layer/ ping-pong lifetimes, and cache/effective-bandwidth evidence. Report base, candidate, and interleaved paired-delta GPU p50/p95 plus contemporaneous whole-frame GPU/CPU p50/p95 and peak live bytes. Mobile-class evidence includes warmup, sustained interval, power state, and throttling result.

Interleave matched frames and compute delta_k=tGPU_k(graph+material)-tGPU_k(graph) before taking p50/p95; never subtract independent quantiles. Marginal evidence diagnoses the material; whole-frame gates accept it. CPU frame intervals are not GPU timings.

The material integration is Gated to avoid an undeclared extra full-scene render. Reuse the scene owner's MRT and account for every attachment byte, or declare and budget the additional scene pass at application level. Bloom, AO, grading, and other post passes retain sibling ownership and separate ledgers.

Per-material accounting:

  • TSL noise/octaves: retain only bands passing the projected-footprint and quality-error gates; choose direct versus cached evaluation with the procedural-fields cost equation, not an octave count.
  • Installed r185 triplanarTexture() issues Derived three filtered samples per texture and uses simple absolute-normal weights. Two separately bound color/data textures therefore add six samples; a separate normal texture adds three more before any manual taps. Reserve this path for UV-less surfaces after measuring it. Arrays/atlases reduce bindings, not sample operations; custom hex tiling has its own measured sample count and does not solve seams.
  • Atlas/array sampling: use duplicated mip gutters or texture arrays before adding manual sample clamps. Manual anisotropic taps require measured close-inspection value.
  • Derivative normals: one height evaluation plus derivative math where possible; do not reevaluate unrelated height fields for color, roughness, and normal.
  • Instances: use one material graph and per-instance node attributes; never clone a material per object for color, dissolve, wetness, or variant choice.

Mobile sample and binding ledger

Count bindings and executed sample operations separately. WebGPU device defaults are Gated at 16 sampled textures, 16 samplers, and 4 storage textures per shader stage; query renderer.backend.device.limits because the actual device may expose more and Three.js lighting, environment, shadows, and other bindings in the same material pipeline spend from those stage limits. Post passes have separate pipeline layouts and need separate ledgers; they do not reduce the material pipeline's binding limit. Compatibility-mode devices can expose zero vertex-stage storage resources, so query the stage-specific limits rather than the aggregate compute limit. The material allowance is:

B_material = B_device - B_renderer - B_scene_shared
S_material = sum(active-path projectionCount * textureLookups * manualTaps)

B_* comes from the compiled pipeline layout; S_material comes from generated WGSL/graph inspection and includes the worst hot branch. As an Authored trial, teams may record a candidate sample ceiling in the workload manifest, but it has no portable acceptance status. Any sample count requires Measured A/B, whole-frame, traffic, and thermal evidence on the target. Pack lanes only when color space, coordinates, derivatives, precision, filter, and update cadence agree.

PBR Energy And Normal-Filtering Contract

  • Dielectric normal-incidence Fresnel is Derived F0 = ((n2 - n1) / (n2 + n1))^2; against air (n1 = 1), ior = n2 = 1.5 gives F0 = 0.04, and water at ior ~= 1.333 gives F0 ~= 0.0204. Drive MeshPhysicalNodeMaterial IOR/specular slots and let Three.js retain its GGX and multiscattering energy path. Do not multiply a second Fresnel lobe into colorNode.
  • Metalness is identity, not highlight strength. Homogeneous dielectrics use 0; exposed homogeneous metal uses 1. Oxide, dirt, coating, and substrate are separate masks/response bundles. Fractional metalness is only a declared subpixel-mixture approximation; broad fractional values make metals waxy.
  • Blend normalized identity weights. If one GGX lobe approximates a subpixel roughness mixture, blend alpha = roughness^2 then take sqrt; a weighted mixture of distinct lobes is not exactly another GGX lobe, so keep visible material regions discrete or use explicit layers.
  • Filter height/normal content before specular AA. Store/sample normal mean plus variance (or a cone/Toksvig statistic) across mips; normalizing the averaged normal and discarding its length falsely sharpens the BRDF. For unit normals whose mip stores the unnormalized vector mean m, Derived unresolved vector variance is max(1 - dot(m,m), 0); combine it with within-pixel derivative variance.
  • Three.js r185 already adds geometric-normal variation in getRoughness() from normalViewGeometry. Custom specular AA must add only unresolved material-detail variance, or it double counts geometric roughness.
  • For small-angle material-detail variation, a one-pixel box model gives Derived v_box ~= (|dNdx|^2 + |dNdy|^2)/12. Map that statistic into GGX alpha = roughness^2 with an Authored calibration, then replace it with a Measured fit to a supersampled reference; there is no universal multiplier. Clamp and expose pre/post roughness and variance views.
  • For inline procedural height whose normal already uses screen derivatives, do not blindly differentiate that normal again. Band-limit each component and transfer removed slope energy instead. For a random-phase sinusoid, Derived v_j=(2*pi*A_j*f_j)^2/2 * (1-w_j^2); sum independent bands. Noise support and variance coefficients start Authored and become Measured only after spectrum/reference fitting. Do not add both this term and a mip/box statistic for the same unresolved energy.
  • Installed r185 clearcoat fixes its lobe at Gated F0 = 0.04; it is not an exact water-film lobe (F0 ~= 0.0204). If wetness uses clearcoat, label that an Authored approximation and validate the mismatch under grazing light.

Color And Output

  • Color textures use SRGBColorSpace; generated color fields stay linear until the output owner.
  • Normal, roughness, metalness, masks, noise, height, LUT, weather, and generated variant textures use NoColorSpace unless the channel is explicitly color.
  • HDR material and post buffers stay HalfFloatType until tone mapping.
  • The app has one tone-map owner and one output conversion owner: RenderPipeline.outputColorTransform or one explicit renderOutput() node.
  • Materials do not manually encode display color and do not hide unstable highlights with post.

RenderPipeline, pass(), mrt(), PassNode.setResolutionScale(), outputColorTransform, and renderOutput() are the current node post path.

Required Controls

  • coordinate mode: UV, object, world, planet radial, or generated texture space;
  • real-world or perceptual texture scale, in meters or documented art units;
  • material identity weights and authored response bundles;
  • roughness range, micro-normal strength, specular-variance mapping calibration, and clamp;
  • causal fields for wetness, burn, erosion, lava exposure, climate, or dissolve;
  • terrain/coast input revision, coast-distance sign, water-rest elevation, substrate IDs, wet-line/run-up envelope, and missing-data behavior;
  • per-identity grass, cliff, dry/wet sand, submerged substrate, and reef response bundles plus palette family and facet-normal policy;
  • texture-array/atlas tile index, mip gutter status, anisotropy tier, and triplanar or hex-tiling cost mode;
  • static-map device transcode format, mip bytes, and per-channel compression error; dynamic storage remains uncompressed;
  • derivative filtering thresholds and height-to-normal scale;
  • emissive intensity in HDR scene-linear units, plus bloom contribution debug;
  • instance variant, wetness, dissolve threshold, and lifetime when instanced;
  • channel, mask, footprint, roughness-before/after-AA, normal variance, and no-post debug views.

Failure Conditions

  • PBR channels sample unrelated noise or unrelated coordinate spaces;
  • roughness is a scalar afterthought instead of identity-driven;
  • height/normal bands survive after violating the projected-footprint gate, or their removed variance is discarded;
  • triplanar or hex tiling hides seams by spending samples everywhere;
  • atlas padding is ignored under mipmapping;
  • static compression is selected without max/RMS error gates for normal, roughness, height, or threshold channels;
  • material displacement and caster position diverge beyond their visual gate, or a collision proxy exceeds its declared surface/normal error bound; exact parity with fragment microdisplacement is not required when the bounded proxy preserves the contact contract;
  • emissive color owns both lighting and bloom without a raw-emission debug;
  • projected environmental occlusion darkens emission or all ambient response;
  • output conversion is duplicated in material and post;
  • per-instance material state creates cloned materials instead of node attributes or storage-backed attributes.
  • broad fractional metalness is used for tarnish, dirt, or coating instead of separate conductor/dielectric identities;
  • shallow-water color is faked with emissive/cyan terrain, foam is baked into shore albedo, or material-local noise produces a second coastline;
  • terrain identity transitions disagree across color, roughness, normals, semantic IDs, and wetness, or authored cliff facets are erased by material normal blending;
  • a physics solver derives a property or PhysicsMaterialId from PBR channels, filenames, shader state, or the current render LOD;
  • pair lookup depends on contact ordering, mixes material-state revisions, or stacks restitution and damping laws whose combined energy is not admissible;

Routing Boundary

Use $threejs-procedural-fields when the hard part is the shared scalar/vector cause design. Use $threejs-procedural-planets for complete planet bodies and geometry/material parity. Use $threejs-water-optics for physically coupled water. Use $threejs-bloom, $threejs-image-pipeline, and $threejs-exposure-color-grading when the material requires HDR extraction, tone mapping, or final image ownership. Use $threejs-scalable-real-time-shadows for CSMShadowNode, TileShadowNode, or material-aware shadow decisions.

Secondary provider surfaces

Preserved concept proxies and generated-asset previews. They are excluded from primary completion counts and link to the canonical lab through the schema-v2 registry.