반응형

 

 

웹 브라우저에서 3D 그래픽을 구현하는 것은 한때 Flash나 별도 플러그인의 영역이었습니다. 하지만 WebGL의 등장과 함께 브라우저 네이티브 3D 렌더링이 가능해졌고, Three.js는 이 WebGL을 가장 쉽고 강력하게 다룰 수 있는 JavaScript 라이브러리로 자리잡았습니다. 이 글에서는 Three.js의 핵심 아키텍처, 주요 개념, 그리고 실전 활용 패턴까지 정리합니다.

요약

  • Three.js란: WebGL을 추상화한 JavaScript 3D 라이브러리로, 복잡한 셰이더·행렬 연산 없이 선언적으로 3D 장면을 구성할 수 있음.
  • 핵심 구성요소: Scene, Camera, Renderer, Mesh(Geometry + Material), Light.
  • 활용 분야: 인터랙티브 웹사이트, 데이터 시각화, 게임, 제품 뷰어, AR/VR 등.

1. Three.js가 필요한 이유

WebGL의 한계

WebGL은 GPU 가속 2D/3D 렌더링을 위한 저수준 API입니다. 삼각형 하나를 그리기 위해서도 다음이 필요합니다:

  • 버텍스/프래그먼트 셰이더 작성 (GLSL)
  • 버퍼 생성 및 데이터 바인딩
  • 행렬 변환 (모델, 뷰, 프로젝션)
  • 렌더 루프 관리
// 순수 WebGL로 삼각형 하나 그리기 — 약 100줄 이상의 보일러플레이트
const gl = canvas.getContext('webgl');
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, `
  attribute vec4 aPosition;
  void main() { gl_Position = aPosition; }
`);
// ... 셰이더 컴파일, 링킹, 버퍼 설정 등 수십 줄 ...

Three.js의 추상화

Three.js는 이 모든 저수준 작업을 감추고, 직관적인 객체 모델을 제공합니다:

// Three.js로 동일한 결과를 얻기
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

2. 핵심 아키텍처 — Scene Graph

Three.js의 근간은 Scene Graph(장면 그래프) 패턴입니다. 이는 3D 객체들을 트리 구조로 관리하는 디자인 패턴으로, 부모-자식 관계를 통해 변환(위치, 회전, 스케일)이 상속됩니다.

Scene (루트)
├── Group (자동차)
│   ├── Mesh (차체)
│   ├── Mesh (바퀴 1)
│   ├── Mesh (바퀴 2)
│   └── PointLight (헤드라이트)
├── DirectionalLight (태양)
└── Mesh (지면)

주요 구성요소

구성요소 역할 주요 클래스
Scene 모든 3D 객체를 담는 컨테이너 THREE.Scene
Camera 장면을 바라보는 시점 정의 PerspectiveCamera, OrthographicCamera
Renderer Scene + Camera → 2D 이미지 변환 WebGLRenderer
Geometry 꼭짓점, 면 등 형상 데이터 BoxGeometry, SphereGeometry, BufferGeometry
Material 표면의 색상, 질감, 반사 속성 MeshStandardMaterial, ShaderMaterial
Mesh Geometry + Material의 결합체 THREE.Mesh
Light 장면의 조명 DirectionalLight, PointLight, AmbientLight

3. 렌더링 파이프라인

Three.js의 렌더링은 매 프레임마다 다음 과정을 거칩니다:

Scene Graph 순회
    → 월드 행렬 계산 (위치·회전·스케일)
    → 카메라 시점 기준 절두체 컬링 (보이지 않는 객체 제외)
    → Material별 정렬 (불투명 → 투명 순)
    → WebGL Draw Call 실행
    → 프레임 버퍼 → 화면 출력

렌더 루프 구현

function animate() {
  requestAnimationFrame(animate);

  // 상태 업데이트
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;

  // 렌더링
  renderer.render(scene, camera);
}
animate();

requestAnimationFrame을 사용하면 브라우저의 화면 갱신 주기(보통 60fps)에 맞춰 렌더링이 수행되며, 탭이 비활성화되면 자동으로 일시정지됩니다.

4. 카메라 시스템

PerspectiveCamera (원근 투영)

사람의 눈과 유사한 원근감을 제공합니다. 가까운 물체는 크게, 먼 물체는 작게 보입니다.

