Rain with Splashes

Fast falling raindrops with wind and fog; reset on ground contact.

Key topics: three.js, particles, rain, weather, fog

What this code does

- PointsMaterial raindrops fall under constant speed with lateral wind.
- Ground plane creates a visual horizon; fog adds depth in the distance.
- GUI controls: fall speed, wind, and raindrop size.

Continue Learning

JavaScript Implementation

ES6+
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
camera.position.set(0, 8, 25)
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(width, height)
document.querySelector('#app').appendChild(renderer.domElement)
// Raindrops as points
const count = 4000
const positions = new Float32Array(count * 3)
const wind = new Float32Array(count * 2)
const fallSpeeds = new Float32Array(count)
const gustPhase = new Float32Array(count)
const gustFreq = new Float32Array(count)
for (let i = 0; i < count; i++) {
  const i3 = i * 3
  positions[i3] = (Math.random() - 0.5) * 200
  positions[i3 + 1] = Math.random() * 120
  positions[i3 + 2] = -180 + Math.random() * 220
  wind[i * 2] = (Math.random() - 0.5) * 6
  wind[i * 2 + 1] = (Math.random() - 0.5) * 6
  fallSpeeds[i] = 32 + Math.random() * 45
  gustPhase[i] = Math.random() * Math.PI * 2
  gustFreq[i] = 0.4 + Math.random()
}
const geo = new THREE.BufferGeometry()
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3))
const mat = new THREE.PointsMaterial({ color: 0x88aaff, size: 2, sizeAttenuation: true, transparent: true })
const rain = new THREE.Points(geo, mat)
scene.add(rain)

function animate () {
  requestAnimationFrame(animate)
  const dt = 1 / 60
  const time = performance.now() * 0.001
  for (let i = 0; i < count; i++) {
    const i3 = i * 3
    const gx = Math.sin(time * gustFreq[i] + gustPhase[i])
    const gz = Math.cos(time * gustFreq[i] + gustPhase[i])
    positions[i3] += (wind[i * 2] + gx) * dt
    positions[i3 + 1] -= fallSpeeds[i] * dt
    positions[i3 + 2] += (wind[i * 2 + 1] + gz) * dt
    if (positions[i3 + 1] < 0) {
      positions[i3] = (Math.random() - 0.5) * 200
      positions[i3 + 1] = 120
      positions[i3 + 2] = -180 + Math.random() * 220
    }
  }
  geo.attributes.position.needsUpdate = true
  renderer.render(scene, camera)
}
animate()