ektogamat/webgpu-mesh-transmission-material: A high-end refractive glass material that brings the spirit of Drei’s Mesh Transmission Material int

github.com · 2026-08-04 · by Long Nguyen ·github
A high-end refractive glass material that brings the spirit of Drei’s Mesh Transmission Material into the WebGPU era. Built with Three.js node materials (TSL), a custom volume-refraction lighting model, and a multi-pass backdrop pipeline that samples the real scene behind the glass — including an optional backside pass for true thickness. - ektogamat/webgpu-mesh-transmission-material

ektogamat/webgpu-mesh-transmission-material

A high-end refractive glass material that brings the spirit of Drei’s Mesh Transmission Material into the WebGPU era. Built with Three.js node materials (TSL), a custom volume-refraction lighting m...

2 stars · 0 forks · 0 watchers

README

WebGPU Glass Material Mesh Transmission Material for React Three Fiber — powered by WebGPU & Three.js TSL

A high-end refractive glass material that brings the spirit of Drei’s Mesh Transmission Material into the WebGPU era. Built with Three.js node materials (TSL), a custom volume-refraction lighting model, and a multi-pass backdrop pipeline that samples the real scene behind the glass — including an optional backside pass for true thickness. Live demo: webgpu-glass-material.vercel.app

Why this glass looks incredible Most “glass” in the browser is either a cheap opacity trick or a screen-space refraction that ignores thickness, attenuation, and chromatic spread. This material treats transmission as a physical volume:

What you see What’s happening under the hood

Distorted checkerboard through the mesh Ray-exit refraction projected into screen UVs, then sampled from a dedicated backdrop buffer

Rainbow edges / prism split Per-channel IOR spread (R / G / B) driven by dispersion, with an optional spectral sampling mode

Soft frosted look Multi-sample thickness smear + roughness-aware LOD on the backdrop mips

Colored depth / tint in thick regions Beer–Lambert style volume attenuation (attenuationColor × attenuationDistance)

Reflections that sit on top of the refraction Full MeshPhysicalNodeMaterial stack (Fresnel via DFG LUT, clearcoat, iridescence, sheen, anisotropy)

Internal “second surface” Optional backside pass: back faces refract the clean scene into a second target that the front faces then sample

All of that runs on WebGPU via THREE.WebGPURenderer and TSL nodes — not a hand-rolled GLSL string glued onto WebGL.

Stack

React 19 + React Three Fiber 9 Three.js (three/webgpu + three/tsl) @react-three/drei for helpers / environment Vite for the demo app Custom UI panel for live tuning (GlassPanel)

Requires a browser with WebGPU support (Chrome / Edge / Safari recent builds, etc.).

Advanced techniques 1. Custom transmission lighting model GlassPhysicalNodeMaterial extends MeshPhysicalNodeMaterial and swaps in TransmissionPhysicalLightingModel. Instead of Three’s default IBL volume refraction, the lighting model injects a custom backdrop node into the builder context and blends it with the physical transmission term. Specular / Fresnel still come from the physical stack; only the transmitted light is replaced by the screen-space volume path. 2. Multi-pass transmission backdrop Every frame (priority -1, before the main draw), TransmissionBackdropManager captures what sits behind the glass:

Clean pass — hide all glass meshes, render the scene into cleanTarget (HalfFloat + mipmaps). Geometry on TRANSMISSION_BACKDROP_LAYER can appear only in this buffer (e.g. bubbles inside the gelatinous cube). Backside pass (optional) — flip glass materials to BackSide, temporarily use backsideThickness, refract the clean buffer into backsideTarget. Main pass — front faces sample the final backdrop texture through the volume refraction node.

Tone mapping is disabled during backdrop capture so the buffer stays linear and composable with the final ACES-tonemapped frame. 3. Volume refraction in TSL transmissionNodes.js builds the refraction path entirely with TSL (Fn, Loop, refract, screen projection, etc.):

