Task 4 of 4

Ripples: Think in Polar

Gradients, cells and curves all thought in x and y. The last move of this module is to change coordinate systems inside the kernel: subtract the canvas center (63.5, 63.5 — halfway between the two middle rows and columns), and Pythagoras turns the thread's position into a radius. Anything you compute from that radius is automatically a perfect circle.

Feed the radius into a cosine and you get concentric ripples; multiply by a fade so they die out toward the edge; tint the channels and the flat canvas turns into water. One gotcha, handled in the starter: this.thread.x is an integer on the GPU, so promote it to a float (this.thread.x / 1) before subtracting the fractional center — otherwise the GPU rounds your 63.5 away.

One more thing comes prewired: a dial. slider('phase', …) returns the value this run is using and puts a control under the console; move it and the whole program re-runs. Phase slides the radius the cosine sees — one radian of it is 1 / 0.35 px of radius — so at 0 the rings stand still, and anywhere else the same rings sit further out. Drag it and the crests travel; at 2π every crest has moved out by exactly one ring, which is the picture you started from. The fade is computed before the slide, on the true radius, so the vignette never moves — only the water does.

subtract the center and (x, y) becomes r — anything you do to r is a circle
Goal: finish the ripple kernel — a cosine wave over the radius, faded toward the edge, tinted blue: this.color(0.4v, 0.75v, v, 1).

Requirements

Hint 1 — the ripple

Math.cos(r * 0.35) swings between −1 and 1 as r grows — 0.5 + 0.5 * cos remaps that to 0…1, a crest roughly every 18 pixels.

Hint 2 — the last three lines
const wave = 0.5 + 0.5 * Math.cos(r * 0.35);
const v = wave * fade;
this.color(0.4 * v, 0.75 * v, v, 1);

Use the fade the starter already computed rather than writing it again — it was measured on the true radius, before the phase slide.

Same idea elsewhere

Radius-and-angle reasoning is everywhere in GPU code: vignette and lens-distortion passes in Metal and WebGPU post-processing, CUDA and ROCm image warps that resample in polar space, every "tunnel" demo ever shipped. Center the coordinates, transform them, color by the result — that opening move never changes. Nor does the next one: add a phase to the transformed coordinate and the picture starts to move, which is all the time uniform in an animated shader has ever done.

All tasks in Pixels from Scratch

  1. Paint with Coordinates
  2. Checkerboard Logic
  3. Plot a Function
  4. Ripples: Think in Polar

This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.