#include "gltf_loader.h" #include "render_assets.h" #include "spdlog/spdlog.h" #include "tiny_gltf.h" #include enum class SceneFileType { GLTF, GLB, UNKNOWN, }; bool load_gltf(const std::string_view path, iris::Scene &scene) { tinygltf::Model model; tinygltf::TinyGLTF loader; std::string error; std::string warning; SceneFileType file_type = [&path] { if (path.find_last_of(".") != std::string::npos) { std::string_view extension = path.substr(path.find_last_of(".") + 1); if (extension == "glb") { return SceneFileType::GLB; } else if (extension == "gltf") { return SceneFileType::GLTF; } } return SceneFileType::UNKNOWN; }(); switch (file_type) { case SceneFileType::GLTF: if (!loader.LoadASCIIFromFile(&model, &error, &warning, path.data())) { spdlog::error("Failed to load glTF file: {}", error); return false; } break; case SceneFileType::GLB: if (!loader.LoadBinaryFromFile(&model, &error, &warning, path.data())) { spdlog::error("Failed to load glTF file: {}", error); return false; } break; case SceneFileType::UNKNOWN: spdlog::error("Unknown file type: {}", path); return false; } spdlog::info("loaded glTF file {} has:\n" "{} accessors\n" "{} animations\n" "{} buffers\n" "{} bufferViews\n" "{} materials\n" "{} meshes\n" "{} nodes\n" "{} textures\n" "{} images\n" "{} skins\n" "{} samplers\n" "{} cameras\n" "{} scenes\n" "{} lights", path, model.accessors.size(), model.animations.size(), model.buffers.size(), model.bufferViews.size(), model.materials.size(), model.meshes.size(), model.nodes.size(), model.textures.size(), model.images.size(), model.skins.size(), model.samplers.size(), model.cameras.size(), model.scenes.size(), model.lights.size()); for (const auto &mesh : model.meshes) { auto mesh_primitive_counter = 0u; const std::string mesh_name = mesh.name == "" ? "mesh" : mesh.name; // TODO: A Mesh in glTF can have multiple primitives, each with its own set of attributes // But our current abstract doesn't support multiple primitives within a mesh, so we load // each primitive as a separate mesh instead. This is a temporary solution and should be fixed. for (const auto &primitive : mesh.primitives) { std::string name = mesh_name + "_" + std::to_string(mesh_primitive_counter++); spdlog::info("loading mesh primitive: {}", name); iris::Mesh iris_mesh { .name = mesh.name, .vertices = {}, .normals = {}, .texcoords = {}, .indices = {}, .material_index = primitive.material, }; const auto &index_accessor = model.accessors[primitive.indices]; const auto &index_buffer_view = model.bufferViews[index_accessor.bufferView]; const auto &index_buffer = model.buffers[index_buffer_view.buffer]; const uint8_t *index_data_ptr = index_buffer.data.data() + index_buffer_view.byteOffset + index_accessor.byteOffset; auto extract = [&index_data_ptr]() -> T { const T *index_data = reinterpret_cast(index_data_ptr); index_data_ptr += sizeof(T); return *index_data; }; switch (index_accessor.componentType) { case TINYGLTF_COMPONENT_TYPE_BYTE: for (size_t i = 0; i < index_accessor.count; i++) { iris_mesh.indices.push_back(extract.operator()()); } break; case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: for (size_t i = 0; i < index_accessor.count; i++) { iris_mesh.indices.push_back(extract.operator()()); } break; case TINYGLTF_COMPONENT_TYPE_SHORT: for (size_t i = 0; i < index_accessor.count; i++) { iris_mesh.indices.push_back(extract.operator()()); } break; case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: for (size_t i = 0; i < index_accessor.count; i++) { iris_mesh.indices.push_back(extract.operator()()); } break; case TINYGLTF_COMPONENT_TYPE_INT: for (size_t i = 0; i < index_accessor.count; i++) { iris_mesh.indices.push_back(extract.operator()()); } break; case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: for (size_t i = 0; i < index_accessor.count; i++) { iris_mesh.indices.push_back(extract.operator()()); } break; default: spdlog::error("Unsupported index component type: {}", index_accessor.componentType); return false; } switch (primitive.mode) { case TINYGLTF_MODE_TRIANGLES: { // All float ? for (const auto &[attrib_name, accessor_index] : primitive.attributes) { const auto &accessor = model.accessors[accessor_index]; const auto &buffer_view = model.bufferViews[accessor.bufferView]; const auto &buffer = model.buffers[buffer_view.buffer]; const uint8_t *data = buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset; auto extract_data = [&data]() -> T { const T *typed_data = reinterpret_cast(data); data += sizeof(T); return *typed_data; }; // spdlog::info("attribute: {}, count: {}", attrib_name, accessor.count); if (attrib_name == "POSITION") { spdlog::info("loading POSITION, count {}", accessor.count); // TODO: support other types. Currently only float3 is supported if (accessor.type != TINYGLTF_TYPE_VEC3 || accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) { spdlog::error("Field {} type unsupported: {}, {}", attrib_name, accessor.type, accessor.componentType); continue; } iris_mesh.p_min = glm::vec3(accessor.minValues[0], accessor.minValues[1], accessor.minValues[2]); iris_mesh.p_max = glm::vec3(accessor.maxValues[0], accessor.maxValues[1], accessor.maxValues[2]); for (size_t i = 0; i < accessor.count; i++) { iris_mesh.vertices.push_back(extract_data.operator()()); } } else if (attrib_name == "NORMAL") { spdlog::info("loading NORMAL, count {}", accessor.count); // TODO: support other types. Currently only float3 is supported if (accessor.type != TINYGLTF_TYPE_VEC3 || accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) { spdlog::error("Field {} type unsupported: {}, {}", attrib_name, accessor.type, accessor.componentType); continue; } for (size_t i = 0; i < accessor.count; i++) { iris_mesh.normals.push_back(extract_data.operator()()); } } else if (attrib_name == "TEXCOORD_0") { spdlog::info("loading TEXCOORD_0, count {}", accessor.count); // TODO: support other types. Currently only float2 is supported if (accessor.type != TINYGLTF_TYPE_VEC2 || accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) { spdlog::error("Field {} type unsupported: {}, {}", attrib_name, accessor.type, accessor.componentType); continue; } for (size_t i = 0; i < accessor.count; i++) { iris_mesh.texcoords.push_back(extract_data.operator()()); } } else if (attrib_name == "TANGENT") { spdlog::info("loading TANGENT, count {}", accessor.count); // TODO: support other types. Currently only float4 is supported if (accessor.type != TINYGLTF_TYPE_VEC4 || accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) { spdlog::error("Field {} type unsupported: {}, {}", attrib_name, accessor.type, accessor.componentType); continue; } for (size_t i = 0; i < accessor.count; i++) { iris_mesh.tangents.push_back(extract_data.operator()()); } } else { spdlog::warn("Unsupported attribute: {}", attrib_name); } } break; } // TODO add support for other modes default: spdlog::error("Unsupported primitive mode: {}", primitive.mode); return false; } scene.meshes.push_back(iris_mesh); } } std::vector images; images.reserve(model.images.size()); for (const tinygltf::Image &image : model.images) { images.emplace_back(iris::Image { .data = image.image, .extent = glm::uvec3(image.width, image.height, image.component), .bits_per_channel = image.bits, }); } std::vector samplers; samplers.reserve(model.samplers.size()); for (const tinygltf::Sampler &sampler : model.samplers) { samplers.emplace_back(iris::Sampler2D { .min_filter = sampler.minFilter, .mag_filter = sampler.magFilter, .wrap_s = sampler.wrapS, .wrap_t = sampler.wrapT, }); } // load textures for (const tinygltf::Texture &texture : model.textures) { const std::string texture_name = texture.name == "" ? "texture" : texture.name; if (texture.source < 0 || unsigned(texture.source) >= images.size()) { spdlog::error("Invalid texture source index: {}", texture.source); return false; } if (texture.sampler < 0 || unsigned(texture.sampler) >= samplers.size()) { spdlog::error("Invalid texture sampler index: {}", texture.sampler); return false; } iris::Texture iris_texture { .name = texture_name, .image = images[texture.source], .sampler = samplers[texture.sampler], }; spdlog::info("Texture: {}", texture_name); } for (const tinygltf::Material &material : model.materials) { const std::string material_name = material.name == "" ? "material" : material.name; const auto &pbr = material.pbrMetallicRoughness; iris::Material iris_material { .name = material_name, .base_color = glm::vec4( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3]), .metallic = float(pbr.metallicFactor), .roughness = float(pbr.roughnessFactor), }; // Where to detect if the texture is valid? Can we early intercept? iris_material.base_color_texture = pbr.baseColorTexture.index; iris_material.metallic_roughness_texture = pbr.metallicRoughnessTexture.index; iris_material.normal_texture = material.normalTexture.index; iris_material.occulsion_texture = material.occlusionTexture.index; iris_material.emissive_texture = material.emissiveTexture.index; // TODO: load texture information, skip for now spdlog::info("Material: {}", material_name); } for (const tinygltf::Camera &camera : model.cameras) { iris::Camera iris_camera { .position = glm::vec3(0.0f), .direction = glm::vec3(0.0f), .up = glm::vec3(0.0f, 1.0f, 0.0f), }; spdlog::info("Camera: {}, type: {}", camera.name, camera.type); if (camera.type == "perspective") { const auto &perspective = camera.perspective; iris_camera.intrinsic_tag = iris::Camera::Tag::Perspective; iris_camera.intrinsic.perspective.fovx = 2 * glm::atan(glm::tan(perspective.yfov / 2) * perspective.aspectRatio); iris_camera.intrinsic.perspective.fovy = perspective.yfov; iris_camera.intrinsic.perspective.aspect = perspective.aspectRatio; iris_camera.intrinsic.perspective.znear = perspective.znear; iris_camera.intrinsic.perspective.zfar = perspective.zfar; } else { const auto &orthographic = camera.orthographic; iris_camera.intrinsic_tag = iris::Camera::Tag::Orthographic; iris_camera.intrinsic.orthographic.xmag = orthographic.xmag; iris_camera.intrinsic.orthographic.ymag = orthographic.ymag; iris_camera.intrinsic.orthographic.znear = orthographic.znear; iris_camera.intrinsic.orthographic.zfar = orthographic.zfar; } scene.camera = iris_camera; } return true; }