Experiment 08
3D Glass
A three.js render as the displacement map — 3D forms refracting live HTML
Published: Aug 12, 2026
Updated: Oct 14, 2026
Experiment 08
A three.js render as the displacement map — 3D forms refracting live HTML
Published: Aug 12, 2026
Updated: Oct 14, 2026
Open to new projects, ideas, and conversations
Experiment 08
A three.js render as the displacement map — 3D forms refracting live HTML
Published: Aug 12, 2026
Updated: Oct 14, 2026
Liquid Glass bends the live page through a browser filter, steered by a displacement map: an image whose colours say how far, and in which direction, to shift each pixel underneath it. That map is a rounded rectangle drawn by code, so the glass can only ever be that one shape. I wanted to know whether a 3D model could be the map instead, so that any form, even a moving one, could become glass over a real page.
Here the GPU draws a fresh map from a Three.js model every frame. What it bends is still the page itself, not a picture of it: the text under the glass is real text, and you can select it straight through the lens.
For every pixel the model covers, the shader follows a ray of light into the glass, bends it the way glass would, and measures how far it drifts sideways before it comes out the other side. That drift is written as colour: red for left and right, green for up and down, and middle grey for no shift at all, which is why the map beside the lens is grey wherever the model isn't.
// Bend the view ray as it enters the glassvec3 R = refract(-V, N, 1.0 / uIor);// How far it drifts sideways while crossing the glassvec2 offset = (R.xy / max(-R.z, 0.15)) * thick * uViewScale;An earlier pass measures how thick the model is at each point, so the bend grows where light crosses more glass. Without it a solid shape and a hollow shell look identical, and the model reads as a sticker rather than an object. IOR sets how strongly the glass bends light; Thickness sets how much that measured depth counts.
The outline took the longest to get right. A rendered edge is normally softened by blending its pixels with the background, which is right for a picture and wrong for a map: a half-covered edge pixel carries only half the shift, the filter pulls the page from the wrong place there, and a dark ring appears around the glass. The fix is three steps, and dropping any one of them brings the ring back:
Much of Liquid Glass's look comes from a thin white rim. The first version here stored its highlight inside the map, but the map is only a few hundred pixels across and gets stretched over a lens several times that size, so the rim came out as a soft grey smudge. The highlight never needs to pass through the filter, since it is just a white layer on top, so it is now drawn separately, straight to the screen at full resolution, and shown on the same frame as the map it belongs to.
It traces a line a pixel and a half wide wherever the surface falls away behind an edge: around the outline, and where the torus knot passes in front of itself. As in Liquid Glass, it is brightest on the edges that face along the light's direction. Lighting every edge where two faces meet made the cube look like a wireframe, so those are left to the refraction, which already breaks at each one.
The browser's filter can't read straight from the GPU, only from an image, so every frame the map is copied back, saved as a PNG and decoded again. The Map pipeline figure under the panels is what that costs. An earlier version recorded the loading animation's loop once and played the saved frames back, which was cheaper, but a recording isn't a live model, and that was the point.
The filter itself got lighter. Liquid Glass runs thirteen passes over the whole lens every frame, most of them its colour fringing and its highlight. Here the highlight and the frost are ordinary layers on top of the filter rather than steps inside it, and the fringing is off unless the Chroma slider asks for it, which leaves the four counted under the panels.
Any 3D form can be glass over a live page, at full frame rate. Whether it reads as glass, though, is decided almost entirely at the edges: a hard outline in the map, and a rim of light sharp enough to see. The dark ring, the grey rim and the wireframe cube were each obvious at a glance and invisible in the numbers.