4.2 GLB 구조와 용어

GLB 바이너리 레이아웃, glTF JSON 컴포넌트, 데이터 3계층(buffer/bufferView/accessor), 용어 사전, 그리고 바이트가 GPU 픽셀로 변환되는 파이프라인. 업데이트: 2026-05-14


핵심 요약

구분내용
📖 정의GLB = Header + JSON Chunk + Binary Chunk. JSON이 씬 그래프를 기술하고 BIN이 실제 바이트를 담는다. 모든 참조는 배열 인덱스(integer) 기반.
💡 핵심buffer → bufferView → accessor 3단 계층을 거쳐 raw 바이트가 의미를 부여받는다. GPU 업로드 직전까지 중간 변환 없음.
🎯 대상GLB를 생성·수정·검증·파싱하거나 렌더링 품질 문제를 분석하는 개발자
⚠️ 주의(bufferView.byteOffset + accessor.byteOffset) % sizeof(componentType) == 0 정렬 규칙을 어기면 GPU 업로드는 성공하지만 시각적으로 무작위 결과가 나온다. Validator로 사전 검증 필수.

목차

  1. GLB 바이너리 레이아웃
  2. glTF JSON 최상위 컨테이너
  3. 씬 그래프 용어 (scene·node·mesh·primitive)
  4. 데이터 3계층 (buffer·bufferView·accessor)
  5. 머티리얼·텍스처 용어
  6. Sampler — 텍스처 필터링
  7. 애니메이션·스키닝 용어
  8. 확장(KHR/EXT) 용어
  9. 렌더링 파이프라인 7단계
  10. 디버깅 시 자주 보게 되는 함정
  11. JSON 최상위 객체 한눈에 보기

GLB 바이너리 레이아웃

GLB는 리틀 엔디언(Little-endian) 단일 바이너리 컨테이너다. 1개(JSON only) 또는 2개(JSON + BIN) 청크를 가진다.

12바이트 헤더

필드오프셋크기
magic04B0x46546C67 (ASCII “glTF”)
version44B2
length84B전체 파일 바이트 수 (헤더 포함)

청크 구조

+0   [chunkLength: 4B uint32]  ← chunkData의 바이트 수 (패딩 제외)
+4   [chunkType:   4B uint32]  ← 청크 종류
+8   [chunkData:   N bytes  ]  ← 실제 데이터
+8+N [padding:     P bytes  ]  ← (4 - N%4) % 4 바이트 패딩
청크chunkType (hex)패딩 문자순서·필수 여부
JSON0x4E4F534A (ASCII “JSON”)0x20 (Space)첫 번째 · 필수
BIN0x004E4942 (ASCII “BIN\0”)0x00 (Null)두 번째 · 선택

전체 레이아웃

┌──────────────────────────────────────────┐
│  Header (12 bytes)                       │
│  [magic:4B] [version:4B] [length:4B]     │
├──────────────────────────────────────────┤
│  JSON Chunk Header (8 bytes)             │
│  [chunkLength:4B] [0x4E4F534A:4B "JSON"] │
├──────────────────────────────────────────┤
│  JSON Chunk Data (UTF-8) + 0x20 padding  │
│  { "asset": {"version":"2.0"}, ... }     │
├──────────────────────────────────────────┤
│  BIN Chunk Header (8 bytes)              │
│  [chunkLength:4B] [0x004E4942:4B "BIN\0"]│
├──────────────────────────────────────────┤
│  BIN Chunk Data + 0x00 padding           │
│  [vertices][indices][images][...]        │
└──────────────────────────────────────────┘

바이트 오프셋 계산 예시

JSON이 217바이트라면:
  JSON 패딩 = ceil(217/4)*4 - 217 = 3 (0x20 × 3)
  BIN 청크 시작 = 12 (header) + 8 (JSON header) + 220 = 240

GLB에 임베드된 buffer: JSON에서 buffers[0]uri 필드를 생략하면, 그 buffer는 자동으로 BIN 청크를 참조한다. 단, buffers[0] 임베드 가능하다.


glTF JSON 최상위 컨테이너

