본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 29. · 3 Views
3D 모델링 기초 실습 완벽 가이드
Blender를 활용한 3D 모델링의 기본기를 다집니다. 나무, 지형, 텍스트부터 복잡한 형태까지, 실전에서 바로 활용할 수 있는 모델링 기법을 단계별로 학습합니다.
목차
- 좋은_모델링_vs_나쁜_모델링_비교
- 나무_모델링_실습_올바른_방법
- 지형_Ground_모델링_기초
- 텍스트_및_표지판_추가하기
- 기본_도형으로_복잡한_형태_만들기
- 모델링_베스트_프랙티스
1. 좋은 모델링 vs 나쁜 모델링 비교
어느 날 최모델 씨가 열심히 만든 3D 나무 모델을 팀장에게 보여주었습니다. "음...
겉으로는 괜찮아 보이는데, 폴리곤 수를 한번 확인해볼까요?" 팀장님이 정보 패널을 열자 폴리곤 수가 무려 50만 개. "이러면 웹에서 절대 못 쓰죠."
좋은 모델링이란 단순히 보기 좋은 것이 아닙니다. 적절한 폴리곤 수로 효율적인 구조를 만들고, 재사용 가능한 형태로 설계하는 것입니다.
마치 집을 지을 때 튼튼한 기둥을 먼저 세우듯이, 3D 모델링도 기본 구조가 탄탄해야 합니다.
다음 코드를 살펴봅시다.
// 나쁜 예: 불필요하게 많은 세그먼트
const badTree = new THREE.CylinderGeometry(1, 1, 10, 128, 128);
// 폴리곤 수: 32,768개 - 단순한 나무 기둥에 과도함
// 좋은 예: 적절한 세그먼트 수
const goodTree = new THREE.CylinderGeometry(1, 1, 10, 8, 1);
// 폴리곤 수: 16개 - 충분히 원형으로 보이면서 효율적
// 거리에 따른 LOD(Level of Detail) 적용
const lod = new THREE.LOD();
lod.addLevel(goodTree, 0); // 가까울 때
lod.addLevel(badTree, 100); // 멀 때는 더 단순하게
최모델 씨는 3D 아티스트로 커리어를 시작한 지 2개월 째입니다. 포트폴리오를 만들기 위해 밤낮으로 작업하며 멋진 3D 모델들을 만들어냈습니다.
디테일도 살리고, 텍스처도 고퀄리티로 입혔습니다. 자신감 넘치는 얼굴로 팀장님께 결과물을 보여드렸죠.
하지만 팀장님의 반응은 예상과 달랐습니다. "최모델 씨, 이거 웹에서 돌려봤어요?" 브라우저에서 모델을 로딩하자 페이지가 버벅거리기 시작했습니다.
프레임이 뚝뚝 끊기고, 심지어 모바일에서는 아예 크래시가 발생했습니다. 그렇다면 무엇이 문제였을까요.
좋은 모델링과 나쁜 모델링의 차이는 단순히 시각적 퀄리티에 있지 않습니다. 마치 요리에서 맛만 좋다고 좋은 요리가 아니듯이, 3D 모델도 성능과 효율성을 함께 고려해야 합니다.
아무리 예쁜 모델이라도 사용자의 기기에서 제대로 실행되지 않는다면 무용지물입니다. 나쁜 모델링의 첫 번째 특징은 과도한 폴리곤 수입니다.
초보 모델러들이 가장 많이 하는 실수 중 하나입니다. "더 많은 폴리곤이 더 부드러운 표면을 만들겠지"라는 생각으로 세그먼트 수를 무작정 늘립니다.
원기둥 하나를 만드는데 128개의 세그먼트를 사용하고, 구체에는 256개를 사용합니다. 화면에서는 확실히 부드러워 보입니다.
하지만 문제는 렌더링 성능입니다. 웹 3D는 게임 엔진과 다릅니다.
브라우저는 GPU 리소스가 제한적입니다. 모바일 환경은 더욱 열악합니다.
불필요하게 많은 폴리곤은 프레임 드롭의 주범이 됩니다. 두 번째 문제는 비효율적인 구조입니다.
같은 나무를 100그루 배치해야 한다고 가정해봅시다. 각각의 나무를 개별 모델로 만들면 어떻게 될까요?
메모리는 폭발하고, 드로우 콜은 기하급수적으로 늘어납니다. 똑똑한 개발자라면 인스턴싱을 활용할 것입니다.
하나의 모델을 만들고 여러 번 재사용하는 것이죠. 좋은 모델링의 핵심은 적절한 균형입니다.
위의 코드를 보면 같은 나무 기둥을 만드는데 두 가지 방식이 있습니다. 첫 번째는 128개의 radial 세그먼트를 사용합니다.
완벽하게 부드러운 원기둥이 만들어지지만, 폴리곤 수는 32,768개에 달합니다. 두 번째는 8개의 세그먼트만 사용합니다.
폴리곤은 단 16개이지만, 일정 거리에서 보면 충분히 원형으로 보입니다. 실무에서는 어떻게 활용할까요.
게임 회사나 웹 3D 에이전시에서는 폴리곤 예산이라는 개념을 사용합니다. 씬 전체에서 사용할 수 있는 총 폴리곤 수를 미리 정하고, 각 오브젝트마다 할당량을 나눕니다.
중요한 주인공 캐릭터에는 10,000개를 할당하고, 배경의 나무 하나에는 100개만 할당하는 식입니다. LOD 시스템도 빼놓을 수 없습니다.
Level of Detail의 약자로, 카메라와의 거리에 따라 모델의 디테일을 조절하는 기법입니다. 가까이 있을 때는 고퀄리티 모델을 보여주고, 멀리 있을 때는 저퀄리티 모델로 자동 전환됩니다.
사용자는 차이를 거의 느끼지 못하지만, 성능은 크게 향상됩니다. 하지만 주의할 점도 있습니다.
너무 적은 폴리곤도 문제가 될 수 있습니다. 삼각형이 뾰족뾰족하게 보이거나, 애니메이션 시 부자연스러운 변형이 일어날 수 있습니다.
특히 굽어지는 부분이나 실루엣이 중요한 부분은 폴리곤을 조금 더 할당해야 합니다. 다시 최모델 씨의 이야기로 돌아가봅시다.
팀장님의 피드백을 받은 최모델 씨는 모델을 전면 수정했습니다. 불필요한 세그먼트를 줄이고, 인스턴싱을 적용했습니다.
LOD 시스템도 구현했습니다. 결과는 놀라웠습니다.
같은 씬이 60fps로 부드럽게 돌아갔고, 모바일에서도 문제없이 작동했습니다. 좋은 모델링은 기술과 예술의 균형입니다.
아름다움을 추구하되, 성능을 잊지 않는 것. 그것이 프로 모델러의 자세입니다.
실전 팁
💡 - 폴리곤 수 체크: Blender에서 Statistics 패널로 항상 폴리곤 수를 확인하세요
- 타겟 플랫폼 고려: 모바일용이라면 더욱 보수적으로 폴리곤을 사용하세요
- LOD 미리 계획: 처음부터 여러 레벨의 모델을 염두에 두고 작업하세요
2. 나무 모델링 실습 올바른 방법
박나무 씨가 처음으로 3D 나무를 만들기로 했습니다. "그냥 원기둥 하나 만들고 위에 구 얹으면 되겠지?" 하지만 선배 정숲 씨가 고개를 저었습니다.
"그렇게 하면 유치원 수준이에요. 제대로 된 나무를 만드는 방법을 알려드릴게요."
나무 모델링은 3D의 기본이자 핵심입니다. 기둥과 나뭇잎을 각각 만들고, 재질을 입히고, 인스턴싱으로 효율화하는 전 과정을 통해 모델링의 기초를 완벽히 다질 수 있습니다.
마치 요리의 기본이 칼질이듯, 3D의 기본은 나무 만들기입니다.
다음 코드를 살펴봅시다.
// 나무 기둥 생성 - 아래가 굵고 위로 갈수록 가늘게
const trunkGeometry = new THREE.CylinderGeometry(
0.3, // 윗부분 반지름
0.5, // 아랫부분 반지름
5, // 높이
8, // radial segments - 8각형이면 충분
1 // height segments
);
// 나무 재질 - 갈색 bark 질감
const trunkMaterial = new THREE.MeshStandardMaterial({
color: 0x8B4513,
roughness: 0.9 // 거친 나무 표면
});
const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
// 나뭇잎 - 구체로 간단하게 표현
const leavesGeometry = new THREE.SphereGeometry(2, 8, 8);
const leavesMaterial = new THREE.MeshStandardMaterial({
color: 0x228B22,
roughness: 0.7
});
const leaves = new THREE.Mesh(leavesGeometry, leavesMaterial);
leaves.position.y = 4; // 기둥 위에 배치
// 나무 그룹으로 묶기
const tree = new THREE.Group();
tree.add(trunk);
tree.add(leaves);
박나무 씨는 웹 개발자로 일하다가 최근 3D 웹에 관심을 갖게 되었습니다. Three.js 튜토리얼을 따라하면서 큐브와 구는 만들어봤지만, 뭔가 더 실감나는 것을 만들고 싶었습니다.
"그래, 나무를 만들어보자!" 처음에는 간단할 것 같았습니다. 원기둥 하나 만들고, 구 하나 올리면 끝.
5분이면 될 것 같았습니다. 하지만 막상 만들어보니 뭔가 이상했습니다.
유치原 어린이들이 그린 나무 같은 느낌이었죠. 정숲 씨가 박나무 씨의 화면을 보더니 웃으며 말했습니다.
"첫 시도치고 나쁘지 않은데요? 하지만 몇 가지만 바꾸면 훨씬 좋아질 거예요." 진짜 나무는 어떤 모양일까요.
잠시 창밖을 내다보세요. 실제 나무를 관찰해보면 기둥은 아래가 굵고 위로 갈수록 가늘어집니다.
완벽한 원기둥이 아니라 원뿔에 가깝죠. 이것이 첫 번째 개선 포인트입니다.
CylinderGeometry의 첫 번째 파라미터(위쪽 반지름)와 두 번째 파라미터(아래쪽 반지름)를 다르게 설정하면 됩니다. 또 하나 중요한 것은 세그먼트 수입니다.
나무 기둥은 완벽한 원형일 필요가 없습니다. 오히려 약간 각진 느낌이 더 자연스러울 때도 있습니다.
8각형 정도면 충분히 원형으로 보이면서도 폴리곤 수를 크게 절약할 수 있습니다. 32각형이나 64각형을 쓰면 부드럽긴 하지만, 그만큼 렌더링 부담이 커집니다.
재질 설정도 중요합니다. MeshStandardMaterial은 물리 기반 렌더링을 지원하는 재질입니다.
roughness 값을 조절해서 표면의 거칠기를 표현할 수 있습니다. 나무 껍질은 매우 거칠기 때문에 0.9 정도가 적당합니다.
반대로 나뭇잎은 조금 덜 거칠게, 0.7 정도로 설정하면 햇빛을 받았을 때 자연스러운 반사가 일어납니다. 코드를 살펴보면 두 개의 메시를 만들고 있습니다.
첫 번째는 trunk, 즉 나무 기둥입니다. CylinderGeometry로 만들고, 갈색 재질을 입혔습니다.
두 번째는 leaves, 나뭇잎 부분입니다. SphereGeometry로 간단하게 구 형태로 표현했습니다.
실제로는 수많은 잎사귀들이 모여있지만, 멀리서 보면 큰 덩어리처럼 보이기 때문에 이렇게 단순화해도 괜찮습니다. position.y = 4라는 코드가 보이시나요.
이것은 나뭇잎을 Y축 방향으로 4만큼 이동시킵니다. 기둥의 높이가 5이고, 기둥의 중심이 원점에 있으므로, 기둥의 윗부분은 Y=2.5 위치에 있습니다.
나뭇잎의 반지름이 2이므로, Y=4에 배치하면 기둥과 자연스럽게 연결됩니다. 마지막으로 Group으로 묶었습니다.
이것이 매우 중요한 부분입니다. 기둥과 나뭇잎을 하나의 그룹으로 만들면, 나무 전체를 하나의 오브젝트처럼 다룰 수 있습니다.
tree.position을 변경하면 기둥과 나뭇잎이 함께 움직입니다. tree.rotation을 바꾸면 함께 회전합니다.
개별적으로 관리하는 것보다 훨씬 편리하죠. 실무에서는 이런 나무를 어떻게 활용할까요.
숲을 만든다고 가정해봅시다. 나무 100그루가 필요합니다.
가장 단순한 방법은 이 코드를 100번 반복하는 것입니다. 하지만 그러면 메모리 낭비가 심합니다.
더 좋은 방법은 InstancedMesh를 사용하는 것입니다. 하나의 지오메트리를 여러 번 재사용하면서 각각 다른 위치에 배치할 수 있습니다.
메모리는 1그루 수준으로 유지하면서 100그루를 렌더링할 수 있죠. 주의할 점도 있습니다.
모든 나무를 똑같이 만들면 인공적으로 보입니다. 실제 자연에서는 각 나무마다 크기가 조금씩 다르고, 기울기도 다릅니다.
tree.scale을 랜덤하게 0.8~1.2 사이로 조절하고, rotation.y를 랜덤하게 설정하면 훨씬 자연스러운 숲이 만들어집니다. 박나무 씨는 정숲 씨의 조언대로 코드를 수정했습니다.
기둥에 테이퍼를 주고, 세그먼트를 줄이고, 재질을 조정했습니다. 결과물을 보는 순간 감탄사가 나왔습니다.
"와, 이게 진짜 나무 같아요!" 나무 하나를 제대로 만들 수 있다면, 집도, 자동차도, 캐릭터도 만들 수 있습니다. 모든 복잡한 모델은 결국 단순한 지오메트리들의 조합이니까요.
실전 팁
💡 - 자연스러운 비율: 기둥 높이 대 나뭇잎 크기는 2:1 정도가 적당합니다
- 랜덤 변화: scale과 rotation에 약간의 랜덤값을 주면 더 자연스럽습니다
- 그룹 활용: 복잡한 모델일수록 Group으로 계층 구조를 만드세요
3. 지형 Ground 모델링 기초
김지형 씨는 멋진 3D 세계를 만들고 싶었습니다. 나무도 만들었고, 건물도 만들었는데 뭔가 허전했습니다.
"아, 땅이 없네!" 모든 오브젝트가 허공에 둥둥 떠 있는 것처럼 보였습니다. 선배 이대지 씨가 조언했습니다.
"3D 세계의 기본은 지형이에요."
지형 모델링은 3D 씬의 무대를 만드는 작업입니다. 평평한 평면부터 시작해서, 높이 맵으로 언덕과 계곡을 만들고, 텍스처로 흙과 풀을 표현합니다.
마치 연극 무대를 준비하듯이, 지형은 모든 오브젝트가 서 있을 기반을 제공합니다.
다음 코드를 살펴봅시다.
// 기본 평면 지형 생성
const groundGeometry = new THREE.PlaneGeometry(
100, // 너비
100, // 높이
50, // width segments - 높낮이 표현을 위해 필요
50 // height segments
);
// 지형에 랜덤한 높낮이 추가
const vertices = groundGeometry.attributes.position.array;
for (let i = 0; i < vertices.length; i += 3) {
// Z 좌표만 수정해서 높낮이 만들기
vertices[i + 2] = Math.random() * 3;
}
groundGeometry.attributes.position.needsUpdate = true;
// 풀밭 재질
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x567d46,
roughness: 0.8,
flatShading: true // 각진 폴리곤 느낌
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2; // 바닥으로 눕히기
ground.receiveShadow = true; // 그림자 받기
김지형 씨는 처음으로 작은 3D 마을을 만들고 있었습니다. 집 몇 채를 배치하고, 나무도 심고, 가로등도 세웠습니다.
화면을 돌려가며 구경하는데 뭔가 이상했습니다. 모든 건물이 검은 배경 위에 둥둥 떠 있는 것처럼 보였습니다.
"뭔가 어색한데..." 한참을 고민하던 김지형 씨는 문제를 깨달았습니다. 땅이 없었던 것입니다.
현실 세계에서는 당연히 존재하는 지면이, 3D 세계에서는 직접 만들어주어야 합니다. 지형을 만드는 가장 기본적인 방법은 평면을 사용하는 것입니다.
PlaneGeometry는 말 그대로 평평한 판을 만듭니다. 하지만 여기서 중요한 것은 세그먼트 수입니다.
세그먼트가 1x1이면 그냥 네모난 판입니다. 하지만 50x50으로 세그먼트를 늘리면 2,500개의 작은 사각형으로 나뉩니다.
이것이 왜 중요할까요? 각 꼭짓점의 높이를 개별적으로 조절할 수 있기 때문입니다.
실제 땅은 완벽하게 평평하지 않습니다. 약간의 언덕이 있고, 작은 움푹한 곳도 있습니다.
이런 자연스러운 높낮이를 표현하려면 각 정점의 Z 좌표를 조절해야 합니다. 위 코드에서 vertices[i + 2]가 바로 그 부분입니다.
정점 배열은 [x, y, z, x, y, z, ...] 형태로 저장되므로, 3칸씩 건너뛰면서 Z값만 수정하는 것이죠. Math.random() * 3이라는 코드를 주목해보세요.
이것은 0부터 3 사이의 랜덤한 값을 만듭니다. 각 정점마다 다른 높이를 주는 것이죠.
결과적으로 울퉁불퉁한 지형이 만들어집니다. 만약 더 평평한 땅을 원한다면 Math.random() * 0.5처럼 작은 값을 사용하면 됩니다.
반대로 험한 산악 지형을 만들고 싶다면 Math.random() * 10처럼 큰 값을 쓰면 됩니다. needsUpdate = true는 매우 중요한 부분입니다.
Three.js는 성능 최적화를 위해 지오메트리 데이터를 GPU에 캐싱합니다. 한 번 만들어진 지오메트리는 변하지 않는다고 가정하는 것이죠.
따라서 정점 위치를 직접 수정했다면, "이 데이터가 바뀌었으니 다시 GPU로 보내주세요"라고 알려줘야 합니다. 이 플래그를 설정하지 않으면 변경사항이 화면에 반영되지 않습니다.
재질 설정에서 flatShading이 보이시나요. 일반적으로 3D 렌더링은 부드러운 음영을 위해 정점 노멀을 보간합니다.
하지만 flatShading을 true로 설정하면 각 폴리곤이 뚜렷하게 구분되어 보입니다. 레트로 게임이나 로우폴리 스타일을 만들 때 유용합니다.
지형의 경우 이렇게 하면 각진 느낌이 살아서 오히려 흥미로운 비주얼이 됩니다. rotation.x = -Math.PI / 2는 꼭 필요한 코드입니다.
PlaneGeometry는 기본적으로 XY 평면에 생성됩니다. 즉, 벽처럼 세로로 서 있는 상태입니다.
우리가 원하는 것은 바닥이므로, X축을 중심으로 -90도 회전시켜서 눕혀야 합니다. Math.PI / 2는 라디안 단위로 90도를 의미합니다.
실무에서는 더 정교한 지형을 만들 수 있습니다. Perlin Noise나 Simplex Noise 알고리즘을 사용하면 훨씬 자연스러운 언덕을 만들 수 있습니다.
랜덤 값은 들쭉날쭉하지만, 노이즈 함수는 부드럽게 변화하는 값을 생성합니다. 마치 실제 산맥처럼 보이는 지형을 만들 수 있죠.
또한 높이 맵 이미지를 사용하는 방법도 있습니다. 흑백 이미지에서 밝은 부분은 높은 지형으로, 어두운 부분은 낮은 지형으로 변환합니다.
실제 위성 사진의 고도 데이터를 사용하면 현실의 지형을 그대로 재현할 수도 있습니다. 그림자 설정도 빼놓을 수 없습니다.
receiveShadow = true를 설정하면 이 메시가 다른 오브젝트의 그림자를 받습니다. 나무나 건물의 그림자가 땅에 드리워지면 훨씬 실감나는 씬이 됩니다.
물론 이것이 작동하려면 광원에서도 그림자를 켜야 하고, 그림자를 던지는 오브젝트에서는 castShadow를 true로 설정해야 합니다. 김지형 씨는 이대지 씨의 조언대로 지형을 추가했습니다.
100x100 크기의 평면에 랜덤 높이를 주고, 풀밭 색상을 입혔습니다. 렌더링 결과를 보는 순간 세계가 완성된 느낌이었습니다.
"이제 진짜 마을 같네요!" 지형은 3D 씬의 기초입니다. 탄탄한 기초 위에 모든 것을 쌓아올리세요.
실전 팁
💡 - 세그먼트 수 조절: 평평한 지형은 10x10, 복잡한 산악은 100x100으로
- 노이즈 함수 활용: simplex-noise 라이브러리로 자연스러운 지형 생성
- 텍스처 추가: 단색보다는 흙이나 풀 텍스처를 입히면 더 현실적입니다
4. 텍스트 및 표지판 추가하기
이글자 씨는 3D 박물관을 만들고 있었습니다. 전시물은 다 배치했는데, 설명이 없으니 뭐가 뭔지 알 수가 없었습니다.
"3D에서 글자는 어떻게 쓰지?" 선배 한글님이 답했습니다. "TextGeometry를 쓰면 되는데, 몇 가지 주의할 점이 있어요."
3D 텍스트는 단순한 UI 텍스트와 다릅니다. 실제 3D 오브젝트로 존재하며, 조명을 받고 그림자를 드리웁니다.
하지만 폰트 로딩과 지오메트리 생성에 시간이 걸리므로, 효율적으로 사용하는 방법을 알아야 합니다.
다음 코드를 살펴봅시다.
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js';
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js';
// 폰트 로더 생성
const fontLoader = new FontLoader();
// 폰트 로드 후 텍스트 생성
fontLoader.load('fonts/helvetiker_regular.typeface.json', (font) => {
// 텍스트 지오메트리 생성
const textGeometry = new TextGeometry('Welcome', {
font: font,
size: 1, // 텍스트 크기
height: 0.2, // 두께 (얇을수록 폴리곤 절약)
curveSegments: 5, // 곡선 부드러움 (낮을수록 최적화)
bevelEnabled: true, // 모서리 베벨
bevelThickness: 0.02,
bevelSize: 0.02,
bevelSegments: 2
});
// 텍스트 중앙 정렬
textGeometry.center();
const textMaterial = new THREE.MeshStandardMaterial({ color: 0xffffff });
const textMesh = new THREE.Mesh(textGeometry, textMaterial);
scene.add(textMesh);
});
이글자 씨는 웹 개발 경력 5년차 베테랑입니다. HTML과 CSS로 텍스트 다루는 것은 식은 죽 먹기였습니다.
하지만 3D 세계는 완전히 다른 규칙이 적용되었습니다. <div>Hello</div>처럼 간단하게 쓸 수 없었습니다.
처음에는 Canvas에 텍스트를 그려서 텍스처로 사용하려고 했습니다. 작동은 했지만, 확대하면 흐릿해지고, 각도에 따라 왜곡되었습니다.
"분명히 더 나은 방법이 있을 텐데..." TextGeometry가 바로 그 해답입니다. 이것은 문자를 실제 3D 메시로 만들어줍니다.
마치 나무 블록으로 알파벳을 조각한 것처럼, 각 글자가 입체적인 형태를 갖습니다. 빛을 받으면 하이라이트가 생기고, 그림자도 드리웁니다.
완벽한 3D 오브젝트인 것이죠. 하지만 사용하기 전에 폰트를 로드해야 합니다.
웹에서 구글 폰트를 쓰듯이, Three.js도 폰트 파일이 필요합니다. 단, 일반 TTF나 OTF 파일이 아니라 typeface.json 형식이어야 합니다.
Three.js는 examples 폴더에 몇 가지 기본 폰트를 제공합니다. helvetiker, optimer, gentilis 등이 있죠.
FontLoader는 비동기로 작동합니다. 파일을 네트워크에서 다운로드해야 하므로 시간이 걸립니다.
따라서 콜백 함수를 사용해야 합니다. 폰트 로딩이 완료되면 그때 TextGeometry를 생성하는 것이죠.
만약 로딩 중에 텍스트를 만들려고 하면 에러가 발생합니다. TextGeometry의 옵션들을 살펴봅시다.
size는 글자의 크기입니다. 1이면 1 유닛 높이의 텍스트가 만들어집니다.
height는 두께를 의미합니다. 0이면 납작한 평면 글자, 큰 값이면 두툼한 블록 글자가 됩니다.
보통 0.1~0.3 정도가 적당합니다. curveSegments는 곡선의 부드러움을 결정합니다.
'O'나 'C' 같은 곡선 글자는 사실 여러 개의 직선 세그먼트로 근사됩니다. 세그먼트가 많을수록 부드럽지만, 폴리곤 수가 증가합니다.
12가 기본값이지만, 5 정도로 낮춰도 대부분의 경우 충분합니다. bevelEnabled는 모서리를 둥글게 만드는 옵션입니다.
베벨이 없으면 글자가 각지고 딱딱해 보입니다. 베벨을 추가하면 부드럽고 고급스러운 느낌이 됩니다.
하지만 이것 역시 폴리곤 수를 증가시키므로, bevelSegments는 낮게 유지하는 것이 좋습니다. 2~3이면 충분합니다.
center() 메서드는 매우 유용합니다. 기본적으로 텍스트는 왼쪽 아래를 기준점으로 생성됩니다.
따라서 position을 (0, 0, 0)으로 설정하면 글자가 오른쪽으로 삐져나갑니다. center()를 호출하면 바운딩 박스를 계산해서 정확히 중앙에 위치시킵니다.
표지판이나 타이틀을 만들 때 필수적입니다. 실무에서는 어떻게 활용할까요.
박물관이나 전시관 같은 3D 환경에서 설명 패널을 만들 수 있습니다. 게임에서는 메뉴 버튼이나 점수 표시에 사용됩니다.
건축 비주얼라이제이션에서는 건물 이름이나 층수 표시에 쓰입니다. 하지만 주의할 점이 있습니다.
TextGeometry는 폴리곤 수가 많습니다. "Hello World" 정도만 해도 수천 개의 폴리곤이 생성될 수 있습니다.
따라서 긴 문장을 3D 텍스트로 만드는 것은 비효율적입니다. 짧은 타이틀이나 라벨 정도로만 사용하는 것이 좋습니다.
긴 설명문이 필요하다면 어떻게 할까요. HTML 오버레이를 사용하세요.
CSS로 스타일링한 일반 HTML 텍스트를 3D 씬 위에 겹쳐 표시하는 것입니다. 훨씬 가볍고, 텍스트 선택이나 복사도 가능하며, 반응형 디자인도 쉽습니다.
3D 텍스트는 정말 입체적으로 보여야 할 때만 사용하세요. 또 다른 방법은 Sprite를 사용한 빌보드 텍스트입니다.
Canvas에 텍스트를 그려서 텍스처로 만들고, 항상 카메라를 바라보게 설정하는 것입니다. 게임의 플레이어 이름이나 HP 표시같은 것들이 이 방식을 사용합니다.
이글자 씨는 한글님의 조언대로 TextGeometry로 짧은 타이틀만 만들고, 긴 설명은 HTML로 처리했습니다. "그래, 적재적소에 맞는 기술을 쓰는 게 중요하지!" 3D 텍스트는 강력하지만, 남용하면 성능 저하를 일으킵니다.
현명하게 사용하세요.
실전 팁
💡 - 폰트 프리로드: 페이지 로딩 시 미리 폰트를 로드해서 깜빡임 방지
- 재사용: 같은 폰트로 여러 텍스트를 만들 때는 폰트를 한 번만 로드
- LOD 적용: 멀리 있을 때는 더 낮은 curveSegments 사용
5. 기본 도형으로 복잡한 형태 만들기
최조합 씨는 복잡한 3D 모델을 만들고 싶었지만, Blender 같은 전문 툴은 너무 어려웠습니다. "코드만으로 복잡한 모양을 만들 수 있을까?" 선배 박합성 씨가 웃으며 답했습니다.
"레고 블록으로 성을 짓듯이, 기본 도형을 조합하면 됩니다."
**CSG(Constructive Solid Geometry)**는 기본 도형들을 더하고, 빼고, 교차시켜서 복잡한 형태를 만드는 기법입니다. 큐브, 구, 실린더 같은 단순한 도형만으로도 놀라운 결과물을 만들 수 있습니다.
마치 조각가가 돌을 깎듯이, 도형을 조합하세요.
다음 코드를 살펴봅시다.
// 집 만들기 - 큐브와 피라미드 조합
const houseBase = new THREE.BoxGeometry(4, 3, 4);
const houseMaterial = new THREE.MeshStandardMaterial({ color: 0xD2691E });
const house = new THREE.Mesh(houseBase, houseMaterial);
house.position.y = 1.5;
// 지붕 - 원뿔을 눕혀서 사용
const roofGeometry = new THREE.ConeGeometry(3, 2, 4);
const roofMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 });
const roof = new THREE.Mesh(roofGeometry, roofMaterial);
roof.position.y = 4;
roof.rotation.y = Math.PI / 4; // 45도 회전으로 지붕 모양 맞추기
// 문 - 작은 큐브
const doorGeometry = new THREE.BoxGeometry(1, 2, 0.2);
const doorMaterial = new THREE.MeshStandardMaterial({ color: 0x654321 });
const door = new THREE.Mesh(doorGeometry, doorMaterial);
door.position.set(0, 1, 2.1); // 집 앞면에 배치
// 창문 - 평평한 큐브
const windowGeometry = new THREE.BoxGeometry(0.8, 0.8, 0.2);
const windowMaterial = new THREE.MeshStandardMaterial({ color: 0x87CEEB });
const window1 = new THREE.Mesh(windowGeometry, windowMaterial);
window1.position.set(-1.2, 2, 2.1);
const window2 = window1.clone();
window2.position.set(1.2, 2, 2.1);
// 집 그룹으로 묶기
const completeHouse = new THREE.Group();
completeHouse.add(house, roof, door, window1, window2);
최조합 씨는 3D 도시를 만들고 싶었습니다. 하지만 Blender는 인터페이스부터 막막했고, 3ds Max는 너무 비쌌습니다.
"코드로만 할 수 있는 방법은 없을까?" 고민하던 중, 박합성 씨의 프로젝트를 보고 깜짝 놀랐습니다. 코드만으로 만든 집이 정말 집처럼 보였으니까요.
비밀은 조합에 있습니다. 실제 세상의 복잡한 물체들도 따지고 보면 단순한 형태의 조합입니다.
집은 상자에 삼각지붕을 얹은 것입니다. 자동차는 직육면체 몸통에 원기둥 바퀴를 붙인 것입니다.
사람도 구와 원기둥의 조합으로 단순화할 수 있습니다. Three.js가 제공하는 기본 도형들을 살펴봅시다.
BoxGeometry는 직육면체, SphereGeometry는 구, CylinderGeometry는 원기둥, ConeGeometry는 원뿔, TorusGeometry는 도넛 모양입니다. 이것들만으로도 엄청난 것들을 만들 수 있습니다.
위의 코드는 집 하나를 만드는 과정입니다. 먼저 집의 본체는 4x3x4 크기의 상자입니다.
position.y를 1.5로 설정해서 지면에서 약간 띄웁니다. 왜 1.5일까요?
집의 높이가 3이고, 중심이 원점에 있으므로, 바닥이 Y=0에 오려면 높이의 절반인 1.5만큼 올려야 합니다. 지붕은 ConeGeometry로 만듭니다.
원뿔의 radialSegments를 4로 설정하면 사각뿔이 됩니다. 이것을 45도 회전시키면 집 지붕처럼 보입니다.
position.y를 4로 설정해서 집 본체 위에 얹습니다. 본체의 윗면이 Y=3이고, 지붕의 높이가 2이므로, 지붕의 중심은 Y=4가 되어야 딱 맞습니다.
문은 작은 직육면체입니다. 1x2x0.2 크기로 얇고 세로로 긴 모양입니다.
position.z를 2.1로 설정해서 집 앞면보다 약간 앞에 배치합니다. 집의 깊이가 4이므로 앞면은 Z=2 위치입니다.
0.1만큼 더 앞에 두면 벽과 겹치지 않고 깔끔하게 보입니다. 창문은 더 흥미롭습니다.
하나의 창문을 만든 후, clone() 메서드로 복사합니다. 이렇게 하면 같은 지오메트리와 재질을 공유하면서 독립적인 메시를 만들 수 있습니다.
메모리 효율이 좋고, 코드도 간결해집니다. 두 창문의 X 좌표만 다르게 설정해서 좌우 대칭으로 배치합니다.
Group으로 묶는 것이 핵심입니다. 집, 지붕, 문, 창문을 하나의 그룹으로 만들면 전체를 하나의 오브젝트처럼 다룰 수 있습니다.
completeHouse.position을 변경하면 모든 부품이 함께 움직입니다. completeHouse.rotation을 바꾸면 집 전체가 회전합니다.
도시를 만들 때 각 집을 배치하기가 훨씬 쉬워집니다. 실무에서는 이런 기법을 어떻게 활용할까요.
게임 레벨 에디터를 만들 수 있습니다. 미리 정의된 건물 조합들을 라이브러리로 만들어두고, 사용자가 클릭만으로 도시를 구성하게 하는 것입니다.
건축 비주얼라이제이션에서는 기본 모듈들을 조합해서 빠르게 프로토타입을 만들 수 있습니다. 더 발전된 기법도 있습니다.
Three-CSGMesh 라이브러리를 사용하면 도형끼리 더하기, 빼기, 교집합 연산이 가능합니다. 예를 들어 큐브에서 구를 빼면 움푹 파인 홈을 만들 수 있습니다.
두 도형의 교집합만 취하면 복잡한 곡면 형태가 만들어집니다. 주의할 점도 있습니다.
너무 많은 메시를 개별적으로 만들면 드로우 콜이 증가합니다. 도시에 집이 1000채 있고, 각 집이 5개의 메시로 구성되어 있다면 총 5000번의 드로우 콜이 발생합니다.
이런 경우 BufferGeometryUtils.mergeBufferGeometries로 여러 지오메트리를 하나로 합치는 최적화가 필요합니다. 또한 재질 공유도 중요합니다.
모든 집의 벽이 같은 색이라면, 같은 Material 인스턴스를 재사용하세요. 각 집마다 새로운 Material을 만들면 메모리 낭비입니다.
Three.js는 같은 재질을 사용하는 메시들을 배칭해서 최적화할 수 있습니다. 최조합 씨는 박합성 씨의 조언대로 기본 도형 조합으로 도시를 만들기 시작했습니다.
처음에는 어색했지만, 점점 손에 익었습니다. "레고 놀이하는 것 같아요!" 복잡함은 단순함의 조합에서 나옵니다.
기본기를 탄탄히 하세요.
실전 팁
💡 - 대칭 활용: clone()으로 좌우 대칭 형태를 효율적으로 만드세요
- 비율 일관성: 모든 오브젝트의 크기 비율을 일관되게 유지하세요
- 모듈화: 재사용할 형태는 함수로 만들어두세요 (createHouse() 같은 식으로)
6. 모델링 베스트 프랙티스
신입 모델러 정실전 씨는 6개월 동안 열심히 공부했습니다. 나무도 만들고, 집도 만들고, 심지어 자동차까지 만들었습니다.
하지만 팀장님의 코드 리뷰에서 빨간 줄이 가득했습니다. "기능은 되는데...
왜 문제인 거죠?" 시니어 강베스트 씨가 말했습니다. "작동하는 것과 잘 만든 것은 다르니까요."
모델링 베스트 프랙티스는 단순히 보기 좋은 모델을 만드는 것을 넘어, 유지보수 가능하고, 성능 좋고, 확장 가능한 코드를 작성하는 방법입니다. 프로와 아마추어의 차이는 디테일에 있습니다.
다음 코드를 살펴봅시다.
// 나쁜 예: 하드코딩된 값들
const tree1 = new THREE.Mesh(
new THREE.CylinderGeometry(0.5, 0.5, 5, 8),
new THREE.MeshStandardMaterial({ color: 0x8B4513 })
);
// 좋은 예: 설정을 객체로 관리
const TREE_CONFIG = {
trunk: {
radiusTop: 0.3,
radiusBottom: 0.5,
height: 5,
segments: 8,
color: 0x8B4513
},
leaves: {
radius: 2,
segments: 8,
color: 0x228B22
}
};
// 재사용 가능한 팩토리 함수
function createTree(config = TREE_CONFIG) {
const trunk = new THREE.Mesh(
new THREE.CylinderGeometry(
config.trunk.radiusTop,
config.trunk.radiusBottom,
config.trunk.height,
config.trunk.segments
),
new THREE.MeshStandardMaterial({ color: config.trunk.color })
);
const leaves = new THREE.Mesh(
new THREE.SphereGeometry(config.leaves.radius, config.leaves.segments, config.leaves.segments),
new THREE.MeshStandardMaterial({ color: config.leaves.color })
);
leaves.position.y = config.trunk.height / 2 + config.leaves.radius - 0.5;
const tree = new THREE.Group();
tree.add(trunk, leaves);
return tree;
}
// 사용: 다양한 나무를 쉽게 생성
const normalTree = createTree();
const tallTree = createTree({ ...TREE_CONFIG, trunk: { ...TREE_CONFIG.trunk, height: 8 } });
정실전 씨는 자신의 코드를 자랑스럽게 생각했습니다. 모든 기능이 작동했고, 화면도 예쁘게 나왔습니다.
하지만 팀장님의 첫 마디는 충격이었습니다. "이거 나중에 수정할 수 있어요?" 정실전 씨는 당황했습니다.
"수정이요? 작동하는데요?" 강베스트 씨가 화면을 가리키며 설명했습니다.
"지금은 괜찮아요. 하지만 6개월 후에 나무 색을 바꾸라고 하면?
나무가 100개인데 어떻게 할 건가요?" 프로그래밍에서 가장 중요한 것은 변화에 대응하는 능력입니다. 요구사항은 항상 바뀝니다.
클라이언트는 마음을 바꿉니다. 디자이너는 색상을 수정합니다.
기획자는 스펙을 추가합니다. 이런 변화에 유연하게 대응할 수 있는 코드가 좋은 코드입니다.
첫 번째 원칙은 설정과 로직의 분리입니다. 위의 나쁜 예를 보면 0.5, 5, 8, 0x8B4513 같은 매직 넘버들이 코드 안에 박혀있습니다.
이것들이 무엇을 의미하는지 한눈에 알기 어렵습니다. 나중에 나무 기둥의 두께를 바꾸려면 코드를 다시 읽고 분석해야 합니다.
좋은 예에서는 TREE_CONFIG라는 객체로 모든 설정을 한 곳에 모았습니다. 이렇게 하면 어떤 이점이 있을까요?
첫째, 값의 의미가 명확합니다. radiusTop, radiusBottom처럼 이름을 보면 바로 알 수 있습니다.
둘째, 수정이 쉽습니다. 색상을 바꾸고 싶으면 TREE_CONFIG.trunk.color만 바꾸면 됩니다.
셋째, 여러 프리셋을 만들 수 있습니다. 참나무, 소나무, 야자수 설정을 각각 만들어두면 됩니다.
두 번째 원칙은 재사용 가능한 함수 작성입니다. createTree() 함수를 보세요.
설정 객체를 받아서 나무를 생성합니다. 기본값도 있어서 아무 인자 없이 호출해도 작동합니다.
하지만 원한다면 일부 값만 오버라이드할 수도 있습니다. 스프레드 연산자를 활용하면 기본 설정을 유지하면서 일부만 변경할 수 있습니다.
이런 패턴을 팩토리 패턴이라고 합니다. 객체 생성 로직을 함수로 캡슐화하는 것이죠.
이렇게 하면 같은 유형의 오브젝트를 일관되게 만들 수 있습니다. 나중에 생성 로직이 복잡해져도 호출하는 쪽 코드는 변경할 필요가 없습니다.
세 번째 원칙은 명확한 네이밍입니다. 변수명, 함수명, 설정 키 이름 모두 의도를 명확히 드러내야 합니다.
a, b, tmp 같은 이름은 피하세요. trunkGeometry, leavesMaterial, createTree 처럼 무엇을 하는지 바로 알 수 있는 이름을 사용하세요.
코드는 컴퓨터가 실행하지만, 사람이 읽습니다. 네 번째 원칙은 성능 최적화입니다.
같은 재질이나 지오메트리를 반복해서 생성하지 마세요. 한 번 만들어두고 재사용하세요.
수백 개의 나무가 필요하다면 InstancedMesh를 고려하세요. 메모리와 렌더링 성능을 크게 개선할 수 있습니다.
다섯 번째 원칙은 정리와 폐기입니다. Three.js는 가비지 컬렉션을 완벽히 처리하지 못합니다.
사용하지 않는 지오메트리와 재질은 명시적으로 **dispose()**를 호출해야 합니다. 그렇지 않으면 메모리 누수가 발생해 브라우저가 점점 느려집니다.
여섯 번째 원칙은 주석과 문서화입니다. 복잡한 계산이나 매직 넘버가 불가피한 경우, 왜 그렇게 했는지 주석으로 설명하세요.
6개월 후의 자신도, 다른 팀원도 감사할 것입니다. 하지만 코드가 자명하다면 주석은 불필요합니다.
좋은 네이밍이 최고의 문서입니다. 실무 프로젝트에서는 어떻게 적용할까요.
큰 프로젝트에서는 모델 생성 로직을 별도 모듈로 분리합니다. models/tree.js, models/house.js 같은 식으로요.
설정은 JSON 파일로 외부화할 수도 있습니다. 아티스트가 코드를 건드리지 않고도 파라미터를 조절할 수 있게 됩니다.
테스트 코드도 작성하세요. "3D 코드를 어떻게 테스트해?"라고 생각할 수 있지만, 의외로 가능합니다.
createTree() 함수가 올바른 타입의 객체를 반환하는지, 자식 메시 개수가 맞는지, 위치 계산이 정확한지 등을 테스트할 수 있습니다. 리팩토링할 때 회귀 버그를 막아줍니다.
정실전 씨는 강베스트 씨의 조언을 받아들여 코드를 전면 수정했습니다. 처음에는 시간이 더 걸리는 것 같았지만, 두 번째 기능을 추가할 때부터 차이가 느껴졌습니다.
"아, 이래서 베스트 프랙티스가 중요하구나!" 좋은 코드는 미래의 자신에게 주는 선물입니다. 오늘 조금 더 신경 쓰면, 내일은 훨씬 편해집니다.
실전 팁
💡 - 일관된 스타일: 팀 전체가 같은 코딩 컨벤션을 따르세요
- 점진적 개선: 처음부터 완벽하지 않아도 됩니다. 리팩토링하면서 개선하세요
- 커뮤니티 참고: Three.js 공식 예제와 오픈소스 프로젝트에서 패턴을 배우세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Blender 텍스처링 완벽 가이드
3D 모델에 생명을 불어넣는 텍스처링 기법을 처음부터 차근차근 배워봅니다. 이미지 준비부터 UV 매핑, Shader Editor 활용까지 실무에 바로 적용할 수 있는 내용으로 구성했습니다.
Blender 캐릭터 모델링 완벽 가이드
Blender를 활용한 캐릭터 모델링의 전체 워크플로우를 다룹니다. 메시 편집부터 UV 언래핑, 재질 적용, 최적화까지 실무에서 바로 활용할 수 있는 기법을 배웁니다.
Three.js 기초 개념 완벽 가이드
Three.js의 핵심 개념인 Scene, Camera, Renderer부터 조명, 재질, 애니메이션까지 실무에서 바로 사용할 수 있는 3D 웹 개발 기초를 다룹니다. 초급 개발자도 쉽게 따라할 수 있도록 단계별로 설명합니다.
Blender 기초 조작법 완벽 가이드
3D 아티스트의 첫 걸음, Blender 인터페이스와 기본 조작법을 마스터하는 실전 가이드입니다. 뷰포트 내비게이션부터 객체 조작, 단축키 활용까지 실무에 필요한 모든 것을 담았습니다.
Three.js와 Blender로 시작하는 3D 웹 개발
3D 웹의 세계로 첫발을 내딛는 개발자들을 위한 가이드입니다. Blender로 모델을 만들고 Three.js로 웹에 구현하는 전체 과정을 실무 스토리와 함께 쉽게 풀어냅니다. 설치부터 첫 3D 씬 구현까지 단계별로 배워봅시다.