const camera = new THREE.PerspectiveCamera(
  75,                           // fov: 시야각 (도)
  window.innerWidth / window.innerHeight, // aspect: 종횡비
  0.1,                          // near: 가까운 클리핑 면
  1000                          // far: 먼 클리핑 면
);
camera.position.set(0, 5, 10);
camera.lookAt(0, 0, 0);

OrthographicCamera (직교 투영)

거리에 관계없이 동일한 크기로 렌더링됩니다. 2D 게임, CAD, 아이소메트릭 뷰에 적합합니다.

const camera = new THREE.OrthographicCamera(
  -10, 10,   // left, right
  10, -10,   // top, bottom
  0.1, 1000  // near, far
);

5. Material과 조명 시스템

PBR (Physically Based Rendering)

Three.js의 MeshStandardMaterial은 PBR 워크플로우를 지원하여 물리적으로 정확한 렌더링 결과를 제공합니다.

const material = new THREE.MeshStandardMaterial({
  color: 0x8B4513,       // 기본 색상
  metalness: 0.3,        // 금속성 (0: 비금속, 1: 금속)
  roughness: 0.7,        // 거칠기 (0: 매끄러움, 1: 거침)
  map: texture,          // 텍스처 맵
  normalMap: normalTex,  // 노멀 맵 (표면 디테일)
  envMap: envTexture,    // 환경 맵 (반사)
});

주요 조명 유형

// 환경광 — 장면 전체를 균일하게 밝힘
const ambient = new THREE.AmbientLight(0x404040, 0.5);

// 방향광 — 태양처럼 평행한 빛 (그림자 지원)
const directional = new THREE.DirectionalLight(0xffffff, 1);
directional.position.set(5, 10, 5);
directional.castShadow = true;

// 점광원 — 전구처럼 한 점에서 퍼지는 빛
const point = new THREE.PointLight(0xff6600, 1, 50);
point.position.set(0, 5, 0);

6. 지오메트리와 커스텀 메시

내장 지오메트리

Three.js는 다양한 기본 도형을 제공합니다:

new THREE.BoxGeometry(1, 1, 1);           // 정육면체
new THREE.SphereGeometry(1, 32, 32);      // 구
new THREE.CylinderGeometry(0.5, 0.5, 2);  // 원기둥
new THREE.PlaneGeometry(10, 10);          // 평면
new THREE.TorusKnotGeometry(1, 0.3, 100, 16); // 토러스 매듭

BufferGeometry로 커스텀 형상 만들기

성능을 위해 BufferGeometryFloat32Array를 직접 사용할 수 있습니다:

const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array([
  -1, -1, 0,
   1, -1, 0,
   0,  1, 0,
]);
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));

외부 3D 모델 로드

실무에서는 Blender 등에서 제작한 모델을 불러오는 경우가 대부분입니다:

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const loader = new GLTFLoader();
loader.load('/models/robot.glb', (gltf) => {
  const model = gltf.scene;
  model.scale.set(0.5, 0.5, 0.5);
  scene.add(model);
});

glTF(.glb/.gltf)는 웹 3D의 사실상 표준 포맷으로, 메시·재질·애니메이션·스켈레톤을 하나의 파일에 담을 수 있습니다.

7. 성능 최적화 전략

3D 웹 애플리케이션에서 성능은 핵심 과제입니다. 주요 최적화 기법을 정리합니다.

Draw Call 최소화

GPU 성능 병목의 대부분은 Draw Call 수에서 발생합니다.

// 같은 지오메트리를 여러 개 배치할 때 — InstancedMesh 사용
const mesh = new THREE.InstancedMesh(geometry, material, 1000);
const dummy = new THREE.Object3D();

for (let i = 0; i < 1000; i++) {
  dummy.position.set(Math.random() * 100, 0, Math.random() * 100);
  dummy.updateMatrix();
  mesh.setMatrixAt(i, dummy.matrix);
}
scene.add(mesh); // 1000개 객체를 단 1회 Draw Call로 렌더링

LOD (Level of Detail)

카메라와의 거리에 따라 디테일 수준을 조절합니다:

const lod = new THREE.LOD();
lod.addLevel(highDetailMesh, 0);    // 가까울 때
lod.addLevel(mediumDetailMesh, 50); // 중간 거리
lod.addLevel(lowDetailMesh, 100);   // 멀 때
scene.add(lod);

