Fog Scattering Playground (r183-inspired)

Atmospheric spotlight demo using exponential fog and additive beam scattering to visualize how light volumetrics react to fog density and penumbra.

Key topics: three.js fog scattering, volumetric spotlight, fogexp2, atmospheric lighting, r183

What this code does

- Exponential Fog Base: `THREE.FogExp2` controls atmospheric thickness and distance fade.
- Spotlight Through Media: a `SpotLight` drives a cone-shaped additive beam to approximate forward scattering.
- Beam Texture Gradient: a procedural canvas gradient reduces harsh banding and keeps the shaft readable.
- Penumbra Interaction: changing spotlight penumbra reshapes soft beam falloff against fog.
- Dynamic Sweep: animated target motion demonstrates directional light scattering changes over time.
- Practical Use: useful as a lightweight volumetric look when full volumetric raymarching is unnecessary.

JavaScript Implementation

ES6+
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

const scene = new THREE.Scene()
scene.background = new THREE.Color(0x0f1628)
scene.fog = new THREE.FogExp2(0x0f1628, 0.08)

const camera = new THREE.PerspectiveCamera(65, width / height, 0.1, 120)
camera.position.set(0, 2.2, 7)

const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(width, height)
document.querySelector('#app').appendChild(renderer.domElement)

const controls = new OrbitControls(camera, renderer.domElement)
controls.target.set(0, 1, 0)
controls.update()

const spot = new THREE.SpotLight(0xbfd8ff, 18, 40, Math.PI / 6, 0.45, 1.2)
spot.position.set(0, 8, 2)
const target = new THREE.Object3D()
target.position.set(0, 0.5, -3)
scene.add(target)
spot.target = target
scene.add(spot)

const beam = new THREE.Mesh(
  new THREE.ConeGeometry(1.8, 11, 48, 1, true),
  new THREE.MeshBasicMaterial({
    color: 0xbad4ff,
    transparent: true,
    opacity: 0.45,
    depthWrite: false,
    blending: THREE.AdditiveBlending,
    side: THREE.DoubleSide
  })
)
beam.position.copy(spot.position)
beam.lookAt(target.position)
scene.add(beam)

function animate () {
  requestAnimationFrame(animate)
  controls.update()
  renderer.render(scene, camera)
}
animate()