Raycasting

레이캐스팅

Firing a ray from a screen point (like the pointer) into 3D space to find which object it hits.

Also known as: 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 turns a 2D screen coordinate into a line through 3D space. raycaster.setFromCamera(pointerNDC, camera) sets up a ray from the camera in that direction, and raycaster.intersectObjects(objects) returns whichever objects the ray hit, sorted nearest-to-camera first.

Pointer coordinates must be normalized device coordinates (NDC, -1 to 1), not pixels — x is (clientX/width)*2-1, y is -(clientY/height)*2+1. The flipped y sign is easy to forget.

Since the card preview has pointer-events disabled and can’t receive real mouse input, this demo follows the house rule: a virtual pointer sweeps the grid along a Lissajous curve until a real pointermove event arrives, at which point control hands over. Whatever box the ray hits changes color and grows slightly.

When to use

Whenever objects in a 3D scene need to be clicked or hovered — editors, product configurators, in-game UI.