Work01 of 02Mock content
Hero Shader
The first screen of this site is a shader you can take apart, built as four stages that each render on their own.
Problem
A portfolio for someone who works with graphics should open with graphics. The easy version of that is a full-screen noise field behind a name, and it is everywhere: it looks the same on every site, it hides the text, and it falls apart on a mid-range phone.
I wanted the opposite. The image had to be legible as an image of the word “foundy” first, behave like ink on paper second, and cost almost nothing before the user touched it. It also had to be something I could explain, because a hero that cannot be explained is only decoration.
The constraints I set before writing any shader code:
- The largest contentful paint is text and SVG, never the canvas.
- Every stage of the effect renders as a standalone image, so it can be shown in isolation later.
- The same page works with WebGL2 and with no GL at all (a static poster).
- A mid-range phone holds 60 fps at the default quality tier.
Choices and alternatives
Shape from a signed distance field. The wordmark is rasterised once into a signed distance field, so every later stage can ask “how far inside the letter is this pixel?” instead of sampling a bitmap. I tried drawing the letters analytically with arcs and boxes, which gave perfect edges but made every wordmark change a maths exercise. An SDF baked from the font was less pure and much easier to live with.
Domain warp for the ink edge. Offsetting the lookup coordinates with two octaves of curl-ish noise gives the edge its bleed. Three octaves looked richer and cost 40 percent more per frame at 1080p; mock the third octave was invisible at the sizes people actually see.
A flow field instead of particles. Particles that scatter the name are the most common move on this kind of site. A velocity field advects the ink instead: the cursor adds force to the field, the field pushes the ink, and the word always settles back into itself. Nothing is spawned, so cost does not scale with how much you wiggle the mouse.
Composite last. The final stage maps the ink density onto a paper texture with a little grain and a faint offset, so the result reads as print, not light.
Renderer. I prototyped the pipeline twice: as three.js TSL nodes (WebGPU with a WebGL2 fallback) and as raw WebGL2 with small GLSL passes. The raw version shipped, because it is far smaller and its stages are already separate passes, which is the shape the inspector needs. The three.js version is kept in the repository as a reference only.
What it does
The page ships a static SVG poster of the wordmark first. When the GL chunk has loaded and compiled, the canvas cross-fades over the poster; if anything fails, the poster simply stays. The pipeline is:
- SDF wordmark. Distance to the letterforms, baked once at load.
- Domain warp. Two octaves of noise bend the lookup coordinates.
- Flow field. A low-resolution velocity grid, updated from pointer motion, advects the ink.
- Ink and paper composite. Density becomes tone, grain and edge softness.
Turn on Inspect at the top of the home page (or press I). A slider peels the hero apart into these four stages, each with a plain-English explanation and, if you want them, the developer numbers. The ink stays live while you look, so you can push it and watch each stage react.
Performance budget
| Item | Budget | Measured |
|---|---|---|
| Render-blocking JS | 0 KB | 0 KB |
| GL chunk, gzipped | 15 KB | 8.2 KB |
| First frame after chunk load | 120 ms | 84 ms mock |
| Frame time, default tier, mid-range phone | 8 ms | 6.2 ms mock |
| Shader compile, cold | 250 ms | 190 ms mock |
The renderer picks one of three quality tiers from measured frame times rather than from a device list. If the 90th-percentile frame interval over a two-second window shows a missed frame in ten it steps the quality tier down, and it pauses entirely when the canvas is off screen or the tab is hidden.
Outcome and limits
The hero holds its frame budget on the devices I tested, the text is always the largest paint, and with GL disabled the page still looks like a poster rather than an error. Each stage can be rendered on its own, which is what the inspector on the home page builds on.
Limits worth stating plainly:
- The WebGPU prototype and the shipped WebGL2 renderer did not produce pixel-identical ink. The difference was small, and WebGL2 is both the reference and the only path the page ships.
- The flow field is low resolution on purpose. Pushing it higher added detail nobody asked for.
prefers-reduced-motionfreezes the flow and keeps a single static frame, which is a decision about comfort, not about performance.- Numbers marked mock are placeholders until a measurement pass on a real phone.