리플렉터(거울) 바닥

Reflector (mirror) floor

평면 하나를 거울처럼 만들어, 그 위에 있는 물체를 실시간으로 비춰 보여주는 기법.

다른 이름: THREE.ReflectorPlanar reflectionMirror surface
···
js
import * as THREE from 'three';
import { Reflector } from 'three/addons/objects/Reflector.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(0x05060c);
const camera = new THREE.PerspectiveCamera(50, 1, 0.1, 100);
camera.position.set(0, 1.6, 5.2);
camera.lookAt(0, 0.4, 0);

const groundGeo = new THREE.CircleGeometry(3.4, 64);
const reflector = new Reflector(groundGeo, { textureWidth: 512, textureHeight: 512, color: 0x334455 });
reflector.rotation.x = -Math.PI / 2;
scene.add(reflector);

const group = new THREE.Group();
const colors = [0xff5c8a, 0x5cc9ff, 0xffd15c];
for (let i = 0; i < 3; i++) {
  const m = new THREE.Mesh(new THREE.IcosahedronGeometry(0.55, 0), new THREE.MeshStandardMaterial({ color: colors[i], roughness: 0.35, metalness: 0.2 }));
  const a = (i / 3) * Math.PI * 2;
  m.position.set(Math.cos(a) * 1.6, 0.9, Math.sin(a) * 1.6);
  group.add(m);
}
scene.add(group);
scene.add(new THREE.AmbientLight(0x445577, 1.2));
const dl = new THREE.DirectionalLight(0xffffff, 1.4);
dl.position.set(3, 5, 2);
scene.add(dl);

function resize() { renderer.setSize(innerWidth, innerHeight); camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); }
addEventListener('resize', resize); resize();
renderer.setAnimationLoop((t) => {
  const time = t * 0.001;
  group.rotation.y = time * 0.4;
  group.children.forEach((m, i) => { m.position.y = 0.9 + Math.sin(time * 1.5 + i) * 0.25; });
  camera.position.x = Math.sin(time * 0.2) * 1.2;
  camera.lookAt(0, 0.4, 0);
  renderer.render(scene, camera);
});

three/addons/objects/Reflector.js는 평면 지오메트리를 받아 "그 평면을 기준으로 카메라를 대칭 이동시킨 두 번째 카메라"로 씬을 한 번 더 렌더링하고, 그 결과를 렌더 타깃 텍스처로 자기 표면에 입힙니다. portal 항목과 원리는 같은 render-to-texture지만, Reflector는 이 카메라 대칭 계산과 클리핑 평면 처리를 내부에서 자동으로 해줍니다 — onBeforeRender 훅에 다 들어 있어서 별도 렌더 호출 없이 scene.add(reflector)만 하면 매 프레임 알아서 갱신됩니다.

new Reflector(geometry, { textureWidth, textureHeight, color, clipBias })에서 textureWidth/Height는 반사 텍스처의 해상도(낮추면 흐릿하지만 빠름), color는 반사에 섞이는 표면 자체의 틴트, clipBias는 반사면 바로 아래 물체가 유령처럼 비치는 걸 막는 보정값입니다.

데모는 원형 바닥을 Reflector로 만들고 그 위에 색이 다른 다면체 세 개를 띄워 반사가 흔들리는 걸 보여줍니다. 진짜 물리 반사이므로 카메라 각도가 바뀌면 반사도 정확히 그만큼 바뀝니다 — 다만 평면에만 동작하고(굴곡진 표면은 못 함), 매 프레임 씬을 한 번 더 그리므로 비용이 두 배 가까이 든다는 점은 물 위 반사(water-shader)처럼 항상 감안해야 합니다.

언제 쓰나

대리석 바닥, 쇼케이스 받침대, 물 위 반사처럼 평평한 표면에서 사실적인 반사가 필요할 때.