Depth of Field Gallery

Post-processing depth of field effect with objects at varying distances.

What this code does

- BokehPass: simulates camera depth of field with realistic blur based on distance from focus plane.
- EffectComposer: post-processing pipeline that applies effects after scene rendering.
- Focus Distance: objects at this distance appear sharp, others blur based on distance difference.
- Aperture Control: smaller aperture values create stronger blur effects (shallow depth of field).
- Object Gallery: three rows of objects at different Z-depths demonstrate the effect.
- Interactive Controls: W/S (focus), A/D (aperture), Q/E (max blur), or use arrow keys.
- Console Output: check browser console for current values and control instructions.

JavaScript (plain)

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

const scene = new THREE.Scene()
scene.background = new THREE.Color(0x111122)

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100)
camera.position.set(0, 2, 8)

const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight)

document.body.appendChild(renderer.domElement)

const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true

// Create objects at different depths
const materials = [
  new THREE.MeshStandardMaterial({ color: 0xff4444, metalness: 0.8, roughness: 0.2 }),
  new THREE.MeshStandardMaterial({ color: 0x44ff44, metalness: 0.3, roughness: 0.7 }),
  new THREE.MeshStandardMaterial({ color: 0x4444ff, metalness: 0.1, roughness: 0.9 })
]

const geometries = [
  new THREE.SphereGeometry(0.6, 32, 16),
  new THREE.BoxGeometry(1.2, 1.2, 1.2),
  new THREE.ConeGeometry(0.6, 1.4, 8)
]

// Front row (close)
for (let i = 0; i < 3; i++) {
  const mesh = new THREE.Mesh(geometries[i], materials[i])
  mesh.position.set((i - 1) * 2.5, 0, 4)
  scene.add(mesh)

}

// Back row (far)
for (let i = 0; i < 3; i++) {
  const mesh = new THREE.Mesh(geometries[i], materials[i])
  mesh.position.set((i - 1) * 2.5, 0, -4)
  scene.add(mesh)

}

// Lighting
const ambientLight = new THREE.AmbientLight(0x404040, 0.3)
scene.add(ambientLight)

const directionalLight = new THREE.DirectionalLight(0xffffff, 1)
directionalLight.position.set(5, 10, 5)
scene.add(directionalLight)

// Setup post-processing for depth of field
const composer = new EffectComposer(renderer)
const renderPass = new RenderPass(scene, camera)
composer.addPass(renderPass)

const bokehPass = new BokehPass(scene, camera, {
  focus: 8.0,       // Focus distance (camera at z=8)
  aperture: 0.0025, // Aperture size (smaller = more blur)
  maxblur: 1.0,     // Maximum blur amount
  width: window.innerWidth,
  height: window.innerHeight
})
composer.addPass(bokehPass)

// Animation loop

function animate() {
  requestAnimationFrame(animate)
  controls.update()
  composer.render() // Render with post-processing
}
animate()

// Interactive controls
document.addEventListener('keydown', (event) => {
  switch (event.key) {
    case 'w':
      bokehPass.uniforms.focus.value = Math.min(bokehPass.uniforms.focus.value + 1, 12)
      break
    case 's':
      bokehPass.uniforms.focus.value = Math.max(bokehPass.uniforms.focus.value - 1, 1)
      break
    case 'a':
      bokehPass.uniforms.aperture.value = Math.max(bokehPass.uniforms.aperture.value - 0.0005, 0.0005)
      break
    case 'd':
      bokehPass.uniforms.aperture.value = Math.min(bokehPass.uniforms.aperture.value + 0.0005, 0.01)
      break
  }
})