Skip to main content

procedural-weather

Generate dynamic procedural weather effects in Three.js using WebGPU compute with WebGL2 fallback. Covers rain (drizzle to monsoon), snow (flurries to blizzard), hail, fog and mist (ground fog, volumetric), lightning (bolt, sheet, cloud-internal), dust/sandstorms, wind-driven debris, rainbows, aurora borealis, and a weather state machine for smooth transitions. All precipitation uses GPU particle systems with collision, splashes, and accumulation. Provides per-effect lighting changes, ambient sound cue hints, and camera post-processing (wet lens, frost). Use when building weather systems, atmospheric effects, environmental ambience, climate simulation, or any scene needing dynamic weather. Triggers: "procedural weather", "rain effect", "snow particles", "lightning", "fog shader", "weather system", "storm", "blizzard", "sandstorm", "aurora", "rainbow", "precipitation", "weather transition", "wet lens".

Source facts

Repository
CK42BB/procedural-weather-threejs
Last source activity
February 8, 2026 at 16:30
Detected SKILL.md language
English
Stars
12
Forks
1

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
procedural-weather
description
Generate dynamic procedural weather effects in Three.js using WebGPU compute with WebGL2 fallback. Covers rain (drizzle to monsoon), snow (flurries to blizzard), hail, fog and mist (ground fog, volumetric), lightning (bolt, sheet, cloud-internal), dust/sandstorms, wind-driven debris, rainbows, aurora borealis, and a weather state machine for smooth transitions. All precipitation uses GPU particle systems with collision, splashes, and accumulation. Provides per-effect lighting changes, ambient sound cue hints, and camera post-processing (wet lens, frost). Use when building weather systems, atmospheric effects, environmental ambience, climate simulation, or any scene needing dynamic weather. Triggers: "procedural weather", "rain effect", "snow particles", "lightning", "fog shader", "weather system", "storm", "blizzard", "sandstorm", "aurora", "rainbow", "precipitation", "weather transition", "wet lens".
# Procedural Weather Dynamic, layered weather effects in Three.js — GPU particle precipitation, volumetric fog, lightning, and smooth state transitions. ## Architecture Overview ``` ┌──────────────────────────────────────────────────────┐ │ Weather System │ │ │ │ WeatherController (state machine) │ │ ├── current state + target state │ │ ├── transition progress (0→1) │ │ └── drives all subsystems: │ │ │ │ ┌─ Precipitation ──────────────────────────────┐ │ │ │ GPU particles: rain, snow, hail │ │ │ │ Ground splashes, accumulation │ │ │ └──────────────────────────────────────────────┘ │ │ ┌─ Atmosphere ─────────────────────────────────┐ │ │ │ Fog, mist, dust, haze │ │ │ │ Volumetric (WebGPU) or exponential (WebGL) │ │ │ └──────────────────────────────────────────────┘ │ │ ┌─ Electrical ─────────────────────────────────┐ │ │ │ Lightning bolts, sheet flashes │ │ │ │ Cloud-internal illumination │ │ │ └──────────────────────────────────────────────┘ │ │ ┌─ Optical ────────────────────────────────────┐ │ │ │ Rainbows, aurora, god rays │ │ │ │ Wet lens, frost overlay │ │ │ └──────────────────────────────────────────────┘ │ │ ┌─ Environment ────────────────────────────────┐ │ │ │ Sky darkening, light color shift │ │ │ │ Wind direction (shared across systems) │ │ │ └──────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────┘ ``` ## Renderer Setup ```javascript import * as THREE from 'three'; async function createRenderer(canvas) { let renderer, gpuAvailable = false; try { const WebGPU = (await import('three/addons/capabilities/WebGPU.js')).default; if (WebGPU.isAvailable()) { const { default: WebGPURenderer } = await import( 'three/addons/renderers/webgpu/WebGPURenderer.js' ); renderer = new WebGPURenderer({ canvas, antialias: true }); await renderer.init(); gpuAvailable = true; } } catch (e) {} if (!renderer) { renderer = new THREE.WebGLRenderer({ canvas, antialias: true }); renderer.toneMapping = THREE.ACESFilmicToneMapping; } renderer.setSize(innerWidth, innerHeight); renderer.setPixelRatio(Math.min(devicePixelRatio, 2)); return { renderer, gpuAvailable }; } ``` ## Shared Wind System Wind drives all weather subsystems — precipitation angle, fog drift, debris direction. ```javascript class WindSystem { constructor() { this.direction = new THREE.Vector3(1, 0, 0.3).normalize(); this.baseSpeed = 5; // m/s this.gustSpeed = 0; this.gustFrequency = 0.5; this.turbulence = 0.1; this._time = 0; } update(dt) { this._time += dt; // Gusts: low-frequency intensity variation const gustEnvelope = Math.sin(this._time * this.gustFrequency) * 0.5 + 0.5; this.gustSpeed = gustEnvelope * this.baseSpeed * 0.6; } get speed() { return this.baseSpeed + this.gustSpeed; } // Wind force vector for particle displacement get force() { const s = this.speed; const turb = new THREE.Vector3( Math.sin(this._time * 2.3) * this.turbulence, 0, Math.cos(this._time * 1.7) * this.turbulence ); return this.direction.clone().multiplyScalar(s).add(turb); } } ``` ## Precipitation System GPU particle system for rain, snow, and hail. Particles spawn in a box above the camera, fall with gravity + wind, and recycle when below ground. ### Particle Buffer Layout ```javascript function createPrecipitationBuffers(maxParticles) { // Each particle: vec4(x, y, z, life) + vec4(vx, vy, vz, size) const positions = new Float32Array(maxParticles * 4); const velocities = new Float32Array(maxParticles * 4); return { positions, velocities, count: maxParticles }; } ``` ### Rain System ```javascript class RainSystem { constructor(scene, options = {}) { this.scene = scene; this.count = options.count ?? 50000; this.spawnRadius = options.spawnRadius ?? 40; this.spawnHeight = options.spawnHeight ?? 30; this.dropLength = options.dropLength ?? 0.3; this.intensity = options.intensity ?? 1.0; // 0-1 this._buildGeometry(); } _buildGeometry() { // Each raindrop: a short line segment (2 vertices) const positions = new Float32Array(this.count * 6); // 2 verts × xyz const randoms = new Float32Array(this.count * 2); // per-drop seed + phase for (let i = 0; i < this.count; i++) { const x = (Math.random() - 0.5) * this.spawnRadius * 2; const y = Math.random() * this.spawnHeight; const z = (Math.random() - 0.5) * this.spawnRadius * 2; // Top vertex positions[i * 6] = x; positions[i * 6 + 1] = y; positions[i * 6 + 2] = z; // Bottom vertex (offset by drop length) positions[i * 6 + 3] = x; positions[i * 6 + 4] = y - this.dropLength; positions[i * 6 + 5] = z; randoms[i * 2] = Math.random(); // seed randoms[i * 2 + 1] = Math.random(); // phase offset } const geometry = new THREE.BufferGeometry(); geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); geometry.setAttribute('aRandom', new THREE.BufferAttribute(randoms, 2, false, this.count)); this.material = new THREE.ShaderMaterial({ uniforms: { time: { value: 0 }, intensity: { value: this.intensity }, windForce: { value: new THREE.Vector3() }, gravity: { value: -15.0 }, spawnHeight: { value: this.spawnHeight }, spawnRadius: { value: this.spawnRadius }, dropLength: { value: this.dropLength }, opacity: { value: 0.35 }, cameraPos: { value: new THREE.Vector3() }, }, vertexShader: RAIN_VERT, // See references/weather-shaders.md fragmentShader: RAIN_FRAG, // See references/weather-shaders.md transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, }); this.mesh = new THREE.LineSegments(geometry, this.material); this.mesh.frustumCulled = false; this.scene.add(this.mesh); } update(time, wind, cameraPos) { this.material.uniforms.time.value = time; this.material.uniforms.windForce.value.copy(wind.force); this.material.uniforms.cameraPos.value.copy(cameraPos); this.material.uniforms.intensity.value = this.intensity; // Recenter spawn box around camera this.mesh.position.x = cameraPos.x; this.mesh.position.z = cameraPos.z; } dispose() { this.scene.remove(this.mesh); this.mesh.geometry.dispose(); this.material.dispose(); } } ``` ### Snow System Snow uses `Points` with per-particle flutter and tumble. Larger particles, slower fall, more wind influence. ```javascript class SnowSystem { constructor(scene, options = {}) { this.scene = scene; this.count = options.count ?? 20000; this.spawnRadius = options.spawnRadius ?? 50; this.spawnHeight = options.spawnHeight ?? 25; this.intensity = options.intensity ?? 1.0; this._build(); } _build() { const positions = new Float32Array(this.count * 3); const seeds = new Float32Array(this.count * 3); // seed, phase, size for (let i = 0; i < this.count; i++) { positions[i * 3] = (Math.random() - 0.5) * this.spawnRadius * 2; positions[i * 3 + 1] = Math.random() * this.spawnHeight; positions[i * 3 + 2] = (Math.random() - 0.5) * this.spawnRadius * 2; seeds[i * 3] = Math.random(); seeds[i * 3 + 1] = Math.random() * Math.PI * 2; seeds[i * 3 + 2] = 0.5 + Math.random() * 1.5; // size variation } const geometry = new THREE.BufferGeometry(); geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); geometry.setAttribute('aSeed', new THREE.BufferAttribute(seeds, 3)); this.material = new THREE.ShaderMaterial({ uniforms: { time: { value: 0 }, intensity: { value: this.intensity }, windForce: { value: new THREE.Vector3() }, gravity: { value: -1.5 }, spawnHeight: { value: this.spawnHeight }, spawnRadius: { value: this.spawnRadius }, flutterAmp: { value: 2.0 }, opacity: { value: 0.85 }, pointSize: { value: 3.0 }, cameraPos: { value: new THREE.Vector3() }, }, vertexShader: SNOW_VERT, // See references/weather-shaders.md fragmentShader: SNOW_FRAG, // See references/weather-shaders.md transparent: true, depthWrite: false, }); this.mesh = new THREE.Points(geometry, this.material); this.mesh.frustumCulled = false; this.scene.add(this.mesh); } update(time, wind, cameraPos) { this.material.uniforms.time.value = time; this.material.uniforms.windForce.value.copy(wind.force); this.material.uniforms.cameraPos.value.copy(cameraPos); this.material.uniforms.intensity.value = this.intensity; this.mesh.position.x = cameraPos.x; this.mesh.position.z = cameraPos.z; } dispose() { this.scene.remove(this.mesh); this.mesh.geometry.dispose(); this.material.dispose(); } } ``` ### Splash / Impact System Ground splashes when rain hits surfaces — instanced ring sprites at impact points. ```javascript class SplashSystem { constructor(scene, maxSplashes = 500) { this.scene = scene; this.max = maxSplashes; const geo = new THREE.PlaneGeometry(0.15, 0.15); geo.rotateX(-Math.PI / 2); this.material = new THREE.ShaderMaterial({ uniforms: { time: { value: 0 } }, vertexShader: SPLASH_VERT, fragmentShader: SPLASH_FRAG, transparent: true, depthWrite: false, }); this.mesh = new THREE.InstancedMesh(geo, this.material, maxSplashes); this.mesh.frustumCulled = false; this.lifetimes = new Float32Array(maxSplashes); this.nextIdx = 0; this.scene.add(this.mesh); } spawn(position) { const dummy = new THREE.Object3D(); dummy.position.copy(position); dummy.position.y += 0.01; dummy.scale.setScalar(0.5 + Math.random() * 0.5); dummy.updateMatrix(); this.mesh.setMatrixAt(this.nextIdx, dummy.matrix); this.lifetimes[this.nextIdx] = 1.0; this.mesh.instanceMatrix.needsUpdate = true; this.nextIdx = (this.nextIdx + 1) % this.max; } update(dt) { for (let i = 0; i < this.max; i++) { if (this.lifetimes[i] > 0) { this.lifetimes[i] -= dt * 4; // ~0.25s lifetime } } this.material.uniforms.time.value += dt; } } ``` ## Fog & Atmosphere ### Exponential Fog (WebGL — built-in) ```javascript function applyFog(scene, density = 0.01, color = 0xcccccc) { scene.fog = new THREE.FogExp2(color, density); scene.background = new THREE.Color(color); } ``` ### Ground Fog (Shader-based) Height-attenuated fog that pools in valleys. Applied as a post-process or scene material. ```javascript function createGroundFog(size = 200, maxHeight = 8) { const geo = new THREE.PlaneGeometry(size, size, 1, 1); geo.rotateX(-Math.PI / 2); const material = new THREE.ShaderMaterial({ uniforms: { time: { value: 0 }, fogColor: { value: new THREE.Color(0xdddddd) }, maxHeight: { value: maxHeight }, density: { value: 0.8 }, windDir: { value: new THREE.Vector2(1, 0) }, windSpeed: { value: 2.0 }, }, vertexShader: ` varying vec3 vWorldPos; varying vec2 vUv; void main() { vWorldPos = (modelMatrix * vec4(position, 1.0)).xyz; vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: GROUND_FOG_FRAG, // See references/weather-shaders.md transparent: true, depthWrite: false, side: THREE.DoubleSide, }); const mesh = new THREE.Mesh(geo, material); mesh.position.y = maxHeight * 0.5; mesh.renderOrder = 999; return mesh; } ``` ### Dust / Sandstorm Dense particle field with color tinting and reduced visibility. ```javascript function createDustStorm(scene, options = {}) { const count = options.count ?? 30000; const color = options.color ?? new THREE.Color(0xc4a060); const positions = new Float32Array(count * 3); const seeds = new Float32Array(count); for (let i = 0; i < count; i++) { positions[i * 3] = (Math.random() - 0.5) * 80; positions[i * 3 + 1] = Math.random() * 15; positions[i * 3 + 2] = (Math.random() - 0.5) * 80; seeds[i] = Math.random(); }
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub