3D Tiles란 무엇인가
대용량 3D 지리공간 데이터를 웹/모바일에서 스트리밍 렌더링하기 위한 OGC 표준. 이 문서는 표준의 핵심 개념과 데이터 구조를 정리한다.
핵심 요약
| 구분 | 내용 |
|---|---|
| 📖 정의 | Cesium이 설계하고 2019년 OGC 커뮤니티 표준이 된, 대용량 3D 데이터 계층 스트리밍 규격 |
| 💡 핵심 | 공간 트리(quadtree/octree) + 거리 기반 LOD(SSE) + 콘텐츠 메타데이터의 결합 |
| 🎯 대상 | 도시 스케일 3D, BIM, 포인트 클라우드, 지형, 인스턴스 객체를 웹에서 렌더링하는 개발자 |
| ⚠️ 주의 | 2D 타일맵(Slippy Map)과는 트리 구조·LOD 선택 방식 모두 다르다 |
문서 탐색
목차
- 기본 개념
- 2D 타일맵과의 차이
- HLOD(Hierarchical Level of Detail) 원리
- Tileset.json 구조
- boundingVolume — 3가지 종류
- refine — ADD vs REPLACE
- geometricError와 SSE
- Implicit Tiling vs Explicit Tiling
- 콘텐츠 포맷
기본 개념
3D Tiles는 현실 세계의 대용량 3D 데이터를 효율적으로 분할·스트리밍하기 위한 데이터 포맷이다.
- 도심 전체 건물, 수십억 포인트의 라이다(LiDAR), 광역 지형 메시는 단일 파일로 GPU에 올릴 수 없다.
- 3D Tiles는 이런 데이터를 계층적 공간 트리로 분할하고, 카메라 거리·시야각에 따라 필요한 부분만 점진적으로 로드한다.
| 해결하는 문제 | 방법 |
|---|---|
| 메모리/GPU 한계 초과 | 화면에 보이는 타일만 로드 |
| 원거리 렌더링 품질 저하 | 거리별 단순화 모델(LOD) 제공 |
| 복합 데이터 타입 혼재 | 건물·포인트 클라우드·지형·인스턴스 모델을 동일 트리에 결합 |
| 시맨틱 정보 전달 | 각 피처(건물 1동 등)에 속성 메타데이터 연결 |
표준 채택 이력
| 날짜 | 이벤트 |
|---|---|
| 2015 | Cesium이 3D Tiles 개념 공개 |
| 2019-01 | OGC 3D Tiles 1.0 커뮤니티 표준 채택 (문서 번호 18-053r2) |
| 2021-11 | ”3D Tiles Next” 블로그 발표 (1.1의 전신) |
| 2022-12-17 | OGC 3D Tiles 1.1 커뮤니티 표준 채택 (22-025r4) |
2D 타일맵과의 차이
흔히 쓰는 웹 지도 타일(Slippy Map, OSM 등)과는 근본적으로 구조가 다르다.
[ Slippy Map (2D) ] [ 3D Tiles ]
z/x/y 정수 좌표로 식별 트리 구조, 각 노드가 바운딩 볼륨 보유
래스터 이미지(PNG/JPEG) 3D 지오메트리 + 속성 메타데이터
고정된 줌 레벨(0~22) 연속적 geometricError 기반 LOD
위도/경도 2D 범위만 기술 region/box/sphere 3D 공간 범위 기술
타일 크기 일정(256px / 512px) 타일마다 크기·형태가 다를 수 있음
렌더러가 단순 이미지 붙임 렌더러가 3D 씬 그래프 구성
가장 근본적인 차이는 “화면 픽셀 오차(SSE) 기반 동적 해상도 선택” vs **“고정 줌 레벨 기반 타일 선택”**이다. 3D Tiles는 줌 레벨이라는 개념이 없고, “지금 이 거리에서 이 타일을 더 잘게 쪼개야 하는가?”를 매 프레임 판단한다.
HLOD(Hierarchical Level of Detail) 원리
HLOD는 공간 트리의 각 노드가 하위 노드들의 단순화된 대표 표현을 갖는 구조다.
Root (geometricError=512m)
전체 도심을 단순 박스 메시로 표현
└─ Level 1 (geometricError=128m)
구역별 건물 군집 표현
└─ Level 2 (geometricError=32m)
블록별 건물 실루엣
└─ Level 3 (geometricError=4m)
개별 건물 외벽 상세
└─ Level 4 (geometricError≈0m)
창문·텍스처 최고 상세
뷰어는 카메라와 타일 사이 거리·FOV·화면 해상도를 조합해 **Screen Space Error(SSE)**를 계산한다.
- SSE ≥ 임계값(기본 16px) → 자식 타일을 로드하고 렌더링
- SSE < 임계값 → 현재 노드의 단순화 표현으로 충분 (하위 로드 중단)
이 방식 덕분에 **“멀리 있는 건물은 박스, 가까운 건물은 창문까지”**를 동시에 표현할 수 있다.
Tileset.json 구조
타일셋의 진입점은 항상 tileset.json이다. 이 안에 트리의 루트와 메타데이터가 들어있다.
{
"asset": {
"version": "1.1", // 사양 버전 (필수)
"tilesetVersion": "1.2.3" // 사용자 정의 데이터 버전 (선택)
},
"geometricError": 512, // 루트의 최대 허용 오차 (미터)
"root": {
"boundingVolume": { "region": [...] },
"geometricError": 256,
"refine": "REPLACE",
"content": { "uri": "root.glb" },
"children": [
{
"boundingVolume": { "box": [...] },
"geometricError": 64,
"content": { "uri": "child_0.glb" }
}
]
},
"schema": { ... }, // 1.1 신규: 메타데이터 스키마
"groups": [ ... ], // 1.1 신규: 콘텐츠 그룹
"metadata": { ... } // 1.1 신규: 타일셋 수준 메타데이터
}| 필드 | 필수 여부 | 설명 |
|---|---|---|
asset.version | 필수 | "1.0" 또는 "1.1" |
geometricError | 필수 | 루트 노드의 기하 오차(미터) |
root | 필수 | 최상위 타일 객체 |
root.boundingVolume | 필수 | 공간 범위 |
root.geometricError | 필수 | 이 타일의 단순화 오차 |
root.refine | 루트에서 필수 | "ADD" 또는 "REPLACE" |
root.content | 선택 | 렌더링할 파일 URI |
root.children | 선택 | 자식 타일 배열 |
schema | 선택(1.1) | 메타데이터 클래스·속성 타입 정의 |
boundingVolume — 3가지 종류
각 타일은 자기 콘텐츠가 차지하는 3D 공간 범위(=바운딩 볼륨)를 가진다. 뷰 절두체 컬링(frustum culling)과 SSE 계산에 모두 사용된다.
region (지리 좌표계 / WGS84)
[west, south, east, north, minHeight, maxHeight]
단위: 경도·위도 rad, 높이 m
용도: 지구 표면을 덮는 대면적 지형·도시 타일
┌─────────────────┐
│ north │
│ west east │ ← 경위도 바운딩 박스 + 최소·최대 고도
│ south │
└─────────────────┘
box (직교 좌표계 OBB)
[cx,cy,cz, hx,hy,hz, rx,ry,rz, tx,ty,tz] (12개 값)
센터 + 3개의 반축 벡터
용도: 회전된 건물·교량 등 임의 방향 객체
┌──────/
│ /│ ← 임의 방향 Oriented Bounding Box
│ / │
└───/──┘
sphere (구)
[cx, cy, cz, radius] (4개 값)
용도: 포인트 클라우드, 구형 분포 데이터
⊙ ← 중심점 + 반지름
| 종류 | 값 개수 | 좌표계 | 적합한 용도 |
|---|---|---|---|
region | 6 | WGS84 지리 | 대면적 지형·도시 |
box | 12 | 3D 직교(OBB) | 건물·인프라·CAD |
sphere | 4 | 3D 직교 | 포인트 클라우드 |
주의:
boundingVolume은 실제 콘텐츠보다 약간 크게 설정해야 클리핑 아티팩트가 없다.
refine — ADD vs REPLACE
타일이 자식을 가질 때, “자식이 부모를 대체하는가, 부모 위에 얹히는가”를 결정한다.
REPLACE (치환 방식) ADD (추가 방식)
───────────────── ─────────────────
Parent ─┐ Parent ─┐
├── Child A ├── Child A (+ Parent도 렌더)
└── Child B └── Child B (+ Parent도 렌더)
자식 로드 시 부모를 자식 로드 시 부모와
'대체'하여 렌더링 자식을 '동시에' 렌더링
| refine | 동작 | 적합한 데이터 |
|---|---|---|
REPLACE | 자식이 부모를 완전히 교체 | 건물·지형·BIM (각 LOD가 완전한 표현) |
ADD | 자식이 부모 위에 더해짐 | 포인트 클라우드 (자식이 부모의 부분 세부화) |
geometricError와 SSE
geometricError는 **“이 타일의 단순화 표현이 원본 데이터에 대해 가지는 오차(미터)“**다. 값이 클수록 더 거친 표현이다.
뷰어는 매 프레임 SSE(Screen Space Error)를 계산해 LOD 결정에 쓴다.
SSE 계산식
SSE = (geometricError × screenHeight) / (distance × 2 × tan(FOV/2))
screenHeight: 화면 높이(픽셀)distance: 카메라와 타일 사이 거리(미터)FOV: 수직 시야각(라디안)
예시
geometricError=10m, screenHeight=1080px, distance=500m, FOV=60°(π/3) 일 때:
SSE = (10 × 1080) / (500 × 2 × tan(30°))
= 10800 / (500 × 1.1547)
= 10800 / 577.35
≈ 18.7 px
18.7px > 임계값 16px 이므로 자식 타일 로드 필요.
| 조건 | 판단 |
|---|---|
SSE ≥ maximumScreenSpaceError (기본 16) | 자식 로드 필요 (refine 실행) |
SSE < maximumScreenSpaceError | 현재 타일로 충분 (하위 로드 중단) |
CesiumJS의
tileset.maximumScreenSpaceError값을 키우면 더 거친 LOD를 허용 → 메모리·대역폭 절약. 값을 줄이면 더 선명하지만 더 많은 타일 로드.
Implicit Tiling vs Explicit Tiling
Explicit Tiling (1.0 기본 방식)
모든 타일의 메타데이터를 JSON에 명시적으로 나열한다.
tileset.json
└─ root tile
├─ child0.json (subtileset)
│ ├─ tile_0_0.b3dm
│ └─ tile_0_1.b3dm
└─ child1.json
├─ tile_1_0.b3dm
└─ tile_1_1.b3dm
- 장점: 비균일 데이터(밀도가 들쭉날쭉한 경우)에 유연
- 단점: 수백만 타일이면
tileset.json이 수 GB가 된다
Implicit Tiling (1.1 표준화)
좌표 규칙으로 타일을 주소화하고, 존재 여부를 비트스트림으로 저장한다.
tileset.json
└─ root (implicitTiling 설정)
subdivisionScheme: "QUADTREE" | "OCTREE"
subtreeLevels: 4
availableLevels: 20
subtrees: { uri: "subtrees/{level}/{x}/{y}.subtree" }
각 .subtree 파일 (availability bitstream)
┌─ tileAvailability (해당 좌표 타일 존재 여부)
├─ contentAvailability (콘텐츠 파일 존재 여부)
└─ childSubtreeAvailability (하위 subtree 존재 여부)
- 좌표
(level, x, y)또는(level, x, y, z)규칙으로 타일 주소화 - Morton Z-order 곡선으로 비트스트림 정렬
- 타일 존재 여부를 비트 1개로 표현 → 극도로 압축된 인덱스
비교표
| 비교 항목 | Explicit | Implicit |
|---|---|---|
| 메타데이터 저장 | 각 타일을 JSON에 나열 | 비트스트림 subtree 파일 |
| 공간 탐색 | JSON 파싱 필요 | Morton code → 직접 계산 |
| 적합한 데이터 | 비균일 밀도 데이터 | 규칙적 격자(지형, 포인트 클라우드) |
| 랜덤 접근 | 어려움 | 좌표 계산만으로 즉시 접근 |
콘텐츠 포맷
각 타일의 content.uri가 가리키는 실제 데이터 파일.
1.0 레거시 포맷
| 포맷 | 약자 | 용도 |
|---|---|---|
| b3dm | Batched 3D Model | 건물 등 여러 독립 모델을 한 타일에 batch |
| i3dm | Instanced 3D Model | 동일 모델을 여러 위치에 인스턴싱 (나무, 가로등) |
| pnts | Point Cloud | 라이다·포토그래메트리 포인트 클라우드 |
| cmpt | Composite | 여러 타일 포맷을 하나로 묶은 컨테이너 |
| vctr | Vector | GeoJSON 스타일 벡터 (1.0 확장 제안, 표준화 실패) |
b3dm 파일 레이아웃 예시:
┌──────────────────────────────────────┐
│ 헤더 (28 bytes) │
│ magic="b3dm", version, byteLength, │
│ featureTableJSONByteLength, │
│ featureTableBinaryByteLength, │
│ batchTableJSONByteLength, │
│ batchTableBinaryByteLength │
├──────────────────────────────────────┤
│ Feature Table JSON │
├──────────────────────────────────────┤
│ Feature Table Binary (선택) │
├──────────────────────────────────────┤
│ Batch Table JSON (속성 메타데이터) │
├──────────────────────────────────────┤
│ Batch Table Binary (선택) │
├──────────────────────────────────────┤
│ glTF Binary (GLB) │
└──────────────────────────────────────┘
- Batch Table: 각 객체(배치 ID)에 이름·층수·용도 등 속성 저장
- Feature Table:
BATCH_LENGTH,RTC_CENTER등 렌더링 메타데이터
1.1 — glTF 직접 사용
1.1부터 .glb(Binary glTF 2.0) 파일을 타일 콘텐츠로 직접 참조한다. b3dm 같은 래퍼가 사라지고 glTF 확장(EXT_mesh_features 등)이 그 역할을 흡수한다.
자세한 1.0 → 1.1 변경점은 1.2 3D Tiles 1.0 vs 1.1에서 다룬다.