# Glass that remembers what is behind it: transmission in WebGPU and TSL

> How a physical glass material works in React Three Fiber with WebGPU: a backdrop captured every frame, volume refraction, dispersion and a backside pass.

Canonical page: https://andersonmancini.dev/blog/webgpu-glass-material

Published: 2026-09-28 · 3 min read · Tags: three.js, webgpu, tsl, react-three-fiber, shaders

Most glass in a browser is a trick. It is a transparent material with some reflection on top, and it does not do the one thing that makes glass look like glass: bend the world behind it. A checkerboard seen through a glass sphere is not a smaller checkerboard, it is an upside-down, distorted one with rainbow edges.

I wrote a WebGPU version of the kind of material drei calls `MeshTransmissionMaterial`, a physical glass for React Three Fiber, written with TSL. It is a demo and a set of files to copy, and the live demo is at [webgpu-glass-material.vercel.app](https://webgpu-glass-material.vercel.app/). This post explains the three ideas that make it work.

![A glass shader ball on a checkerboard, with refraction, rainbow dispersion and the configuration panel](https://andersonmancini.dev/blog/webgpu-glass-material/cover.webp)

## 1. The glass needs a picture of what is behind it

A pixel of glass has to show what is behind it, displaced. The renderer does not have that picture while it is drawing the glass, so the material makes one. Before each frame, a manager renders the scene a second time, with every glass object hidden, into a render target. That is the **backdrop**. It uses half-float with mipmaps, because the next steps need to sample it blurred.

The glass is then drawn in the main pass and, for every pixel, samples the backdrop. A mesh with `GlassMaterial` registers itself with the manager, and the capture runs with a priority that puts it before the main draw.

## 2. Refraction is a ray that leaves a volume

The refraction does not displace the UV by a fixed amount. It builds the physical path:

- the view direction is bent by the index of refraction,
- the ray is carried through the volume by the thickness, scaled by the model scale,
- the point where it leaves is projected to the screen, and that projection is the place in the backdrop that the pixel will read.

On top of that come the effects that make it read as glass:

| What you see | What is happening |
|---|---|
| rainbow edges | the IOR is slightly different for the red, green and blue channels (`dispersion`), or a spectrum is sampled in stratified steps |
| frost | several samples around the refracted point, jittered in a Vogel disk, and a higher mip of the backdrop when the roughness grows |
| tint in thick parts | volume attenuation, which is a color and a distance, so thicker glass absorbs more |
| reflections on top | the material is a `MeshPhysicalNodeMaterial`, with Fresnel, clearcoat, iridescence, sheen and anisotropy |

The number of samples is baked into the loop bounds when the graph is built, and changing it recompiles the shader. In exchange, the unused iterations never run.

One detail is worth knowing if you write something like this. The mip level for the backdrop comes from derivatives of the refracted UV, and the derivatives are measured once, outside the sample loop. Implicit derivatives inside a loop with a dynamic count produced speckled noise in the mip selection.

## 3. The inside of the glass: a backside pass

A sphere has a front and a back surface, and a thick glass shows the second one through the first. The optional **backside pass** switches the glass materials to render back faces, refracts the clean backdrop through them into a second target, and the front faces then sample that. It is what gives a thick object an inside.

Geometry can also live on a layer that is only drawn in the backdrop, so things like bubbles appear inside the volume and not outside it. The demo uses it for the bubbles in the gelatinous cube.

## What it costs

It is not cheap, and the knobs are the honest part of the API:

- `backdropResolutionScale` and `backsideResolutionScale` render the offscreen passes at a fraction of the canvas size,
- `samples`, from 1 to 16, trades quality for cost,
- `forceSinglePass` skips the double-sided handling when you do not need it.

The demo has a configuration panel with a refraction and dispersion chart, and three scenes: a gelatinous cube, an aquarium and a shader ball. The shader ball is the best one to judge dispersion and roughness.

## Using it

```jsx
<mesh>
  <sphereGeometry args={[1, 64, 64]} />
  <GlassMaterial materialName="MyGlass" />
</mesh>
```

The code is in [the repository](https://github.com/ektogamat/webgpu-mesh-transmission-material) and needs a browser with WebGPU. The [project page](https://andersonmancini.dev/projects/advanced-glass.md) here has a video.

## Related projects

- [Advanced Glass](https://andersonmancini.dev/projects/advanced-glass.md) — Coming soon for WebGPU
