Three.js Scroll Asteroid Belt — InstancedMesh Dodge Run

Three.js Scroll Asteroid Belt Run · Scroll · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

320 asteroids in ONE InstancedMesh draw call, matrices recomposed per frame via a shared dummy Object3D
DynamicDrawUsage hint on the instance buffer for per-frame matrix updates
Seeded per-vertex jitter on one DodecahedronGeometry makes every instance read as unique debris
Analytic weaving path from incommensurate sines — flyable corridor guaranteed at construction, zero collision tests
Gate asteroids at reduced clearance (3.4 vs 6.5) for parameterized, not random, danger
Banking from the path's numerical X-derivative — the camera leans into turns like a cockpit
Near-miss counting with re-arm logic that survives scrub oscillation and reverse runs
Per-instance hot-flash via setColorAt, still within the single draw call; FogExp2 density illusion

About this UI Snippet

How to Build a Scroll-Driven Asteroid Belt Run With InstancedMesh and GSAP

Screenshot of the Three.js Scroll Asteroid Belt Run snippet rendered live

The Three.js Scroll Asteroid Belt Run snippet flies the camera on a weaving, banking path through 320 tumbling asteroids — every rock a single InstancedMesh instance, so the whole field costs one draw call — with "gate" asteroids seeded deliberately close to the flight path that flash hot and increment a NEAR MISSES counter as the camera threads them. GSAP's ScrollTrigger scrubs the camera's Z; the corridor, the banking, and the drama are all construction-time guarantees.

One draw call for the whole field

Rendering 320 individual Mesh objects means 320 draw calls and 320 scene-graph nodes. InstancedMesh submits one geometry, one material, and a buffer of per-instance matrices — the GPU stamps the rock 320 times in a single call. Each frame the loop recomposes every matrix through a shared Object3D dummy (position from placement, rotation from clock-time tumble, scale from seed) and flags instanceMatrix.needsUpdate. With DynamicDrawUsage hinting the driver that this buffer changes every frame, the field animates as cheaply as a particle system while remaining fully lit, shadowed, solid geometry. The base rock itself is a DodecahedronGeometry with seeded per-vertex radial jitter, so instances read as debris rather than dice.

A corridor guaranteed at construction

Dodge-field scenes fail when a rock happens to spawn on the flight path. Here the path is two analytic functions — pathX(z) and pathY(z), sums of incommensurate sines that weave without repeating — and every asteroid is placed *relative to the path at its own Z*: a random angle around the centerline at a radius of at least 6.5 units of clearance. The corridor is therefore flyable by construction, no collision testing ever needed. The 12% of rocks flagged as gates get clearance 3.4 instead — close enough to feel dangerous, still outside the camera's envelope. Threat placement is a parameter, not luck.

Banking sells the steering

A camera translated along a curve looks like it is being dragged; a camera that *banks* looks like it is flying. The roll angle is the numerical X-derivative of the path — (pathX(z−1) − pathX(z+1)) × 0.09 — so the camera leans into every turn exactly as hard as the turn demands, with look-at aimed 14 units down-path. One derivative turns a slide into a cockpit, the first-person sibling of the bridge crossing's footstep bob.

Near-misses with scrub-safe counting

Gate rocks within 6 units of the camera tint hot orange via setColorAt — per-instance color, still one draw call — and increment the counter once per pass. The counted flag re-arms only when the camera retreats fully above the gate, so oscillating mid-pass cannot double-count, and scrolling back up genuinely re-arms the gates for another run. It is the honest-counter philosophy of the portal gate sequence adapted to stateful counting on a scrubbed axis.

Fog as density illusion

FogExp2 matched to the background makes rocks materialize gradually out of the dark ahead — the belt feels endless although only 320 rocks exist across 480 units, the same budget-infinity trick as the scroll tunnel. Star dust points scattered through the corridor depth supply parallax between rock encounters. For the physics-flavored cousin where the field reacts to you, see magnetic particles; for the warp-speed version with no obstacles, starfield warp.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You do not need to learn the InstancedMesh API by trial and error — this snippet is the complete working pattern: shared dummy, per-frame matrix recomposition, DynamicDrawUsage, per-instance color. Paste its HTML, CSS, and JS into an AI assistant like Claude and ask it to explain the corridor guarantee, the derivative banking, or the near-miss re-arm logic. The same assistant can escalate the run — a screen-edge red vignette flash on each near miss, engine particles trailing the camera, a second asteroid size class on its own InstancedMesh, thruster audio tied to bank angle via the WebAudio API, or 2,000 rocks with the count constant and clearances retuned. It can also flip the scene into an autopilot cruise by driving Z from clock time instead of scroll. Treat the code as a starting point to interrogate and reshape, not a finished artifact.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a "scroll-driven asteroid belt run" in plain HTML, CSS, and JavaScript using Three.js and GSAP's ScrollTrigger plugin, all loaded from a CDN (no bundler, no build step).

