PointSwell

A sea of glowing points rolling toward you, with the camera diving into it as the page scrolls.

scroll to dive in, and the cursor parts the surface

Paste into Claude Code, Cursor or any agent. Pinned to a tag, so it will not change under you.

Options

background #080616

css colour

Behind the sea. Nothing is drawn there, so it is a flat fill, and it wants to be near black: the points are additive, so every bit of brightness in the fill is brightness the crests cannot add to.

colorLow #3f2a9e

css colour

The troughs. Close to the background, because a trough is the part of the sea with no light on it.

colorHigh #72f2ff

css colour

The crests. The whole range of the sea lies between this and colorLow, so the distance between the two is the contrast of the item.

density 300

40 to 400, good: 140 to 260

Points across the sheet. The count goes as the square of it, so this is the whole cost. Below about 120 the sea stops being a surface and becomes a scatter.

pointSize 7.2

0.5 to 10, good: 1.8 to 4

Point size at the near edge, falling off with distance. Large enough and the crests fuse into a solid ribbon, which loses the thing that makes it points.

opacity 0.95

0.05 to 1, good: 0.3 to 0.7

How much light each point adds. They are additive, so this compounds where they overlap: the crests are bright because many dim points are stacked there, not because any one of them is.

swell 1.15

0 to 4, good: 0.7 to 2

Height of the swell in scene units. At 0 it is a flat sheet of points, which is a different and quieter item. Past about 3 the crests reach the camera at the near edge.

scale 1.5

0.3 to 5, good: 1 to 2.5

Waves across the sheet, for the longest one. The rest of the spectrum is built up from it, so this sets the size of the whole sea.

waves 18

3 to 24, good: 12 to 24

Modes in the surface. The one that decides whether it reads as water: below about twelve the sum is quasi-periodic and the swell arranges itself on a visible lattice however the rest is tuned.

dive 1

0 to 1

How much of the camera's descent the scroll performs. At 0 the view stays high and the item is a sea seen from above; at 1 it finishes skimming the surface. Eased over the first third of the travel, because the interesting part of the move is the arrival.

parallax 1.2

0 to 4

How far the pointer moves the camera, in scene units. The pointer does not touch the water: it moves the viewer. Parting the surface under the cursor was tried first and reads as the sea noticing you, where moving the camera reads as leaning over it, which is what somebody looking at a sea is doing. The move is instant rather than eased, because easing means keeping the last position and moving a fraction of the way each frame, and that is an accumulator: it would make the frame depend on the frames before it and renderAtTime could no longer be seeked.

period 32

8 to 120 s, good: 24 to 60

Seconds for one cycle. Every mode completes a whole number of turns in it, so the sea returns exactly and the loop has no join.

seed 4

0 to 9999

Seed for the headings and phases. Two of these side by side want two seeds, or they are the same sea twice.

What it needs

Category
Backdrops, an effect
Dependencies
three@>=0.160.0
Browser
WEBGL2
Frame budget
~2 ms · 1 context
Pinned to
v0.13.0
  • Travelling waves, not scrolling noise. The usual way to build this is two octaves of simplex with the sample point sliding forward each frame, which looks right and cannot loop: simplex is not periodic in any direction, so a field that slides forward never returns. It also cannot be driven by renderAtTime, because sliding forward means accumulating and the surface then depends on how it got there rather than on the clock
  • Each mode is a sine of k dot x minus omega t, which is a closed form in t, and every omega is a whole number of turns per period, so the sea is exactly where it started when the period comes round. The waves travel toward the camera because that is what a positive omega against a forward wavevector does: the streaming is the physics rather than an offset added to a sample point
  • Eighteen modes, because a sum of a few plane waves is quasi-periodic and lays itself out in a visible lattice however it is weighted. The count is what stops it reading as a pattern
  • The headings fan about straight-on rather than spreading over the full circle. A real swell has a direction; waves arriving from everywhere average into chop, and what reads as a sea rolling toward you is a narrow fan with a little disagreement in it
  • Deep-water dispersion, so a long wave runs faster than a short one as the root of the wavelength, rounded to whole turns because that is what closes the loop
  • No bloom pass. The glow is tens of thousands of additively blended points overlapping, which is where the brightness in this kind of scene actually comes from. A composer stack buys a halo for three full-screen render targets and a second pair of passes, and this is a backdrop that sits behind a page rather than being the page
  • A grid, not a reshaped sphere. Scenes of this kind often start from a SphereGeometry and throw the shape away in the vertex shader, which works and leaves the point spacing bunched at the poles of a sphere nobody can see
  • The grid is jittered off its lattice. A perfectly regular sheet of points shows its rows as moire at a shallow angle, and a shallow angle is the only one this is ever seen at
  • The cursor parts the surface sideways along the ground plane rather than lifting it. A sea that bulges towards whoever is looking at it is the wrong physical story and a much louder one
  • Built for a dark ground and declared as one. Additive points on a near-black fill is the whole idea, and added light on paper is invisible
  • A single draw of the sheet, with the surface solved in the vertex shader