threejs-parallax-occlusion-mapping skill (Threejs-Awesome-Graphics-Agent-Skills)

From Public Agent Wiki

What it does. Build silhouette-aware parallax occlusion mapping in Three.js WebGPU and TSL. Use for height-field ray marching, relief UVs, clipped flat or curved silhouettes, inflated shells, self-shadowing, relief-aware shadow depth, and height-derived normals. Part of scottstts/Threejs-Awesome-Graphics-Agent-Skills (scottstts/Threejs-Awesome-Graphics-Agent-Skills).

Upstream scottstts/Threejs-Awesome-Graphics-Agent-Skills
Skill file skills/threejs-parallax-occlusion-mapping/SKILL.md
License MIT
Author Scott Sun (scottstts)
Fetched 2026-09-10

Install

  • npx skills add scottstts/Threejs-Awesome-Graphics-Agent-Skills --skill threejs-parallax-occlusion-mapping, or copy the skill folder into ~/.claude/skills/threejs-parallax-occlusion-mapping/.
  • Raw file: curl -sL https://raw.githubusercontent.com/scottstts/Threejs-Awesome-Graphics-Agent-Skills/HEAD/skills/threejs-parallax-occlusion-mapping/SKILL.md

SKILL.md (verbatim)

name: threejs-parallax-occlusion-mapping
description: Build silhouette-aware parallax occlusion mapping in Three.js WebGPU and TSL. Use for height-field ray marching, relief UVs, clipped flat or curved silhouettes, inflated shells, self-shadowing, relief-aware shadow depth, and height-derived normals.

Parallax Occlusion Mapping

Treat relief as a coupled intersection, coverage, normal, and shadow system. Do not stop at offsetting texture coordinates.

This skill contains exemplary examples and assets beyond descriptive guidance, they're worth studying, referencing, or even copying. Use them sufficiently when relevant and do NOT blindly skip them.

Build order

tangent frame and height convention
  -> view-ray march and hit refinement
  -> bounded or curved silhouette coverage
  -> shared marched sampling
  -> height-derived shading normal
  -> light-ray self-shadow march
  -> relief-aware cast/received shadow positions

Read references/silhouette-relief-contract.md for the intersection contract, flat and curved silhouette modes, shell inflation, shadow integration, quality controls, and diagnostics.

Read the silhouette relief implementation for the complete TSL march, binary hit refinement, gradient-safe sampling, coverage, horizon trimming, curved sag, and self-shadow function.

Read the complete bulkhead assembly and its packed procedural height maps for a wall, deck, relief columns, and overhead pipes using height-derived normals, inflated cylinder shells, alpha-to-coverage, shadow-mask carving, marched shadow depth, and received-shadow positions.

Required controls

  • world or UV relief scale;
  • minimum and maximum view-march layers;
  • silhouette bounds and feathering;
  • curved-surface curvature or curvature callback;
  • horizon trimming and edge erosion;
  • self-shadow steps, bias, and strength;
  • geometry, carved, and full-relief shadow modes;
  • height, coverage, marched UV, normal, and shadow diagnostics.

Failure conditions

  • color, normal, and roughness rebuild separate view marches unintentionally;
  • a curved host uses flat silhouette clipping at its geometric horizon;
  • an inflated shell changes the relief floor instead of keeping it on the base surface;
  • alpha-tested beauty coverage is assumed to carve shadow maps automatically;
  • derivative sampling runs behind discard on drivers where it erodes coverage;
  • grazing rays divide by an unbounded view-space Z component;
  • relief self-shadowing darkens fill or emission indiscriminately.

Routing boundary

Use $threejs-procedural-materials when no ray-marched height intersection or silhouette ownership is required. Use $threejs-procedural-geometry when the silhouette must be actual mesh topology rather than a view-dependent relief.

Other files in this skill

references/silhouette-relief-contract.md (verbatim)

Silhouette relief contract

Contents

  • Height and tangent-space convention
  • Adaptive view march
  • Silhouette coverage
  • Curved hosts and inflated shells
  • Normals and sampling
  • Self-shadow and shadow-map ownership
  • Diagnostics
  • Quality and step contract
  • Curvature representations
  • Shell inflation contract
  • Shadow positions
  • Complete bulkhead assembly
  • Failure modes

Height and tangent-space convention

Read red-channel white as the peak and convert it to depth with 1 - height. March the view ray in the geometry tangent frame. Clamp the grazing-angle denominator with minViewZ; use more layers at grazing angles than head-on.

Adaptive view march