Requirements:
- A pinned full-viewport section with a canvas, WebGLRenderer, PerspectiveCamera (~70° FOV, resized with aspect on window resize), FogExp2 matched to a near-black background, ambient + warm directional light, and ~1,000 star-dust points through the corridor depth.
- ONE InstancedMesh of ~320 asteroids: base DodecahedronGeometry(1, 0) with seeded per-vertex radial jitter (0.82–1.18, sin-hash, no Math.random) and computeVertexNormals; MeshStandardMaterial; instanceMatrix.setUsage(THREE.DynamicDrawUsage).
- An analytic weaving flight path: pathX(z) = sin(z × 0.021) × 9 + sin(z × 0.007) × 5, pathY(z) = cos(z × 0.016) × 5.
- Corridor guarantee: each asteroid spawns at its own Z, positioned at a seeded random angle around the path centerline with radial clearance ≥ 6.5 — except ~12% flagged as "gates" with clearance 3.4 and larger scale. No collision detection anywhere.
- One GSAP tween (ease "none") scrubbing camera Z from +6 to −486 on a ScrollTrigger with pin: true, scrub ~0.55, end ~+=600%.
- Each frame: camera position = (pathX(z), pathY(z), z), lookAt the path 14 units ahead, and BANKING via rotation.z = (pathX(z−1) − pathX(z+1)) × 0.09. Recompose every instance matrix through a shared Object3D dummy: seeded position/scale, clock-time tumble rotation; set instanceMatrix.needsUpdate.
- Near-miss system: gates within 6 units of the camera tint hot orange via setColorAt (flag instanceColor.needsUpdate) and increment a NEAR MISSES counter once per pass — counted on approach (dz > −1), re-armed only when the camera retreats 6+ units above the gate, so scrub jitter cannot double-count but a reverse run re-arms everything.
- An intro overlay fading once the flight starts.
- Confirm the corridor is never blocked and reverse scrolling flies the run backwards with gates flashing again.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Load the three CDN scriptsAdd three.min.js, gsap.min.js, and ScrollTrigger.min.js in that order before the snippet JS.
  2. 2
    Paste HTML, CSS, and JSYou hover at the edge of a fog-shrouded asteroid field, rocks tumbling ahead — NEAR MISSES 0 in the HUD.
  3. 3
    Scroll to flyThe camera weaves and banks along the corridor as asteroids materialize from the fog and slide past on both sides.
  4. 4
    Thread the gatesOccasional rocks sit deliberately close — they flash hot orange as you pass and the near-miss counter ticks up.
  5. 5
    Scroll backThe run reverses; gates re-arm as you retreat above them, ready to count again on the next pass.
  6. 6
    Tune the dangerRaise the gate probability (0.88 threshold), lower gate clearance (3.4), or add rocks — the corridor stays flyable by construction.

Real-world uses

Common Use Cases

Space shooter and racer promos
The dodge-run is the genre's core fantasy, scrubbed — hand off to a product viewer for the ship itself.
"Navigating complexity" B2B narratives
Compliance, security, and logistics products get threats threaded in first person, with near-misses as the risk counter.
Teaching InstancedMesh fundamentals
The complete pattern in one scene: shared dummy, matrix recomposition, DynamicDrawUsage, and per-instance color — the single most important Three.js scaling technique.
High-energy portfolio openers
A banking flythrough outpaces static heroes; the fog reveal paces long scrolls like the scroll tunnel.
Risk and anomaly storytelling
Map gates to real incidents — each flash a logged event, the counter a running total, annotated via scroll pin steps.
Ambient space scenes
Zero out the gates and slow the path for a drifting debris-field meditation, kin to the comet trail.

Got questions?

Frequently Asked Questions

Each Mesh costs a draw call plus scene-graph overhead; 320 of them would bottleneck most devices. InstancedMesh submits geometry and material once with a buffer of per-instance matrices — one draw call stamps all 320 rocks. The frame loop recomposes matrices through a shared Object3D dummy and sets instanceMatrix.needsUpdate, with DynamicDrawUsage hinting the GPU driver that the buffer updates every frame. Scaling to 2,000 rocks changes one constant.

The path is analytic — pathX(z) and pathY(z) are sums of sines — and every asteroid is placed RELATIVE to the path evaluated at its own Z: random angle, radius of at least 6.5 units (3.4 for gates). Since placement starts from the centerline outward, no rock can occupy the corridor. It is a construction-time guarantee, so the scene needs no collision detection at runtime, ever.

Banking: camera.rotation.z is set to the numerical derivative of the lateral path — (pathX(z−1) − pathX(z+1)) × 0.09 — so it rolls into each turn proportionally to how sharp the turn is, while lookAt aims 14 units down-path. Translation alone reads as being carried; the derivative-driven roll reads as steering, for one subtraction per frame.

Each gate has a counted flag: it counts when the camera first comes within 6 units approaching from the front (dz > −1), and only re-arms once the camera retreats more than 6 units back above it. Small oscillations around the gate cannot re-trigger it, but a genuine reverse run re-arms every gate — deliberate state on top of a scrubbed axis, designed so both directions behave sensibly.

Yes. Export via the JSX, Vue, Angular, or Tailwind buttons. Build the InstancedMesh, asteroid parameter array, and ScrollTrigger in a mount effect against a canvas ref; the misses counter and dummy Object3D live in effect scope, with the HUD updated through a ref. On cleanup kill the ScrollTrigger, dispose the rock geometry/material (one of each — that is the instancing win), the star system, and call renderer.dispose().