Volumetric Light Shafts

Fake god rays with additive cone; adjust density and decay.

What this code does

- Fake Volumetric Lighting: simulates god rays using a translucent cone mesh with additive blending.
- ConeGeometry: open cone (no bottom) creates light shaft geometry from point light source.
- Additive Blending: overlapping rays add together for bright concentration effects.
- Depth Write Disabled: prevents cone from blocking other transparent objects.
- Animated Occluder: rotating cube creates dynamic shadow patterns in light shafts.
- Interactive Controls: adjust ray density (opacity) and decay (scale falloff) in real-time.

JavaScript (plain)

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

const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(70, width / height, 0.1, 200)
camera.position.set(0, 2, 6)

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, 0.5, 0)
controls.update()

const plane = new THREE.Mesh(
  new THREE.PlaneGeometry(20, 20),
  new THREE.MeshStandardMaterial({ color: 0x0f0f0f })
)
plane.rotation.x = -Math.PI / 2
plane.receiveShadow = true
scene.add(plane)

const occluder = new THREE.Mesh(
  new THREE.BoxGeometry(1, 1, 1),
  new THREE.MeshStandardMaterial({ color: 0x222222 })
)
occluder.position.set(0, 0.5, 0)
scene.add(occluder)

const light = new THREE.PointLight(0xffddaa, 1.2, 30, 2)
light.position.set(-2, 3, -2)
scene.add(light)

const coneGeom = new THREE.ConeGeometry(0.6, 5, 32, 1, true)
const coneMat = new THREE.MeshBasicMaterial({
  color: 0xffeedd,
  transparent: true,
  opacity: 0.2,
  blending: THREE.AdditiveBlending,
  depthWrite: false
})
const rays = new THREE.Mesh(coneGeom, coneMat)
rays.position.copy(light.position)
rays.rotation.x = -Math.PI / 2
scene.add(rays)

const params = { density: 0.2, decay: 0.95 }

function animate(timeMs) {
  requestAnimationFrame(animate)
  occluder.rotation.y += 0.01
  rays.material.opacity = params.density
  const d = THREE.MathUtils.lerp(1.0, 0.6, 1 - params.decay)
  rays.scale.set(d, 1, d)
  controls.update()
  renderer.render(scene, camera)
}
animate()