diff --git a/assets/basicmesh.glb b/assets/basicmesh.glb index ab41bcb7..51d8890b 100644 Binary files a/assets/basicmesh.glb and b/assets/basicmesh.glb differ diff --git a/assets/cube.glb b/assets/cube.glb new file mode 100644 index 00000000..9e5f005c Binary files /dev/null and b/assets/cube.glb differ diff --git a/assets/sphere.glb b/assets/sphere.glb new file mode 100644 index 00000000..80f7139f Binary files /dev/null and b/assets/sphere.glb differ diff --git a/src/core/Mesh.cpp b/src/core/Mesh.cpp index 5f94c47d..f496c69d 100644 --- a/src/core/Mesh.cpp +++ b/src/core/Mesh.cpp @@ -1,17 +1,40 @@ +// Mesh.cpp #include "core/Mesh.h" -Mesh::Mesh(const std::string &filePath) { +Mesh::Mesh(const std::string &filePath) : _currentModelPath(filePath) { VulkanEngine &engine = VulkanEngine::Get(); - _rid = engine.registerMesh(filePath); } Mesh::~Mesh() { + remove_model(); +} + +void Mesh::set_model(const std::string &filePath) { VulkanEngine &engine = VulkanEngine::Get(); - engine.unregisterMesh(_rid); + // Unregister current model if exists + if (!_currentModelPath.empty()) { + engine.unregisterMesh(_rid); + } + + // Register new model + _currentModelPath = filePath; + _rid = engine.registerMesh(filePath); + + // Reapply transform to new model + engine.setMeshTransform(_rid, _transform); +} + +void Mesh::remove_model() { + if (!_currentModelPath.empty()) { + VulkanEngine &engine = VulkanEngine::Get(); + engine.unregisterMesh(_rid); + _currentModelPath.clear(); + } } + void Mesh::set_transform(glm::mat4 t) { VulkanEngine &engine = VulkanEngine::Get(); diff --git a/src/graphics/vulkan/vk_engine.cpp b/src/graphics/vulkan/vk_engine.cpp index 51d230b7..fc360907 100644 --- a/src/graphics/vulkan/vk_engine.cpp +++ b/src/graphics/vulkan/vk_engine.cpp @@ -1195,8 +1195,10 @@ int64_t VulkanEngine::registerMesh(const std::string& filePath) { } void VulkanEngine::unregisterMesh(int64_t id) { - meshes.erase(id); - transforms.erase(id); + if (meshes.find(id) != meshes.end()) { + meshes.erase(id); + transforms.erase(id); + } } void VulkanEngine::setMeshTransform(int64_t id, glm::mat4 mat) { diff --git a/src/graphics/vulkan/vk_loader.cpp b/src/graphics/vulkan/vk_loader.cpp index c05311d5..d33c8ffc 100644 --- a/src/graphics/vulkan/vk_loader.cpp +++ b/src/graphics/vulkan/vk_loader.cpp @@ -203,26 +203,18 @@ VkSamplerMipmapMode extract_mipmap_mode(fastgltf::Filter filter) { std::optional> loadGltf(VulkanEngine* engine, std::string_view filePath) { fmt::print("Loading GLTF: {}", filePath); - auto scene = std::make_shared(); scene->creator = engine; LoadedGLTF& file = *scene; - fastgltf::Parser parser{}; - constexpr auto gltfOptions = fastgltf::Options::DontRequireValidAssetMember | fastgltf::Options::AllowDouble | fastgltf::Options::LoadGLBBuffers | fastgltf::Options::LoadExternalBuffers; - // fastgltf::Options::LoadExternalImages; - fastgltf::GltfDataBuffer data; data.loadFromFile(filePath); - fastgltf::Asset gltf; - std::filesystem::path path = filePath; - auto type = fastgltf::determineGltfFileType(&data); if (type == fastgltf::GltfType::glTF) { auto load = parser.loadGltf(&data, path.parent_path(), gltfOptions); @@ -248,35 +240,31 @@ std::optional> loadGltf(VulkanEngine* engine, return {}; } - // we can stimate the descriptors we will need accurately + // Estimate descriptor pool size based on materials std::vector sizes = { {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3}, {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3}, {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1}}; + file.descriptorPool.init( + engine->_device, + static_cast(std::max(gltf.materials.size(), size_t(1))), + sizes); - file.descriptorPool.init(engine->_device, - static_cast(gltf.materials.size()), - sizes); - - // load samplers + // Load samplers for (fastgltf::Sampler& sampler : gltf.samplers) { VkSamplerCreateInfo sampl = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .pNext = nullptr}; sampl.maxLod = VK_LOD_CLAMP_NONE; sampl.minLod = 0; - sampl.magFilter = extract_filter( sampler.magFilter.value_or(fastgltf::Filter::Nearest)); sampl.minFilter = extract_filter( sampler.minFilter.value_or(fastgltf::Filter::Nearest)); - sampl.mipmapMode = extract_mipmap_mode( sampler.minFilter.value_or(fastgltf::Filter::Nearest)); - VkSampler newSampler; vkCreateSampler(engine->_device, &sampl, nullptr, &newSampler); - file.samplers.push_back(newSampler); } @@ -285,22 +273,24 @@ std::optional> loadGltf(VulkanEngine* engine, std::vector images; std::vector> materials; - // load all textures + // Load all textures images.reserve(gltf.images.size()); for (size_t i = 0; i < gltf.images.size(); i++) { images.push_back(engine->_errorCheckerboardImage->get()); } - // create buffer to hold the material data + // Create buffer to hold the material data + size_t materialCount = gltf.materials.size() ? gltf.materials.size() : 1; file.materialDataBuffer = engine->create_buffer( - sizeof(GLTFMetallic_Roughness::MaterialConstants) * - gltf.materials.size(), + sizeof(GLTFMetallic_Roughness::MaterialConstants) * materialCount, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU); + uint32_t data_index = 0; auto* sceneMaterialConstants = (GLTFMetallic_Roughness::MaterialConstants*) file.materialDataBuffer.info.pMappedData; + // Process all materials from the GLTF for (fastgltf::Material& mat : gltf.materials) { auto newMat = std::make_shared(); materials.push_back(newMat); @@ -311,10 +301,9 @@ std::optional> loadGltf(VulkanEngine* engine, constants.colorFactors.y = mat.pbrData.baseColorFactor[1]; constants.colorFactors.z = mat.pbrData.baseColorFactor[2]; constants.colorFactors.w = mat.pbrData.baseColorFactor[3]; - constants.metal_rough_factors.x = mat.pbrData.metallicFactor; constants.metal_rough_factors.y = mat.pbrData.roughnessFactor; - // write material parameters to buffer + sceneMaterialConstants[data_index] = constants; auto passType = MaterialPass::MainColor; @@ -323,17 +312,16 @@ std::optional> loadGltf(VulkanEngine* engine, } GLTFMetallic_Roughness::MaterialResources materialResources; - // default the material textures + materialResources.colorImage = engine->_whiteImage->get(); + materialResources.colorSampler = engine->_defaultSamplerLinear; materialResources.metalRoughImage = engine->_whiteImage->get(); materialResources.metalRoughSampler = engine->_defaultSamplerLinear; - - // set the uniform buffer for the material data materialResources.dataBuffer = file.materialDataBuffer.buffer; materialResources.dataBufferOffset = data_index * sizeof(GLTFMetallic_Roughness::MaterialConstants); - // grab textures from gltf file + if (mat.pbrData.baseColorTexture.has_value()) { size_t img = gltf.textures[mat.pbrData.baseColorTexture.value() .textureIndex] @@ -341,21 +329,40 @@ std::optional> loadGltf(VulkanEngine* engine, size_t sampler = gltf.textures[mat.pbrData.baseColorTexture.value() .textureIndex] .samplerIndex.value(); - materialResources.colorImage = images[img]; materialResources.colorSampler = file.samplers[sampler]; } - // build material + newMat->data = engine->metalRoughMaterial.write_material( engine->_device, passType, materialResources, file.descriptorPool); - data_index++; } - // use the same vectors for all meshes so that the memory doesnt reallocate - // as - // often + // Add a fallback material if no materials were defined in the GLTF + if (materials.empty()) { + auto defaultMat = std::make_shared(); + materials.push_back(defaultMat); + + GLTFMetallic_Roughness::MaterialConstants constants = {}; + constants.colorFactors = glm::vec4(1.0f); // White base color + constants.metal_rough_factors = glm::vec4(0.0f); // Non-metallic, smooth + + sceneMaterialConstants[0] = constants; + + GLTFMetallic_Roughness::MaterialResources resources; + resources.colorImage = engine->_whiteImage->get(); + resources.colorSampler = engine->_defaultSamplerLinear; + resources.metalRoughImage = engine->_whiteImage->get(); + resources.metalRoughSampler = engine->_defaultSamplerLinear; + resources.dataBuffer = file.materialDataBuffer.buffer; + resources.dataBufferOffset = 0; + + defaultMat->data = engine->metalRoughMaterial.write_material( + engine->_device, MaterialPass::MainColor, resources, + file.descriptorPool); + } + std::vector indices; std::vector vertices; @@ -364,8 +371,6 @@ std::optional> loadGltf(VulkanEngine* engine, meshes.push_back(newmesh); file.meshes[name.c_str()] = newmesh; newmesh->name = name; - - // clear the mesh arrays each mesh, we dont want to merge them by error indices.clear(); vertices.clear(); @@ -374,27 +379,24 @@ std::optional> loadGltf(VulkanEngine* engine, newSurface.startIndex = (uint32_t)indices.size(); newSurface.count = (uint32_t)gltf.accessors[p.indicesAccessor.value()].count; - auto initial_vtx = static_cast(vertices.size()); - // load indexes + // Load indices { fastgltf::Accessor& indexaccessor = gltf.accessors[p.indicesAccessor.value()]; indices.reserve(indices.size() + indexaccessor.count); - fastgltf::iterateAccessor( gltf, indexaccessor, [&](std::uint32_t idx) { indices.push_back(idx + initial_vtx); }); } - // load vertex positions + // Load vertex positions { fastgltf::Accessor& posAccessor = gltf.accessors[p.findAttribute("POSITION")->second]; vertices.resize(vertices.size() + posAccessor.count); - fastgltf::iterateAccessorWithIndex( gltf, posAccessor, [&](glm::vec3 v, size_t index) { Vertex newvtx; @@ -407,7 +409,7 @@ std::optional> loadGltf(VulkanEngine* engine, }); } - // load vertex normals + // Load vertex normals auto normals = p.findAttribute("NORMAL"); if (normals != p.attributes.end()) { fastgltf::iterateAccessorWithIndex( @@ -417,7 +419,7 @@ std::optional> loadGltf(VulkanEngine* engine, }); } - // load UVs + // Load UVs auto uv = p.findAttribute("TEXCOORD_0"); if (uv != p.attributes.end()) { fastgltf::iterateAccessorWithIndex( @@ -428,7 +430,7 @@ std::optional> loadGltf(VulkanEngine* engine, }); } - // load vertex colors + // Load vertex colors auto colors = p.findAttribute("COLOR_0"); if (colors != p.attributes.end()) { fastgltf::iterateAccessorWithIndex( @@ -438,24 +440,21 @@ std::optional> loadGltf(VulkanEngine* engine, }); } + // Assign material safely if (p.materialIndex.has_value()) { newSurface.material = materials[p.materialIndex.value()]; } else { - newSurface.material = materials[0]; + newSurface.material = materials[0]; // Always valid now } newmesh->surfaces.push_back(newSurface); } - newmesh->meshBuffers = engine->uploadMesh(indices, vertices); } - // load all nodes and their meshes + // Load all nodes and their meshes for (fastgltf::Node& node : gltf.nodes) { std::shared_ptr newNode; - - // find if the node has a mesh, and if it does hook it to the mesh - // pointer and allocate it with the meshnode class if (node.meshIndex.has_value()) { newNode = std::make_shared(); dynamic_cast(newNode.get())->mesh = @@ -463,7 +462,6 @@ std::optional> loadGltf(VulkanEngine* engine, } else { newNode = std::make_shared(); } - nodes.push_back(newNode); file.nodes[node.name.c_str()]; @@ -483,36 +481,34 @@ std::optional> loadGltf(VulkanEngine* engine, const glm::vec3 sc(transform.scale[0], transform.scale[1], transform.scale[2]); - const glm::mat4 tm = glm::translate(glm::mat4(1.f), tl); const glm::mat4 rm = glm::toMat4(rot); const glm::mat4 sm = glm::scale(glm::mat4(1.f), sc); - newNode->localTransform = tm * rm * sm; }}, node.transform); } - // run loop again to setup transform hierarchy + // Setup transform hierarchy for (size_t i = 0; i < gltf.nodes.size(); i++) { fastgltf::Node& node = gltf.nodes[i]; std::shared_ptr& sceneNode = nodes[i]; - for (auto& c : node.children) { sceneNode->children.push_back(nodes[c]); nodes[c]->parent = sceneNode; } } - // find the top nodes, with no parents - for (auto& node : nodes) { - if (node->parent.lock() == nullptr) { - file.topNodes.push_back(node); - node->refreshTransform(glm::mat4{1.f}); + // Find top-level nodes + for (auto& n : nodes) { + if (n->parent.lock() == nullptr) { + file.topNodes.push_back(n); + n->refreshTransform(glm::mat4{1.f}); } } + return scene; } diff --git a/src/include/core/Mesh.h b/src/include/core/Mesh.h index 637a0481..a8e22857 100644 --- a/src/include/core/Mesh.h +++ b/src/include/core/Mesh.h @@ -1,3 +1,4 @@ +// Mesh.h #pragma once #include @@ -10,11 +11,15 @@ class Mesh { Mesh(const std::string& filePath); ~Mesh(); + // Add model change functionality + void set_model(const std::string& filePath); + void remove_model(); + void set_transform(glm::mat4 t); glm::mat4 get_transform(); private: glm::mat4 _transform; - + std::string _currentModelPath; // Track current model path int64_t _rid; }; \ No newline at end of file