
웹 브라우저에서 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로 커스텀 형상 만들기
성능을 위해 BufferGeometry와 Float32Array를 직접 사용할 수 있습니다:
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, 텍스처 압축 등)을 적용하면 모바일 환경에서도 안정적인 렌더링이 가능합니다.
참고 자료
- Three.js 공식 문서
- Three.js Journey — 체계적인 Three.js 학습 코스
- React Three Fiber 문서
- drei 라이브러리
- WebGL Fundamentals — WebGL 기초 이해
'Develop' 카테고리의 다른 글
| Mac에서 BlueStacks + 메신저봇R로 카카오톡 오픈채팅 자동응답 봇 만들기 (0) | 2026.02.24 |
|---|---|
| [RAG] LLM의 정확도를 향상시키는 법 (0) | 2026.02.11 |
| [Tibero] JDBC-8101:Column used in the ON clause cannot be updated. 오류 해결하기 (0) | 2022.11.08 |
| [Spring JPA] JPA를 배워야하는 이유 (0) | 2022.05.13 |
| [Eclipse] Tomcat 에러, Serval port(8005, 8888) required by Tomcat 해결하는 법 (0) | 2022.05.11 |