glTF JSON 루트(root)에 정의되는 최상위 객체들이다. 대부분 배열이며 정수 인덱스로 상호 참조한다.

용어설명비고
glTF”GL Transmission Format”. Khronos가 정의한 3D 자산 런타임 포맷.”3D 계의 JPEG”
GLBglTF의 단일 바이너리 컨테이너. JSON·BIN·텍스처를 하나의 .glb 파일로 묶는다.미디어 타입 model/gltf-binary
assetglTF 버전·생성기 정보를 담는 필수 객체.{"version": "2.0"} 필수
scene화면에 렌더링될 노드들의 집합. 여러 개 정의 가능.기본 씬은 scene 정수 인덱스
scenes모든 씬 정의의 배열.
nodes씬 그래프 노드 배열. 각 노드는 transform과 mesh/camera/skin 참조를 가진다.
meshes메시 정의 배열. 각 메시는 primitive 배열을 가진다.
accessorsbufferView를 typed array처럼 해석하는 메타데이터 배열.
bufferViewsbuffer의 한 구간을 가리키는 뷰 배열.
buffers원시 바이트 덩어리 배열 (URI 또는 GLB의 BIN 청크).
materialsPBR 머티리얼 배열.
texturesimage + sampler 조합 배열.
images이미지 소스 배열 (URI/data URI 또는 bufferView 참조).PNG/JPEG, KTX2(확장)
samplers텍스처 샘플링 파라미터 배열 (filter·wrap).
skins스키닝(본 애니메이션) 정의 배열.
animations애니메이션 클립 배열.
cameras카메라 정의 배열 (perspective / orthographic).
extensionsUsed파일이 사용하는 확장 이름 배열.정보용
extensionsRequired파일이 반드시 필요로 하는 확장 이름 배열.로더가 지원 못 하면 거부

인덱스 참조 규칙: 예를 들어 node.mesh = 3meshes[3]을 가리킨다. ID 문자열이 아닌 정수 인덱스만 쓴다 — O(1) 조회와 JSON 용량 최소화 목적.


씬 그래프 용어 (scene·node·mesh·primitive)

용어설명비고
scene화면에 그릴 root node 인덱스들의 집합. { "nodes": [0, 3] }.카메라·라이트도 노드로 포함 가능
node씬 그래프의 한 노드. transform(matrix 또는 TRS)과 선택적으로 mesh/camera/skin/light/children을 가진다.DAG 금지(트리만 허용)
children자식 노드 인덱스 배열. 부모의 transform을 상속한다.한 노드는 한 부모만
matrix4×4 변환 행렬. 열 우선(column-major) 16-float 배열.TRS와 동시 사용 금지
TRStranslation·rotation·scale 3개 필드. 합성 순서 M = T × R × S.권장 방식
translation3-float [tx, ty, tz].
rotation단위 쿼터니언 [qx, qy, qz, qw] — w가 마지막이다.헷갈리기 쉬움
scale3-float [sx, sy, sz].음수도 가능(반전)
mesh같은 transform을 공유하는 primitive들의 묶음. 한 노드는 한 메시만 참조.
primitiveGPU의 draw call 1개에 대응되는 단위. attributes + indices + material + mode.보통 머티리얼별로 나뉜다
modeGPU 토폴로지: POINTS(0), LINES(1), LINE_LOOP(2), LINE_STRIP(3), TRIANGLES(4), TRIANGLE_STRIP(5), TRIANGLE_FAN(6).기본값 = 4 (TRIANGLES)
attributesprimitive의 버텍스 속성 맵. POSITION, NORMAL, TEXCOORD_0 등 시맨틱 이름 → accessor 인덱스.
indices인덱스 버퍼 accessor 인덱스. 없으면 non-indexed draw.
morph targetprimitive의 위치 변형 정보 (블렌드 셰이프). primitive.targets 배열.얼굴 표정 등
weights모프 타깃 가중치 배열. mesh 또는 node 단위로 정의 가능.

표준 attribute 시맨틱

