Hi everyone! I’d like to share my personal portfolio site:
I had not written anything interactive for the web in over ten years. Coming back to three.js and seeing how much is now practical in WebGL is what made me want to try again.
Each page is its own scene with its own render pipeline — different post-processing, different GPGPU systems — so this ended up closer to five separate experiments than one site.
Home
A portrait rendered as a GPGPU point cloud, floating in front of a dome of scrolling instanced characters. Hold the mouse button and the particles break apart; release and they settle into the next portrait.
- Two
GPUComputationRenderersystems — one morphs particles between a blob state and the portrait, the other drives the instanced character dome - While the mouse button is held, the low band of the transition sound effect is read back through
AudioAnalyserand drives a multiplier on the particle displacement, so the scatter follows what you are hearing - A stencil mask keeps the character dome outside the face silhouette, so the depth-of-field pass never blurs the portrait
- Screen-space DOF — 8-tap Poisson disc, depth-aware, with logarithmic depth decode
Award
A point-cloud globe with city markers that pop in one by one, linked by spline connectors to an award list. Drag to rotate; hovering a city highlights the matching entries.
- The Earth map is an offscreen render target — a separate GPGPU point field rendered to a texture, then sampled by the globe material
- City markers and labels run a staggered GPGPU transition keyed by latitude and longitude
- The cage and the connectors are custom
LineSegmentsmaterials with per-spline data UVs
Product
A LiDAR-style site scan with agents walking a graph, light trails and drifting dust. Selecting a product expands its thumbnail into a detail panel that fills in cell by cell.
- Multi-pass frame: depth prepass → scan to FBO → chromatic aberration composite → selective bloom (7 render targets, 6-pass separable blur) → frame capture → overlay pass
- CA intensity is driven by camera orbit velocity, so the scan smears while you turn
- Ground height is baked into an R32F render target and sampled to place agents and connections
- Segment lines are CPU-built quad-expanded geometry (4 verts / 6 indices each) for screen-space width without a line library
A pulse wave periodically sweeps out across the site, and the terrain reveals its own structure as the front passes over it.
- The wireframe is not geometry. Edges are recovered from barycentric coordinates and antialiased with
fwidth, so the lines stay one pixel wide at any distance, and face brightness comes from slope using a normal reconstructed from screen-space derivatives of world position - The chromatic split is applied to the barycentric coordinates themselves rather than to a sampled texture, so the RGB fringing follows the grid lines instead of the screen
- A single spring-driven radius is pushed into the ground, the point cloud, the segments, the grid annotations and the thumbnail panels, so one coherent wavefront crosses every material in the scene at once
Sketch
Three procedural surfaces you can switch between: a contour terrain with drifting hills, an elastic grid dented by invisible spheres, and a rippling curtain wall.
- Each pattern is custom vertex displacement paired with a matching depth material, so the DOF pass sees the displaced geometry rather than the flat base mesh
- The simulations run on the CPU and upload as uniform arrays — hole positions and radii, sphere strengths, surface parameters
Contact
Extruded lettering drawn three ways at once: outlines with vertex dots, sketchy overlapping strokes, and colored scribble trails that crawl along the glyph shapes.
- Glyph contours are parsed from raw font outlines and extruded into static geometry
- A Jump Flooding Algorithm builds an SDF of the lettering (seed → 10 step passes → distance) that steers the curve agents
- The agents are a GPGPU simulation writing trail history into a manual ring-buffer render target. The trail geometry is static and reads that ring buffer in the vertex shader, so there is no per-frame CPU geometry work
- The form is real DOM — its CSS
perspectiveis derived from the R3F camera FOV so its parallax matches the 3D scene exactly
Shared across pages
Typography is MSDF everywhere (three-text-geometry + a custom shader) with a scramble-in animation. The frosted panels are a copyFramebufferToTexture of the already-rendered frame, blurred in a shader. Navigation is 3D as well, not a DOM overlay.
Built with three.js 0.183 / React Three Fiber 9 / Next.js.
Happy to go deeper on any of it — questions welcome!











