About

.blend 는 웹에서 안 열린다 — Blender → 웹은 변환이 아니라 「GPU 가 먹을 것만 남기는 번역」

3D 게임을 만들었는데 모델 파일이 0개인 이유까지

Blender 에서 만든 걸 웹에 띄운다는 건 파일 형식을 바꾸는 일이 아니다. GPU 가 바로 먹을 수 있는 것만 남기고 나머지를 버리는 번역이다. 옮겨지는 건 형상이지 그림이 아니다. 이 글을 읽으면 export 할 때 무엇이 사라지는지 미리 알고, 「Blender 에선 예뻤는데 웹에선 왜 이래」 에 덜 놀랄 수 있다.

3D 게임인데 모델 파일이 0개다

우리가 만든 3D 숨바꼭질 게임은 광장에 NPC 24명이 걸어 다니고 그 사이에 플레이어가 섞여 드는 구조다. three.js 로 돌아간다. 그런데 저장소를 뒤지면 .glb .gltf .fbx .blend 가 한 개도 없다. 아바타는 코드가 박스로 조립한다.

const GEO = {
  head:  new THREE.BoxGeometry(0.42, 0.42, 0.4),
  torso: new THREE.BoxGeometry(0.58, 0.68, 0.32),
  limb:  new THREE.BoxGeometry(0.17, 0.62, 0.2)
}

「Blender 로 사람 하나 만들어서 넣으면 되지 않나?」 이 말이 건너뛰는 단계가 넷이다.

.blend glTF / .glbexport GLTFLoaderthree.js GPU dropped on export modifier stack (baked or gone)Geometry Nodes, procedural shadersCycles node graphphysics, particles, simulation what the web gets triangles onlyvertex data in binary buffersPBR metallic-roughness values, texturesskins, animation clips (time based) The browser redraws the picture from scratch. You move the shape, not the render.

1. .blend 는 주고받으라고 만든 파일이 아니다

.blend 는 Blender 가 메모리에 들고 있던 구조체를 거의 그대로 덤프한 파일이다. 내부 구조가 버전에 묶여 있고, 제대로 읽는 건 사실상 Blender 자신뿐이다. 브라우저에 .blend 로더가 없는 건 아무도 안 만들어서가 아니라 교환용으로 설계된 적이 없어서다. 포토샵 .psd 를 웹에 그대로 안 올리는 것과 같다.

2. glTF 는 GPU 쪽으로 기운 저장 방식이다

glTF 2.0(.gltf + .bin, 한 덩어리로 묶으면 .glb)이 표준이 된 건 압축률 때문이 아니다. 저장 구조가 이미 GPU 쪽으로 기울어 있다. 면은 삼각형으로 쪼개져 있고, 정점 데이터는 바이너리 버퍼에 들어 있어서 읽은 걸 거의 그대로 GPU 버퍼로 올릴 수 있다. 머티리얼은 PBR metallic-roughness(금속성 · 거칠기로 재질을 표현하는 방식) 값 몇 개로 정해져 있다.

형식 웹에서
.blend 못 연다. Blender 전용
OBJ 열리지만 애니메이션이 없다
FBX Autodesk 독점 규격
glTF / GLB 사실상 표준. three.js · <model-viewer> 가 바로 읽는다

3. export 는 무손실이 아니다 — 여기가 핵심

Blender 에 있는 것 export 후
모디파이어 스택 (Subdivision, Array, Boolean) 정점으로 굽히거나 사라진다
지오메트리 노드 · 절차적 머티리얼 텍스처로 bake 안 하면 증발
Cycles 노드 그래프 PBR 값 몇 개로 뭉개진다
물리 · 파티클 · 시뮬레이션 없다

Blender 뷰포트의 그림은 EEVEE 나 Cycles(Blender 의 렌더러)가 계산한 결과물이다. 웹이 받는 건 정점과 텍스처와 PBR 상수뿐이고, 그림은 브라우저가 처음부터 다시 그린다. 그래서 렌더 결과가 Blender 와 다르게 보이는 건 버그가 아니라 원래 그렇게 되는 것이다.

4. 로더에서 GPU 까지

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

우리 저장소엔 three 0.183.1 이 들어 있고 이 경로가 그대로 먹는다. GLTFLoader 가 JSON(씬 구성)과 바이너리 버퍼를 읽어 BufferGeometry · Material · Texture 를 만들고, 그게 GPU 로 올라간 뒤엔 매 프레임 그리기 호출(드로우콜)이다.

웹에 띄우는 방법은 둘로 갈린다. 그냥 보여 주기만 하면 <model-viewer> 웹 컴포넌트에 .glb URL 만 꽂으면 끝이다. 우리 다른 앱도 이 방식이다. 씬이나 게임에 넣어 조명 · 카메라 · 충돌을 직접 쥐어야 하면 three.js + GLTFLoader 다.

그런데 우리 게임은 왜 GLB 를 안 썼나

귀찮아서가 아니라 게임 규칙에서 나온 결론이었다. 이 게임의 규칙은 「플레이어는 NPC 가 못 하는 동작을 할 수 없다」 다. 그래서 걷기 동작이 시간이 아니라 누적 이동 거리에 묶여 있다. 멈추면 다리도 멈춘다. 제자리걸음이 보이면 그게 가장 큰 티이기 때문이다.

const s = Math.sin(walkPhase) * swing   // walkPhase 는 누적 이동 거리에서 나온다
avatar.legL.rotation.x = s
avatar.legR.rotation.x = -s

Blender 에서 구운 walk 애니메이션을 AnimationMixer 로 돌리면 시간 기준이다. 거리 기준으로 다시 묶으려면 mixer.setTime(walkPhase) 를 직접 먹여야 하고, 그 순간 「Blender 에서 애니메이션을 구워 왔다」 는 이점이 대부분 사라진다.

게다가 모두가 똑같아 보여야 한다. 숨는 사람만 다른 데가 하나라도 있으면 그게 곧 정답 표시다. 모델을 쓰면 셔츠 색만 바꾸려고 머티리얼을 나누는 일이 오히려 늘어난다. 제약이 빡빡할수록 코드로 만드는 지오메트리가 유리해진다.

성능은 아직 숙제

지금은 사람마다 6파트라 메시가 150개 안팎이다. 지오메트리를 공유해서 메모리는 아끼지만 InstancedMesh 를 안 쓰니 드로우콜은 메시 수만큼 나간다. GLB 스킨드 메시로 바꾸면 메시 수는 사람 수로 줄지만 정점 셰이더에 스키닝 비용이 붙는다. 어느 쪽이 싼지는 재 봐야 알고, 모바일 FPS 비교는 아직 못 했다.

3D 모델을 웹 서비스나 게임에 올리는 일로 고민하고 있다면 negabaro 에 물어봐도 된다. 위 판단도 우리 게임을 만들면서 직접 내린 것이다.

핵심 포인트

1

.blend 는 교환 형식이 아니다 — 웹에 올리려면 glTF / GLB 로 export

2

모디파이어 · 지오메트리 노드 · Cycles 노드 · 물리는 export 에서 굽히거나 사라진다

3

보여 주기만이면 <model-viewer>, 씬 · 게임이면 three.js + GLTFLoader

4

애니메이션을 시간이 아닌 다른 값에 묶어야 하면 코드로 만드는 지오메트리가 나을 수 있다

사용 사례

제품 · 캐릭터 3D 모델을 웹 페이지에 띄우기 브라우저 게임에서 모델을 쓸지 코드로 만들지 정하기
← 「100% 로컬」 은 미디어 파일 얘기다 — Whisper 와 ffmpeg 를 WASM 으로 밀어 넣은 대가