앰비언트 오클루전

Ambient occlusion

물체가 서로 가깝거나 오목하게 파인 곳에 주변광이 잘 닿지 않아 자연스럽게 어두워지는 현상을 흉내 내는 기법.

다른 이름: AOContact shadowSSAO
···
html
<span class="ao-cap">ambient occlusion · contact darkening</span>
css
.ao-cap{position:absolute;left:0;right:0;bottom:5%;text-align:center;font-size:clamp(9px,2.4vmin,12px);color:#dfe3ffcc}
js
import * as THREE from 'three';
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(0x0d0d14);
const camera = new THREE.PerspectiveCamera(42, 1, 0.1, 100);
camera.position.set(0, 1.5, 5.6);

const floor = new THREE.Mesh(new THREE.PlaneGeometry(8, 8), new THREE.MeshStandardMaterial({ color: 0x2a2a3a, roughness: 0.9 }));
floor.rotation.x = -Math.PI / 2;
scene.add(floor);
const wall = new THREE.Mesh(new THREE.PlaneGeometry(8, 4), new THREE.MeshStandardMaterial({ color: 0x232332, roughness: 0.9 }));
wall.position.set(0, 2, -2); scene.add(wall);

const sphere = new THREE.Mesh(new THREE.SphereGeometry(1.1, 48, 48), new THREE.MeshStandardMaterial({ color: 0xffb648, roughness: 0.6 }));
sphere.position.set(-0.2, 1.1, -0.8);
scene.add(sphere);
scene.add(new THREE.HemisphereLight(0xffffff, 0x1a1a22, 1.1));

function makeAoTex() {
  const c = document.createElement('canvas'); c.width = c.height = 128;
  const g = c.getContext('2d');
  const rad = g.createRadialGradient(64, 64, 4, 64, 64, 64);
  rad.addColorStop(0, 'rgba(0,0,0,0.75)'); rad.addColorStop(1, 'rgba(0,0,0,0)');
  g.fillStyle = rad; g.fillRect(0, 0, 128, 128);
  return new THREE.CanvasTexture(c);
}
const aoTex = makeAoTex();
const aoFloor = new THREE.Mesh(new THREE.CircleGeometry(1.6, 32), new THREE.MeshBasicMaterial({ map: aoTex, transparent: true, depthWrite: false }));
aoFloor.rotation.x = -Math.PI / 2;
aoFloor.position.set(-0.2, 0.01, -0.6);
scene.add(aoFloor);
const aoWall = aoFloor.clone();
aoWall.rotation.x = 0;
aoWall.position.set(-0.2, 1.6, -1.98);
scene.add(aoWall);

function resize() { renderer.setSize(innerWidth, innerHeight); camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); }
addEventListener('resize', resize); resize();
renderer.setAnimationLoop((t) => {
  const cycle = (Math.sin(t * 0.0007) + 1) / 2;
  aoFloor.material.opacity = cycle;
  aoWall.material.opacity = cycle;
  sphere.rotation.y = t * 0.0003;
  camera.lookAt(-0.1, 0.9, -0.6);
  renderer.render(scene, camera);
});

직접광(태양·조명)만으로는 물체가 서로 맞닿은 곳이나 틈, 구석이 왜 더 어두워 보이는지 설명이 안 됩니다. 사방에서 튕겨 들어오는 간접광(앰비언트)이 좁은 틈에서는 절반도 채 들어오지 못하기 때문입니다. AO는 이 "얼마나 가려졌는가"를 표면의 각 점마다 미리 계산해서 구석·주름·접촉면을 살짝 어둡게 눌러줍니다.

실시간 렌더링에서는 화면 공간 앰비언트 오클루전(SSAO)처럼 카메라가 보는 깊이 정보만으로 근사치를 매 프레임 계산하거나, 정적인 장면은 각 표면 점의 가려짐 정도를 텍스처에 미리 구워(베이크) 둡니다. 둘 다 "실제 조명 시뮬레이션"이 아니라 "그럴듯한 근사"라는 점은 같습니다.

AO 자체는 그림자가 아니라 그림자를 그럴듯하게 만드는 재료에 가깝습니다 — 물체를 바닥에 붙어 있는 것처럼 보이게 하는 "접촉 그림자"의 핵심이고, 빠지면 물체가 붕 떠 있는 듯한 느낌을 줍니다. Blender·Maya·C4D 모두 렌더 패스에서 AO를 따로 뽑아 합성 단계에서 강도를 조절할 수 있게 해줍니다.

언제 쓰나

물체가 바닥·다른 물체에 떠 있는 듯 어색할 때, 접촉 지점에 자연스러운 무게감을 주고 싶을 때.