시맨틱타입설명비고
POSITIONFLOAT VEC3버텍스 위치 (모델 공간)필수, accessor에 min/max 필수
NORMALFLOAT VEC3단위 법선 벡터정규화됨
TANGENTFLOAT VEC4탄젠트 + w(handedness ±1)노멀맵 사용 시 필요
TEXCOORD_0, TEXCOORD_1, …FLOAT/UBYTE/USHORT VEC2UV 좌표. 원점은 좌상단normalized 가능
COLOR_0FLOAT/UBYTE/USHORT VEC3 또는 VEC4버텍스 컬러 (선형 RGB/RGBA)normalized 가능
JOINTS_0, JOINTS_1UBYTE/USHORT VEC4영향 받는 관절 인덱스스키닝용
WEIGHTS_0, WEIGHTS_1FLOAT/UBYTE/USHORT VEC4각 관절의 가중치 (합 = 1.0)normalized 가능
_FEATURE_ID_0, _BATCHIDINT 계열커스텀 또는 확장 attribute_ prefix 필수

node transform — matrix vs TRS

각 노드는 두 가지 방식 중 하나로 transform을 표기한다 (둘 다 정의 시 invalid).

matrix 방식 (열 우선 16-float):

{
  "matrix": [
    1, 0, 0, 0,
    0, 1, 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1
  ]
}

TRS 방식 (권장):

{
  "translation": [0, 1.5, 0],
  "rotation":    [0, 0, 0, 1],
  "scale":       [1, 1, 1]
}

합성 순서: M = T × R × S (스케일 먼저, 그다음 회전, 마지막 이동).

⚠️ rotation의 quaternion 순서: [qx, qy, qz, qw]w가 맨 마지막이다. Three.js/CesiumJS도 같은 규약이지만, 다른 수학 라이브러리(예: Eigen)는 w가 앞에 오는 경우가 있다. 변환 시 주의.

부모-자식 transform 누적

자식 노드는 부모의 transform을 상속한다. 어떤 노드 N의 월드 행렬은:

M_world(N) = M_root × M_intermediate_1 × ... × M_parent(N) × M_local(N)

3D Tiles 1.1에서는 이 누적된 모델 공간 좌표 위에 다시 tile.transform과 tileset root transform이 곱해진다. → 상세


데이터 3계층 (buffer·bufferView·accessor)

glTF의 가장 중요한 추상화. 원시 바이트 → 의미 있는 typed array로 단계적으로 해석된다.

buffer (raw bytes)
  └── bufferView (구간 + GPU 힌트)
        └── accessor (자료형 + 카운트 + 통계)
용어설명비고
buffer원시 바이트 덩어리. uri(외부 .bin 또는 data URI) 또는 GLB의 BIN 청크 참조(uri 생략).byteLength 필수
bufferViewbuffer의 한 구간을 가리키는 뷰. byteOffset, byteLength, 선택적으로 byteStride·target.GPU 업로드 단위
byteOffsetbuffer 시작에서의 오프셋(바이트).4바이트 정렬 권장
byteLength뷰의 크기(바이트).
byteStride한 요소의 stride(바이트). interleaved 버텍스 데이터에서 사용 (4~252).bufferView 속성
target34962 = ARRAY_BUFFER (버텍스 어트리뷰트) / 34963 = ELEMENT_ARRAY_BUFFER (인덱스).GL hint
accessorbufferView를 typed array처럼 해석하는 메타데이터.”이게 무슨 자료형이고 몇 개냐”를 알려준다
componentType자료형 GL 상수 (아래 표 참조).
type컴포넌트 묶음: SCALAR / VEC2 / VEC3 / VEC4 / MAT2 / MAT3 / MAT4.
count요소의 개수 (바이트 수 아님).
min / max각 컴포넌트별 최솟값/최댓값 배열. POSITION에 필수 (컬링 박스 계산용).Cesium에서 결정적
normalized정수 → 정규화된 float 변환 (UBYTE/USHORT/SBYTE/SSHORT만). UNSIGNED_INT에는 사용 금지.
sparse대부분 0인 데이터를 효율 저장. count, indices, values 하위 객체.모프 타깃 등

componentType + type 조합표