Volume transmission ray — refract view direction by IOR, scale by thickness × model scale, exit the volume in world space, project to NDC → backdrop UVs. Chromatic dispersion — spread IOR across R/G/B (classic three-tap) or walk a stratified spectrum with lobe weights (spectralDispersion). Multi-sample blur — Vogel disk jitter in a tangent frame + thickness smear for frosted / anisotropic blur. Sample count is baked into the loop bounds (recompiled when samples changes) so unused iterations never run. Explicit backdrop LOD — screen-space footprint of the refracted UV is measured once in uniform control flow (dFdx / dFdy) before the sample loop. Implicit derivatives inside dynamic loops produced speckled mip noise; this avoids that. Hardware trilinear taps — roughness maps into mip LOD via applyIorToRoughness, preferring a single .level() read over expensive bicubic reconstruction. Ordered dither — interleaved gradient noise phases the thickness smear so high-contrast backdrops don’t sparkle. Temporal / spatial distortion — optional triNoise3D offset on the normal for gel / liquid motion.

4. Physical surface stack On top of transmission, the material exposes the full physical surface toolkit:

Roughness / metalness / specular Clearcoat + clearcoat roughness Iridescence (IOR + thickness range) Sheen Anisotropy Environment map intensity Base color tinting the transmitted light

5. Performance knobs that actually matter

backdropResolutionScale / backsideResolutionScale — render the expensive offscreen passes at a fraction of the canvas size. samples (1–16) — quality vs. cost; loop is recompiled to the exact count. forceSinglePass — skip double-sided material complications when you don’t need them. Half-float render targets with generated mipmaps for cheap roughness blur.

6. Demo extras

Three scene presets: Gelatinous Cube, Aquarium, Shader Ball — each with its own glass defaults. Instanced transmission bubbles that live on the backdrop-only layer so they refract inside the volume. PCSS contact shadows (TSL) for soft grounding under the glass. Live WebGPU Glass Config panel with a refraction / critical-angle chart.

Quick start npm install npm run dev Open the local Vite URL in a WebGPU-capable browser. npm run build # production build npm run preview # preview the build

Using the glass material Attach GlassMaterial as a child of any mesh (R3F attach="material" pattern): import GlassMaterial from "./glass/GlassMaterial";

function MyGlassMesh() { return ( <mesh> <sphereGeometry args={[1, 64, 64]} /> <GlassMaterial materialName="MyGlass" /> </mesh> ); } The component:

Creates a GlassPhysicalNodeMaterial with transmission uniforms. Registers itself with the backdrop manager for the current WebGPU renderer. Captures the backdrop each frame. Syncs live values from glassSettings (and the config panel / __glassSettings in dev).

Global defaults live in src/glass/glassSettings.js. Mutate via setGlassSetting(key, value) or the panel; subscribers (including the material) update automatically. import { setGlassSetting, glassSettings } from "./glass/glassSettings";

setGlassSetting("ior", 1.45); setGlassSetting("dispersion", 8); console.log(glassSettings.thickness); In development, globalThis.__glassSettings mirrors the same object for console tweaking.

Material parameters Defaults below match glassSettings / glassDefaults. Ranges follow the live config panel schema. Optics / transmission

Parameter Type Default Range Description

color string (hex) '#c9ffa1' — Base albedo; tints transmitted light

ior number 1.25 1 – 2.5 Index of refraction (air ≈ 1, water ≈ 1.33, glass ≈ 1.5)

thickness number 3.5 typically -2 – 2 in UI (scene-dependent) Optical path length through the volume; drives refraction offset and attenuation distance

dispersion number 6.5 0 – 20 Chromatic aberration strength (per-channel IOR spread)

transmission number 1 0 – 1 How much light is transmitted vs. opaque

anisotropicBlur number 0.47 0 – 1 Extra thickness smear / frost independent of roughness (internalBlur alias)

attenuationDistance number 0.5 0 – 10 Distance over which attenuation color fully absorbs

attenuationColor string (hex) '#ffffff' — Beer–Lambert absorption tint

envMapIntensity number 0.85 0 – 5 Strength of environment reflections / IBL

specularIntensity number 1 0 – 1 Specular reflectance strength (panel label: Reflectivity)

specularColor string (hex) '#ffffff' — Specular tint

spectralDispersion boolean false — If true, stratified spectral lobes instead of fixed R/G/B taps (recompiles the node graph)

Surface

Parameter Type Default Range Description

roughness number 0 0 – 1 Microfacet roughness; also raises backdrop mip LOD

metalness number 0 0 – 1 Metallic blend

clearcoat number 0.2 0 – 1 Clearcoat layer weight

