glTF model loader #82

Open
opened 2026-10-25 23:10:08 -07:00 by burkkyy · 0 comments
Owner

Problem

There's no way to load models from files. ResourceRegistryRef::load<Mesh>(path) (engine/src/resources/resource_registry_ref.cpp) is a stub that asserts, so every mesh has to be built by hand as a MeshData or come from primitives.

glTF 2.0 is the format to support first. It's an open standard, most tools export it, and its conventions already match the engine's:

  • right-handed with +Y up
  • counter-clockwise front faces (matches MeshData)
  • UV origin at the top-left (matches MeshVertex::uv)
  • metallic-roughness PBR materials (matches the planned material system)

Proposal

Add a parser library to third_party/. Candidates:

  • fastgltf: modern C++, fast (simdjson), handles .gltf, .glb and data URIs.
  • cgltf: single-header C, minimal and easy to vendor.

Phase 1: geometry

  • Implement load<Mesh>(path) for .gltf and .glb.
  • Read the POSITION, NORMAL, TEXCOORD_0 and COLOR_0 attributes and the indices into MeshData, then create the mesh through the existing path.
  • Default any missing attribute: normal (0,0,1) or computed, uv (0,0), color white.
  • Handle the different index component types (u8, u16, u32) by widening them to u32.
  • A glTF mesh can have several primitives. Decide whether each becomes its own MeshHandle or whether Mesh gains submeshes, since each primitive can use a different material.

Phase 2: materials and textures (depends on the material system)

  • Map pbrMetallicRoughness (base color factor and texture, metallic and roughness factors, metallic-roughness texture), normalTexture, occlusionTexture and emissive onto material instances.
  • Load images with stb_image, embedded or external.
  • Color textures (base color, emissive) are sRGB. Data textures (metallic-roughness, normal, occlusion) are linear. Texture creation needs a format choice to match, or colors will be wrong.
  • The metallic-roughness texture packs roughness in G and metallic in B.

Phase 3: scenes

  • Load the node hierarchy into a Scene as entities with a TransformComponent and a mesh component (node TRS to position, rotation, scale), keeping parent/child relationships if the ECS supports them.

Out of scope for now

Animation, skinning, morph targets, cameras and lights, KHR extensions, and Draco or meshopt compression.

Acceptance criteria

  • resources().load<Mesh>("model.glb") returns a mesh that renders correctly, in both .gltf and .glb form.
  • A model with missing optional attributes still loads.
  • A malformed or missing file produces a clear error naming the path.
  • Tested against a few Khronos glTF sample models (e.g. Box, Duck, DamagedHelmet).
## Problem There's no way to load models from files. `ResourceRegistryRef::load<Mesh>(path)` ([engine/src/resources/resource_registry_ref.cpp](engine/src/resources/resource_registry_ref.cpp)) is a stub that asserts, so every mesh has to be built by hand as a `MeshData` or come from `primitives`. glTF 2.0 is the format to support first. It's an open standard, most tools export it, and its conventions already match the engine's: - right-handed with +Y up - counter-clockwise front faces (matches `MeshData`) - UV origin at the top-left (matches `MeshVertex::uv`) - metallic-roughness PBR materials (matches the planned material system) ## Proposal Add a parser library to `third_party/`. Candidates: - **fastgltf**: modern C++, fast (simdjson), handles `.gltf`, `.glb` and data URIs. - **cgltf**: single-header C, minimal and easy to vendor. ### Phase 1: geometry - Implement `load<Mesh>(path)` for `.gltf` and `.glb`. - Read the `POSITION`, `NORMAL`, `TEXCOORD_0` and `COLOR_0` attributes and the indices into `MeshData`, then create the mesh through the existing path. - Default any missing attribute: normal `(0,0,1)` or computed, uv `(0,0)`, color white. - Handle the different index component types (u8, u16, u32) by widening them to `u32`. - A glTF mesh can have several primitives. Decide whether each becomes its own `MeshHandle` or whether `Mesh` gains submeshes, since each primitive can use a different material. ### Phase 2: materials and textures (depends on the material system) - Map `pbrMetallicRoughness` (base color factor and texture, metallic and roughness factors, metallic-roughness texture), `normalTexture`, `occlusionTexture` and `emissive` onto material instances. - Load images with stb_image, embedded or external. - Color textures (base color, emissive) are **sRGB**. Data textures (metallic-roughness, normal, occlusion) are **linear**. Texture creation needs a format choice to match, or colors will be wrong. - The metallic-roughness texture packs roughness in **G** and metallic in **B**. ### Phase 3: scenes - Load the node hierarchy into a `Scene` as entities with a `TransformComponent` and a mesh component (node TRS to position, rotation, scale), keeping parent/child relationships if the ECS supports them. ## Out of scope for now Animation, skinning, morph targets, cameras and lights, KHR extensions, and Draco or meshopt compression. ## Acceptance criteria - `resources().load<Mesh>("model.glb")` returns a mesh that renders correctly, in both `.gltf` and `.glb` form. - A model with missing optional attributes still loads. - A malformed or missing file produces a clear error naming the path. - Tested against a few Khronos glTF sample models (e.g. Box, Duck, DamagedHelmet).
burkkyy added the enhancement label 2026-10-25 23:10:08 -07:00
burkkyy added this to the sophia project 2026-10-25 23:10:16 -07:00
burkkyy moved this to Todo in sophia on 2026-10-25 23:10:29 -07:00
Sign in to join this conversation.