componentTypeGL 상수C 타입크기type 예TypedArray
5120BYTEint8_t1BSCALARInt8Array
5121UNSIGNED_BYTEuint8_t1BVEC4 (normalized)Uint8Array
5122SHORTint16_t2BSCALARInt16Array
5123UNSIGNED_SHORTuint16_t2BSCALARUint16Array
5125UNSIGNED_INTuint32_t4BSCALARUint32Array
5126FLOATfloat4BVEC3 (12B)Float32Array

정렬 규칙: (bufferView.byteOffset + accessor.byteOffset) % sizeof(componentType) == 0 이어야 한다. FLOAT accessor는 합이 4의 배수, USHORT는 2의 배수.

sparse accessor

bufferView를 생략하고 sparse 객체만 두면 “기본값은 0, 일부 인덱스만 값 있음”으로 해석된다. 모프 타깃처럼 대부분 0인 데이터에 사용.

{
  "componentType": 5126, "type": "VEC3", "count": 1000,
  "sparse": {
    "count": 5,
    "indices": { "bufferView": 3, "componentType": 5125 },
    "values":  { "bufferView": 4 }
  }
}

머티리얼·텍스처 용어

용어설명비고
materialPBR 머티리얼. primitive에 인덱스로 연결.
PBRPhysically Based Rendering. glTF 2.0은 Metallic-Roughness 워크플로우를 기본 채택.
pbrMetallicRoughnessbaseColor·metallic·roughness 필드를 묶은 객체.
baseColorFactorRGBA 4-float. 기본 색상 배율.선형 색공간
baseColorTexture베이스 컬러 텍스처.RGB=색상, A=알파, sRGB
metallicFactor / roughnessFactor스칼라 배율 (0~1).기본 1.0
metallicRoughnessTextureG=roughness, B=metallic 채널 패킹된 텍스처.linear
normalTexture탄젠트 공간 노멀맵. scale 필드로 강도 조절.linear
occlusionTextureR=AO 텍스처. strength로 강도 조절.linear
emissiveTexture / emissiveFactor자발광 텍스처 / 배율.sRGB
alphaModeOPAQUE (기본) / MASK (alphaCutoff로 on-off) / BLEND (반투명).BLEND는 비용 ↑
alphaCutoffMASK 모드 임계값 (기본 0.5).
doubleSidedtrue면 양면 렌더링.잎사귀·천
textureimage + sampler 조합.
image이미지 데이터. uri(외부/data URI) 또는 bufferView+mimeType.PNG, JPEG, KTX2
TextureInfo머티리얼에서 텍스처를 가리킬 때 쓰는 객체 { "index": n, "texCoord": 0 }.texCoord는 사용할 TEXCOORD_n

텍스처 채널 매핑 (반드시 지킬 것)

텍스처채널색공간
baseColorTextureRGB=색상, A=알파sRGB
metallicRoughnessTextureG=roughness, B=metalliclinear
normalTextureRGB=탄젠트 노멀linear
occlusionTextureR=AOlinear
emissiveTextureRGB=자발광sRGB

sRGB 텍스처는 GPU에서 자동 감마 디코드 → 셰이더가 linear 값으로 계산.


Sampler — 텍스처 필터링

Sampler는 GPU가 텍스처를 읽는 방식을 정의한다. 텍스처 이미지 데이터와 별도로, “어떻게 보간/축소할 것인가” 를 지정한다.

⚠️ 핵심 주의: minFiltermagFilternull(미설정)이면 glTF 스펙에 기본값이 명시되어 있지 않다. WebGL 구현체마다 다른 기본값을 사용하며, CesiumJS 3D Tiles 경로에서는 Mipmap이 존재해도 무시된다.보정 코드와 분석

minFilter — 축소 필터 (카메라가 멀리 있을 때)

상수명동작권장
9728NEARESTMipmap 미사용, 최근접 텍셀 1개
9729LINEARMipmap 미사용, 4개 텍셀 선형 보간
9984NEAREST_MIPMAP_NEAREST최근접 Mipmap 1개 → NEAREST
9985LINEAR_MIPMAP_NEAREST최근접 Mipmap 1개 → LINEAR
9986NEAREST_MIPMAP_LINEAR인접 Mipmap 2개 보간 → NEAREST
9987LINEAR_MIPMAP_LINEAR인접 Mipmap 2개 보간 → LINEAR (Trilinear)✅ 최고 품질

