cgltf는 단일 헤더 라이브러리입니다. 구현은 .cpp 파일 딱 하나에서만 포함해야 합니다. 두 군데서 CGLTF_IMPLEMENTATION을 정의하면 링크 단계에서 중복 정의 에러가 납니다.
#define CGLTF_IMPLEMENTATION // 한 파일에서만
#include "cgltf.h"
1.파일 열기와 해제
| 함수 | 역할 |
| cgltf_parse_file(options, path, &data) | .gltf(JSON) 또는 .glb를 파싱해 cgltf_datta 구조를 만듭니다. 외부 .bin 버퍼는 아직 읽지 않습니다. |
| cgltf_load_buffers(options, data, path) | 정점과 인덱스가 담긴 버퍼를 메모리에 올립니다. 외부 .bin, data URL, .glb의 BIN 청크를 처리합니다. 세 번째 인자는 상대 경로의 기준이 되는 원본 파일 경로입니다. |
| cgltf_validate(data) | (선택) 인덱스 범위 등 데이터 무결성을 검사합니다. 이번에는 사용하지 않았습니다. |
| cgltf_free(data) | 파싱한 데이터와 버퍼를 모두 삭제(메모리 해제)합니다. |
모든 함수는 cgltf_result를 반환하고, 성공하면 cgltf_result_success입니다.
cgltf_optioins를 0으로 초기화하면 파일 형식(.gltf/.glb)을 자동으로 판별합니다.
cgltf_options options{};
cgltf_data* data = nullptr;
if (cgltf_parse_file(&options, path.c_str(), &data) != cgltf_result_success)
throw std::runtime_error("glTF parse failed");
if (cgltf_load_buffers(&options, data, path.c_str()) != cgltf_result_success)
{
cgltf_free(data); // 파싱은 성공했으므로 실패해도 해제는 필요
throw std::runtime_error("glTF buffer load failed");
}
// ... 데이터 사용 ...
cgltf_free(data);
cgltf_data에서 꺼낸 포인터들은모두 cgltf_free 이후에는 무효입니다. 필요한 값은 그 전에 엔진 구조로 복사해 둬야 합니다.
2. 전체 구조(cgltf_data)
| 필드 | 내용 |
| meshes, meshes_count | 메시 목록, 개수 |
| materials, materials_count | 머티리얼 목록, 개수 |
| nodes, nodes_count | 노드 목록, 개수. 계층과 변환을 가짐 |
| images, images_count | 이미지 목룍, 개수 |
| textures, textures_count | 텍스처(이미지+샘플러) 목록, 개수 |
| scene, scenes | 기본 씬과 씬 목록 |
glTF안에서는 서로를 포인터로 참조합니다. 배열에서의 인덱스가 필요하면 포인터 뺄셈으로 구합니다.
size_t meshIndex = node.mesh - data->meshes;
size_t materialIndex = prim.material - data->materials;
3. 메시와 정점 데이터
cgltf_mesh
| 필드 | 내용 |
| name | 메시 이름 (없으면 nullptr) |
| primitives, primitives_count | 프리미티브 목록, 개수. 머티리얼 하나를 쓰는 조각 단위 |
cgltf_primitive
| 필드 | 내용 |
| type | 그리기 방식, 삼각형 목록은 cgltf_primitive_type_triangles |
| attributes, attributes_count | 정점 속성 목록(위치, 노멀 등), 개수 |
| indices | 인덱스 accessor. 없으면 nullptr(정점 순서대로 그림) |
| material | 사용하는 머티리얼. 없으면 nullptr |
cgltf_attribute
| 필드 | 내용 |
| type | 속성 종류: cgltf_attribute_type_position, _normal, _tangent, _texcoord, _color, _joints, _weights |
| index | 같은 종류의 몇 번째 세트인지. TEXCOORD_0이면 0, TEXCOORD_1이면 1 |
| data | 실제 값을 읽을 accessor |
const cgltf_accessor* posAcc = nullptr;
for (cgltf_size a = 0; a < prim.attributes_count; ++a)
{
const cgltf_attribute& attr = prim.attributes[a];
if (attr.type == cgltf_attribute_type_position) posAcc = attr.data;
if (attr.type == cgltf_attribute_type_texcoord && attr.index == 0) uvAcc = attr.data;
}
accessor 읽기
| 함수 | 역할 |
| cgltf_accessor_read_float(acc, i, out, n) | i번째 요소를 float n개로 읽습니다. 저장 타입이 정규화된 정수 (예: UV를 16비트로 압축)여도 float로 변환해 줍니다. sparse accessor도 처리합니다. |
| cgltf_accessor_read_index(acc, i) | i번째 인덱스를 정수로 읽습니다. 저장 타입이 8/16/32비트 중 무엇이든 같은 함수로 읽습니다. |
| acc-->count | 요소 개수. 정점 수나 인덱스 수가 됩니다. |
for (cgltf_size v = 0; v < posAcc->count; ++v)
{
float p[3];
cgltf_accessor_read_float(posAcc, v, p, 3);
}
for (cgltf_size i = 0; i < prim.indices->count; ++i)
idx[i] = (uint32_t)cgltf_accessor_read_index(prim.indices, i);
버퍼의 바이트 오프셋, stride, 컴포넌트 타입을 직접 계산하지 않아도 된다는 게 이 두 함수의 장점입니다.
4. 노드와 변환
cgltf_node
| 필드 | 내용 |
| name | 노드 이름 |
| mesh | 이 노드가 그리는 메시. 없으면 nullptr(빈 노드, 카메라, 라이트 등) |
| parent, children, children_count | 계층 구조 |
| has_matrix, matrix[16] | 행렬로 지정된 로컬 변환 |
| has_translation, translation[3] | 위치 |
| has_rotation, rotation[4] | 회전 쿼터니언(x, y, z, w 순서) |
| has_scale, scale[3] | 스케일 |
변환 계산 함수
| 함수 | 역할 |
| cgltf_node_transform_local(node, out16) | 이 노드 자신의 로컬 행렬, TRS로 지정돼 있어도 행렬로 합쳐줍니다. |
| cgltf_node_transform_world(node, out16) | 부모까지 모두 곱한 월드 행렬 |
결과는 열 우선(column-major) float[16]입니다. 메모리 배치가 DirectXMath의 XMFLOAT4X4(행 우선, 행 벡터 규약)와 같아서 그대로 쓸 수 있습니다. 이동 성분이 [12], [13], [14]에 있습니다.
cgltf_float wm[16];
cgltf_node_transform_world(&node, wm);
XMFLOAT4X4 f;
memcpy(&f, wm, sizeof(f));
5. 머티리얼
cgltf_material
| 필드 | 내용 |
| name | 머티리얼 이름 |
| has_pbr_metallic_roughness | 아래 PBR 블록이 유효한지 |
| pbr_metallic_roughness | PBR 값과 텍스처 (아래 표) |
| normal_texture | 노멀 맵(scale 필드에 세기) |
| occlusion_texture | AO 맵(scale 필드에 세기) |
| emissive_texture | 발광 맵 |
| emissive_factor[3] | 발광 색(선형) |
| alpha_mode, alpha_cutoff | 불투면 / 마스크 / 블렌드 |
| double_sided | 양면 렌더링 여부 |
pbr_metallic_roughness
| 필드 | 내용 |
| base_color_factor[4] | 기본 색RGBA (선형) |
| base_color_texture | 기본 색 텍스처 (sRGB) |
| metallic_factor | 금속성 계수 |
| roughness_factor | 거칠기 계수 |
| metallic_roughness_texture | G채널 = 거칠기, B채널 = 금속성 (선형) |
계수(factor)와 텍스처는 곱해서 씁니다. 텍스처가 있는 모델은 보통 계수가 1.0이고, 실제 값은 텍스처가 픽셀 단위로 정합니다.
6. 텍스처와 이미지
참조는 세 단계로 이어집니다.
cgltf_texture_view → cgltf_texture → cgltf_image
(머티리얼의 슬롯) (이미지 + 샘플러) (실제 픽셀 데이터 위치)
| 구조체 | 주요 필드 |
| cgltf_texture_view | texture: 참조하는 텍스처(없으면 nullptr) texcoord: 사용할 UV 세트 번호 scale: 노멀/AO 맵의 세기 |
| cgltf_texture | image: 참조하는 이미지 sampler: 필터와 래핑 설정 |
| cgltf_image | uri: 외부 파일 경로 또는 data: URI buffer_view: .glb 내장 이미지의 위치 mime_type: "image/png" 등 |
| cgltf_buffer_view | buffer, offset, size |
| cgltf_buffer | data: cgltf_load_buffers 이후 메모리에 올라온 바이트 |
이미지 바이트를 얻는 두 경로
const cgltf_image* img = view.texture->image;
if (img->buffer_view) // .glb: 파일 안에 PNG/JPG 바이트가 그대로 들어 있음
{
const cgltf_buffer_view* bv = img->buffer_view;
const uint8_t* bytes = (const uint8_t*)bv->buffer->data + bv->offset;
// bytes, bv->size 를 stbi_load_from_memory에 전달
}
else if (img->uri) // .gltf: 옆에 있는 이미지 파일
{
std::string uri = img->uri;
cgltf_decode_uri(uri.data()); // "%20" 같은 URL 인코딩을 제자리에서 해제
uri.resize(strlen(uri.c_str()));
// 모델 폴더 경로 + uri 로 파일 로딩
}
| 함수 | 역할 |
| cgltf_decode_uri(char* uri) | URI의 퍼센트 인코딩을 문자열 안에서 바로 풀고, 새 길이를 반환합니다. |
| cgltf_buffer_view_data(bv) | 최신 버전에 있는 헬퍼입니다. buffer->data + offset 계산을 대신해 줍니다. |
7. 한눈에 보는 로딩 순서
cgltf_parse_file 파일 구조 파싱
cgltf_load_buffers 정점/인덱스/내장 이미지 바이트 로드
│
├─ materials[i] → pbr_metallic_roughness, normal/emissive_texture
│ └─ texture_view → texture → image → (buffer_view | uri)
│
├─ meshes[m].primitives[p]
│ ├─ attributes[a] → cgltf_accessor_read_float
│ ├─ indices → cgltf_accessor_read_index
│ └─ material → 포인터 뺄셈으로 인덱스
│
└─ nodes[n]
├─ mesh → 포인터 뺄셈으로 인덱스
└─ cgltf_node_transform_world
cgltf_free 전부 해제
'DirectX12 > 엔진 만들어보기' 카테고리의 다른 글
| 스카이박스와 IBL(Image-Based Lighting) (0) | 2026.10.12 |
|---|---|
| glTF 모델 로딩 (0) | 2026.10.09 |
| 라이팅에서 PBR 머티리얼까지 (0) | 2026.10.06 |
| 그리드, Scene/Entity, Hierarchy·Inspector, 기즈모 (0) | 2026.10.05 |
| 서브모듈 정리, 카메라 (0) | 2026.10.03 |
