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.
How a glass pixel is made
Section titled “How a glass pixel is made”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.
The scene props
Section titled “The scene props”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.
enableReflectiondraws the non-glass shapes into a pre-pass texture soreflectionhas something to mirror.enableRefractioncomposites 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(default1, 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 bylog2(prepassScale)mip levels, soroughnessblurs shapes and background alike.bicubic(defaultfalse) 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.
What each stage costs
Section titled “What each stage costs”| Stage | Cost |
|---|---|
| Any glass at all | The frame splits into two passes with a texture copy between them. Three backdrop reads per glass pixel |
| Either scene flag | One more pass that marches the visible non-glass shapes again, whether or not a glass shape reads it |
enableRefraction | A depth read per backdrop read, and a shape read where it passes |
roughness, anisotropy, anisotropyBlur | The 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 level | The shape mip chain — five more, on frames the pre-pass runs |
innerSteps | One distance evaluation per step, per glass pixel, for non-sphere shapes under 'volume' |
anisotropy | Five 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 |
chromaticAberration | Arithmetic only — it moves two of the three reads |
attenuationColor | A log and an exp per pixel. SDF glass skips both while the colour is white |
outline | A power per pixel. SDF glass skips it at 0 |
reflection | A depth read and a shape read per pixel, with enableReflection on |
bicubic | Four texture reads for every backdrop, shape and reflection read above |
prepassScale above 1 | The 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.