magFilter — 확대 필터 (카메라가 매우 가까이 있을 때)

상수명동작
9728NEAREST픽셀아트 느낌 (픽셀화)
9729LINEAR부드러운 경계 (bilinear) ✅

magFilter에는 9728(NEAREST)과 9729(LINEAR)만 유효하다. Mipmap 관련 값(9984~9987)은 minFilter에서만 사용 가능.

wrapS / wrapT — 래핑 모드

상수명설명
33071CLAMP_TO_EDGE가장자리 색상으로 채움
33648MIRRORED_REPEAT거울 반전 반복
10497REPEAT그대로 반복 (기본값)

권장 sampler 설정값

{
  "samplers": [{
    "minFilter": 9987,
    "magFilter": 9729,
    "wrapS": 10497,
    "wrapT": 10497
  }]
}

애니메이션·스키닝 용어

용어설명비고
animation시간 기반 키프레임 클립. channels + samplers.
channel어떤 노드의 어떤 속성을 애니메이션 할지 지정. { "sampler": n, "target": { "node": m, "path": "..." } }.
path애니메이션 대상 속성: translation / rotation / scale / weights.
sampler (animation)시간(input accessor) → 값(output accessor) 매핑 + 보간법.텍스처 sampler와 별개 개념
interpolationLINEAR / STEP / CUBICSPLINE.rotation의 LINEAR은 slerp
skin스키닝 정의. joints(노드 인덱스 배열), inverseBindMatrices(accessor), 선택적 skeleton.
joint스키닝의 본(bone)에 해당하는 노드.
inverseBindMatrix각 joint의 바인드 포즈 역행렬. 모델 공간 → joint 로컬 공간 변환.MAT4
skinningJOINTS_0/WEIGHTS_0 attribute를 사용해 버텍스를 본에 따라 변형.

스키닝 공식

JOINTS_0[v]   = [j0, j1, j2, j3]    (관절 인덱스)
WEIGHTS_0[v]  = [w0, w1, w2, w3]    (합 = 1.0)

jointMatrix[j] = globalTransform(nodes[skin.joints[j]]) × inverseBindMatrices[j]

skinMatrix    = Σ WEIGHTS_0[v][i] × jointMatrix[JOINTS_0[v][i]]   (i=0~3)
finalPos      = skinMatrix × vec4(POSITION[v], 1.0)

확장(KHR/EXT) 용어

용어설명비고
extension스펙에 추가 기능을 더하는 모듈. 객체 안 "extensions": { "EXT_xxx": {...} }로 삽입.
KHR_Khronos 표준화 확장 (Ratified).
EXT_다중 벤더 확장 (community/multi-vendor).
벤더_ (예: CESIUM_)단일 벤더 확장.
extensionsUsed파일이 사용하는 확장 이름 배열 (정보용).
extensionsRequired파일이 반드시 필요로 하는 확장 — 미지원 로더는 거부해야 한다.
KHR_draco_mesh_compressionDraco 지오메트리 압축 (손실, 최대 95% ↓).정점 압축
KHR_texture_basisuKTX2/Basis Universal 텍스처 압축. GPU 직접 사용.VRAM 절약
EXT_meshopt_compressionmeshopt 압축 (bufferView 단위, ~1 GB/s 디코드).빠른 스트리밍
KHR_materials_unlit조명 계산 무시, baseColor만 출력.포토그래메트리 필수
KHR_materials_clearcoat클리어코트 레이어 (자동차 도장 등).
KHR_materials_sheen천·벨벳 표현.
KHR_materials_transmission / volume / ior유리·물 표현.
KHR_materials_specular스페큘러 워크플로우 보강.
KHR_materials_emissive_strengthemissive 밝기 1.0 이상 허용.HDR 발광
KHR_mesh_quantizationPOSITION/NORMAL/UV를 정수형으로 저장 → 크기 1/2~1/4.
KHR_texture_transformUV offset/rotation/scale 변환.텍스처 아틀라스
KHR_lights_punctual점광원 정의.

3D Tiles 1.1 전용 확장(EXT_mesh_features 등)은 4.4 참조.