Advance by one normalized layer at a time until ray depth reaches sampled surface depth. Interpolate between the final two layers. Materialize the march result once so color, roughness, emission, and coverage reuse it. The normal graph needs its own call because Three.js compiles normalNode in a distinct build context.

Silhouette coverage

For bounded flat tiles, evaluate coverage from the final marched coordinate, not the starting UV. Clamp height fetches separately when sampleBounds are provided so rays cannot hit a wrapped neighboring tile. Feather only the coverage edge; use alpha-to-coverage for antialiasing.

Curved hosts and inflated shells

For convex hosts, add sag to sampled depth as the ray travels. Support a curvature callback, per-axis constant curvature, or a screen-derivative estimate. Continue a coarse horizon chase after the regular march so tall relief can still be hit beyond the base tangent horizon. Inflate the host shell by maximum relief height so the height-field floor remains on the original surface and peaks can extend beyond the base silhouette.

Normals and sampling

Derive the tangent normal from central height taps around the marched UV. When coverage can discard fragments, use explicit LOD taps for the normal graph to avoid derivative sampling behind discard. Keep texture gradients from the unshifted UV footprint for regular material sampling.

Self-shadow and shadow-map ownership

March a second ray from the hit toward the light. Weight blockers by proximity and apply the term to direct surface response rather than emission. To carve cast shadows, assign the coverage test to maskShadowNode. For full relief shadows, write marched depth in the shadow camera and use the same marched world position for received-shadow lookup.

Diagnostics

Expose height, final UV offset, hit/miss distance, coverage, curved sag, horizon threshold, normal, self-shadow occlusion, shadow-mask coverage, and marched shadow position. Sweep front, grazing, and axial views at every quality tier.

Quality and step contract

Use these tiers as a coherent starting point:

low     min 8   max 32 view layers
medium  min 16  max 96 view layers
high    min 32  max 160 view layers
self shadow 20 steps, strength 12, bias 0.03

Interpolate layer count from maximum at grazing view to minimum head-on:

layers = mix(maxLayers, minLayers, saturate(abs(viewDir.z)));
deltaUV = viewDir.xy / max(abs(viewDir.z), minViewZ)
          * scale / layers;

Curvature representations

Choose one representation per host:

curvature(coord) callback -> varying curvature across rounded boxes/capsules
[ku, kv] per-axis         -> cylinders and other separable parameterizations
screen derivative estimate -> fallback when no authored curvature is available

For a cylinder tiled n times around its circumference, use curvature [2π / n, 0]. The axial component remains zero, so a ray travelling along the cylinder does not accumulate false sag.

Shell inflation contract

tileAround = circumference / aroundTiles
reliefWorld = depthScale * reliefFactor * tileAround
positionNode = positionLocal + normalLocal * reliefWorld

The march treats red-channel white as the shell peak and black as the base floor. Inflating by maximum relief world height lets the march subtract depth back toward the original host surface.

Shadow positions

marchedWorld = positionWorld
  + tangentWorld * reliefOffset.x * worldPerTile.x
  + bitangentWorld * reliefOffset.y * worldPerTile.y
  - normalWorld * reliefDrop;

marchedClip = projectionMatrix * viewMatrix * vec4(marchedWorld, 1);
depthNode = marchedClip.z / marchedClip.w;
receivedShadowPositionNode = marchedWorld;

Build this graph in the active shadow-camera context. A beauty-pass depth value cannot be reused as the light's shadow depth.

Complete bulkhead assembly

Read the complete relief assembly with its procedural packed maps to preserve the full worked example. It contains a front/back blast-door wall, a sixteen-unit relief deck, two curved relief columns, and two horizontal relief pipes. The red channel owns height, green owns emissive strips, and blue owns panel-tone variation across all three generated maps.

Do not reduce this example to isolated cylinders: the wall demonstrates flat bounded silhouette carving, the columns and pipes demonstrate curved horizon handling and shell inflation, and the deck demonstrates unbounded tiled relief without silhouette clipping.

Failure modes

  • UV wrapping lets a grazing ray hit a neighboring tile;
  • the miss test clips tall relief that should bridge the base horizon;
  • every PBR channel triggers another full march in one build context;
  • shell inflation and relief scale use different world conversions;
  • beauty alpha test is mistaken for cast-shadow carving;
  • self-shadow uses the world light without transforming it into the view/tangent frame;
  • height normals sample implicit derivatives after discard.

Back to scottstts/Threejs-Awesome-Graphics-Agent-Skills or Agent skills.