Real-time procedural volumetric clouds rendered with WebGPU. The project combines a compute pass that populates a 3D density cache with a full-screen ray-march render pass, exposing a set of artistic controls via lil-gui.
- WebGPU ray-marched volumetric clouds
- Compute shader density cache for faster rendering
- Adjustable cloud appearance and lighting controls
- Interactive orbit camera with inertia
- Performance panel via
stats.js
- A WebGPU-capable browser. If WebGPU is unavailable, the page renders a simple message instead of the scene.
- Node.js + npm for local development
npm install
npm run devThen open the URL printed by Vite (typically http://localhost:5173).
The UI panel exposes these parameters:
Density: Overall cloud density multiplierCoverage: Macro coverage factorScale: Noise scale (now up to 15)Altitude: Base altitude of the cloud layerDetail: Higher-frequency noise contributionWind Speed: Time scale for cloud motionSkip Light March: Toggles light marching in the shaderRay Steps: Number of ray-march steps per pixelLight Steps: Number of light-march stepsShadow Dark: Shadow intensitySun Intensity: Direct light intensityCloud Height: Vertical thickness of the cloud layerCache Res: Density cache resolution (3D texture size)Cache Update: Update frequency of the cache (every N frames)Cache Smooth: Blend smoothing between cache updates
Camera controls:
- Drag to orbit
- Scroll to zoom
- Compute pass (
shaders/noise.wgsl+shaders/cloud.wgsl):- Writes a 3D density texture (the cache) at a configurable resolution.
- Render pass (
shaders/cloud.wgsl):- Ray-marches through the cached density to shade clouds with lighting.
- Parameter packing (
main.js):- UI values are packed into a uniform buffer and consumed by both passes.
The renderer uses two pipelines and a small set of bind groups to keep per-frame work minimal.
- Compute pipeline (
csentry point): populates a 3D density cache each update. - Render pipeline (
vs/fsentry points): draws a single full-screen triangle and performs ray marching in the fragment shader.
- Uniform buffers:
cameraBuffer(80 bytes): inverse view-projection matrix (64 bytes) + camera position (vec3 + pad).paramsBuffer(96 bytes): packed params used by both compute and render stages.
- Bind groups:
- Group 0:
cameraBuffer,paramsBuffer. - Group 1: sampler + two 3D texture views (for sampling the density cache).
- Group 2 (compute only): storage texture view for writing the current cache.
- Group 0:
- Format:
rgba16float, sizecacheResolution³. - Usage:
STORAGE_BINDING | TEXTURE_BINDING. - Two textures are used for ping-pong:
- One texture is written by the compute pass.
- The other is sampled by the render pass.
- The roles swap based on
cacheUpdateRate, with optional temporal smoothing viacacheSmooth.
- The density cache is sampled with a linear sampler for smooth transitions.
- The render pass uses the two cached volumes and blends between them to avoid popping.
- The camera orbits a target point using spherical coordinates (
theta,phi,dist). - Each frame builds
view,projection, andinverse view-projectionmatrices on the CPU. - The inverse view-projection is used in the fragment shader to reconstruct world-space ray directions.
- Camera motion uses simple exponential smoothing (inertia).
- Update camera smoothing and time-dependent parameters.
- Update uniform buffers via
queue.writeBuffer. - If needed, run the compute pass to refresh the density cache.
- Run the render pass to ray-march clouds into the swap chain texture.
- Increasing
Ray Steps,Light Steps, andCache Rescan be expensive. Cache UpdateandCache Smoothlet you trade temporal stability for speed.
See LICENSE.