RelightImage

A lamp moved across a printed photograph, finding the relief in the impression.

move the cursor over the picture

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

Options

light #fff3df

css colour

Colour of the lamp. Keep it close to white, warm or cool. It is painted over the picture as a sheen rather than mixed into it, so a saturated colour tints the highlights rather than reading as illumination.

height 0.32

0.02 to 2

How far above the sheet the lamp is held, as a share of the frame's height. Low is a raking light that finds every ridge in the impression; high is a lamp overhead that finds almost none. This is the first dial to reach for and most of the character is in it.

relief 4

0 to 30

How much relief the impression has. At 0 the sheet is flat and all you have is a soft gradient moving about. Past about 15 the paper stops reading as paper and starts reading as hammered metal.

smooth 4

0.5 to 8

Pixels either side the gradient is taken across. Low-passes the height field on the way, so this is the difference between lighting the shape of the impression and lighting the noise in the file. Below about 1.5 you are mostly lighting JPEG blocks.

strength 0.45

0 to 1.5

How much modelling the lamp lays over the picture. The photograph is exactly itself at the midpoint of the light, so this opens the gradient out either side of it rather than re-exposing anything.

ambient 0.35

0 to 1

How much light the room has already, which is the point either side of which the lamp works. The light a lamp delivers across a frame averages nothing like a half, so leaving the neutral at a half dims the whole picture before it lights anything. Near the average gives you a gradient; at 0 the lamp only ever adds.

gloss 0.18

0 to 1

How much the ink catches the light that the paper does not. This is the part that says the dark areas are ink rather than dark paper, and it is what separates this from a gradient.

shine 26

2 to 160

How tight that catch is. Low is a broad satin sheen; high is a small hard glint that only appears where a ridge faces the lamp exactly.

reach 1.1

0.1 to 3

How far the lamp throws, as a share of the frame's height. Small is a reading lamp held close with the corners falling away; large is a window on the far side of the room.

grain 0.25

0 to 1

Paper tooth over the whole thing.

What it needs

Category
Surfaces, an effect
Dependencies
None at all
Browser
WEBGL2
Frame budget
~0.8 ms · 1 context
Pinned to
v0.12.0
  • The picture is the host element's own <img> child. It is hidden from sight and left in the document, so the alt text is whatever you wrote and a page with no JavaScript still shows it
  • This lights the print rather than the scene. There is no depth in a photograph and no honest way to get one out of a single frame, so the height field is the picture's own luminance and what the lamp finds is relief in the sheet
  • Known limit, and the reason the modelling is laid over the picture rather than replacing it: lighting already in the photograph becomes relief, so a cast shadow across a wall turns into a step in the paper. A flat-lit image is the one this flatters most
  • The gradient is taken across several pixels rather than one, which low-passes the height field on the way and is what keeps sensor noise and compression blocks out of the normal
  • Nothing is integrated against the previous frame. The lamp is exactly where the pointer is, so renderAtTime is pure in t and a scripted path replays identically
  • One WebGL2 context, one full-screen triangle, no buffers and no attributes
  • Five texture reads per pixel: one for the picture and four for the central differences the normal is built from
  • No second pass and no render target. The normal is computed where it is used rather than baked, which costs four reads and saves a framebuffer
  • The image uploads once, on load. Nothing is uploaded per frame