Skip to content

Glass

Glass is a material, not a component. It applies to any SDF shape and to a <Mesh>.

<Sphere radius={1} material={{ shader: 'glass', ior: 1.5, thickness: 0.4 }} />
<Mesh url="/bottle.glb" material={{ shader: 'glass', attenuationColor: '#a05a00' }} />

A glass shape does not shade a surface. It works out where a ray would come out the other side, looks up what the screen already shows there, and returns that — bent, blurred, tinted and coated. Everything it can read is what was drawn before it: particles, text and axes in the background copy, and the other SDF shapes in a pre-pass if you turn that on. The mesh is in neither, so it never shows through SDF glass or reflects in it.

This is the SDF path, fs_refract, in the order it runs. The mesh path is the same with one substitution, noted at the end.

1. Find the entry. The shape raymarches its own surface, exactly as a <Fast> shape does, modifiers included. On a miss it draws a soft silhouette rim and stops.

2. Bend the ray in. With the surface normal — perturbed by grain if set — the ray refracts by 1 / ior.

3. Find the exit. The refracted ray is traced through the volume to the back surface. For a sphere the exit is solved in closed form: one quadratic, exact, and the back normal is radial for free. The quadratic is the bare sphere’s, so a modifier on a glass sphere shapes its front surface but not its exit. For every other shape a short inner march runs from just inside the surface until the distance goes positive again, capped at innerSteps — default 12, at most 40. The length of that path is travelDist; the point is where the second refraction happens.

This is what a rasterised transmission material cannot do — it has no volume to march through. refraction: 'slab' switches it off on purpose: the ray walks thickness from the entry and reads there, with no exit and no second bend — three.js’s model, kept so the two can be compared on the same scene.

4. Bend the ray out. The ray refracts again at the exit with the back normal, by ior. The point exit + direction × thickness is projected to the screen; that screen position is where the background is read. If total internal reflection makes the exit invalid, the read falls back to the pixel’s own position — no bend.

5. Distort. distortion offsets that screen position by a noise field scaled by distortionScale; temporalDistortion drifts the noise over time.

6. Split the channels. chromaticAberration reads red, green and blue at three slightly different positions along the bend, so edges disperse.

7. Read the backdrop. Each read goes through one function that samples the background copy — through a cubic filter when bicubic is on — and, when enableRefraction is on, composites the shape pre-pass over it — but only where the pre-pass depth is at or beyond the glass exit. A shape in front of the glass, or inside its volume, is rejected even when the refracted coordinate lands on it; that is what stops shapes in front from ghosting into it. Without the flag the read is the background copy alone: a shape behind the glass is not seen through it, the glass shows the background in its place.

Where the read lands on a pixel nothing has drawn — the canvas is premultiplied, so empty means transparent — the result is composited over the material’s color, the colour the shader assumes the page to be.

roughness chooses the mip level of that read, so blur is a cheaper texture sample rather than more samples. There are three reads — red, green and blue, each at its own position, which coincide without chromaticAberration. anisotropy replaces each with five along a direction, weighted like a Gaussian, with anisotropyBlur adding mip bias so the smear is creamy rather than a row of copies.

8. Absorb. Beer–Lambert over travelDist: light travelling attenuationDistance through the glass arrives as attenuationColor. A short path barely tints; a thick section darkens toward the colour.

9. Coat the surface. In order, each blending into the result: iridescence (thin-film interference at iridescenceThickness nanometres), metalness (a Fresnel-shaped mirror in the attenuation colour), reflection (a read of the shape pre-pass along the mirrored ray, blurred by reflectionRoughness, kept only where the shape there is nearer than the glass surface — a shape behind the glass refracts but does not reflect), clearcoat (a second Fresnel rim with its own clearcoatRoughness), gloss (a procedural studio environment — sky, ground, two bulbs — along the reflected direction), sheen (a broad soft rim). Then the shape’s own color is multiplied in as a tint, at the strength of its opacity; then the fresnel rim in fresnelColor; then outline darkening at the silhouette.

10. Write depth. The entry point’s depth goes to the depth buffer, so glass sorts against fast shapes and the mesh like any solid.

On a mesh step 3 is replaced: a pre-pass draws the mesh’s back faces into a position texture, and the glass shader reads its exit point from that instead of marching. Otherwise the two shaders run the same stages with the same options.

Four props on <Scene> shape glass. The two flags are off by default and concern the other SDF shapes — the background copy has particles and text but no shapes and no mesh, because those draw after it.

  • enableReflection draws the non-glass shapes into a pre-pass texture so reflection has something to mirror.
  • enableRefraction composites that same texture into the backdrop read in step 7, so shapes show through glass rather than only behind it.

They share the one pre-pass. Turning on the second costs almost nothing once the first is on.

The other two set the quality of what glass reads, for SDF and mesh glass alike. At their defaults nothing changes.

  • prepassScale (default 1, clamped to 0.25–4) sizes the pre-pass at that multiple of the canvas. Above 1, a shape seen through or mirrored in glass keeps more detail under a strong bend, and the pre-pass draws scale² as many pixels. Shape reads are biased by log2(prepassScale) mip levels, so roughness blurs shapes and background alike.
  • bicubic (default false) reads the backdrop through a cubic filter built from four bilinear reads. Magnified refraction comes out smooth instead of blocky; there is no more detail, since the texels are the same.
StageCost
Any glass at allThe frame splits into two passes with a texture copy between them. Three backdrop reads per glass pixel
Either scene flagOne more pass that marches the visible non-glass shapes again, whether or not a glass shape reads it
enableRefractionA depth read per backdrop read, and a shape read where it passes
roughness, anisotropy, anisotropyBlurThe background mip chain — five small passes
reflectionRoughness — or any of the three above with enableRefraction on, since the backdrop read samples the shape texture at the same levelThe shape mip chain — five more, on frames the pre-pass runs
innerStepsOne distance evaluation per step, per glass pixel, for non-sphere shapes under 'volume'
anisotropyFive backdrop reads per channel instead of one — fifteen a pixel. SDF glass reads once per channel while anisotropy is 0; a glass <Mesh> reads fifteen whenever an anisotropy option is in your source
chromaticAberrationArithmetic only — it moves two of the three reads
attenuationColorA log and an exp per pixel. SDF glass skips both while the colour is white
outlineA power per pixel. SDF glass skips it at 0
reflectionA depth read and a shape read per pixel, with enableReflection on
bicubicFour texture reads for every backdrop, shape and reflection read above
prepassScale above 1The pre-pass draws scale² as many pixels
A glass <Mesh>The back-face pre-pass

Every stage you do not use is stripped from the shader at build time. Glass is fifteen independently removable regions, so a scene using only refraction does not ship iridescence — see Lazy subsystems.

Every option with its type and default is in Materials › glass.