렌더링 파이프라인 7단계

GLB 바이트가 JSON으로, accessor로, GPU 버퍼로, 마지막으로 픽셀로 변환되는 흐름.

#단계명설명입력 → 출력
1GLB 바이트 분해헤더·청크 분리Uint8Array{ json: string, bin: ArrayBuffer }
2JSON 파싱UTF-8 JSON 디코딩stringglTFRoot 객체
3buffer 확보BIN 청크 또는 URI 페치glTFRoot.buffersArrayBuffer[]
4bufferView → accessor구간 + 자료형 해석ArrayBuffer + 메타 → TypedArrayView
5primitive attribute 매핑시맨틱 → GPU 버퍼 바인딩accessor → VertexBuffer, IndexBuffer
6머티리얼·텍스처 업로드image 디코드 + sampler 설정image bytes → GPU Texture
7transform 누적 + drawscene 순회 + 머티리얼 적용scene + 카메라 → 픽셀
sequenceDiagram
    autonumber
    participant U as User Code
    participant L as Loader<br/>(GLTFLoader 등)
    participant P as Parser
    participant G as GPU/WebGL

    U->>L: fetch(model.glb)
    L->>L: HTTP GET → ArrayBuffer
    L->>P: parse(arrayBuffer)
    P->>P: Header 12B 확인<br/>(magic=glTF, version=2)
    P->>P: JSON 청크 분리<br/>(0x4E4F534A)
    P->>P: BIN 청크 분리<br/>(0x004E4942)
    P->>P: JSON.parse() → glTFRoot
    P->>P: buffers 해석<br/>(BIN 청크 or fetch URI)
    P->>P: bufferView 슬라이스 생성
    P->>P: accessor → TypedArray 뷰
    loop primitive마다
        P->>G: createBuffer(VBO/IBO)
        P->>G: bufferData(attribute)
    end
    loop image마다
        P->>P: decodeImage()<br/>(PNG/JPEG/KTX2)
        P->>G: createTexture()<br/>+ sampler 적용
    end
    P->>P: scene 그래프 빌드<br/>(node transform 캐시)
    P-->>L: GLTFScene 객체 반환
    L-->>U: scene 핸들 전달
    U->>G: render(scene, camera)
    G->>G: 노드 순회 → drawCall

Step 1 — GLB 바이트 분해

function parseGlb(arrayBuffer) {
  const view = new DataView(arrayBuffer);
  if (view.getUint32(0, true) !== 0x46546C67) throw new Error('Not a GLB');
  const version = view.getUint32(4, true);   // 2
  const length  = view.getUint32(8, true);
 
  let offset = 12;
  let json, bin;
  while (offset < length) {
    const chunkLen = view.getUint32(offset, true);
    const chunkType = view.getUint32(offset + 4, true);
    const dataStart = offset + 8;
    if (chunkType === 0x4E4F534A) {  // JSON
      json = new TextDecoder('utf-8').decode(
        new Uint8Array(arrayBuffer, dataStart, chunkLen)
      );
    } else if (chunkType === 0x004E4942) {  // BIN
      bin = arrayBuffer.slice(dataStart, dataStart + chunkLen);
    }
    offset = dataStart + chunkLen;
  }
  return { json, bin };
}

Step 4 — bufferView → accessor 해석 (핵심)

bufferView:  { buffer: 0, byteOffset: 0,   byteLength: 288, target: 34962 }
accessor:    { bufferView: 0, byteOffset: 0, componentType: 5126, type: "VEC3", count: 24 }
buffer[0]:   ArrayBuffer(360)

→ positions = new Float32Array(buffer, 0 + 0, 24 * 3) = [...72 floats]

Step 5 — 시맨틱 → 셰이더 attribute (Three.js GLTFLoader 예시)

glTF 시맨틱Three.js attribute 이름
POSITIONposition
NORMALnormal
TANGENTtangent
TEXCOORD_0uv
TEXCOORD_1uv1
COLOR_0color
JOINTS_0skinIndex
WEIGHTS_0skinWeight

Step 7 — 드로우 콜 의사 코드

function renderNode(node, parentWorld) {
  const local = node.matrix
    ? mat4.clone(node.matrix)
    : mat4.fromRotationTranslationScale(out, node.rotation, node.translation, node.scale);
  const world = mat4.multiply(out2, parentWorld, local);
 
  if (node.mesh !== undefined) {
    for (const primitive of meshes[node.mesh].primitives) {
      bindMaterial(primitive.material);
      bindAttributes(primitive.attributes);
      bindIndices(primitive.indices);
      setUniform('u_ModelMatrix', world);
      gl.drawElements(primitive.mode, indexCount, indexType, 0);
    }
  }
  for (const childIdx of node.children || []) {
    renderNode(nodes[childIdx], world);
  }
}

디버깅 시 자주 보게 되는 함정

증상원인해결
모델이 회전 누워서 보임Y-up ↔ Z-up 변환 누락 또는 중복3D Tiles 클라이언트가 자동 변환 — 추가 회전 행렬 넣지 말 것
모델이 화면 어딘가 사라짐accessor.min/max 누락 → 컬링 박스 잘못 계산POSITION accessor에 정확한 min/max 채울 것
모델이 떨림 (jitter)ECEF 좌표를 float32에 그대로 저장 → 정밀도 손실tile.transform으로 중심 빼고 vertex는 상대 좌표로 (상세)
텍스처가 자글자글/aliasingsampler.minFilter 미설정 → Mipmap 무시minFilter: 9987 (LINEAR_MIPMAP_LINEAR) 명시 (보정 코드)
색이 너무 어둡거나 밝음sRGB ↔ linear 색공간 혼동baseColor/emissive는 sRGB, 나머지는 linear
roughness/metallic 채널 뒤바뀜R/G/B 채널 매핑 실수G=roughness, B=metallic 고정
Three.js에서 일부 모델 로드 실패extensionsRequired에 미지원 확장 포함KHR_draco는 DRACOLoader, KHR_texture_basisu는 KTX2Loader 등록 필요
Cesium에서 모델만 뜨고 클릭 안 됨EXT_mesh_features 없이 b3dm batchId만 있음3d-tiles-tools로 1.1 업그레이드 (상세)

검증 체크리스트

  1. gltf-validator 통과 — 오류·경고 0건
  2. accessor.min/max가 모든 POSITION에 있음
  3. extensionsRequired에 명시된 확장이 타깃 클라이언트에서 지원됨
  4. sampler가 모든 텍스처에 명시적 설정됨 (minFilter 포함)
  5. material의 alphaMode가 의도와 일치 (특히 OPAQUE인데 BLEND 쓰면 성능 저하)
  6. bufferView·accessor의 정렬 규칙 준수

JSON 최상위 객체 한눈에 보기

{
  "asset": { "version": "2.0", "generator": "Blender 3.6" },
  "scene": 0,
  "scenes":     [{ "nodes": [0] }],
  "nodes":      [{ "mesh": 0, "translation": [0, 0, 0] }],
  "meshes":     [{ "primitives": [{ "attributes": { "POSITION": 0 }, "indices": 1, "material": 0 }] }],
  "accessors":  [
    { "bufferView": 0, "componentType": 5126, "count": 24, "type": "VEC3", "min": [-1,-1,-1], "max": [1,1,1] },
    { "bufferView": 1, "componentType": 5123, "count": 36, "type": "SCALAR" }
  ],
  "bufferViews":[
    { "buffer": 0, "byteOffset": 0,   "byteLength": 288, "target": 34962 },
    { "buffer": 0, "byteOffset": 288, "byteLength": 72,  "target": 34963 }
  ],
  "buffers":    [{ "byteLength": 360 }],
  "materials":  [{ "pbrMetallicRoughness": { "baseColorFactor": [0.8, 0.2, 0.2, 1.0] } }],
  "extensionsUsed":     ["KHR_materials_unlit"],
  "extensionsRequired": []
}

이 작은 예제 하나로 모든 핵심 요소(scene/node/mesh/primitive/accessor/bufferView/buffer/material/extensions)가 등장한다. 실전 GLB는 이 구조의 반복일 뿐이다.


문서 탐색


참고 자료