본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 29. · 2 Views
Three.js 기초 개념 완벽 가이드
Three.js의 핵심 개념인 Scene, Camera, Renderer부터 조명, 재질, 애니메이션까지 실무에서 바로 사용할 수 있는 3D 웹 개발 기초를 다룹니다. 초급 개발자도 쉽게 따라할 수 있도록 단계별로 설명합니다.
목차
- Scene, Camera, Renderer 이해하기
- 기본 3D 객체 생성하기
- 조명 추가 및 설정
- 재질과 텍스처 기본
- 애니메이션 루프 구조
- OrbitControls로 카메라 조작
- 웹 브라우저에서 첫 3D 씬 렌더링
1. Scene, Camera, Renderer 이해하기
어느 날 김개발 씨는 회사에서 새로운 프로젝트를 맡게 되었습니다. "이번에는 웹에서 3D 제품을 보여주는 페이지를 만들어야 해요." 팀장님의 말씀을 듣고 김개발 씨는 Three.js를 처음 접하게 되었습니다.
그런데 문서를 보니 Scene, Camera, Renderer라는 생소한 용어들이 나옵니다.
Three.js에서 3D 화면을 만들려면 세 가지 필수 요소가 필요합니다. Scene은 3D 객체들을 담는 무대이고, Camera는 그 무대를 바라보는 시점이며, Renderer는 최종적으로 화면에 그려주는 역할을 합니다.
마치 영화 촬영에서 무대, 카메라, 영사기가 필요한 것과 같습니다.
다음 코드를 살펴봅시다.
// Three.js 기본 설정
import * as THREE from 'three';
// 1. Scene 생성 - 3D 객체들을 담을 무대
const scene = new THREE.Scene();
// 2. Camera 생성 - 무대를 바라볼 시점
const camera = new THREE.PerspectiveCamera(
75, // 시야각
window.innerWidth / window.innerHeight, // 화면 비율
0.1, // 가까운 렌더링 거리
1000 // 먼 렌더링 거리
);
camera.position.z = 5; // 카메라를 뒤로 이동
// 3. Renderer 생성 - 화면에 그려주는 역할
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
김개발 씨는 입사 6개월 차 프론트엔드 개발자입니다. 그동안 HTML, CSS, JavaScript로 평면적인 웹 페이지만 만들어왔는데, 이번에는 3D를 다뤄야 한다니 막막하기만 합니다.
선배 개발자 박시니어 씨가 김개발 씨의 고민을 듣고 친절하게 설명해줍니다. "Three.js는 어렵지 않아요.
영화 촬영을 생각해보면 쉽게 이해할 수 있습니다." 그렇다면 Scene, Camera, Renderer란 정확히 무엇일까요? 쉽게 비유하자면, Scene은 영화 촬영 세트장과 같습니다.
배우들과 소품들이 놓이는 무대입니다. Three.js에서는 큐브, 구, 조명 등 모든 3D 객체들이 이 Scene 안에 배치됩니다.
Scene이 없다면 아무것도 표현할 수 없습니다. Camera는 말 그대로 카메라입니다.
세트장을 어떤 각도에서, 어떤 거리에서 바라볼 것인지를 결정합니다. 같은 무대라도 카메라 위치에 따라 완전히 다른 장면이 보입니다.
Three.js에는 여러 종류의 카메라가 있지만, 가장 일반적으로 사용하는 것이 PerspectiveCamera입니다. Renderer는 영사기라고 생각하면 됩니다.
카메라가 찍은 장면을 실제 스크린에 보여주는 역할입니다. Three.js에서는 WebGL 기술을 사용해서 브라우저의 canvas 요소에 3D 장면을 그려냅니다.
Three.js가 등장하기 전에는 어땠을까요? 개발자들은 WebGL API를 직접 다뤄야 했습니다.
수백 줄의 복잡한 코드를 작성해야 했고, 행렬 계산도 직접 해야 했습니다. 더 큰 문제는 브라우저마다 호환성 이슈가 있었다는 점입니다.
간단한 3D 큐브 하나를 표시하는 데도 며칠이 걸렸습니다. 바로 이런 문제를 해결하기 위해 Three.js가 등장했습니다.
Three.js를 사용하면 복잡한 WebGL 코드를 몇 줄로 줄일 수 있습니다. 또한 크로스 브라우저 호환성 문제도 라이브러리가 알아서 처리해줍니다.
무엇보다 직관적인 API로 초보자도 쉽게 3D 개발을 시작할 수 있다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 Scene을 생성합니다. 이것이 모든 3D 객체를 담을 컨테이너입니다.
다음으로 PerspectiveCamera를 만듭니다. 첫 번째 인자 75는 시야각(FOV)입니다.
사람의 눈과 비슷한 자연스러운 시야를 만들어줍니다. 두 번째 인자는 화면의 가로세로 비율입니다.
세 번째와 네 번째는 렌더링할 거리 범위를 정의합니다. 마지막으로 WebGLRenderer를 생성하고, 화면 크기를 설정한 뒤 DOM에 추가합니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 온라인 가구 쇼핑몰을 개발한다고 가정해봅시다.
고객이 소파를 360도 회전하며 볼 수 있는 기능을 만들 때 이 세 가지 요소가 기본이 됩니다. 카카오, 네이버 같은 대기업에서도 3D 지도나 제품 뷰어를 만들 때 이런 구조를 사용합니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 Renderer를 여러 개 만드는 것입니다.
하나의 페이지에서는 보통 하나의 Renderer만 있으면 됩니다. 여러 개를 만들면 성능이 크게 떨어집니다.
따라서 하나의 Renderer를 재사용하는 것이 올바른 방법입니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "아, 영화 촬영처럼 생각하니까 이해가 되네요!" Scene, Camera, Renderer를 제대로 이해하면 Three.js의 기본 구조를 파악할 수 있습니다.
여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - Camera의 position.z 값을 조정하면 객체와의 거리를 쉽게 변경할 수 있습니다
- 반응형 웹을 위해 window resize 이벤트에서 camera와 renderer 크기를 업데이트해야 합니다
- Scene.background를 설정하면 배경색을 쉽게 변경할 수 있습니다
2. 기본 3D 객체 생성하기
Scene, Camera, Renderer 설정을 마친 김개발 씨는 이제 실제로 화면에 무언가를 그려보고 싶어졌습니다. "어떻게 하면 3D 물체를 만들 수 있을까요?" 박시니어 씨는 미소를 지으며 말합니다.
"가장 기본이 되는 Geometry와 Mesh를 알아야 해요."
Three.js에서 3D 객체를 만들려면 Geometry와 Material을 결합한 Mesh가 필요합니다. Geometry는 물체의 형태를 정의하고, Material은 표면의 재질을 정의합니다.
이 둘을 합쳐 Mesh를 만들면 Scene에 추가할 수 있는 완전한 3D 객체가 됩니다.
다음 코드를 살펴봅시다.
// 큐브 만들기
const geometry = new THREE.BoxGeometry(1, 1, 1); // 가로, 세로, 깊이
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); // 녹색
const cube = new THREE.Mesh(geometry, material); // Geometry + Material
scene.add(cube); // Scene에 추가
// 구 만들기
const sphereGeometry = new THREE.SphereGeometry(0.7, 32, 32); // 반지름, 가로 세그먼트, 세로 세그먼트
const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 }); // 빨간색
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.x = 2; // x축으로 2만큼 이동
scene.add(sphere);
// 렌더링
renderer.render(scene, camera);
김개발 씨는 코드 에디터 앞에 앉아 있습니다. Scene, Camera, Renderer는 설정했지만 화면은 여전히 검은색입니다.
"뭔가 나와야 할 것 같은데..." 하고 중얼거립니다. 박시니어 씨가 옆에서 설명을 시작합니다.
"3D 객체를 만드는 건 레고 블록 조립과 비슷해요. 블록의 모양과 색깔을 정해주면 되죠." 그렇다면 Geometry, Material, Mesh란 정확히 무엇일까요?
쉽게 비유하자면, Geometry는 뼈대입니다. 물체가 정육면체인지, 구인지, 원기둥인지를 결정합니다.
이것은 순수하게 형태 정보만 담고 있습니다. 점들의 위치, 면들의 연결 정보 등이 여기에 포함됩니다.
Material은 피부입니다. 뼈대에 어떤 색깔을 입힐 것인지, 반짝이게 할 것인지, 투명하게 할 것인지를 결정합니다.
같은 정육면체 형태라도 Material에 따라 완전히 다른 느낌을 줄 수 있습니다. Mesh는 이 둘의 결합입니다.
뼈대에 피부를 입힌 완성된 물체라고 생각하면 됩니다. Mesh만 Scene에 추가할 수 있고, 화면에 표시됩니다.
3D 라이브러리가 없던 시절에는 어땠을까요? 개발자들은 정점(vertex) 하나하나를 일일이 정의해야 했습니다.
정육면체를 만들려면 8개의 꼭짓점과 6개의 면을 수작업으로 계산해야 했습니다. 색상도 RGB 값을 버퍼에 직접 넣어줘야 했습니다.
간단한 큐브 하나 만드는데도 100줄이 넘는 코드가 필요했습니다. 바로 이런 문제를 해결하기 위해 Three.js의 Geometry 시스템이 등장했습니다.
Three.js를 사용하면 한 줄로 복잡한 3D 형태를 만들 수 있습니다. BoxGeometry, SphereGeometry, CylinderGeometry 등 자주 사용하는 형태들이 미리 준비되어 있습니다.
또한 Material 시스템으로 다양한 시각 효과를 쉽게 적용할 수 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 BoxGeometry를 생성합니다. 가로, 세로, 깊이가 모두 1인 정육면체 형태입니다.
다음으로 MeshBasicMaterial을 만듭니다. 0x00ff00은 16진수 색상 코드로 녹색을 의미합니다.
이 둘을 Mesh로 결합하고 Scene에 추가합니다. 구를 만들 때는 SphereGeometry를 사용하며, position.x를 조정해서 큐브 옆에 배치합니다.
마지막으로 renderer.render()를 호출해야 화면에 그려집니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 건축 시각화 서비스를 개발한다고 가정해봅시다. 건물은 BoxGeometry를 여러 개 조합하고, 나무는 CylinderGeometry와 SphereGeometry를 결합해서 만들 수 있습니다.
포트폴리오 사이트에서 3D 로고를 회전시키는 효과도 이런 기본 Geometry로 시작합니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 불필요하게 복잡한 Geometry를 사용하는 것입니다. SphereGeometry의 세그먼트를 너무 많이 설정하면 성능이 떨어집니다.
화면에서 작게 보이는 객체라면 세그먼트를 줄여도 충분합니다. 따라서 용도에 맞게 적절한 디테일 수준을 선택해야 합니다.
또 다른 실수는 renderer.render()를 호출하지 않는 것입니다. Scene에 객체를 추가했다고 자동으로 화면에 나타나지 않습니다.
반드시 렌더링 함수를 호출해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
코드를 작성하고 실행하자 화면에 녹색 큐브와 빨간색 구가 나타났습니다. "와, 됐어요!" 김개발 씨는 신이 났습니다.
Geometry와 Material, 그리고 Mesh의 관계를 이해하면 어떤 3D 객체든 만들 수 있습니다. 여러분도 다양한 Geometry를 실험해 보세요.
실전 팁
💡 - MeshBasicMaterial은 조명의 영향을 받지 않습니다. 항상 같은 색으로 보입니다
- Geometry의 세그먼트 수는 성능에 직접적인 영향을 미칩니다. 필요한 만큼만 사용하세요
- position, rotation, scale 속성으로 Mesh의 위치, 회전, 크기를 자유롭게 조정할 수 있습니다
3. 조명 추가 및 설정
큐브와 구를 화면에 표시하는 데 성공한 김개발 씨는 뭔가 허전함을 느낍니다. "왜 이렇게 밋밋하게 보이죠?" 박시니어 씨가 화면을 보더니 말합니다.
"아, 조명이 없네요. 3D 세계에도 빛이 필요합니다."
Three.js에서 조명은 3D 객체에 입체감과 현실감을 부여합니다. AmbientLight는 전체적인 밝기를 제공하고, DirectionalLight는 태양처럼 특정 방향에서 비추는 빛입니다.
PointLight는 전구처럼 한 점에서 사방으로 퍼지는 빛을 표현합니다. 조명을 제대로 활용하면 평면적인 3D 객체가 생생하게 살아납니다.
다음 코드를 살펴봅시다.
// 기본 Material을 조명 반응 Material로 변경
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 전체 밝기를 제공하는 환경광
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // 색상, 강도
scene.add(ambientLight);
// 특정 방향에서 비추는 직사광
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 5, 5); // 위 오른쪽에서 비춤
scene.add(directionalLight);
// 한 점에서 퍼지는 점광원
const pointLight = new THREE.PointLight(0xff0000, 1, 100); // 색상, 강도, 거리
pointLight.position.set(-5, 3, 0);
scene.add(pointLight);
김개발 씨는 조명 없이 만든 3D 장면을 보며 고개를 갸우뚱합니다. 분명히 녹색 큐브를 만들었는데, 화면에는 단순한 녹색 사각형처럼 보입니다.
입체감이 전혀 느껴지지 않습니다. 박시니어 씨가 설명합니다.
"실제 세계를 생각해보세요. 어두운 방에서는 아무것도 보이지 않잖아요.
3D 세계도 마찬가지입니다." 그렇다면 Three.js의 조명 시스템은 정확히 어떻게 작동할까요? 쉽게 비유하자면, AmbientLight는 방 전체를 은은하게 밝혀주는 간접 조명과 같습니다.
특정 방향이 없이 모든 면을 고르게 밝혀줍니다. 그림자가 생기지 않고, 너무 어두운 부분이 없도록 기본 밝기를 제공합니다.
DirectionalLight는 태양빛과 같습니다. 멀리 있는 광원에서 평행하게 쏟아지는 빛입니다.
방향이 명확하기 때문에 그림자를 만들 수 있고, 물체의 입체감을 표현하는 데 가장 효과적입니다. 실외 장면을 만들 때 주로 사용합니다.
PointLight는 전구나 촛불과 같습니다. 한 점에서 사방으로 빛이 퍼져나갑니다.
가까운 곳은 밝고 먼 곳은 어두워지는 자연스러운 감쇠 효과가 있습니다. 실내 조명이나 특수 효과를 만들 때 유용합니다.
조명이 없던 초기 3D 웹 개발은 어땠을까요? 개발자들은 셰이더 코드를 직접 작성해서 빛의 계산을 구현해야 했습니다.
램버트 반사, 스펙큘러 하이라이트 같은 물리 공식을 일일이 코딩했습니다. 한 가지 조명 효과를 추가하는 데만 며칠이 걸렸고, 코드는 수백 줄로 늘어났습니다.
바로 이런 문제를 해결하기 위해 Three.js의 조명 시스템이 등장했습니다. Three.js를 사용하면 몇 줄의 코드로 사실적인 조명 효과를 구현할 수 있습니다.
물리 기반 렌더링도 자동으로 처리됩니다. 또한 실시간으로 조명 위치나 색상을 변경할 수 있어서 동적인 장면을 쉽게 만들 수 있습니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 중요한 점은 Material을 MeshStandardMaterial로 변경해야 한다는 것입니다.
MeshBasicMaterial은 조명의 영향을 받지 않습니다. AmbientLight를 추가하면 전체적인 밝기가 확보됩니다.
강도 0.5는 적당히 은은한 밝기입니다. DirectionalLight는 position.set으로 방향을 지정합니다.
위 오른쪽에서 비추므로 왼쪽 아래 부분이 어두워집니다. PointLight는 빨간색 빛을 내며, 100은 빛이 도달하는 최대 거리입니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 게임 캐릭터 뷰어를 개발한다고 가정해봅시다.
캐릭터를 돋보이게 하려면 DirectionalLight로 주 조명을 만들고, AmbientLight로 그림자 부분이 너무 어둡지 않게 합니다. PointLight를 캐릭터 주변에 배치하면 드라마틱한 효과를 낼 수 있습니다.
명품 브랜드 웹사이트에서 제품을 전시할 때도 이런 조명 기법을 사용합니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 조명을 너무 많이 추가하는 것입니다. 조명 하나하나가 렌더링 성능에 영향을 미칩니다.
특히 그림자를 생성하는 조명은 성능 비용이 큽니다. 따라서 꼭 필요한 만큼만 사용하고, 가능하면 AmbientLight와 DirectionalLight 조합으로 시작하는 것이 좋습니다.
또 다른 실수는 조명 강도를 잘못 설정하는 것입니다. 너무 밝으면 디테일이 날아가고, 너무 어두우면 아무것도 보이지 않습니다.
실제로 렌더링해보면서 적절한 값을 찾아야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
조명을 추가하고 나니 큐브가 진짜 입체로 보이기 시작했습니다. "와, 완전히 다르네요!" 김개발 씨는 감탄했습니다.
조명을 제대로 이해하면 밋밋한 3D 객체를 생동감 있게 만들 수 있습니다. 여러분도 다양한 조명을 실험해 보세요.
실전 팁
💡 - MeshBasicMaterial 대신 MeshStandardMaterial이나 MeshPhongMaterial을 사용해야 조명 효과가 나타납니다
- 그림자를 사용하려면 renderer.shadowMap.enabled = true로 설정하고, 조명과 객체에도 그림자 설정을 해야 합니다
- 조명 색상을 변경하면 분위기를 크게 바꿀 수 있습니다. 주황색 조명은 따뜻한 느낌, 파란색은 차가운 느낌을 줍니다
4. 재질과 텍스처 기본
조명까지 추가한 김개발 씨의 3D 장면은 제법 그럴듯해 보입니다. 하지만 팀장님이 피드백을 주셨습니다.
"좋은데, 표면이 너무 단조로워 보여요. 실제 나무 재질처럼 보이게 할 수는 없나요?" 김개발 씨는 다시 박시니어 씨를 찾아갑니다.
Material은 3D 객체의 표면 특성을 결정합니다. MeshStandardMaterial은 물리 기반 렌더링을 지원하며, roughness와 metalness로 표면의 거칠기와 금속성을 조절할 수 있습니다.
Texture는 이미지를 표면에 입혀서 더욱 사실적인 느낌을 만듭니다. 나무 결, 돌 표면, 금속 광택 등을 표현할 수 있습니다.
다음 코드를 살펴봅시다.
// TextureLoader로 이미지 불러오기
const textureLoader = new THREE.TextureLoader();
const woodTexture = textureLoader.load('/textures/wood.jpg');
// 물리 기반 재질 설정
const material = new THREE.MeshStandardMaterial({
map: woodTexture, // 기본 텍스처
roughness: 0.7, // 거칠기 (0: 매끄러움, 1: 거칠음)
metalness: 0.1 // 금속성 (0: 비금속, 1: 금속)
});
// 금속 재질 예시
const metalMaterial = new THREE.MeshStandardMaterial({
color: 0xcccccc,
roughness: 0.2,
metalness: 0.9
});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
김개발 씨는 단색 큐브를 보며 고민에 빠졌습니다. 팀장님의 요청대로 나무 재질처럼 보이게 하려면 어떻게 해야 할까요?
포토샵으로 이미지를 만들어야 하는 걸까요? 박시니어 씨가 친절하게 설명합니다.
"Three.js의 Material 시스템은 정말 강력해요. 실제 물리 법칙을 시뮬레이션할 수 있습니다." 그렇다면 Material과 Texture는 정확히 무엇일까요?
쉽게 비유하자면, Material은 물체의 재질 자체입니다. 플라스틱인지, 나무인지, 금속인지를 결정합니다.
roughness는 표면이 얼마나 거친지를 나타냅니다. 매끄러운 거울은 roughness가 0에 가깝고, 거친 돌은 1에 가깝습니다.
metalness는 금속인지 아닌지를 결정합니다. 이 값들이 빛의 반사를 계산하는 방식을 바꿉니다.
Texture는 표면에 붙이는 이미지입니다. 마치 벽에 벽지를 바르는 것과 같습니다.
나무 결 이미지를 입히면 나무처럼 보이고, 벽돌 이미지를 입히면 벽돌처럼 보입니다. 실제 사진을 사용할 수도 있고, 그래픽 도구로 만든 패턴을 사용할 수도 있습니다.
Texture가 없던 시절에는 어땠을까요? 개발자들은 셰이더 코드로 일일이 패턴을 그려야 했습니다.
체크무늬 하나 만드는 데도 복잡한 수학 계산이 필요했습니다. 사실적인 재질을 표현하는 것은 거의 불가능에 가까웠습니다.
모든 3D 객체가 단색 플라스틱처럼 보였습니다. 바로 이런 문제를 해결하기 위해 텍스처 맵핑 기술이 발전했습니다.
Three.js를 사용하면 이미지 파일 하나로 복잡한 표면을 쉽게 표현할 수 있습니다. TextureLoader가 자동으로 이미지를 WebGL 텍스처로 변환해줍니다.
또한 roughness와 metalness 값을 조정해서 다양한 재질을 시뮬레이션할 수 있습니다. 나무, 금속, 플라스틱, 천 등 실제 세계의 모든 재질을 표현할 수 있습니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 TextureLoader를 생성합니다.
이것은 이미지 파일을 불러오는 유틸리티입니다. load 메서드로 이미지 경로를 지정하면 텍스처 객체가 반환됩니다.
MeshStandardMaterial의 map 속성에 이 텍스처를 할당하면 표면에 이미지가 입혀집니다. roughness 0.7은 약간 거친 표면을 의미하고, metalness 0.1은 거의 비금속임을 의미합니다.
금속 재질을 만들 때는 metalness를 0.9 정도로 높이고 roughness를 낮춥니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 실내 인테리어 시뮬레이터를 개발한다고 가정해봅시다. 바닥은 나무 텍스처를, 테이블은 대리석 텍스처를 사용할 수 있습니다.
roughness와 metalness를 조정해서 광택 있는 대리석과 무광 대리석을 구분할 수 있습니다. 자동차 구성 도구에서는 차체를 금속 재질로, 타이어를 고무 재질로 표현할 수 있습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 너무 큰 이미지 파일을 사용하는 것입니다.
4K 해상도 텍스처는 로딩 시간도 길고 메모리도 많이 차지합니다. 웹에서는 보통 512x512나 1024x1024 정도면 충분합니다.
따라서 적절한 크기로 최적화된 이미지를 사용해야 합니다. 또 다른 실수는 텍스처의 반복(repeat)을 고려하지 않는 것입니다.
넓은 면에 작은 텍스처를 입히면 이미지가 늘어나 보이거나 흐려집니다. texture.wrapS와 texture.wrapT를 설정하고 repeat 값을 조정해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 나무 텍스처를 적용하자 큐브가 진짜 나무 상자처럼 보이기 시작했습니다.
"이제 진짜 같아요!" 김개발 씨는 뿌듯해했습니다. Material과 Texture를 제대로 활용하면 단순한 3D 객체를 사실적인 물체로 변화시킬 수 있습니다.
여러분도 다양한 재질을 실험해 보세요.
실전 팁
💡 - 무료 텍스처는 Poly Haven, Textures.com 같은 사이트에서 구할 수 있습니다
- normalMap을 추가하면 실제로 요철이 없어도 입체적으로 보이게 할 수 있습니다
- 텍스처 이미지는 2의 거듭제곱 크기(256, 512, 1024 등)로 만드는 것이 성능에 유리합니다
5. 애니메이션 루프 구조
정적인 3D 장면을 만드는 데 성공한 김개발 씨는 이제 움직임을 추가하고 싶어졌습니다. "큐브를 회전시키려면 어떻게 해야 하죠?" 박시니어 씨는 애니메이션 루프의 개념을 설명하기 시작합니다.
"영화처럼 연속된 프레임을 보여주면 됩니다."
Three.js에서 애니메이션을 만들려면 requestAnimationFrame을 사용한 렌더링 루프가 필요합니다. 이 함수는 브라우저의 다음 프레임이 그려지기 직전에 콜백을 호출합니다.
보통 초당 60회 실행되며, 각 프레임마다 객체의 위치나 회전을 조금씩 변경하면 부드러운 애니메이션이 만들어집니다.
다음 코드를 살펴봅시다.
// 애니메이션 함수 정의
function animate() {
// 다음 프레임 요청 (재귀 호출)
requestAnimationFrame(animate);
// 객체 회전 (매 프레임마다 조금씩)
cube.rotation.x += 0.01; // x축 회전
cube.rotation.y += 0.01; // y축 회전
// 구를 위아래로 움직이기
sphere.position.y = Math.sin(Date.now() * 0.001) * 2;
// 화면에 렌더링
renderer.render(scene, camera);
}
// 애니메이션 시작
animate();
김개발 씨는 회전하는 큐브를 보고 싶었습니다. rotation.x를 변경하는 코드를 작성했지만, 큐브는 여전히 정지해 있습니다.
"왜 안 움직이죠?" 김개발 씨는 당황했습니다. 박시니어 씨가 웃으며 설명합니다.
"한 번만 회전시켰으니까요. 계속 회전하려면 계속 그려줘야 합니다." 그렇다면 애니메이션 루프는 정확히 어떻게 작동할까요?
쉽게 비유하자면, 애니메이션 루프는 플립북과 같습니다. 플립북은 조금씩 다른 그림을 빠르게 넘기면서 움직임의 착각을 만듭니다.
Three.js도 마찬가지입니다. 매 프레임마다 장면을 다시 그리는데, 그 사이사이에 객체의 상태를 조금씩 변경합니다.
이것이 빠르게 반복되면 우리 눈에는 부드러운 움직임으로 보입니다. requestAnimationFrame은 브라우저의 렌더링 타이밍에 맞춰서 함수를 호출합니다.
setTimeout이나 setInterval과 달리, 브라우저가 최적의 타이밍을 자동으로 결정합니다. 보통 모니터 주사율인 60Hz에 맞춰 초당 60번 호출됩니다.
탭이 백그라운드로 가면 자동으로 멈춰서 배터리를 절약합니다. 애니메이션 루프가 없던 시절에는 어땠을까요?
개발자들은 setTimeout을 사용해서 수동으로 타이밍을 맞춰야 했습니다. 하지만 브라우저의 렌더링 주기와 맞지 않아서 버벅거림이 발생했습니다.
또한 탭이 비활성화되어도 계속 실행되어 CPU를 낭비했습니다. 부드러운 애니메이션을 만드는 것이 매우 어려웠습니다.
바로 이런 문제를 해결하기 위해 requestAnimationFrame이 등장했습니다. requestAnimationFrame을 사용하면 브라우저가 최적의 프레임 속도를 유지해줍니다.
화면 새로고침과 완벽하게 동기화되어 버벅거림이 없습니다. 또한 백그라운드에서는 자동으로 멈춰서 성능을 절약합니다.
Three.js에서는 이것을 기반으로 부드러운 3D 애니메이션을 쉽게 만들 수 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 animate라는 함수를 정의합니다. 이 함수의 첫 줄에서 requestAnimationFrame으로 자기 자신을 다시 호출합니다.
이것이 재귀적으로 반복되면서 루프가 형성됩니다. cube.rotation.x에 0.01을 더하면 매 프레임마다 큐브가 조금씩 회전합니다.
구의 y 위치는 사인 함수를 사용해서 위아래로 부드럽게 움직입니다. 마지막으로 renderer.render()를 호출해서 변경된 장면을 화면에 그립니다.
루프 바깥에서 animate()를 한 번 호출하면 애니메이션이 시작됩니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 제품 회전 뷰어를 개발한다고 가정해봅시다. 사용자가 마우스로 드래그하지 않을 때 제품이 천천히 자동으로 회전하게 만들 수 있습니다.
또한 로딩 스피너를 3D로 만들어서 더 세련된 UI를 구현할 수 있습니다. 게임이나 인터랙티브 시각화에서는 이 루프 안에서 사용자 입력을 처리하고 물리 시뮬레이션을 계산합니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 애니메이션 함수 안에서 무거운 계산을 하는 것입니다.
초당 60번 실행되는 함수 안에서 복잡한 연산을 하면 프레임이 떨어집니다. 따라서 가능한 한 가볍게 유지하고, 무거운 작업은 외부에서 처리해야 합니다.
또 다른 실수는 프레임 독립적인 애니메이션을 만들지 않는 것입니다. 모니터 주사율이 다르면 애니메이션 속도가 달라질 수 있습니다.
정확한 속도 제어가 필요하면 deltaTime(이전 프레임과의 시간 차)을 계산해서 사용해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
애니메이션 루프를 추가하자 큐브가 부드럽게 회전하기 시작했습니다. "우와, 진짜 살아 움직이는 것 같아요!" 김개발 씨는 신기해했습니다.
애니메이션 루프를 이해하면 정적인 3D 장면을 역동적으로 만들 수 있습니다. 여러분도 다양한 움직임을 실험해 보세요.
실전 팁
💡 - Date.now()나 performance.now()로 시간 기반 애니메이션을 만들 수 있습니다
- GSAP 같은 트윈 라이브러리를 사용하면 더 복잡한 애니메이션을 쉽게 만들 수 있습니다
- 성능 모니터링을 위해 stats.js 라이브러리를 추가하면 FPS를 실시간으로 확인할 수 있습니다
6. OrbitControls로 카메라 조작
자동으로 회전하는 큐브를 만든 김개발 씨는 이제 사용자가 직접 시점을 조작하게 하고 싶어졌습니다. "마우스로 돌려볼 수 있게 하려면 어떻게 해야 하죠?" 박시니어 씨는 Three.js의 강력한 헬퍼 클래스를 소개합니다.
"OrbitControls가 바로 그겁니다."
OrbitControls는 Three.js에서 제공하는 카메라 컨트롤러입니다. 마우스 드래그로 시점을 회전하고, 휠로 줌 인아웃하며, 우클릭 드래그로 패닝할 수 있습니다.
몇 줄의 코드만으로 전문적인 3D 뷰어의 조작감을 구현할 수 있습니다.
다음 코드를 살펴봅시다.
// OrbitControls 임포트 (Three.js 예제에서 제공)
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
// OrbitControls 생성
const controls = new OrbitControls(camera, renderer.domElement);
// 컨트롤 옵션 설정
controls.enableDamping = true; // 부드러운 감속 효과
controls.dampingFactor = 0.05; // 감속 계수
controls.minDistance = 2; // 최소 줌 거리
controls.maxDistance = 10; // 최대 줌 거리
controls.maxPolarAngle = Math.PI / 2; // 수직 회전 제한 (지평선 아래로 못 가게)
// 애니메이션 루프에서 업데이트
function animate() {
requestAnimationFrame(animate);
controls.update(); // damping 사용 시 필수
renderer.render(scene, camera);
}
김개발 씨는 마우스 이벤트를 듣고 카메라 위치를 계산하는 코드를 작성하려다가 막막해졌습니다. 삼각함수로 구면 좌표를 계산해야 하는데, 수학이 어렵습니다.
박시니어 씨가 다가와서 말합니다. "그럴 필요 없어요.
Three.js 커뮤니티가 이미 완벽한 솔루션을 만들어뒀습니다." 그렇다면 OrbitControls는 정확히 무엇일까요? 쉽게 비유하자면, OrbitControls는 자동차의 파워 스티어링과 같습니다.
직접 바퀴를 손으로 돌릴 수도 있지만, 파워 스티어링을 쓰면 훨씬 편하고 정확합니다. OrbitControls도 마찬가지로 복잡한 카메라 조작을 간단하게 만들어줍니다.
Orbit이라는 이름처럼, 카메라가 타겟을 중심으로 궤도를 도는 방식으로 움직입니다. 마우스 왼쪽 버튼으로 드래그하면 카메라가 객체 주위를 회전합니다.
마우스 휠을 굴리면 줌 인아웃됩니다. 오른쪽 버튼으로 드래그하면 카메라와 타겟이 함께 이동하는 패닝이 됩니다.
enableDamping 옵션은 물리적인 관성을 시뮬레이션합니다. 마우스를 놓았을 때 갑자기 멈추는 게 아니라 부드럽게 감속합니다.
마치 실제 물체를 회전시킬 때처럼 자연스러운 느낌을 줍니다. 카메라 컨트롤이 없던 시절에는 어땠을까요?
개발자들은 마우스 이벤트를 직접 처리하고, 구면 좌표계 변환을 손으로 계산해야 했습니다. 회전 행렬, 쿼터니언 등 복잡한 수학이 필요했습니다.
줌 기능 하나 추가하는 데도 수십 줄의 코드가 필요했고, 버그도 많았습니다. 사용자 경험도 들쭉날쭉했습니다.
바로 이런 문제를 해결하기 위해 OrbitControls가 등장했습니다. OrbitControls를 사용하면 전문적인 3D 뷰어의 조작감을 몇 줄로 구현할 수 있습니다.
마우스, 터치, 키보드 입력을 모두 지원합니다. 또한 다양한 옵션으로 동작을 세밀하게 제어할 수 있습니다.
블렌더나 3ds Max 같은 전문 3D 소프트웨어와 비슷한 조작감을 제공합니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 OrbitControls를 임포트합니다. 이것은 Three.js 코어가 아니라 examples 폴더에 있습니다.
생성자에 camera와 renderer.domElement를 전달합니다. domElement는 마우스 이벤트를 감지할 HTML 요소입니다.
enableDamping을 true로 하면 부드러운 움직임이 추가됩니다. minDistance와 maxDistance로 줌 범위를 제한할 수 있습니다.
maxPolarAngle은 카메라가 지평선 아래로 못 가게 막습니다. damping을 사용하면 애니메이션 루프에서 controls.update()를 호출해야 합니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 건축 시각화 서비스를 개발한다고 가정해봅시다.
고객이 건물 모델을 자유롭게 돌려보며 검토할 수 있게 해야 합니다. OrbitControls를 사용하면 직관적인 조작 인터페이스를 쉽게 만들 수 있습니다.
의료 영상 뷰어에서 3D 스캔 이미지를 돌려보거나, 박물관 가상 전시에서 유물을 살펴볼 때도 이 컨트롤을 사용합니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 damping을 켰는데 controls.update()를 호출하지 않는 것입니다. 이러면 컨트롤이 제대로 작동하지 않습니다.
따라서 damping을 사용한다면 반드시 애니메이션 루프에서 업데이트해야 합니다. 또 다른 실수는 컨트롤 범위를 제한하지 않는 것입니다.
maxPolarAngle을 설정하지 않으면 카메라가 객체 아래로 뒤집어질 수 있습니다. 사용자가 혼란스러워할 수 있으므로 적절한 제한을 두는 것이 좋습니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. OrbitControls를 추가하자 마우스로 큐브를 자유롭게 돌려볼 수 있게 되었습니다.
"이거 진짜 편하네요!" 김개발 씨는 마우스를 이리저리 움직이며 감탄했습니다. OrbitControls를 이해하면 사용자 친화적인 3D 인터페이스를 쉽게 만들 수 있습니다.
여러분도 다양한 옵션을 실험해 보세요.
실전 팁
💡 - autoRotate 옵션을 true로 하면 사용자가 조작하지 않을 때 자동으로 회전합니다
- target 속성을 변경하면 카메라가 바라보는 중심점을 이동시킬 수 있습니다
- TrackballControls, FlyControls 등 다른 종류의 컨트롤도 있으니 용도에 맞게 선택하세요
7. 웹 브라우저에서 첫 3D 씬 렌더링
드디어 김개발 씨는 지금까지 배운 모든 것을 종합할 시간입니다. "이제 처음부터 끝까지 완전한 3D 장면을 만들어볼까요?" 박시니어 씨가 격려합니다.
김개발 씨는 새 프로젝트를 열고 코드를 작성하기 시작합니다.
Three.js로 완전한 3D 씬을 만드는 것은 여러 단계의 조합입니다. Scene, Camera, Renderer 설정, Geometry와 Material로 객체 생성, 조명 추가, 애니메이션 루프 구현, OrbitControls로 상호작용 추가까지, 이 모든 것이 하나로 어우러져 작동합니다.
다음 코드를 살펴봅시다.
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
// Scene, Camera, Renderer 설정
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, 5);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 조명 설정
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 5, 5);
scene.add(directionalLight);
// 3D 객체 생성
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({
color: 0x3498db,
roughness: 0.4,
metalness: 0.6
});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 바닥 추가
const planeGeometry = new THREE.PlaneGeometry(10, 10);
const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x2c3e50 });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -Math.PI / 2;
plane.position.y = -1;
scene.add(plane);
// OrbitControls 설정
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
// 반응형 리사이즈
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// 애니메이션 루프
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.005;
cube.rotation.y += 0.005;
controls.update();
renderer.render(scene, camera);
}
animate();
김개발 씨는 빈 HTML 파일 앞에 앉아 있습니다. 지금까지 배운 모든 내용을 하나로 합쳐서 작동하는 3D 장면을 만들어야 합니다.
조금 긴장되지만, 차근차근 진행하면 됩니다. 박시니어 씨가 옆에서 조언합니다.
"요리를 생각해보세요. 재료 준비, 손질, 조리, 플레이팅까지 순서대로 하면 됩니다." 그렇다면 완전한 3D 씬을 만드는 과정은 정확히 어떻게 될까요?
쉽게 비유하자면, 3D 씬 구축은 무대 연출과 같습니다. 먼저 무대(Scene)를 만들고, 카메라와 조명을 설치합니다.
그다음 배우(3D 객체)를 배치하고, 리허설(애니메이션)을 진행합니다. 마지막으로 관객(사용자)이 편하게 볼 수 있도록 조작 장치를 추가합니다.
antialias 옵션은 계단 현상을 부드럽게 만들어줍니다. 3D 객체의 가장자리가 매끄럽게 보입니다.
성능 비용이 약간 있지만, 시각적 품질이 크게 향상됩니다. 바닥 평면을 추가하면 공간감이 생깁니다.
객체가 공중에 떠 있는 게 아니라 실제 공간에 놓인 것처럼 보입니다. rotation.x를 -Math.PI / 2로 설정하면 평면이 수평이 됩니다.
반응형 리사이즈는 브라우저 창 크기가 바뀔 때 3D 장면도 따라가게 합니다. camera.aspect를 업데이트하고 updateProjectionMatrix()를 호출해야 왜곡이 생기지 않습니다.
완전한 3D 씬을 만들기 어려웠던 시절에는 어땠을까요? 개발자들은 수백 줄의 WebGL 코드를 작성해야 했습니다.
셰이더 프로그램을 컴파일하고, 버퍼를 관리하고, 행렬 연산을 직접 계산했습니다. 간단한 회전하는 큐브 하나 만드는 데도 일주일이 걸렸습니다.
버그를 찾기도 어려웠고, 유지보수는 악몽이었습니다. 바로 이런 문제를 해결하기 위해 Three.js 같은 라이브러리가 등장했습니다.
Three.js를 사용하면 50줄 정도의 직관적인 코드로 전문가 수준의 3D 장면을 만들 수 있습니다. 복잡한 수학은 라이브러리가 처리하고, 개발자는 창의적인 부분에 집중할 수 있습니다.
또한 커뮤니티가 만든 수많은 예제와 플러그인을 활용할 수 있습니다. 누구나 3D 웹 개발을 시작할 수 있게 되었습니다.
위의 코드를 단계별로 살펴보겠습니다. 먼저 기본 설정을 합니다.
Scene의 배경색을 어두운 색으로 설정해서 세련된 느낌을 줍니다. Renderer에 antialias를 켜서 부드러운 렌더링을 얻습니다.
조명은 AmbientLight와 DirectionalLight를 조합해서 입체감을 만듭니다. 다음으로 메인 객체인 큐브를 만듭니다.
MeshStandardMaterial에 roughness와 metalness를 설정해서 약간 금속성 있는 재질을 표현합니다. 바닥 평면을 추가해서 공간감을 더합니다.
OrbitControls를 추가해서 사용자가 자유롭게 시점을 조작할 수 있게 합니다. 반응형 리사이즈 이벤트를 추가해서 모든 화면 크기에 대응합니다.
애니메이션 루프에서 큐브를 천천히 회전시키고, 컨트롤을 업데이트하며, 장면을 렌더링합니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 전자제품 쇼핑몰을 개발한다고 가정해봅시다. 제품을 3D로 보여주는 뷰어를 만들 때 이 구조를 기반으로 시작합니다.
여기에 제품 모델 로딩, 옵션 변경에 따른 재질 교체, 확대해서 디테일 보기 등의 기능을 추가하면 됩니다. 포트폴리오 웹사이트에서 3D 인터랙션을 추가하거나, 교육 플랫폼에서 과학 시뮬레이션을 만들 때도 이 패턴을 사용합니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 성능 최적화를 고려하지 않는 것입니다.
모바일 기기에서는 폴리곤 수와 조명 개수를 제한해야 합니다. 따라서 타겟 디바이스에 맞게 최적화하는 것이 중요합니다.
또 다른 실수는 로딩 상태를 처리하지 않는 것입니다. 3D 모델이나 텍스처 로딩에는 시간이 걸립니다.
사용자에게 로딩 인디케이터를 보여주고, 로딩이 완료된 후에 렌더링을 시작해야 합니다. 메모리 관리도 중요합니다.
Scene에서 객체를 제거할 때는 geometry.dispose()와 material.dispose()를 호출해서 메모리를 해제해야 합니다. 그렇지 않으면 메모리 누수가 발생합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 코드를 실행하자 브라우저에 회전하는 파란색 큐브가 나타났습니다.
마우스로 돌려보니 부드럽게 움직입니다. "드디어 완성했어요!" 김개발 씨는 환호했습니다.
박시니어 씨가 미소 지으며 말합니다. "축하해요.
이제 Three.js의 기초를 마스터했습니다. 여기서부터 무한한 가능성이 열립니다." 완전한 3D 씬을 만드는 방법을 이해하면 어떤 3D 웹 프로젝트든 시작할 수 있습니다.
여러분도 이 기초 위에 창의적인 아이디어를 더해보세요.
실전 팁
💡 - stats.js 라이브러리를 추가하면 FPS를 모니터링하면서 성능을 최적화할 수 있습니다
- dat.gui를 사용하면 실시간으로 파라미터를 조정하며 실험할 수 있습니다
- GLTF/GLB 형식의 3D 모델을 로딩하면 더 복잡하고 현실적인 장면을 만들 수 있습니다
- 프로덕션 배포 전에는 번들 크기를 최적화하고, 텍스처를 압축하며, 폴리곤 수를 줄여야 합니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Three.js 모델 불러오기 완벽 가이드
Blender에서 만든 3D 모델을 웹 브라우저로 불러오는 방법을 배웁니다. GLTFLoader 사용법부터 텍스처 처리, 성능 최적화까지 실무에서 바로 쓸 수 있는 완전한 가이드입니다.
Blender 내보내기 최적화 완벽 가이드
Blender에서 Three.js로 3D 모델을 내보낼 때 반드시 알아야 할 최적화 기법을 다룹니다. 노멀 수정부터 경량화, 포맷 선택까지 실무에서 바로 쓸 수 있는 체크리스트를 제공합니다.
Blender 텍스처링 완벽 가이드
3D 모델에 생명을 불어넣는 텍스처링 기법을 처음부터 차근차근 배워봅니다. 이미지 준비부터 UV 매핑, Shader Editor 활용까지 실무에 바로 적용할 수 있는 내용으로 구성했습니다.
Blender 캐릭터 모델링 완벽 가이드
Blender를 활용한 캐릭터 모델링의 전체 워크플로우를 다룹니다. 메시 편집부터 UV 언래핑, 재질 적용, 최적화까지 실무에서 바로 활용할 수 있는 기법을 배웁니다.
3D 모델링 기초 실습 완벽 가이드
Blender를 활용한 3D 모델링의 기본기를 다집니다. 나무, 지형, 텍스트부터 복잡한 형태까지, 실전에서 바로 활용할 수 있는 모델링 기법을 단계별로 학습합니다.