From b947cc68e0cd8a553649288195caf18fd6ed4188 Mon Sep 17 00:00:00 2001 From: bailwillharr Date: Mon, 5 Feb 2024 16:02:14 +0000 Subject: [PATCH] get gltf mesh loading working --- src/util/gltf_loader.cpp | 206 ++++++++++++++++++++++++++++++++------- test/src/game.cpp | 12 +-- 2 files changed, 176 insertions(+), 42 deletions(-) diff --git a/src/util/gltf_loader.cpp b/src/util/gltf_loader.cpp index 145e2d2..4a354b6 100644 --- a/src/util/gltf_loader.cpp +++ b/src/util/gltf_loader.cpp @@ -13,6 +13,14 @@ namespace tg = tinygltf; namespace engine::util { +template +struct Attribute { + const uint8_t* buffer; + size_t offset; + size_t stride; + const T& operator[](size_t i) { return *reinterpret_cast(&buffer[offset + stride * i]); } +}; + static void DecomposeTransform(glm::mat4 transform, glm::vec3& pos, glm::quat& rot, glm::vec3& scale) { // get position @@ -98,7 +106,7 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic) // find the image first // use missing texture image by default textures.emplace_back(scene.app()->GetResource("builtin.white")); - + if (texture.source == -1) continue; gfx::SamplerInfo samplerInfo{}; @@ -109,36 +117,36 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic) if (texture.sampler != -1) { const tg::Sampler& sampler = model.samplers.at(texture.sampler); switch (sampler.minFilter) { - case TINYGLTF_TEXTURE_FILTER_NEAREST: - case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR: - samplerInfo.minify = gfx::Filter::kNearest; - samplerInfo.mipmap = gfx::Filter::kLinear; - break; - case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST: - samplerInfo.minify = gfx::Filter::kNearest; - samplerInfo.mipmap = gfx::Filter::kNearest; - break; - case TINYGLTF_TEXTURE_FILTER_LINEAR: - case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR: - samplerInfo.minify = gfx::Filter::kLinear; - samplerInfo.mipmap = gfx::Filter::kLinear; - break; - case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST: - samplerInfo.minify = gfx::Filter::kLinear; - samplerInfo.mipmap = gfx::Filter::kNearest; - break; - default: - break; + case TINYGLTF_TEXTURE_FILTER_NEAREST: + case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR: + samplerInfo.minify = gfx::Filter::kNearest; + samplerInfo.mipmap = gfx::Filter::kLinear; + break; + case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST: + samplerInfo.minify = gfx::Filter::kNearest; + samplerInfo.mipmap = gfx::Filter::kNearest; + break; + case TINYGLTF_TEXTURE_FILTER_LINEAR: + case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR: + samplerInfo.minify = gfx::Filter::kLinear; + samplerInfo.mipmap = gfx::Filter::kLinear; + break; + case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST: + samplerInfo.minify = gfx::Filter::kLinear; + samplerInfo.mipmap = gfx::Filter::kNearest; + break; + default: + break; } switch (sampler.magFilter) { - case TINYGLTF_TEXTURE_FILTER_NEAREST: - samplerInfo.magnify = gfx::Filter::kNearest; - break; - case TINYGLTF_TEXTURE_FILTER_LINEAR: - samplerInfo.magnify = gfx::Filter::kLinear; - break; - default: - break; + case TINYGLTF_TEXTURE_FILTER_NEAREST: + samplerInfo.magnify = gfx::Filter::kNearest; + break; + case TINYGLTF_TEXTURE_FILTER_LINEAR: + samplerInfo.magnify = gfx::Filter::kLinear; + break; + default: + break; } } // use aniso if min filter is LINEAR_MIPMAP_LINEAR @@ -147,8 +155,7 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic) const tg::Image& image = model.images.at(texture.source); if (image.as_is == false && image.bits == 8 && image.component == 4 && image.pixel_type == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { // create texture on GPU - textures.back() = std::make_shared(scene.app()->renderer(), image.image.data(), image.width, - image.height, samplerInfo, true); + textures.back() = std::make_shared(scene.app()->renderer(), image.image.data(), image.width, image.height, samplerInfo, true); } } @@ -163,16 +170,143 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic) /* load all meshes found in model */ - std::vector> meshes{}; - meshes.reserve(model.meshes.size()); + struct EnginePrimitive { + std::shared_ptr mesh; + std::shared_ptr material; + }; + std::vector> primitive_arrays{}; // sub-array is all primitives for a given mesh + primitive_arrays.reserve(model.meshes.size()); for (const tg::Mesh& mesh : model.meshes) { - // placeholder mesh for now - + auto& primitive_array = primitive_arrays.emplace_back(); + for (const tg::Primitive& primitive : mesh.primitives) { + if (primitive.attributes.contains("POSITION")) { + const tg::Accessor& pos_accessor = model.accessors.at(primitive.attributes.at("POSITION")); + + const size_t num_vertices = pos_accessor.count; + + // these checks are probably unneccesary assuming a valid glTF file + // if (pos_accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) throw std::runtime_error("Position att. must be float!"); + // if (pos_accessor.type != 3) throw std::runtime_error("Position att. dim. must be 3!"); + + const tg::BufferView& pos_bufferview = model.bufferViews.at(pos_accessor.bufferView); + const tg::Buffer& pos_buffer = model.buffers.at(pos_bufferview.buffer); + + Attribute positions{.buffer = pos_buffer.data.data(), + .offset = pos_accessor.byteOffset + pos_bufferview.byteOffset, + .stride = static_cast(pos_accessor.ByteStride(pos_bufferview))}; + + Attribute normals{}; + if (primitive.attributes.contains("NORMAL")) { + const tg::Accessor& norm_accessor = model.accessors.at(primitive.attributes.at("NORMAL")); + const tg::BufferView& norm_bufferview = model.bufferViews.at(norm_accessor.bufferView); + const tg::Buffer& norm_buffer = model.buffers.at(norm_bufferview.buffer); + normals.buffer = norm_buffer.data.data(); + normals.offset = norm_accessor.byteOffset + norm_bufferview.byteOffset; + normals.stride = static_cast(norm_accessor.ByteStride(norm_bufferview)); + } + else { + // TODO: generate flat normals + throw std::runtime_error(std::string("No normals found in primitive from ") + mesh.name); + } + + Attribute tangents{}; + if (primitive.attributes.contains("TANGENT")) { + const tg::Accessor& tang_accessor = model.accessors.at(primitive.attributes.at("TANGENT")); + const tg::BufferView& tang_bufferview = model.bufferViews.at(tang_accessor.bufferView); + const tg::Buffer& tang_buffer = model.buffers.at(tang_bufferview.buffer); + tangents.buffer = tang_buffer.data.data(); + tangents.offset = tang_accessor.byteOffset + tang_bufferview.byteOffset; + tangents.stride = static_cast(tang_accessor.ByteStride(tang_bufferview)); + } + else { + // TODO: use MikkTSpace to generate tangents + throw std::runtime_error(std::string("No tangents found in primitive from ") + mesh.name); + } + + // UV0 + Attribute uv0s{}; + if (primitive.attributes.contains("TEXCOORD_0")) { + const tg::Accessor& uv0_accessor = model.accessors.at(primitive.attributes.at("TEXCOORD_0")); + const tg::BufferView& uv0_bufferview = model.bufferViews.at(uv0_accessor.bufferView); + const tg::Buffer& uv0_buffer = model.buffers.at(uv0_bufferview.buffer); + uv0s.buffer = uv0_buffer.data.data(); + uv0s.offset = uv0_accessor.byteOffset + uv0_bufferview.byteOffset; + uv0s.stride = static_cast(uv0_accessor.ByteStride(uv0_bufferview)); + } + else { + // TODO: Possibly create a shader variant that doesn't need UVs? + throw std::runtime_error(std::string("No TEXCOORD_0 found in primitive from ") + mesh.name); + } + + // Indices + const tg::Accessor& indices_accessor = model.accessors.at(primitive.indices); + const tg::BufferView& indices_bufferview = model.bufferViews.at(indices_accessor.bufferView); + const tg::Buffer& indices_buffer = model.buffers.at(indices_bufferview.buffer); + const uint8_t* const indices_data_start = indices_buffer.data.data() + indices_accessor.byteOffset + indices_bufferview.byteOffset; + + const size_t num_indices = indices_accessor.count; + std::vector indices; + indices.reserve(num_indices); + + if (indices_accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { + for (size_t i = 0; i < num_indices; ++i) { + indices.push_back(*reinterpret_cast(&indices_data_start[i * 1])); + } + } + else if (indices_accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { + for (size_t i = 0; i < num_indices; ++i) { + indices.push_back(*reinterpret_cast(&indices_data_start[i * 2])); + } + } + else if (indices_accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { + for (size_t i = 0; i < num_indices; ++i) { + indices.push_back(*reinterpret_cast(&indices_data_start[i * 4])); + } + } + else { + throw std::runtime_error(std::string("Invalid index buffer in primtive from: ") + mesh.name); + } + + // combine vertices into one array + std::vector vertices; + vertices.reserve(num_vertices); + for (size_t i = 0; i < num_vertices; ++i) { + Vertex v; + v.pos = positions[i]; + v.norm = normals[i]; + v.tangent = tangents[i]; + v.uv = uv0s[i]; + vertices.push_back(v); + } + + // generate mesh on GPU + std::shared_ptr engine_mesh = std::make_shared(scene.app()->renderer()->GetDevice(), vertices, indices); + + // get material + std::shared_ptr engine_material = nullptr; + if (primitive.material != -1) { + engine_material = materials.at(primitive.material); + } + else { + engine_material = scene.app()->GetResource("builtin.default"); + } + + primitive_array.emplace_back(engine_mesh, engine_material); + } + else { + // skip primitive's rendering + continue; + } + } } - + const Entity parent = scene.CreateEntity("test_node", 0, glm::vec3{}, glm::quat{glm::one_over_root_two(), glm::one_over_root_two(), 0.0f, 0.0f}); + auto ren = scene.AddComponent(parent); + ren->material = primitive_arrays.at(0).at(0).material; + ren->mesh = primitive_arrays.at(0).at(0).mesh; + return parent; } diff --git a/test/src/game.cpp b/test/src/game.cpp index f01d7f4..d9b63a5 100644 --- a/test/src/game.cpp +++ b/test/src/game.cpp @@ -196,12 +196,12 @@ void PlayGame(GameSettings settings) auto teapot = engine::util::LoadGLTF(*scene2, app.GetResourcePath("models/teapot_with_tangents.glb")); scene2->GetComponent(teapot)->scale *= 10.0f; - auto teapot2 = engine::util::LoadGLTF(*scene2, app.GetResourcePath("models/teapot.glb")); - scene2->GetComponent(teapot2)->scale *= 10.0f; - scene2->GetComponent(teapot2)->position.y += 5.0f; - scene2->GetComponent(teapot2)->rotation = glm::angleAxis(glm::pi(), glm::vec3{ 0.0f, 0.0f, 1.0f }); - scene2->GetComponent(teapot2)->rotation *= glm::angleAxis(glm::half_pi(), glm::vec3{1.0f, 0.0f, 0.0f}); - auto walls = engine::util::LoadGLTF(*scene2, app.GetResourcePath("models/walls.glb")); + //auto teapot2 = engine::util::LoadGLTF(*scene2, app.GetResourcePath("models/teapot.glb")); + //scene2->GetComponent(teapot2)->scale *= 10.0f; + //scene2->GetComponent(teapot2)->position.y += 5.0f; + //scene2->GetComponent(teapot2)->rotation = glm::angleAxis(glm::pi(), glm::vec3{ 0.0f, 0.0f, 1.0f }); + //scene2->GetComponent(teapot2)->rotation *= glm::angleAxis(glm::half_pi(), glm::vec3{1.0f, 0.0f, 0.0f}); + //auto walls = engine::util::LoadGLTF(*scene2, app.GetResourcePath("models/walls.glb")); } my_scene->GetSystem()->next_scene_ = scene2;