An endless rainforest, grown from a single fragment shader. No textures. No models. Only mathematics, marching through light.
Rainforest is a real-time GPU renderer that conjures an infinite canopy from pure procedural math. A fragment shader raymarches through hash-placed trees, hills carved by fractal noise, volumetric clouds and atmospheric light β and paints the whole scene to the screen at interactive frame rates.
It is a faithful, modern re-implementation of IΓ±igo Quilez's celebrated demo, rebuilt on WebGL2, wrapped in a lean React shell, and extended with temporal reprojection so the image quietly sharpens as the camera drifts through the trees.
- About the Project
- Features
- Tech Stack
- How It Works
- Quick Start
- Interacting
- Project Structure
- Performance Notes
- Credits
- Roadmap
- License
Some scenes are built; this one is grown. Every leaf, branch and ridge in Rainforest is described by distance functions and layered fractal noise β an infinite landscape that costs nothing to store and nothing to stream, because it is computed on demand, one ray at a time.
The result is a place that feels alive: clouds cast drifting shadows across the valley, the canopy shifts with each step of the camera, and the light has that warm, hazy quality of a late afternoon deep in the tropics.
At its core, this project is a study in the art of the possible on the GPU:
- Everything is procedural. Terrain, trees, clouds and sky are generated in GLSL β zero textures, zero meshes.
- Rendering is real time. Rays march through the scene on every frame, in your browser.
- The image refines itself. A temporal accumulation pass blends each frame with its predecessor, converging toward a clean, anti-aliased result.
- Pure procedural world β an infinite rainforest defined entirely by hash functions and fractal Brownian motion. Nothing is ever loaded from disk.
- Signed-distance raymarching β trees are ellipsoid SDFs scattered on a hash grid; the terrain is a fractal heightfield. Both are marched in a single pass.
- Volumetric clouds β a density field raymarched over 128 steps, lit with self-shadowing, drifting slowly with time.
- Temporal accumulation β the previous frame is reprojected using a stored camera matrix and blended at 10%, so the render progressively denoises and anti-aliases itself.
- Two camera modes β free Orbit with smooth damping, or a cinematic Auto fly-through that glides over the canopy on its own.
- Resolution scaling β render the heavy pass at Full, Half or Quarter resolution and let temporal accumulation rebuild the detail.
- Film-style grading β gamma, contrast and a subtle color grade give the scene its moody, cinematic palette.
- Graceful loading β the scene fades in with a soft blur-to-sharp reveal, so the very first moments feel considered.
| Layer | Technology |
|---|---|
| UI runtime | React 19 |
| 3D engine | Three.js r185 |
| Shading | GLSL ES 3.0 (#version 300 es, WebGL2) |
| Build tool | Vite 8 |
| Camera | OrbitControls from three/examples/jsm |
The scene itself lives almost entirely inside src/shaders.js β a single, self-contained chunk of GLSL. The React side exists to orchestrate it: create the renderer, manage the two-pass pipeline, and expose the on-screen controls.
The renderer runs as a two-pass pipeline, bouncing between a pair of framebuffers:
ββββββββββββββββββββββββββββ
hash + noise ββββΆ β buffer pass β
SDF + raymarch β terrain Β· trees Β· clouds β
βββββββββββββ¬βββββββββββββββ
β ping-pong render targets
βΌ
ββββββββββββββββββββββββββββ
β image pass β
β sample + soft vignette β
βββββββββββββ¬βββββββββββββββ
βΌ
canvas
-
Buffer pass β the heavy lifting. A full-screen quad is rendered into an offscreen target at a scaled resolution (Full / Half / Quarter). Each fragment computes the camera rays, marches the scene, shades it, and fogs it by distance.
-
Temporal accumulation β the pass reads the previous frame from the ping-pong target, reprojects it with the camera matrix stored in the buffer's first row, and mixes it in at 10%. Every frame refines the last, giving smooth, near-converged images even while orbiting.
-
Image pass β samples the buffer at native resolution, applies a gentle vignette, and presents the final frame to the canvas.
The whole loop runs inside a single requestAnimationFrame tick, with the shader receiving just four inputs: time, frame number, resolution, and the camera position/target.
All you need is a recent Node.js and your favorite browser.
# install dependencies
npm install
# start the dev server with hot reload
npm run devThen open the printed URL β the rainforest will fade in and begin to breathe.
| Command | Description |
|---|---|
npm run dev |
Start the Vite dev server with HMR |
npm run build |
Build a production bundle into dist/ |
npm run preview |
Serve the production build locally |
Note. The on-screen controls are currently in Spanish β Γrbita means Orbit, and Cargandoβ¦ means Loadingβ¦. The interface is intentionally minimal; the scene is the star.
| Control | Action |
|---|---|
| Drag anywhere | Orbit the camera around the scene |
| Scroll | Zoom in and out |
| Auto / Γrbita toggle | Switch between cinematic fly-through and manual orbit |
| Full / Half / Quarter selector | Scale the render resolution |
Prefer sitting back? Leave it in Auto mode and let the camera take its own tour of the canopy.
rainforest/
βββ public/
β βββ preview.jpg # README banner
βββ src/
β βββ App.jsx # renderer orchestration Β· controls Β· quality scaling
β βββ main.jsx # React entry point
β βββ shaders.js # GLSL β the entire raymarcher + post pass
βββ index.html # app shell, global styles, font
βββ package.json
βββ vite.config.js
There is a deliberate division of labor here: App.jsx owns the lifecycle of the WebGL context, the ping-pong framebuffers, resizing, and the loading reveal β while shaders.js holds every line of shading code, kept isolated and portable.
Raymarching an endless forest is expensive by nature, so a few deliberate choices keep it fluid:
- Scaled buffer pass. The expensive raymarcher renders at a fraction of the screen resolution; only the cheap image pass runs at full resolution.
- Pixel-ratio discipline.
renderer.setPixelRatio(1)keeps high-DPI overdraw in check. - Framebuffer lightweights. Depth and stencil buffers are disabled on the render targets β no wasted bandwidth.
- Half-float precision. When the GPU supports it (
EXT_color_buffer_float/EXT_color_buffer_half_float), the accumulation pass runs in half float for extra stability.
Tip. If your frame rate dips, drop to Quarter and let temporal accumulation quietly rebuild the detail over a few frames.
This project stands on the shoulders of a giant of computer graphics.
- IΓ±igo Quilez β the original A Rainforest demo, whose distance functions, analytical noise derivatives, and shading techniques form the heart of
src/shaders.js. See it on Shadertoy and read his write-ups on raymarching distance fields. - Three.js and Vite β the patient scaffolding underneath.
Ideas on the horizon, in no particular order:
- Time-of-day cycle with shifting sun position and palette
- Seasonal variants β autumn fog, dawn mist, monsoon light
- Stills export at full resolution
- Mobile performance pass
- English / Spanish interface toggle
This project is private and unlicensed β all rights reserved. It is not currently distributed under an open-source license.
The GLSL in src/shaders.js is derived from IΓ±igo Quilez's A Rainforest, originally published on Shadertoy under Creative Commons licensing. Please respect the terms of the original work when reusing any of the shading code.
Made with β€οΈ by SebastiΓ‘n V
