跪拜 Guibai
← All articles
Frontend · WebGL · Three.js

How Fluid Glass Pulls Off True 3D Refraction in the Browser

By 我的刀盾 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

SVG-filter glass looks cheap — jagged edges, wrong colors, no real depth. This approach gives WebGL UIs a physically accurate refractive material that works on arbitrary HTML-backed content, not just pre-rendered images, and the single-FBO technique keeps it to one render pass instead of the four-pass pipelines common in shader art.

Summary

The Fluid Glass component from React Bits renders a physically plausible glass lens, cube, or navigation bar that refracts whatever sits behind it — text, images, a full scroll gallery — in real time. Instead of faking the effect with SVG displacement maps or post-processing blurs, it isolates the background into a separate Three.js scene, renders that scene to an FBO every frame, then feeds the resulting texture into MeshTransmissionMaterial on a 3D mesh. The shader offsets texture lookups based on surface normals, index of refraction, thickness, and separate RGB channel dispersion.

Three modes ship out of the box: a cylindrical lens that follows the pointer, a multi-face cube, and a bottom-locked bar for frosted-glass nav. All models are preloaded as GLB files to avoid flicker on mode switch. Pointer tracking bypasses R3F’s event system and computes NDC coordinates directly from the canvas bounding rect, because ScrollControls hijacks standard pointer events.

A full-page scroll gallery sits behind the glass, with zoom driven by Drei’s useScroll ranges. Typography and nav items adapt font sizes and spacing across mobile, tablet, and desktop breakpoints via a resize listener. The entire pipeline runs in a single Canvas with a narrow 15° FOV camera at Z=20, placing the glass at Z=15 so it sits optically between the viewer and the background plane.

Takeaways
Background text and images live in an isolated off-screen Scene mounted via createPortal; they never touch the main canvas directly.
Every frame, that off-screen Scene is rendered into an FBO, producing a texture that becomes the refraction sample source for the glass mesh.
MeshTransmissionMaterial performs per-pixel texture lookups offset by surface normals, IOR, thickness, and separate R/G/B chromatic aberration shifts.
Pointer tracking uses raw pointermove events and canvas.getBoundingClientRect to compute NDC, because ScrollControls swallows standard R3F pointer events.
Three GLB models (lens cylinder, cube, bar) are preloaded with useGLTF.preload to eliminate flicker when switching modes.
Adaptive scaling kicks in when no explicit scale prop is set: the mesh sizes itself to 90% of the viewport width at Z=15, capped at 0.15.
A full-screen background plane with meshBasicMaterial displays the FBO texture 1:1 so the glass appears to refract the actual scene rather than a blank backdrop.
The Bar mode locks to the viewport bottom and disables pointer follow, making it usable as a frosted-glass navigation bar with configurable nav items.
Scroll-driven image zoom uses Drei’s useScroll range values mapped to a custom zoom property on each Image mesh’s material.
Conclusions

The single-FBO approach is notably lean compared to the four-pass offscreen pipelines used in shader-art glass editors; it sacrifices multi-bounce realism for runtime performance that fits inside a scroll-driven page.

Baking the entire background scene to a texture every frame means the glass refracts live DOM-backed content (text, scroll galleries) rather than static images — this is the key difference from the liquid-glass-studio project, which only works on pre-rendered pictures.

Bypassing R3F’s pointer system and computing NDC manually is a practical workaround for a common pain point: Drei’s ScrollControls hijacks events, so any interactive 3D object inside a scroll scene needs its own raw DOM event listener.

The adaptive scaling logic — measuring the GLB bounding box and fitting it to 90% of the viewport width at the glass depth plane — is a reusable pattern for any 3D UI component that needs to size itself correctly across viewports without hardcoded scale values.

Concepts & terms
FBO (Framebuffer Object)
An off-screen render target in WebGL. Instead of drawing to the screen, the GPU renders a scene into a texture stored in video memory, which can then be sampled by other shaders — here, the glass material reads it as its refraction source.
MeshTransmissionMaterial
A Drei material that simulates volumetric light transmission through a 3D mesh. Its fragment shader offsets UV lookups into a background texture based on surface normals, index of refraction (IOR), thickness, anisotropy, and per-channel chromatic aberration.
createPortal
A React Three Fiber function that mounts a subtree of R3F elements into a different Scene than the default one. Used here to isolate background content into an off-screen scene that gets rendered to the FBO.
NDC (Normalized Device Coordinates)
A coordinate system where the screen maps to a [-1, 1] range on both axes, with (0,0) at the center. Pointer positions are converted to NDC to compute world-space positions at a given depth plane in a perspective camera.
Chromatic aberration (in shader context)
Simulating the physical effect where different wavelengths of light refract at slightly different angles. The shader samples the R, G, and B channels from the background texture at incrementally offset UV coordinates, producing color fringing at glass edges.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