피사계 심도

Depth of field

카메라의 초점 거리에서 벗어난 앞뒤 물체를 흐리게 만들어, 초점이 맞은 피사체를 시선으로 유도하는 효과.

다른 이름: DOFBokehPassFocus blurBokeh
···
js
import * as THREE from 'three';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { BokehPass } from 'three/addons/postprocessing/BokehPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a14);
const camera = new THREE.PerspectiveCamera(45, 1, 0.5, 30);
camera.position.set(0, 0.3, 1.6);
camera.lookAt(0, 0, -6);

scene.add(new THREE.AmbientLight(0x556080, 1.2));
const dLight = new THREE.DirectionalLight(0xffffff, 1.1);
dLight.position.set(3, 5, 2);
scene.add(dLight);

const meshes = [];
const N = 6;
for (let i = 0; i < N; i++) {
  const mesh = new THREE.Mesh(
    new THREE.IcosahedronGeometry(0.5, 0),
    new THREE.MeshStandardMaterial({ color: new THREE.Color().setHSL(i / N, 0.65, 0.6), roughness: 0.4 })
  );
  mesh.position.set((i % 2 === 0 ? -1 : 1) * 0.6, 0, -i * 1.7 - 1);
  scene.add(mesh);
  meshes.push(mesh);
}

const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
const bokeh = new BokehPass(scene, camera, { focus: 3, aperture: 0.008, maxblur: 0.01 });
composer.addPass(bokeh);
composer.addPass(new OutputPass());

function resize() {
  renderer.setSize(innerWidth, innerHeight);
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  composer.setSize(innerWidth, innerHeight);
}
addEventListener('resize', resize); resize();
renderer.setAnimationLoop((t) => {
  const time = t * 0.001;
  bokeh.uniforms.focus.value = 5.5 + Math.sin(time * 0.6) * 4.0;
  meshes.forEach((m, i) => { m.rotation.y = time * 0.6 + i; m.rotation.x = time * 0.4; });
  composer.render();
});

실제 카메라 렌즈는 초점이 맞은 거리(focus distance) 근처만 선명하고 그 앞뒤는 점점 흐려집니다(보케). three.js 애드온의 BokehPass(scene, camera, { focus, aperture, maxblur })는 먼저 장면을 깊이(depth) 텍스처로 한 번 렌더링한 뒤, 각 픽셀의 깊이가 focus 값에서 얼마나 떨어져 있는지에 비례해 블러를 섞습니다.

focus는 선명하게 보일 카메라로부터의 거리, aperture는 조리개 값(클수록 흐림이 급격해짐), maxblur는 흐림의 최대 강도입니다. 이 세 값은 bokehPass.uniforms.focus.value처럼 런타임에 계속 바꿀 수 있어서, 데모는 focus를 사인파로 흔들어 마치 카메라가 초점을 앞뒤로 훑듯 5개의 도형 중 어느 것이 선명한지가 계속 바뀝니다.

블러 계산이 화면 전체 텍스처 샘플링을 여러 번 하므로 다른 후처리보다 무거운 편입니다. 카메라의 near/far 값을 실제 장면 깊이에 맞게 좁게 잡아야 depth 텍스처의 정밀도가 확보되어 밴딩 없이 부드럽게 흐려집니다.

언제 쓰나

제품 쇼케이스, 시네마틱한 카메라 워크, 특정 피사체에 시선을 집중시켜야 할 때.