threejs-temporal-surfaces skill (Threejs-Awesome-Graphics-Agent-Skills)
- Install
- SKILL.md (verbatim)
- Pipeline
- Rules
- Routing boundary
- Other files in this skill
- references/ping-pong-accumulation.md (verbatim)
- Contents
- Exact pass graph
- Render-target ownership
- Persistent pointer state
- Frost composite
- Refraction output
- Observed defects and required corrections
- Diagnostic contract
- references/refractive-window-rain.md (verbatim)
- Contents
- Droplet field
- Surface normal and refraction
- Blur and presentation
- Controls and diagnostics
- Default parameter contract
- Layer activation contract
- Background fit contract
- Blur budget
- Failure modes
What it does. Build view-aligned and screen-space surface effects in Three.js. Use for touch-history frost and thaw, ping-pong accumulation, reduced-resolution blur, crystalline masks, two-scale refraction, and procedural rain droplets that refract and blur a background through wet glass. 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-temporal-surfaces/SKILL.md |
| License | MIT |
| Author | Scott Sun (scottstts) |
| Fetched | 2026-09-10 |
Install
npx skills add scottstts/Threejs-Awesome-Graphics-Agent-Skills --skill threejs-temporal-surfaces, or copy the skill folder into~/.claude/skills/threejs-temporal-surfaces/.- Raw file:
curl -sL https://raw.githubusercontent.com/scottstts/Threejs-Awesome-Graphics-Agent-Skills/HEAD/skills/threejs-temporal-surfaces/SKILL.md
SKILL.md (verbatim)
name: threejs-temporal-surfaces
description: Build view-aligned and screen-space surface effects in Three.js. Use for touch-history frost and thaw, ping-pong accumulation, reduced-resolution blur, crystalline masks, two-scale refraction, and procedural rain droplets that refract and blur a background through wet glass.
Temporal Surfaces
Choose persistent history or procedural screen-space evolution explicitly. Do not fake accumulation with time-only noise, and do not allocate history for an effect whose complete state is analytic in time.
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.
Pipeline
persistent surface: input -> ping-pong state -> blur -> structure -> refraction
procedural surface: time/coverage -> analytic field -> optical normal -> refraction/blur
Read references/ping-pong-accumulation.md for an exact frost pass graph, pointer-history channels, blur and refraction coupling, and implementation defects that must be corrected.
Read the touch-history frost implementation for the previous/deposit/next state transition, reduced blur, static structures, frost-mask composition, and two-scale refraction.
Read references/refractive-window-rain.md and the refractive window rain implementation for layered static and travelling droplets, finite-difference optical normals, background refraction, stochastic disc blur, aspect fill, and presentation.
Rules
- Separate persistent state, analytic procedural state, and scene color.
- Preserve separate visible-mask and tilt-response channels.
- Use half-float for this history path unless a measured lower format is equivalent.
- Convert per-frame history decay to frame-rate-independent decay.
- Run the two-pass scene blur at reduced resolution.
- Pre-render static procedural textures once.
- Define and test resize/reset behavior for both history targets and static targets.
- Do not route world footprints, object-UV paint, or simulation-plane wetness here; this skill is view-aligned or screen-space.
Routing boundary
Use $threejs-procedural-vfx for world- or object-space residue and particles.
Use $threejs-precipitation-surfaces for world-space rain, puddles, snow, and
weather-surface coupling. Use $threejs-procedural-materials when a body must
transmit its surroundings through its own volume rather than through a
screen-aligned pane. This skill owns view-aligned wet-glass optics and
screen-space persistent history.
Other files in this skill
- agents/openai.yaml
- assets/touch-history-frost/main-normal.webp
- assets/touch-history-frost/noise.webp
- assets/touch-history-frost/sub-normal.webp
- examples/refractive-window-rain/rain-window.frag
- examples/refractive-window-rain/window-rain-effect.js
- examples/touch-history-frost/frost-surface-effect.js
- references/ping-pong-accumulation.md
- references/refractive-window-rain.md
references/ping-pong-accumulation.md (verbatim)
Touch-history frost accumulation
Use this reference for screen-space frost whose visible mask and refractive response depend on persistent touch history, reduced-resolution scene blur, static crystalline fields, and two-scale normals.
Contents
- Exact pass graph
- Render-target ownership
- Persistent pointer state
- Frost composite
- Refraction output
- Observed defects and required corrections
- Diagnostic contract
Exact pass graph
The frame graph owns these targets:
| Target | Resolution | Lifetime | Purpose |
|---|---|---|---|
| root | display DPR | every frame | source scene |
| vertical blur | 0.4 DPR |
every frame | first separable blur axis |
| horizontal blur | 0.4 DPR |
every frame | broad blurred source |
| frost composite | display DPR | every frame | scene, blur, structure, interaction |
| pointer read/write | display DPR, half-float | persistent | touched/cleared history plus tilt |
| frost noise | 0.4 DPR |
once | coarse crystalline structure |
| frozen noise | display DPR | once | dense frozen structure |
| highlight noise | display DPR | once | highlight breakup |
Per-frame order is fixed:
root scene
-> vertical blur
-> horizontal blur
-> frost composite
-> pointer history write and swap
-> display output samples the new frost target
The frost pass therefore reads the pointer state from the previous completed frame. Preserve that one-frame history relationship unless changing it deliberately and validating the response.
Render-target ownership
The single-target helper:
- stores
renderer.autoClear; - sets the requested clear mode;
- binds the target;
- renders;
- restores the default target;
- restores
autoClear; - disposes the target on owner teardown.
The double-target helper writes only to write, exposes read.texture before
the draw, swaps after the draw, and returns the new read.texture.
read = completed state
write = destination for this update
render(write, sampling read)
swap(read, write)
Both targets resize together. A production adaptation must define whether resize preserves, resamples, or clears state. This implementation clears both histories and marks static targets for regeneration on resize; keep an explicit policy when adapting.
Persistent pointer state
The pointer target uses:
R = accumulated interaction mask
A = accumulated tilt-response mask
G/B = duplicate R in the output
Defaults:
decay = 0.002 per 60 Hz frame,
scaled as 0.002 * max(deltaSeconds * 60, 0.25)
noise strength = 0.16
frost-noise strength = 0.10
mask strength = 0.30
radius = 0.15 -> 0.17
corner fade = 0.50 -> 0.60
side fade = 0.00 -> 0.50
The update is:
previous = max(previous - decay, 0)
center = pointer * 0.5 + 0.5
distance = aspect-corrected distance to center
deposit = edge-masked noisy radial brush when touching
Rnext = clamp(Rprevious + noisyDeposit, 0, 1)
Anext = clamp(Aprevious + lower-noiseDeposit, 0, 1)
The side and corner masks keep a circular deposit from clipping abruptly at viewport boundaries. The alpha channel deliberately uses a less noisy brush so device tilt can react smoothly while the visible thaw/frost boundary stays irregular.
Frost composite
The frost pass inverts pointer history:
clearAmount = 1 - pointer.R
It combines three static structures:
base structure = mix(frozenNoise, highlightNoise, 0.30)
coarse frost = contrast(frostNoise * 1.70 + frostAmount, 1.60)
mask = contrast(base structure + coarse frost * clearAmount, 1.80)
The scene treatment is coupled:
blurMix = clamp(clearAmount * (mask + 0.30), 0, 1)
scene = mix(sharpScene, blurredScene, blurMix)
scene *= (0.90, 0.90, 1.03)
saturation *= 1.20
brightness *= 0.70
Frost color then mixes:
thin tint = (0.82, 0.86, 1.05)
thick tint = (0.92, 0.96, 1.10)
frost tint strength = 0.70
highlight tint strength = 0.80
The composite alpha stores the structural frost mask before the pointer is applied. The output pass uses that alpha to gate normal-map refraction.
Refraction output
The final pass samples two mirrored-repeat normal maps in screen coordinates:
main scale size = 1200, strength = 0.30
sub scale size = 350, strength = 2.0
IOR = 1.31
thickness = 1.0
source inset = 0.17
Fresnel strength = 0.80
The main map also produces a grayscale height weight for the sub-map. Device
tilt rotates the view vector, with pointer alpha contributing up to 0.8.
Refraction is mixed only where both frost alpha and the inverse pointer mask
permit it.
Observed defects and required corrections
Keep these invariants when adapting; each one silently breaks the effect when lost:
- Pointer decay must be frame-rate-compensated. The implementation scales it
by delta time (
0.002 * max(deltaSeconds * 60, 0.25)); the exact frame-rate-independent form is1 - exp(-rate * deltaSeconds). Verify decay at 30, 60, and 120 FPS either way. - Blur normalization must be guarded. The blur uses plain normalized Gaussian
weights with an epsilon guard (
sum / max(weightSum, 1e-5)). If an alpha-weighted blur is introduced, normalize RGB and alpha separately and guard fully transparent neighborhoods against a zero denominator. - Pointer aspect must be owned by the actual pointer-target dimensions; assign it from the display size that the pointer targets use, never from a lower-resolution helper target.
- Static targets opt out of auto-resize. Regenerate them and clear history on resize, as this implementation does; a production policy may instead resample.
- Screen-space state follows the viewport, not the depicted surface. Do not use this representation for world footprints or object-bound paint.
Diagnostic contract
Expose:
root scene
vertical blur
horizontal blur
each static noise target
previous pointer R/A
current deposit R/A
next pointer R/A
frost mask before pointer
frost mask after pointer
sharp/blur mix
main and sub refraction offsets
final without refraction
final
Add pause and single-step controls. Verify decay at 30, 60, and 120 FPS.
references/refractive-window-rain.md (verbatim)
Refractive window rain
Contents
- Droplet field
- Surface normal and refraction
- Blur and presentation
- Controls and diagnostics
- Default parameter contract
- Layer activation contract
- Background fit contract
- Blur budget
- Failure modes
Droplet field
Build the window coverage from three exact layers: static droplets, a primary
falling layer, and a second falling layer evaluated at 1.85 times the first
layer's scale. Each moving cell uses a hashed horizontal offset, a saw-shaped
lifetime envelope, a stretched drop body, and a thin trail containing smaller
secondary droplets. Combine the layers before deriving the optical normal.
Read the complete fragment shader for the full field. Preserve its hash functions, layer frequencies, intensity ramps, time scaling, and trail shaping together; changing only one usually makes the drops tile or slide visibly.
Surface normal and refraction
Evaluate the combined coverage at the current coordinate and at two offsets:
vec2 e = vec2(.001, 0.) * u_normal;
float cx = Drops(uv + e, t, staticDrops, layer1, layer2).x;
float cy = Drops(uv + e.yx, t, staticDrops, layer1, layer2).x;
vec2 n = vec2(cx - c.x, cy - c.x);
Use n to offset the background lookup. This is the window's optical surface
normal; do not replace it with unrelated distortion noise.
Blur and presentation
The blur is a stochastic disc around the refracted coordinate. Its angular
start comes from gl_FragCoord, while each tap gets a deterministic radial
distance. Keep the loop's compile-time maximum at 64 and break at the runtime
quality uniform. Preserve aspect-fill correction, cool color shift, optional
lightning, vignette, and brightness as separable presentation controls.
Controls and diagnostics
Expose intensity, speed, normal strength, zoom, blur amount, blur iterations, background fit, panning, color processing, lightning, and brightness. Provide at least final, unblurred drops, and raw-color inspection modes. Report the active layer count and blur tap count.
Default parameter contract
intensity 0.40
speed 0.25
brightness 0.80
normal strength 0.50
zoom 2.61
blur amount 0.50
blur iterations 16
background fill true
color processing true
lightning false
Reset elapsed time on a six-hour modulo when an application can run indefinitely so floating-point time does not erode cell animation.
Layer activation contract
float staticDrops = smoothstep(-.5, 1., rainAmount) * 2.;
float layer1 = smoothstep(.25, .75, rainAmount);
float layer2 = smoothstep(.0, .5, rainAmount);
vec2 coverage = Drops(uv, time, staticDrops, layer1, layer2);
The travelling layers return both drop coverage and trail coverage. Preserve the vector result until combination; flattening it to one scalar loses the secondary trail droplets.
Background fit contract
Compare screen and texture aspect ratios. Scale only one UV axis around 0.5
so the background fills the viewport without stretching:
if (textureAspect > screenAspect) scaleX = screenAspect / textureAspect;
else scaleY = textureAspect / screenAspect;
UV = vec2(scaleX, scaleY) * (UV - 0.5) + 0.5;
Pass drawing-buffer resolution, not CSS size, when device pixel ratio differs
from one because both the procedural coordinate and blur hash use
gl_FragCoord.
Blur budget
Compile a fixed maximum loop of 64. Exit once the loop index exceeds the integer quality uniform. Keep the unblurred background sample before the loop and divide the accumulated color by the configured iteration count exactly as the implementation does. Use one iteration as the diagnostic no-blur path.
Failure modes
- droplets are sampled in texture UV space and stretch with the background;
- refraction uses a second noise field instead of the droplet coverage;
- blur resolution uses CSS pixels while the shader uses device pixels;
- independently randomized blur taps shimmer between identical frames;
- aspect correction stretches the background image;
- window rain is routed as world-space falling particles.
Back to scottstts/Threejs-Awesome-Graphics-Agent-Skills or Agent skills.