Live page: https://smirnov-artur.github.io/webgl/lattice/
There's no 3D model behind this page — no mesh, no textures, no .glb file. The lattice is a graded gyroid, a triply-periodic minimal surface, and it exists purely as a signed distance function (SDF): a formula that, given any point in space, returns how far that point is from the surface. The whole scene is one WebGL2 fragment shader that "sphere-traces" that function — walks a ray forward by the returned distance, over and over, until it's close enough to the surface to count as a hit. Raw WebGL2, no three.js, no library at all: one inline <script>, 31.8 KB of source.
The geometry is a lie, in the best way
The only thing actually submitted to the GPU is a single full-screen triangle, generated in the vertex shader straight from gl_VertexID — no vertex buffer, no attributes, nothing to upload. The page's own on-screen HUD confirms it: geometry: 0 B. Every bit of surface you see on the lattice sphere is computed per-pixel in the fragment shader, not stored anywhere.
I wrapped gl.drawArrays to count how many times it's actually called per frame: 7 — one for the SDF scene pass, and six for the post-processing chain (a bright-pass, a two-level separable Gaussian blur, then a composite that does ACES tonemapping, per-channel chromatic aberration, vignette, and film grain). The HUD's own draw / passes readouts agree.
Making it look like metal, not plastic
The raymarch itself wasn't the hard part — the hard part was getting the surface to read as tempered steel instead of gray plastic. Two tricks did most of the work:
- Dispersion per color channel. Instead of one refraction path, the shader traces three slightly offset paths through the same field, one per channel — that's what gives the edges the faint color fringe you'd expect from real glass or stressed metal.
-
Radial grading, one
smoothstep. The lattice cell size shrinks toward the core and opens up at the skin — dense in the middle, sparse at the edge — driven by a singlesmoothstepon the cell frequency term. No second field, no extra noise layer.
Numbers, measured on this page, not claimed
I don't trust "buttery smooth" as a spec, so here's what I actually measured on this machine (RTX 3050, 1250×1276 canvas, DPR 1) — the page's own live HUD agrees with all of it:
-
60 fps steady, vsync-capped — confirmed two ways: the page's own EMA-smoothed frame-time readout, and an independent
requestAnimationFramecounter over a 6-second steady-state window - ~65 KB total transferred for the entire page — 15 KB gzip for HTML+CSS+JS+shaders combined, ~50 KB for two subsetted variable fonts, zero images, zero model files
- 3 quality tiers (56 / 80 / 88 raymarch steps for low/mid/high), chosen automatically: first a probe for a software-rasterizer fallback (drops straight to low), then touch input + screen size (drops to mid)
Where it breaks
Antialiasing is off on purpose — the shader leans on device-pixel-ratio and the post pass instead of MSAA, so if you force DPR down, the lattice edges alias visibly. If WebGL2 isn't available at all, there's a static fallback panel instead of the canvas; I've only verified that path on evergreen browsers, not on anything old enough to actually need it. And running a raymarcher flat-out at 60fps draws real, continuous GPU power — I haven't added a frame cap or measured battery impact on a laptop.
Happy to go deeper into the distance field, the tiering logic, or the dispersion shader if anyone's curious.
Top comments (0)