clearcoatRoughness number 0 0 – 1 Clearcoat roughness

iridescence number 0.45 0 – 1 Thin-film iridescence weight

iridescenceIOR number 1.14 — Thin-film IOR

iridescenceThicknessMin number 0 — Thin-film thickness range (nm-style units used by Three)

iridescenceThicknessMax number 1000 — Thin-film thickness range max

sheen number 0 — Sheen weight

sheenRoughness number 0.29 — Sheen roughness

sheenColor string (hex) '#ffffff' — Sheen tint

anisotropy number 0.1 — Specular anisotropy

anisotropyRotation number 0 — Anisotropy angle

Distortion

Parameter Type Default Range Description

distortion number 0 0 – 1 Strength of 3D noise bent into the refraction normal

distortionScale number 0.3 0 – 2 Spatial frequency of the distortion noise

temporalDistortion number 0.2 0 – 1 How fast the noise scrolls over time (gel / liquid feel)

Performance / backdrop

Parameter Type Default Range Description

samples number (int) 8 1 – 16 Transmission sample count; baked into the TSL loop (recompile on change)

backside boolean true — Enable second pass that renders back faces into the backdrop

backsideThickness number 1.5 0 – 10 Thickness used only during the backside pass

backdropResolutionScale number 0.75 0.25 – 1 Resolution scale of the (backside / final) backdrop target

backsideResolutionScale number 0.5 0.25 – 1 Resolution scale of the clean pass when backside is on

forceSinglePass boolean false — Force single-pass material rendering

bg string (hex) '#839681' — Solid fill used as scene background while capturing the backdrop

Fresnel helpers (settings object)

Parameter Type Default Description

fresnelIntensity number 0.12 Extra fresnel emphasis used by tooling / presets

fresnelColor string (hex) '#f2f7ff' Fresnel highlight tint for presets

Architecture (source map) src/glass/ GlassMaterial.jsx # R3F material component + GlassPhysicalNodeMaterial glassSettings.js # Shared defaults, apply + subscribe API transmissionBackdrop.js # Clean + backside render-target manager transmissionNodes.js # TSL volume refraction / dispersion / sampling TransmissionPhysicalLightingModel.js # Injects backdrop into PhysicalLightingModel

src/glass-panel/ # Live WebGPU Glass Config UI src/scenes/ # Gelatinous Cube / Aquarium / Shader Ball src/bubbles/ # Instanced bubbles on TRANSMISSION_BACKDROP_LAYER src/canvas/ # WebGPU renderer, lighting, PCSS shadows

Key exports for reuse

Export From Role

GlassMaterial glass/GlassMaterial.jsx Drop-in R3F material

glassSettings / setGlassSetting / applyGlassSettings glass/glassSettings.js Live parameter store

TRANSMISSION_BACKDROP_LAYER glass/transmissionBackdrop.js Layer for geometry visible only in the backdrop

captureTransmissionBackdrop glass/transmissionBackdrop.js Manual capture hook (already called from GlassMaterial)

buildTransmissionBackdropNode glass/transmissionNodes.js Build the TSL refraction node yourself

Scenes in the demo

Scene Idea

Gelatinous Cube Soft organic volume, internal bubbles on the backdrop layer, heavy distortion-friendly look

Aquarium Clearer water-like IOR / thickness, scene content refracted through a tank

Shader Ball Classic studio shader ball on a checker pedestal — best for judging dispersion, roughness, and reflections

Presets live in src/scenes/scenePresets.js and overwrite glassSettings when you switch scenes.

Browser / renderer notes

Create the canvas with THREE.WebGPURenderer (see src/canvas/createWebGPURenderer.js). Call await renderer.init() before the first frame. Tone mapping: ACES Filmic on the main path; NoToneMapping during backdrop capture. Glass meshes are temporarily hidden while the clean buffer is rendered, then restored — do not rely on them being visible mid-capture. Changing samples or spectralDispersion rebuilds the material node graph; expect a short hitch.

Credits Created by Anderson Mancini — a WebGPU evolution of the Mesh Transmission Material approach popularized for React Three Fiber / Drei.

License Check the repository license file for usage terms. If none is present, contact the author before redistributing the material in a commercial product.

source

github.com · 1 images · 104 links

Comments (0)

No comments yet