마칭 큐브(메타볼)

Marching cubes (metaballs)

보이지 않는 "밀도 필드"에서 일정 농도 이상인 경계면만 뽑아내, 방울들이 서로 뭉치고 떨어지는 메타볼을 만드는 기법.

다른 이름: THREE.MarchingCubesMetaballsIsosurface extraction
···
js
import * as THREE from 'three';
import { MarchingCubes } from 'three/addons/objects/MarchingCubes.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(0x06070f);
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
camera.position.set(0, 0, 4.6);
camera.lookAt(0, 0, 0);
scene.add(new THREE.AmbientLight(0x445577, 1.2));
const dl = new THREE.DirectionalLight(0xffffff, 1.3);
dl.position.set(3, 4, 4);
scene.add(dl);

const RES = 32;
const mat = new THREE.MeshStandardMaterial({ color: 0x6fb8ff, roughness: 0.3, metalness: 0.15 });
const mc = new MarchingCubes(RES, mat, true, true, 60000);
mc.position.set(0, 0, 0);
mc.scale.setScalar(1.7);
scene.add(mc);

const NUM = 5;
function resize() { renderer.setSize(innerWidth, innerHeight); camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); }
addEventListener('resize', resize); resize();
renderer.setAnimationLoop((t) => {
  const time = t * 0.0007;
  mc.reset();
  const strength = 1.15 / ((Math.sqrt(NUM) - 1) / 4 + 1);
  const subtract = 11;
  for (let i = 0; i < NUM; i++) {
    const bx = Math.sin(i * 3.1 + time * 1.1 * (1.0 + 0.3 * Math.cos(i * 1.7))) * 0.26 + 0.5;
    const by = Math.abs(Math.cos(i * 2.3 + time * 0.9 * (0.3 + 0.5 * Math.cos(i * 0.8)))) * 0.6 + 0.2;
    const bz = Math.cos(i * 1.9 + time * 1.3 + i) * 0.26 + 0.5;
    mc.addBall(bx, by, bz, strength, subtract);
  }
  mc.update();
  mc.rotation.y = time * 0.3;
  renderer.render(scene, camera);
});

메타볼은 구를 여러 개 겹쳐 그리는 게 아니라, 3D 공간 전체에 "밀도" 값을 채우고 그 값이 일정 기준(isolation)을 넘는 경계만 표면으로 뽑아내는 방식입니다. 이 데모가 쓰는 three/addons/objects/MarchingCubes.js는 공간을 해상도(resolution)만큼 잘게 나눈 격자(복셀)마다 밀도를 저장해 두고, 매 프레임 mc.reset()으로 격자를 비운 뒤 mc.addBall(x, y, z, strength, subtract)를 여러 번 불러 "이 위치에 이만큼 강한 밀도 방울을 더한다"를 누적합니다.

두 방울이 가까워지면 그 사이 격자의 밀도가 겹쳐 합산되고, 합산된 값이 기준을 넘는 순간 두 표면이 하나로 이어집니다 — 이게 메타볼 특유의 "액체처럼 뭉쳤다 떨어지는" 모양의 정체입니다. mc.update()를 부르면 그 밀도 격자를 실제 삼각형 메시로 변환하는 마칭 큐브 알고리즘(격자의 각 셀마다 경계가 어느 모서리를 지나는지 조회 테이블로 찾는 고전적인 기법)이 돌아갑니다.

addBall의 좌표는 필드 공간(대략 0~1)을 기준으로 하고, strength·subtract 값으로 방울의 "부피감"과 뭉쳤을 때 얼마나 매끄럽게 이어지는지를 조절합니다. resolution을 높이면 표면이 매끈해지지만 격자 칸 수가 세제곱으로 늘어나 매 프레임 계산 비용도 급격히 커지므로, 실시간 애니메이션에서는 32 안팎이 실용적인 상한선입니다.

언제 쓰나

슬라임·액체 금속 캐릭터, 화학/분자 시각화, 유기적으로 합쳐지는 로고 애니메이션에.