기타 최적화 체크리스트

  • 텍스처: 2의 거듭제곱 크기 사용 (512×512, 1024×1024), 필요시 압축 텍스처(Basis/KTX2) 적용.
  • 지오메트리: 불필요한 폴리곤 제거, BufferGeometry 사용 (기본값).
  • 그림자: shadowMap 해상도 적절히 조절, 그림자가 불필요한 객체는 castShadow = false.
  • 렌더러 설정: renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)) 로 레티나 디스플레이 대응.
  • dispose: 사용 완료된 지오메트리·재질·텍스처는 반드시 dispose() 호출하여 GPU 메모리 해제.
// 메모리 누수 방지
geometry.dispose();
material.dispose();
texture.dispose();
renderer.dispose();

8. React와의 통합 — React Three Fiber

React 생태계에서는 React Three Fiber(R3F) 를 통해 선언적으로 Three.js를 사용할 수 있습니다:

import { Canvas } from '@react-three/fiber';
import { OrbitControls, Environment } from '@react-three/drei';

function App() {
  return (
    <Canvas camera={{ position: [0, 5, 10] }}>
      <ambientLight intensity={0.5} />
      <directionalLight position={[5, 10, 5]} />
      <mesh rotation={[0, Math.PI / 4, 0]}>
        <boxGeometry args={[2, 2, 2]} />
        <meshStandardMaterial color="royalblue" />
      </mesh>
      <OrbitControls />
      <Environment preset="sunset" />
    </Canvas>
  );
}

R3F의 장점

  • 선언적 문법: JSX로 3D 장면을 구성하여 React 개발자에게 친숙.
  • React 생태계 호환: 상태 관리(Zustand, Jotai), 라우팅 등과 자연스럽게 통합.
  • 자동 리사이즈/dispose: Canvas 크기 변경 및 메모리 정리를 자동 처리.
  • drei 라이브러리: OrbitControls, Environment, Text3D 등 자주 쓰이는 유틸리티 컴포넌트 모음.

9. 실전 활용 사례

분야 사례 핵심 기술
제품 뷰어 Nike, Apple 등 제품 3D 미리보기 GLTFLoader, OrbitControls, PBR Material
데이터 시각화 3D 차트, 지구본 기반 데이터 맵 BufferGeometry, PointsMaterial, 셰이더
인터랙티브 웹 포트폴리오, 브랜드 사이트 ScrollControls, 애니메이션, 포스트프로세싱
게임 웹 기반 캐주얼 게임 물리 엔진(Cannon.js/Rapier), InstancedMesh
AR/VR WebXR 기반 몰입형 경험 WebXRManager, XR Controllers

10. 프로젝트 시작 가이드

설치 및 기본 세팅

# npm으로 설치
npm install three

# Vite + Three.js 프로젝트 생성
npm create vite@latest my-3d-app -- --template vanilla
cd my-3d-app
npm install three
npm run dev

최소 실행 코드

import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

// 기본 설정
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);

const camera = new THREE.PerspectiveCamera(
  75, window.innerWidth / window.innerHeight, 0.1, 1000
);
camera.position.set(3, 3, 3);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);

// 조명
const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 10, 5);
directionalLight.castShadow = true;
scene.add(directionalLight);

// 오브젝트
const cube = new THREE.Mesh(
  new THREE.BoxGeometry(1, 1, 1),
  new THREE.MeshStandardMaterial({ color: 0x6c5ce7, metalness: 0.3, roughness: 0.4 })
);
cube.castShadow = true;
cube.position.y = 0.5;
scene.add(cube);

const floor = new THREE.Mesh(
  new THREE.PlaneGeometry(10, 10),
  new THREE.MeshStandardMaterial({ color: 0x2d3436 })
);
floor.rotation.x = -Math.PI / 2;
floor.receiveShadow = true;
scene.add(floor);

// 컨트롤
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

// 렌더 루프
function animate() {
  requestAnimationFrame(animate);
  cube.rotation.y += 0.01;
  controls.update();
  renderer.render(scene, camera);
}
animate();

// 리사이즈 대응
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

마무리

Three.js는 WebGL의 복잡성을 감추면서도 강력한 3D 그래픽 기능을 제공하는 라이브러리입니다. Scene Graph 기반의 직관적인 객체 모델, PBR 렌더링, 다양한 로더와 유틸리티를 통해 웹에서 고품질 3D 경험을 구현할 수 있습니다.

프론트엔드 개발자라면 React Three Fiber를 통해 기존 워크플로우에 자연스럽게 3D를 통합할 수 있고, 성능 최적화 기법(InstancedMesh, LOD, 텍스처 압축 등)을 적용하면 모바일 환경에서도 안정적인 렌더링이 가능합니다.

참고 자료

반응형

+ Recent posts