웹 브라우저에서 3D 세계를 만드는 Three.js: 개념부터 실전까지 한 번에
이 글을 끝까지 읽으면, Three.js의 핵심 구조부터 실전 3D 씬 구성까지 한 번에 이해할 수 있습니다. 플러그인도, 게임 엔진도 필요 없이 브라우저 하나로 3D 세계를 직접 만들어보세요.
안녕하세요, ICT리더 리치입니다. 얼마 전 포트폴리오 사이트를 리뉴얼하면서 "그냥 평범한 페이지는 이제 아무도 안 본다"는 말을 뼈저리게 실감했습니다. 그래서 직접 Three.js를 공부해 3D 인터랙티브 메인 화면을 만들어봤는데, 처음엔 막막했지만 막상 Scene 하나 띄우고 나니 그다음은 생각보다 빠르게 이해됐습니다.
오늘은 Three.js가 무엇인지, 어떻게 작동하는지, 그리고 실제로 어떻게 써먹는지를 개념부터 실전 예제까지 차근차근 소개해드리겠습니다. 3D 개발이 처음이어도 괜찮습니다. HTML과 JavaScript 기초만 있으면 충분합니다.
📌 바로가기 목차
| 웹 브라우저 3D 구현 가이드 – Three.js 핵심 개념 대표 이미지 |
1. Three.js란 무엇인가요?
혹시 이런 경험 있으신가요? 어느 브랜드 웹사이트에 들어갔더니 마우스를 움직이는 것만으로 3D 제품이 빙글빙글 돌아가고, 배경이 별처럼 쏟아지는 장면을 보면서 "이게 정말 웹페이지야?"라고 깜짝 놀랐던 순간. 그 화려한 경험의 중심에 있는 기술이 바로 Three.js입니다.
Three.js는 웹 브라우저에서 3D 그래픽을 구현하기 위한 오픈소스 JavaScript 라이브러리입니다. 2010년 Ricardo Cabello(mrdoob)가 최초 공개했으며, 현재 GitHub 스타 수 100,000개 이상을 기록하며 웹 3D 개발의 사실상 표준으로 자리 잡고 있습니다. 내부적으로는 WebGL을 사용하지만, 개발자가 복잡한 셰이더 코드 없이도 직관적인 API만으로 3D 오브젝트를 만들 수 있게 해줍니다.
👉 핵심 한 줄 요약: Three.js = WebGL을 쉽게 쓸 수 있게 포장한 3D JavaScript 라이브러리
🌐 실제 Three.js 활용 사례
- NASA Eyes on the Solar System — 태양계 실시간 3D 시뮬레이션
- Apple Mac Pro 제품 페이지 — 스크롤에 반응하는 3D 제품 렌더링
- Google Arts & Culture — 박물관 3D 투어 구현
- Nike 제품 커스터마이저 — 실시간 색상·소재 변경 3D 프리뷰
- Bruno Simon 포트폴리오 — 브라우저에서 구동되는 완전한 3D 드라이빙 게임
다음은 Three.js의 기본 임포트 방식과 라이브러리 구조를 보여주는 예시입니다. CDN 방식과 npm 방식 모두를 주석으로 설명합니다.
// ============================================================
// Three.js 임포트 방식 3가지 비교 예시
// ============================================================
// ── 방법 1: CDN (ES Module) ─────────────────────────────────
// HTML <script type="module"> 안에서 사용
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js';
// ── 방법 2: npm 설치 후 import ──────────────────────────────
// 터미널: npm install three
// Vite, Webpack, Parcel 등 번들러 환경에서 사용
import * as THREE from 'three';
// ── 방법 3: CDN (UMD, 레거시 <script> 태그) ─────────────────
// <script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
// window.THREE 전역 객체로 접근
// const scene = new THREE.Scene();
// ============================================================
// Three.js 주요 패키지 구조 (npm 기준)
// ============================================================
// three → 핵심 3D 엔진 (Scene, Camera, Geometry 등)
// three/addons/ → 추가 기능 모음 (r152+ 기준)
// controls/ → OrbitControls, FirstPersonControls 등
// loaders/ → GLTFLoader, OBJLoader, FBXLoader 등
// postprocessing/ → EffectComposer, BloomPass 등
// helpers/ → AxesHelper, GridHelper 등
// npm 추가 패키지 예시 ──────────────────────────────────────
// npm install @react-three/fiber → React 통합 렌더러
// npm install @react-three/drei → React Three Fiber 유틸리티
// npm install gsap → 3D 애니메이션 타임라인
// npm install cannon-es → 3D 물리 엔진 연동
// ============================================================
// Three.js 버전 확인 및 기본 정보 출력
// ============================================================
console.log('Three.js 버전:', THREE.REVISION);
// → 출력 예: Three.js 버전: 160
// Three.js가 제공하는 주요 클래스 목록 일부
const threeClasses = [
'Scene', // 3D 세계의 무대
'PerspectiveCamera', // 원근 카메라
'WebGLRenderer', // WebGL 기반 렌더러
'BoxGeometry', // 정육면체 지오메트리
'MeshStandardMaterial', // PBR 재질
'DirectionalLight', // 방향성 조명
'TextureLoader', // 텍스처 로더
'Vector3', // 3D 벡터
'Clock', // 시간/델타 타임 관리
];
threeClasses.forEach((cls, i) => {
console.log(` ${i + 1}. THREE.${cls}`);
});
2. Three.js의 동작 원리: WebGL을 쉽게 다루는 방법
WebGL을 날것 그대로 써본 분이라면 아실 겁니다. 단순한 삼각형 하나를 그리는 데도 버텍스 셰이더, 프래그먼트 셰이더, 버퍼 설정 등 수십 줄의 코드가 필요합니다. 실제로 WebGL 순수 코드로 "회전하는 정육면체" 하나를 구현하면 약 200줄이 넘어갑니다. Three.js로 동일한 결과물을 만들면? 단 20줄이면 충분합니다.
| 구분 | 순수 WebGL | Three.js |
|---|---|---|
| 코드 난이도 | 매우 높음 (셰이더 언어 필수) | 낮음 (JavaScript만으로 가능) |
| 정육면체 구현 코드량 | 약 200줄 이상 | 약 20줄 |
| 조명·그림자 처리 | 직접 셰이더 작성 필요 | API 한 줄로 설정 가능 |
| 3D 모델 불러오기 | 복잡한 파서 직접 구현 | GLTFLoader 등 내장 로더 제공 |
| 학습 곡선 | 매우 가파름 | JavaScript 기초면 시작 가능 |
Three.js의 내부 렌더링 파이프라인이 어떻게 동작하는지, requestAnimationFrame과 함께 프레임이 어떻게 계산되는지를 코드로 직접 확인해보세요.
// ============================================================
// Three.js 렌더링 파이프라인 동작 원리 시뮬레이션
// ============================================================
import * as THREE from 'three';
// ── STEP 1: 렌더러 생성 (WebGL 컨텍스트 초기화) ──────────────
const renderer = new THREE.WebGLRenderer({
antialias: true, // 계단 현상(앨리어싱) 제거
alpha: false, // 배경 투명도 (false = 불투명)
powerPreference: 'high-performance', // GPU 고성능 모드
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); // 최대 2배율
renderer.shadowMap.enabled = true; // 그림자 활성화
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 부드러운 그림자
document.body.appendChild(renderer.domElement); // canvas → DOM 삽입
// ── STEP 2: Scene 생성 (3D 세계 컨테이너) ────────────────────
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e); // 다크 블루 배경
scene.fog = new THREE.Fog(0x1a1a2e, 10, 50); // 원근 안개 효과
// ── STEP 3: Camera 설정 (시점 정의) ──────────────────────────
const camera = new THREE.PerspectiveCamera(
60, // fov: 시야각 (도 단위, 45~75 권장)
window.innerWidth / window.innerHeight, // aspect: 화면 비율
0.1, // near: 이 거리 이하는 렌더링 안 함
100 // far: 이 거리 이상은 렌더링 안 함
);
camera.position.set(0, 2, 6); // x, y, z 위치 지정
camera.lookAt(0, 0, 0); // 원점(0,0,0)을 바라봄
// ── STEP 4: Clock으로 프레임 독립적 애니메이션 제어 ──────────
const clock = new THREE.Clock(); // 경과 시간 측정
let frameCount = 0;
// ── STEP 5: 애니메이션 루프 (매 프레임 호출) ─────────────────
function animate() {
requestAnimationFrame(animate); // 다음 프레임 예약 (약 60fps)
const elapsedTime = clock.getElapsedTime(); // 시작 후 총 경과 시간(초)
const deltaTime = clock.getDelta(); // 이전 프레임과의 시간 차
// 프레임 독립적 회전: deltaTime 기반으로 속도 일정 유지
// (고사양PC 60fps와 저사양 30fps에서 동일한 속도로 회전)
if (scene.children.length > 0) {
scene.children[0].rotation.y += 1.0 * deltaTime; // 1라디안/초
}
renderer.render(scene, camera); // 렌더링 실행
frameCount++;
if (frameCount % 60 === 0) { // 60프레임마다 로그 출력
console.log(`경과시간: ${elapsedTime.toFixed(1)}초 | 프레임: ${frameCount}`);
}
}
animate(); // 루프 시작
3. 꼭 알아야 할 핵심 구성 요소 5가지
Three.js를 처음 배울 때 가장 혼란스러운 부분이 바로 용어입니다. Scene이 뭔지, Mesh가 Geometry랑 어떻게 다른지 헷갈리면 코드를 봐도 머릿속에 그림이 안 그려집니다. 아래 다섯 가지만 확실히 이해하면 Three.js 코드의 70%는 읽을 수 있습니다.
- Scene (씬): 3D 세계의 무대이자 컨테이너입니다. 모든 오브젝트, 조명, 카메라가 Scene 안에 배치되며, 이것이 없으면 아무것도 화면에 표시되지 않습니다.
- Camera (카메라): 3D 세계를 바라보는 시점을 결정합니다. 가장 많이 쓰이는 PerspectiveCamera는 인간의 눈처럼 원근감을 표현하며, fov·aspect·near·far 네 가지 속성으로 설정합니다.
- Renderer (렌더러): Scene과 Camera 정보를 받아 실제 픽셀로 화면에 그려주는 엔진입니다. WebGLRenderer가 기본이며, canvas 엘리먼트와 연결해 DOM에 삽입합니다.
- Geometry + Material = Mesh: Geometry는 오브젝트의 형태(모양 데이터), Material은 표면의 재질(색상·광택·텍스처)입니다. 이 둘을 합쳐 Mesh를 만들면 비로소 눈에 보이는 3D 오브젝트가 완성됩니다.
- Light (조명): 빛이 없으면 3D 오브젝트는 새까만 덩어리로 보입니다. AmbientLight(전체 환경광), DirectionalLight(태양광 방향), PointLight(전구) 등 용도에 따라 다양하게 조합합니다.
다섯 가지 핵심 구성 요소를 각각 생성하고 속성을 설정하는 전체 예제 코드입니다. 각 요소의 주요 옵션과 주석을 함께 확인하세요.
// ============================================================
// Three.js 5대 핵심 구성 요소 완전 예제
// ============================================================
import * as THREE from 'three';
// ── 1. SCENE: 3D 세계 무대 설정 ──────────────────────────────
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f4ff); // 연한 파란 배경
// scene.add(object) → 오브젝트를 씬에 등록
// scene.remove(object) → 씬에서 제거
// scene.children → 씬 안의 모든 오브젝트 배열
// ── 2. CAMERA: 시점(뷰포인트) 설정 ──────────────────────────
// PerspectiveCamera: 원근감 있는 일반 카메라 (가장 많이 사용)
const camera = new THREE.PerspectiveCamera(
75, // fov (Field of View): 시야각
window.innerWidth / window.innerHeight, // aspect: 가로/세로 비율
0.1, // near: 최소 렌더 거리
1000 // far: 최대 렌더 거리
);
camera.position.set(3, 3, 3); // 카메라를 비스듬히 위에서 바라보기
camera.lookAt(scene.position); // 씬 중심(0,0,0)을 응시
// ── 3. GEOMETRY: 오브젝트 형태 (모양 데이터) ─────────────────
const boxGeo = new THREE.BoxGeometry(1, 1, 1); // 정육면체 (너비, 높이, 깊이)
const sphereGeo = new THREE.SphereGeometry(0.5, 32, 32); // 구 (반지름, 수평분할, 수직분할)
const torusGeo = new THREE.TorusGeometry(0.5, 0.2, 16, 100); // 도넛 (반지름, 튜브반지름)
// ── 4. MATERIAL: 표면 재질 설정 ──────────────────────────────
// MeshStandardMaterial: PBR(물리기반렌더링) 재질, 가장 현실감 높음
const blueMat = new THREE.MeshStandardMaterial({
color: 0x2196f3, // 색상 (헥스코드)
metalness: 0.2, // 금속성 (0=비금속, 1=완전금속)
roughness: 0.5, // 거칠기 (0=반짝, 1=완전무광)
});
// MeshBasicMaterial: 조명 무시, 빠른 테스트용
const redMat = new THREE.MeshBasicMaterial({ color: 0xff5252 });
// ── 5. MESH: Geometry + Material → 화면에 보이는 오브젝트 ────
const cube = new THREE.Mesh(boxGeo, blueMat);
const sphere = new THREE.Mesh(sphereGeo, redMat);
cube.position.set(-1.5, 0, 0); // 왼쪽 배치
sphere.position.set(1.5, 0, 0); // 오른쪽 배치
cube.castShadow = true; // 그림자 투사
cube.receiveShadow = true; // 그림자 수신
// ── 6. LIGHT: 조명 추가 ─────────────────────────────────────
const ambient = new THREE.AmbientLight(0xffffff, 0.4); // 환경광 (강도 0.4)
const dirLight = new THREE.DirectionalLight(0xffffff, 1.0); // 방향 조명
dirLight.position.set(5, 10, 5);
dirLight.castShadow = true; // 조명의 그림자 투사 활성화
// 모든 요소를 씬에 추가
scene.add(cube, sphere, ambient, dirLight);
console.log('씬 안의 오브젝트 수:', scene.children.length); // → 4
💡 실전 팁: MeshBasicMaterial은 조명의 영향을 받지 않아 테스트용으로 빠르게 쓰기 좋습니다. 실제 조명 효과를 보려면 MeshStandardMaterial 또는 MeshPhongMaterial을 사용하세요.
![]() |
| WebGL vs Three.js 비교 분석 – 웹 3D 개발 생산성 차이 |
4. Three.js 환경 설정과 첫 3D 씬 만들기 실전 가이드
"설치가 복잡하지 않을까?" 걱정부터 하시는 분들이 많은데, 놀랍게도 Three.js는 CDN 한 줄로 바로 시작할 수 있습니다. 물론 실무에서는 npm + Vite 조합을 권장하지만, 처음 익히는 단계에서는 HTML 파일 하나로도 3D 씬을 띄울 수 있습니다.
🛠️ 환경 설정 방법 비교 (입문 → 실무 순서)
- ① CDN (최초 입문) — HTML 파일 하나로 즉시 시작. 설치 불필요. 빠른 프로토타이핑에 최적
- ② npm + Vite (실무 권장) —
npm create vite@latest→npm install three. HMR·번들링 지원 - ③ React Three Fiber —
npm install @react-three/fiber three. React 프로젝트 통합 시 선택 - ④ CodeSandbox / StackBlitz — 브라우저 기반 IDE에서 설치 없이 즉시 Three.js 실행 가능
아래는 HTML 파일 하나로 바로 실행 가능한 완성형 Three.js 씬입니다. 복사 후 .html 파일로 저장하고 Live Server로 열면 즉시 동작합니다.
<!-- ============================================================ -->
<!-- Three.js 완성형 첫 번째 씬: 조명 + 그림자 + 리사이즈 대응 -->
<!-- 파일명: threejs-first-scene.html -->
<!-- ============================================================ -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Three.js 첫 번째 3D 씬</title>
<style>
/* 스크롤바·여백 제거 후 캔버스가 전체 화면 채우도록 설정 */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { overflow: hidden; background: #0a0a1a; }
</style>
</head>
<body>
<script type="module">
// ── Three.js CDN import (ES Module 방식) ─────────────────
import * as THREE from
'https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js';
// ── Scene: 3D 무대 생성 ──────────────────────────────────
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a1a); // 다크 배경
// ── Camera: 원근 카메라 설정 ─────────────────────────────
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
100
);
camera.position.set(0, 2, 5); // 약간 위에서 바라봄
// ── Renderer: WebGL 렌더러 초기화 ───────────────────────
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); // canvas 삽입
// ── Geometry + Material → Mesh 생성 ─────────────────────
const geometry = new THREE.BoxGeometry(1.5, 1.5, 1.5);
const material = new THREE.MeshStandardMaterial({
color: 0x2196f3, // 파란색
metalness: 0.3,
roughness: 0.4,
});
const cube = new THREE.Mesh(geometry, material);
cube.castShadow = true;
scene.add(cube);
// ── 바닥면 (그림자 수신용) ───────────────────────────────
const floor = new THREE.Mesh(
new THREE.PlaneGeometry(10, 10),
new THREE.MeshStandardMaterial({ color: 0x1a1a2e })
);
floor.rotation.x = -Math.PI / 2; // 수평으로 눕힘
floor.position.y = -1.5;
floor.receiveShadow = true;
scene.add(floor);
// ── 조명 추가 ────────────────────────────────────────────
const ambient = new THREE.AmbientLight(0xffffff, 0.3);
const dirLight = new THREE.DirectionalLight(0xffffff, 1.5);
dirLight.position.set(5, 8, 5);
dirLight.castShadow = true;
scene.add(ambient, dirLight);
// ── 창 크기 변경 대응 (반응형) ──────────────────────────
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix(); // aspect 반영 필수
renderer.setSize(window.innerWidth, window.innerHeight);
});
// ── 애니메이션 루프 ──────────────────────────────────────
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const t = clock.getElapsedTime();
cube.rotation.x = t * 0.5; // X축 회전
cube.rotation.y = t * 0.8; // Y축 회전
cube.position.y = Math.sin(t) * 0.3; // 위아래 부유
renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>
⚠️ 주의: type="module" ES Module 방식은 로컬 파일(file://)에서 CORS 오류가 발생합니다. VS Code의 Live Server 확장 또는 npx serve . 명령으로 반드시 서버 환경에서 실행하세요.
5. Three.js vs Babylon.js vs PlayCanvas 비교표
Three.js만 있는 게 아닙니다. 웹 3D 라이브러리 세계엔 Babylon.js, PlayCanvas, A-Frame 등 다양한 선택지가 있습니다. 실제로 "어떤 걸 써야 하나요?"라는 질문이 커뮤니티에서 가장 많이 올라오는 주제이기도 합니다.
| 항목 | Three.js | Babylon.js | PlayCanvas |
|---|---|---|---|
| 라이선스 | MIT (오픈소스) | Apache 2.0 | MIT + 에디터 유료 |
| 주요 용도 | 3D 시각화, 인터랙티브 | 웹 게임, XR(AR/VR) | 게임엔진 수준 웹앱 |
| 학습 난이도 | 중 (자유도 높음) | 중-상 (기능 많음) | 낮음 (비주얼에디터) |
| React 연동 | React Three Fiber | 공식 React 래퍼 | 제한적 |
| GitHub 스타 | 100,000+ ⭐ | 23,000+ ⭐ | 9,000+ ⭐ |
Three.js와 Babylon.js로 동일한 결과물(회전하는 구)을 각각 구현한 코드를 나란히 비교해 보겠습니다.
// ============================================================
// [비교] Three.js vs Babylon.js — 회전하는 구 구현 코드
// ============================================================
// ── ① Three.js 버전 ─────────────────────────────────────────
// npm install three
import * as THREE from 'three';
// 씬 / 카메라 / 렌더러 설정
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 100);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
camera.position.z = 3;
// 구 생성 (반지름 1, 가로32분할, 세로32분할)
const sphere = new THREE.Mesh(
new THREE.SphereGeometry(1, 32, 32),
new THREE.MeshStandardMaterial({ color: 0x2196f3, roughness: 0.4 })
);
scene.add(sphere);
scene.add(new THREE.DirectionalLight(0xffffff, 1.5).position.set(5, 5, 5) && new THREE.DirectionalLight(0xffffff, 1.5));
scene.add(new THREE.AmbientLight(0xffffff, 0.4));
// 애니메이션: 매 프레임 Y축 회전
(function animate() {
requestAnimationFrame(animate);
sphere.rotation.y += 0.01; // 초당 약 0.6 라디안 회전
renderer.render(scene, camera);
})();
// ── ② Babylon.js 버전 (동일 결과) ───────────────────────────
// npm install @babylonjs/core
/*
import { Engine, Scene, ArcRotateCamera,
HemisphericLight, MeshBuilder, StandardMaterial,
Color3, Vector3 } from '@babylonjs/core';
const canvas = document.getElementById('canvas');
const engine = new Engine(canvas, true); // WebGL 엔진 초기화
const bScene = new Scene(engine); // 씬 생성
// ArcRotateCamera: 마우스로 궤도 회전 가능한 카메라
const bCamera = new ArcRotateCamera('cam', -Math.PI/2, Math.PI/3, 5, Vector3.Zero(), bScene);
bCamera.attachControl(canvas, true);
// 조명 추가 (Hemispheric = 위아래 다른 색 조명)
new HemisphericLight('light', new Vector3(0, 1, 0), bScene);
// 구 생성
const bSphere = MeshBuilder.CreateSphere('sphere', { diameter: 2 }, bScene);
const bMat = new StandardMaterial('mat', bScene);
bMat.diffuseColor = new Color3(0.13, 0.59, 0.95); // #2196f3 RGB
bSphere.material = bMat;
// 렌더 루프 (Babylon 방식)
engine.runRenderLoop(() => {
bSphere.rotation.y += 0.01;
bScene.render();
});
window.addEventListener('resize', () => engine.resize());
*/
// ── 결론: 동일 결과, 다른 API 철학 ─────────────────────────
// Three.js → 자유도 높음, 코드 직접 제어, 가볍고 유연
// Babylon.js → 기능 내장, 자동화 많음, 게임 개발에 특화
console.log('라이브러리 비교 완료! 프로젝트 목적에 맞게 선택하세요.');
결론: 웹 포트폴리오·인터랙티브 사이트라면 Three.js, 웹 기반 게임이라면 Babylon.js가 현재 가장 현명한 선택입니다.
6. Three.js 입문자 실수 방지 체크리스트
Three.js를 처음 배우는 분들이 공통적으로 막히는 포인트가 있습니다. 필자가 직접 겪은 시행착오와 커뮤니티에서 가장 많이 올라오는 질문들을 모아 체크리스트로 정리했습니다.
- ☑ 조명을 추가했는가: MeshStandardMaterial 사용 시 조명이 없으면 오브젝트가 완전히 검게 보입니다. AmbientLight 최소 하나는 반드시 추가하세요.
- ☑ animate() 루프를 실행했는가: renderer.render()를 한 번만 호출하면 정지 화면만 나옵니다. requestAnimationFrame 루프 안에 넣어야 애니메이션이 살아납니다.
- ☑ 카메라 위치가 오브젝트와 겹치지 않는가: 기본값은 원점(0,0,0)이라 오브젝트 안에 카메라가 있을 수 있습니다. camera.position.z = 3 처럼 앞으로 빼주세요.
- ☑ 리사이즈 이벤트를 처리했는가: 창 크기 변경 시 camera.aspect와 renderer.setSize()를 함께 업데이트하지 않으면 화면이 뭉개집니다.
- ☑ Geometry와 Material 메모리를 정리했는가: SPA에서 Three.js를 쓸 때 geometry.dispose(), material.dispose()를 호출하지 않으면 메모리 누수가 발생합니다.
위 다섯 가지 실수를 코드로 직접 비교합니다. 잘못된 패턴(❌)과 올바른 패턴(✅)을 나란히 확인하세요.
// ============================================================
// Three.js 입문자 5대 실수 — 잘못된 패턴 vs 올바른 패턴
// ============================================================
import * as THREE from 'three';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 100);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
const cube = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshStandardMaterial({ color: 0x2196f3 })
);
scene.add(cube);
// ── 실수 1: 조명 없음 ────────────────────────────────────────
// ❌ 잘못된 패턴: MeshStandardMaterial인데 조명이 없으면 검정색
// scene.add(cube); // 조명 추가 안 함 → 화면에 검은 박스만 보임
// ✅ 올바른 패턴: 최소 AmbientLight 하나 필수
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
const dirLight = new THREE.DirectionalLight(0xffffff, 1.0);
dirLight.position.set(5, 5, 5);
scene.add(dirLight);
// ── 실수 2: 카메라 위치 미설정 ──────────────────────────────
// ❌ 잘못된 패턴: 카메라 기본 위치는 (0,0,0) → 큐브 안에 들어있음
// camera.position.z = 0; // 아무것도 안 보임
// ✅ 올바른 패턴: 오브젝트보다 Z축 앞으로 뺌
camera.position.set(0, 1, 4);
camera.lookAt(0, 0, 0);
// ── 실수 3: 애니메이션 루프 없이 단 한 번만 렌더 ─────────────
// ❌ 잘못된 패턴: 한 번만 렌더 → 정지 화면
// renderer.render(scene, camera);
// ✅ 올바른 패턴: requestAnimationFrame 루프로 매 프레임 렌더
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
cube.rotation.y = clock.getElapsedTime(); // 시간 기반 회전
renderer.render(scene, camera);
}
animate();
// ── 실수 4: 리사이즈 이벤트 미처리 ─────────────────────────
// ❌ 잘못된 패턴: 창 크기 변경 시 화면이 늘어나거나 뭉개짐
// window.addEventListener('resize', () => renderer.setSize(innerWidth, innerHeight));
// → camera.aspect 업데이트 없으면 비율이 무너짐
// ✅ 올바른 패턴: aspect + updateProjectionMatrix + renderer 크기 모두 갱신
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix(); // aspect 변경 후 반드시 호출
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});
// ── 실수 5: 메모리 누수 (SPA 환경) ──────────────────────────
// ❌ 잘못된 패턴: 씬 전환 시 dispose 없이 제거 → GPU 메모리 누수
// scene.remove(cube); // 씬에서 제거해도 GPU 메모리는 해제 안 됨
// ✅ 올바른 패턴: dispose()로 GPU 자원 명시적 해제 후 제거
function removeCube() {
cube.geometry.dispose(); // 지오메트리 GPU 메모리 해제
cube.material.dispose(); // 재질 GPU 메모리 해제
if (cube.material.map) {
cube.material.map.dispose(); // 텍스처도 있다면 함께 해제
}
scene.remove(cube); // 씬에서 오브젝트 제거
console.log('✅ 메모리 안전하게 해제 완료');
}
// removeCube(); // 씬 전환 시 호출
이 다섯 가지 체크포인트만 챙겨도 Three.js 입문 단계에서 마주치는 90%의 오류를 예방할 수 있습니다. 다음 FAQ 섹션에서 실제 질문들을 모아 정리했으니 꼭 확인해보세요.
![]() |
| Three.js 핵심 구조 한눈에 정리 – Scene Camera Renderer 시각화 |
7. 자주 묻는 질문 (FAQ)
전혀 그렇지 않습니다. Three.js는 WebGL의 복잡한 부분을 모두 추상화해주기 때문에 JavaScript 기초 문법만 알면 바로 시작할 수 있습니다. WebGL 지식은 커스텀 셰이더가 필요한 고급 단계에서 필요하며, 입문 시에는 3번 섹션의 핵심 구성 요소 다섯 가지만 익혀도 충분합니다.
iOS Safari와 Android Chrome 모두 WebGL을 지원하므로 기본적으로 동작합니다. 다만 모바일 GPU 성능이 데스크탑보다 제한적이기 때문에 폴리곤 수를 줄이고, renderer.setPixelRatio()를 1~1.5로 제한하는 최적화가 필요합니다. 4번 섹션의 기본 설정을 출발점으로 활용하세요.
React Three Fiber(R3F)를 사용하는 것이 현재 가장 보편적인 방법입니다. JSX 문법으로 Three.js 오브젝트를 선언적으로 작성할 수 있어 React 개발 방식과 자연스럽게 통합됩니다. Drei 라이브러리를 함께 쓰면 OrbitControls, 텍스트, 환경맵 등 자주 쓰는 기능을 단 한 줄로 추가할 수 있습니다.
네, Three.js에 내장된 GLTFLoader를 사용하면 Blender 등에서 만든 .glb/.gltf 파일을 손쉽게 임포트할 수 있습니다. GLTF 포맷은 텍스처·애니메이션·재질 정보를 모두 포함하는 3D 업계 표준으로, Three.js와의 호환성이 매우 뛰어납니다. 로더 사용법은 4번 섹션의 기본 구조를 바탕으로 확장하면 됩니다.
가장 효과적인 방법은 DrawCall 수 줄이기(Geometry 병합), 텍스처 압축(KTX2/Basis), 오브젝트 수에 따른 Instancing 활용, 그리고 renderer.shadowMap을 필요할 때만 활성화하는 것입니다. 6번 체크리스트의 dispose 처리도 메모리 누수 방지에 핵심적입니다. 더 궁금한 점은 댓글로 남겨주세요!
8. 마무리 요약
✅ 웹 브라우저 3D 시대, Three.js가 당신의 첫 선택이어야 하는 이유
Three.js는 WebGL의 복잡함을 걷어내고 JavaScript만으로 3D 세계를 만들 수 있게 해주는 오픈소스 라이브러리입니다. Scene·Camera·Renderer·Mesh·Light 다섯 가지 핵심 개념을 이해하는 것이 모든 것의 출발점이며, 단 20줄의 코드로 첫 번째 3D 씬을 완성할 수 있습니다. Babylon.js·PlayCanvas와 비교해도 커뮤니티 규모와 생태계에서 압도적 우위를 가지며, React Three Fiber를 통해 현대 프론트엔드 개발 흐름과도 완벽하게 통합됩니다. 입문자 체크리스트의 다섯 가지 항목만 지켜도 흔한 실수의 90%를 예방할 수 있습니다. 지금 당장 HTML 파일 하나를 열고, 이 글의 예제 코드를 복사해 첫 번째 3D 정육면체를 화면에 띄워보세요. 그 순간부터 여러분은 이미 Three.js 개발자입니다.
웹은 이제 더 이상 평면이 아닙니다. Three.js 하나면 누구나 브라우저 안에 3D 우주를 만들 수 있습니다. 오늘 배운 내용 중 가장 먼저 해볼 것은 단 하나, 4번 섹션의 예제 코드를 복사해서 직접 돌려보는 것입니다. 코드가 실행되는 순간 Three.js의 매력이 바로 느껴질 겁니다. 여러분은 Three.js를 배우면서 가장 어렵게 느낀 부분이 무엇이었나요? 댓글로 공유해주시면 다음 포스팅에서 더 깊이 다뤄드리겠습니다.


댓글
댓글 쓰기