3D black-hole hero (R3F) · cinematic postprocessing
NEXUS Loja Geek
Problem
Geek and gamer brands need a storefront that feels premium, with visual impact that stands out from competitors.
Solution
Concept showcase with an Interstellar-style black-hole hero (React Three Fiber): turbulent accretion disk, gravitational lensing, planet and asteroids, HDRI lighting and cinematic postprocessing. A timeline (Theatre.js) dives the camera into the hole on scroll.
How it was built
The hero doesn't use any off-the-shelf black-hole asset: it's a hand-written GLSL shader (BlackHoleScene.jsx) rendered on a single plane inside React Three Fiber. FBM (fractal Brownian motion) drives the accretion-disk turbulence across two noise layers, a radial profile separates the thin disk from the core, and the photon ring plus a Doppler-style brightening (the approaching side reads lighter) are layered on top, all computed per-pixel with no textures involved. On top of that sits a planet with an additive-fresnel atmosphere and an asteroid field via instancedMesh (38 instances, a single draw call) spinning on its own rotation noise.
The gravitational distortion is a custom post-processing effect, not an off-the-shelf filter: a LensingEffect class that extends Effect from the postprocessing library and plugs into the R3F pipeline via wrapEffect. The lens shader offsets the sampling UV of the whole frame based on distance to the event horizon (deflection ~1/d²), bending stars and nebula around the hole like a real Einstein lens. The camera, in turn, doesn't follow scroll directly. A Theatre.js timeline (heroSheet, with position and FOV keyframes saved in theatre-state.json) is scrubbed (sequence.position = t * SEQ_LEN) proportionally to scrollY, and the result is lerped before being applied to the camera. That's what gives the cinematic dive into the hole without any external scroll library.
GPU cost is treated as a budget, not an afterthought. dpr is clamped to [0.75, 1], antialiasing is off (compensated by the bloom and noise in post-processing), and an IntersectionObserver on the Canvas wrapper toggles frameloop between 'always' and 'never'. Once the hero leaves the viewport, R3F stops rendering frames entirely instead of staying invisible and still burning GPU for the rest of the scroll. It's the same performance reasoning that would apply to a dashboard full of heavy charts: only render what's actually on screen.
Below the hero, the store is pure data and backend-free components. Six catalogs (heroes, comics, series, games, setup, gear) live in src/data/products.js as plain arrays, and ProductCard resolves images through a resilient fallback chain: try the real photo (.jpg, then .png if the first fails), fall back to the hero's vector emblem if there's no photo, and fall back further to a procedural SVG visual (hexagon, core and particles, generated from the accent color) if even that's missing. No card ever breaks on a missing image. The Games section has a client-side category filter (cat) that rebuilds the carousel (key={filter} forces an animated re-entry), and the whole UI has sound: procedural blips via WebAudio (useSound.js) with no audio files at all, with AudioContext created on demand on the user's first gesture, which is how you work around browser autoplay policies.
Pure GLSL black hole
Accretion disk, photon ring and core computed per-pixel with FBM, with no textures involved.
Custom gravitational lensing
A hand-built post-processing Effect (~1/d² deflection) that bends stars and nebula around the event horizon.
Theatre.js scroll-scrubbed camera
Page scroll scrubs a keyframed camera timeline, with no external scroll library.
GPU on a budget
IntersectionObserver kills the R3F frameloop outside the viewport; dpr clamped and antialiasing off.
More screens
Stack
- React
- Vite
- Three.js · R3F
- react-postprocessing
- Theatre.js
- GLSL


