← All notes

Three.js

Layered parallax windows with blinds and night glow in TSL

A flat blue rectangle is the thing that makes a house look like a drawing. Modelling a room behind every window is more house than I want to build, and real glass means sorting. The version that holds up from the street is still a flat quad.

This house is that quad, on Three.js 0.186.1, WebGPU and TSL. The page loads windowMaterial.tsl.js beside it. The quad is opaque. Blinds and curtains sit about 8 cm behind the glass, and a blurry room sits about 2.2 m back. As the camera moves, the near layer slides a little and the far one slides a lot, and that difference is the depth. I use AI to write a lot of the node graph. Where the sill sits, and that a daytime room has to stay darker than the brick, comes from 25+ years of JavaScript, game development, modding and level design.

The frame

The shader builds the window's tangent frame from the screen-space derivatives of position and UV. The lengths of those are the width and height in metres, so a PlaneGeometry is enough and the material does not need a size uniform. A layer at depth d is sampled at the fragment's position on the glass, plus the view's sideways travel per metre, times d.

One material is shared by every window. Dressing, colour and whether the light is on come from a hash of the window's world position, quantised to 12.5 cm. Move a window and it reshuffles. A dozen windows is a random draw, so change seed until the row looks right.

const windows = createWindowMaterial({
  dressing: { roller: 2, curtains: 3, venetian: 1, bare: 1 },
  sheerChance: 0.45,
});

Each opening is cut through a 24 cm wall, and the quad sits about 7 cm back in that reveal. A sill and a lintel catch the light around it. The roof is a gable, with an eave, a gutter and a chimney, because a box with a pyramid on it still reads as a diagram. Flush glass with nothing around it still looks painted on.

Day and night

The blinds are lit by the sun, the same as the wall. The room is emissive, so its brightness does not follow the scene lights. By day that term is only the daylight that got in, and it has to stay darker than the brick. A room as bright as the street is the usual way this goes wrong. setWindowNight brings the lamps up. Closed blinds still glow, because the fabric gets the room's light. It is not a hole cut in the curtain.

I leave a little sun and sky light on at night. At zero, the wall and the curtains turn into cut-outs. The glass stays opaque the whole time. Past a steep angle the offset is clamped and the layers stop being right, so the interior fades into a reflection. Glass does that too.

Drag to orbit. Night switches the lamps on. The same quads, day and night.

The frame is that file, and the material next to it. The badge should say WebGPU. Drag right and the house turns with your hand.

WebGPU only, on Three.js 0.186.1, with no WebGL fallback. If the browser has no WebGPU, the page stops. Open it through a server, because file:// will not load the modules. php -S localhost:8000 or npx serve in the folder is enough.

If the slats start to moiré, keep the box filter on the venetian pitch. If the room slides the wrong way, the slope sign is backwards. With the camera to the left of a window, a point straight behind the centre belongs in the left half of the glass.

X Bluesky