레이캐스팅

Raycasting

화면의 한 점(마우스 위치 등)에서 3D 공간으로 광선을 쏴서 어떤 오브젝트와 부딪히는지 찾는 기법.

다른 이름: THREE.RaycasterPickingMouse intersection
···
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(0x0a0a14);
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
camera.position.set(0, 3.4, 4.6);
camera.lookAt(0, 0, 0);
scene.add(new THREE.AmbientLight(0x556080, 1.4));
const dLight = new THREE.DirectionalLight(0xffffff, 1.0);
dLight.position.set(3, 5, 2);
scene.add(dLight);

const GRID = 6;
const boxes = [];
const baseColor = new THREE.Color(0x4d5cff);
const hoverColor = new THREE.Color(0xffce4d);
for (let x = 0; x < GRID; x++) {
  for (let z = 0; z < GRID; z++) {
    const mat = new THREE.MeshStandardMaterial({ color: baseColor.clone(), roughness: 0.5 });
    const box = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.6, 0.6), mat);
    box.position.set((x - (GRID - 1) / 2) * 0.85, 0, (z - (GRID - 1) / 2) * 0.85);
    scene.add(box);
    boxes.push(box);
  }
}

const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
let userControl = false;
addEventListener('pointermove', (e) => {
  userControl = true;
  pointer.x = (e.clientX / innerWidth) * 2 - 1;
  pointer.y = -(e.clientY / innerHeight) * 2 + 1;
});

function resize() { renderer.setSize(innerWidth, innerHeight); camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); }
addEventListener('resize', resize); resize();

let hovered = null;
renderer.setAnimationLoop((t) => {
  if (!userControl) {
    const time = t * 0.00055;
    pointer.x = Math.sin(time * 1.3) * 0.75;
    pointer.y = Math.sin(time * 0.9) * 0.55;
  }
  raycaster.setFromCamera(pointer, camera);
  const hits = raycaster.intersectObjects(boxes);
  const hit = hits.length ? hits[0].object : null;
  if (hit !== hovered) {
    if (hovered) { hovered.material.color.copy(baseColor); hovered.scale.setScalar(1); }
    if (hit) { hit.material.color.copy(hoverColor); hit.scale.setScalar(1.25); }
    hovered = hit;
  }
  renderer.render(scene, camera);
});

THREE.Raycaster는 2D 화면 좌표를 3D 씬 안의 직선으로 바꿔주는 도구입니다. raycaster.setFromCamera(pointerNDC, camera)로 카메라 위치에서 그 방향으로 광선을 세팅하고, raycaster.intersectObjects(objects)를 부르면 광선과 부딪힌 오브젝트들을 카메라와 가까운 순서로 정렬해 돌려줍니다.

포인터 좌표는 픽셀이 아니라 정규화 장치 좌표(NDC, -1~1)로 넘겨야 합니다 — x는 (clientX/너비)*2-1, y는 -(clientY/높이)*2+1로 변환합니다. y축 부호가 반대인 걸 자주 놓칩니다.

카드 미리보기는 pointer-events가 꺼져 있어 실제 마우스 입력을 받을 수 없으므로, 이 데모는 규칙대로 가상의 포인터가 리사주 곡선을 그리며 격자를 스캔하다가 실제 pointermove 이벤트가 오면 그쪽으로 제어권을 넘깁니다. 부딪힌 박스는 색이 바뀌고 살짝 커집니다.

언제 쓰나

3D 씬에서 오브젝트를 클릭·호버로 선택해야 할 때 (에디터, 제품 커스터마이저, 게임 UI).