LOG 02 · RENDERING

WebGL & Three.js

Hand-written GLSL shaders and Three.js scenes for custom 3D web experiences — nebulas, starfields, and real-time lighting, built from scratch rather than assembled from a template.

Shaders, not textures

The nebula backdrop behind this globe isn't a texture — it's a fragment shader running fractal Brownian motion (fbm) noise in real time, layered with a second noise pass to blend two color palettes across the sphere. Stars aren't sprites either: each is a GPU point rendered with per-vertex phase offsets so they twinkle independently, at a fraction of the draw calls a sprite-based approach would cost.

Writing shaders directly means the exact look — color, motion speed, glow falloff — is tunable to the value, not limited to whatever a pre-built particle library happens to expose as an option.

Real assets, real lighting

3D models load as compressed GLTF/DRACO assets, decoded client-side, with a fallback primitive swapped in until the real model finishes streaming — so the scene is never empty while a multi-megabyte asset loads on a slow connection. Materials that arrive without their own texture map get a standard material with tuned roughness/metalness/emissive values, rather than rendering flat and unlit.

Lighting is a small deliberate rig — one key directional light, one dim rim light from the opposite side — rather than relying on ambient light alone, which is what gives the globe actual form instead of looking like a flat green circle.

One camera, informed decisions

Every WebGL scene I build shares a single PerspectiveCamera across all rendered content, which matters more once a scene has multiple moving parts (see the CSS3D log entry) — one source of truth for position and field of view keeps everything in the scene, DOM or WebGL, in true perspective agreement.