Depth of Field Focus
Bokeh depth of field pass with smooth auto-focus and click-to-focus controls across layered geometry.
What this code does
- EffectComposer + BokehPass: simulates camera bokeh blur based on configurable focus distance and aperture.
- Layered Scene Setup: foreground spheres, mid-ground boxes, and distant cones highlight blur falloff.
- Interactive Controls: GUI sliders adjust focus distance, aperture, and blur intensity in real time.
- Click to Focus: raycasting picks any object and moves the focal plane to its distance instantly.
- Auto Focus: optional center ray keeps the focus tracking whichever object crosses the middle of the frame.
JavaScript (plain)
const scene = new THREE.Scene()
scene.background = new THREE.Color(0x1a1a1a)
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
camera.position.set(0, 2, 10)
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2))
renderer.setSize(width, height)
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
document.querySelector('#app').appendChild(renderer.domElement)
// Setup post-processing for depth of field
const composer = new EffectComposer(renderer)
composer.setSize(width, height)
const renderPass = new RenderPass(scene, camera)
composer.addPass(renderPass)
const bokehPass = new BokehPass(scene, camera, {
focus: 7.0,
aperture: 0.04,
maxblur: 0.018
})
composer.addPass(bokehPass)
let focusTarget = 7.0
const focusLerpSpeed = 0.08
const focusSweepRange = { near: 3.5, far: 14 }
const raycaster = new THREE.Raycaster()
const pointer = new THREE.Vector2()
const centerPointer = new THREE.Vector2(0, 0)
let focusSweepEnabled = true
// Create objects at different depths
const objects = []
// Foreground spheres
for (let i = 0; i < 3; i++) {
const geometry = new THREE.SphereGeometry(0.3, 16, 16)
const material = new THREE.MeshStandardMaterial({
color: new THREE.Color().setHSL(i / 3, 0.8, 0.6)
})
const sphere = new THREE.Mesh(geometry, material)
sphere.position.set((i - 1) * 1.5, -1, 2 + i * 0.5)
objects.push(sphere)
scene.add(sphere)
}
// Middle boxes (focus area)
for (let i = 0; i < 5; i++) {
const geometry = new THREE.BoxGeometry(0.6, 0.6, 0.6)
const material = new THREE.MeshStandardMaterial({
color: new THREE.Color().setHSL((i + 3) / 8, 0.8, 0.6)
})
const box = new THREE.Mesh(geometry, material)
box.position.set((i - 2) * 1.8, 0, 6 + i * 0.8)
box.rotation.x = Math.PI * 0.2
box.rotation.y = i * Math.PI * 0.3
objects.push(box)
scene.add(box)
}
// Background cones
for (let i = 0; i < 4; i++) {
const geometry = new THREE.ConeGeometry(0.4, 1.2, 8)
const material = new THREE.MeshStandardMaterial({
color: new THREE.Color().setHSL((i + 6) / 10, 0.8, 0.6)
})
const cone = new THREE.Mesh(geometry, material)
cone.position.set((i - 1.5) * 2, 1, 15 + i * 1.2)
objects.push(cone)
scene.add(cone)
}
// Ground plane
const groundGeometry = new THREE.PlaneGeometry(50, 50)
const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x404040 })
const ground = new THREE.Mesh(groundGeometry, groundMaterial)
ground.rotation.x = -Math.PI / 2
ground.position.y = -2
scene.add(ground)
// 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)
const pointLight = new THREE.PointLight(0xffa500, 0.8, 20)
pointLight.position.set(0, 3, 8)
scene.add(pointLight)
// Bright background highlights for strong bokeh
const highlightGeometry = new THREE.SphereGeometry(0.25, 16, 16)
const highlights = []
for (let i = 0; i < 18; i++) {
const material = new THREE.MeshBasicMaterial({
color: new THREE.Color().setHSL(0.55 + Math.random() * 0.2, 1.0, 0.7),
transparent: true,
opacity: 0.85
})
const sphere = new THREE.Mesh(highlightGeometry, material)
sphere.position.set((Math.random() - 0.5) * 16, -1 + Math.random() * 6, 11 + Math.random() * 8)
sphere.userData = { base: sphere.position.clone(), phase: Math.random() * Math.PI * 2 }
highlights.push(sphere)
objects.push(sphere)
scene.add(sphere)
}
function updateAutoFocus() {
raycaster.setFromCamera(centerPointer, camera)
const hit = raycaster.intersectObjects(objects)
if (hit.length > 0) {
focusTarget = THREE.MathUtils.lerp(focusTarget, hit[0].distance, 0.4)
}
}
renderer.domElement.addEventListener('click', (event) => {
const rect = renderer.domElement.getBoundingClientRect()
pointer.x = ((event.clientX - rect.left) / rect.width) * 2 - 1
pointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1
raycaster.setFromCamera(pointer, camera)
const hit = raycaster.intersectObjects(objects)
if (hit.length > 0) {
focusSweepEnabled = false
focusTarget = hit[0].distance
bokehPass.uniforms.focus.value = focusTarget
}
})
function animate() {
requestAnimationFrame(animate)
const time = Date.now() * 0.001
// Animate objects
objects.forEach((obj, i) => {
obj.rotation.y += 0.01 * (i % 2 === 0 ? 1 : -1)
})
// Animate point light
pointLight.position.x = Math.sin(time * 0.5) * 3
pointLight.position.z = 8 + Math.cos(time * 0.3) * 2
highlights.forEach((highlight) => {
const { base, phase } = highlight.userData
highlight.position.x = base.x + Math.sin(time * 0.2 + phase) * 1.5
highlight.position.y = base.y + Math.cos(time * 0.25 + phase) * 0.8
})
if (focusSweepEnabled) {
const sweep = THREE.MathUtils.mapLinear(
Math.sin(time * 0.35),
-1,
1,
focusSweepRange.near,
focusSweepRange.far
)
focusTarget = sweep
}
updateAutoFocus()
bokehPass.uniforms.focus.value = THREE.MathUtils.lerp(
bokehPass.uniforms.focus.value,
focusTarget,
focusLerpSpeed
)
composer.render()
}
animate()