반응형

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                전부 해제

 

반응형

+ Recent posts