From c7b6dd076c2ccec2f30b6d9aa01b78b2618785ae Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Sat, 9 Dec 2023 14:49:31 -0500 Subject: [PATCH] Add glTF import support, multiple fixes for upstream physis changes Adds basic glTF import (although right now it only imports back positions) and fixes support for more of the vertex data that's available to us. The MDL file isn't written back out yet either, it only displays in the viewport. --- armoury/include/singlegearview.h | 2 + armoury/src/singlegearview.cpp | 87 +++++++++++++++++++++++++++++++ extern/libphysis | 2 +- parts/mdl/mdlpart.cpp | 79 +++++++++++++++++++++++----- parts/mdl/mdlpart.h | 2 + renderer/include/renderer.hpp | 1 + renderer/shaders/mesh.vert | 14 +++-- renderer/shaders/mesh.vert.spv | Bin 5096 -> 5312 bytes renderer/src/renderer.cpp | 63 +++++++++++++++++----- 9 files changed, 217 insertions(+), 33 deletions(-) diff --git a/armoury/include/singlegearview.h b/armoury/include/singlegearview.h index bdcd5d4..dc3fec6 100644 --- a/armoury/include/singlegearview.h +++ b/armoury/include/singlegearview.h @@ -45,6 +45,8 @@ private Q_SLOTS: void reloadGear(); private: + void importModel(const QString &filename); + std::optional currentGear; Race currentRace = Race::Hyur; diff --git a/armoury/src/singlegearview.cpp b/armoury/src/singlegearview.cpp index 81a6d06..03a1c3e 100644 --- a/armoury/src/singlegearview.cpp +++ b/armoury/src/singlegearview.cpp @@ -11,6 +11,7 @@ #include "filecache.h" #include "magic_enum.hpp" +#include "tiny_gltf.h" SingleGearView::SingleGearView(GameData *data, FileCache &cache, QWidget *parent) : QWidget(parent) @@ -88,6 +89,21 @@ SingleGearView::SingleGearView(GameData *data, FileCache &cache, QWidget *parent importButton = new QPushButton(QStringLiteral("Import...")); importButton->setIcon(QIcon::fromTheme(QStringLiteral("document-import"))); + connect(importButton, &QPushButton::clicked, this, [this](bool) { + if (currentGear.has_value()) { + // TODO: deduplicate + const auto sanitizeMdlPath = [](const QString &mdlPath) -> QString { + return QString(mdlPath).section(QLatin1Char('/'), -1).remove(QStringLiteral(".mdl")); + }; + + const QString fileName = QFileDialog::getOpenFileName(this, + tr("Import Model"), + QStringLiteral("%1.glb").arg(sanitizeMdlPath(gearView->getLoadedGearPath())), + tr("glTF Binary File (*.glb)")); + + importModel(fileName); + } + }); topControlLayout->addWidget(importButton); exportButton = new QPushButton(QStringLiteral("Export...")); @@ -297,4 +313,75 @@ QString SingleGearView::getLoadedGearPath() const return gearView->getLoadedGearPath(); } +void SingleGearView::importModel(const QString &filename) +{ + tinygltf::Model model; + + std::string error, warning; + + tinygltf::TinyGLTF loader; + if (!loader.LoadBinaryFromFile(&model, &error, &warning, filename.toStdString())) { + qInfo() << "Error when loading glTF model:" << error; + return; + } + + if (!warning.empty()) { + qInfo() << "Warnings when loading glTF model:" << warning; + } + + auto &mdl = gearView->part().getModel(0); + + for (const auto &node : model.nodes) { + // Detect if it's a mesh node + if (node.mesh >= 0) { + qInfo() << "Importing" << node.name; + + const QStringList parts = QString::fromStdString(node.name).split(QLatin1Char(' ')); + const QString name = parts[0]; + const QStringList lodPartNumber = parts[2].split(QLatin1Char('.')); + + const int lodNumber = lodPartNumber[0].toInt(); + const int partNumber = lodPartNumber[1].toInt(); + + qInfo() << "- LOD:" << lodNumber; + qInfo() << "- Part:" << partNumber; + + auto &mesh = model.meshes[node.mesh]; + auto &primitive = mesh.primitives[0]; + + // All of the accessors are mapped to the same buffer vertex view + const auto &vertexAccessor = model.accessors[primitive.attributes["POSITION"]]; + const auto &vertexView = model.bufferViews[vertexAccessor.bufferView]; + const auto &vertexBuffer = model.buffers[vertexView.buffer]; + + const auto &indexAccessor = model.accessors[primitive.indices]; + const auto &indexView = model.bufferViews[indexAccessor.bufferView]; + const auto &indexBuffer = model.buffers[indexView.buffer]; + + qInfo() << "- Importing mesh of" << vertexAccessor.count << "vertices and" << indexAccessor.count << "indices."; + + auto vertexData = (glm::vec3 *)(&vertexBuffer.data.at(0) + vertexView.byteOffset); + + std::vector newVertices; + for (int i = 0; i < vertexAccessor.count; i++) { + // Replace position data + auto vertex = mdl.model.lods[lodNumber].parts[partNumber].vertices[i]; + vertex.position[0] = vertexData[i].x; + vertex.position[1] = vertexData[i].y; + vertex.position[2] = vertexData[i].z; + + newVertices.push_back(vertex); + } + + auto indexData = (const uint16_t *)(&indexBuffer.data.at(0) + indexView.byteOffset); + + physis_mdl_replace_vertices(&mdl.model, lodNumber, partNumber, vertexAccessor.count, newVertices.data(), indexAccessor.count, indexData); + } + } + + gearView->part().reloadModel(0); + + qInfo() << "Successfully imported model!"; +} + #include "moc_singlegearview.cpp" \ No newline at end of file diff --git a/extern/libphysis b/extern/libphysis index 14fa036..66f64ee 160000 --- a/extern/libphysis +++ b/extern/libphysis @@ -1 +1 @@ -Subproject commit 14fa0367302aa2b3ef9a8adba5cbe61f81a1f672 +Subproject commit 66f64ee7ee510c2e2b30800e1f94b77a21bc0cf0 diff --git a/parts/mdl/mdlpart.cpp b/parts/mdl/mdlpart.cpp index 63f9388..3038f43 100644 --- a/parts/mdl/mdlpart.cpp +++ b/parts/mdl/mdlpart.cpp @@ -199,6 +199,9 @@ void MDLPart::exportModel(const QString &fileName) tinygltf::Model gltfModel; gltfModel.asset.generator = "Novus"; + // TODO: just write the code better! dummy!! + gltfModel.nodes.reserve(1 + model.num_affected_bones + lod.num_parts); + auto &gltfSkeletonNode = gltfModel.nodes.emplace_back(); gltfSkeletonNode.name = skeleton->root_bone->name; @@ -230,11 +233,19 @@ void MDLPart::exportModel(const QString &fileName) auto &real_bone = skeleton->bones[real_bone_id]; if (real_bone.parent_bone != nullptr) { + bool found = false; for (int k = 0; k < model.num_affected_bones; k++) { if (strcmp(model.affected_bone_names[k], real_bone.parent_bone->name) == 0) { gltfModel.nodes[k + 1].children.push_back(i + 1); // +1 for the skeleton node taking up the first index + found = true; } } + + // Find the next closest bone that isn't a direct descendant + // of n_root, but won't have a parent anyway + if (!found) { + gltfSkeletonNode.children.push_back(i + 1); + } } else { gltfSkeletonNode.children.push_back(i + 1); } @@ -280,13 +291,13 @@ void MDLPart::exportModel(const QString &fileName) } for (int i = 0; i < lod.num_parts; i++) { + gltfSkeletonNode.children.push_back(gltfModel.nodes.size()); + auto &gltfNode = gltfModel.nodes.emplace_back(); - ; + gltfNode.name = models[0].name.toStdString() + " Part " + std::to_string(i) + ".0"; gltfNode.skin = 0; - gltfSkeletonNode.children.push_back(gltfModel.nodes.size()); - gltfNode.mesh = gltfModel.meshes.size(); auto &gltfMesh = gltfModel.meshes.emplace_back(); @@ -295,9 +306,11 @@ void MDLPart::exportModel(const QString &fileName) auto &gltfPrimitive = gltfMesh.primitives.emplace_back(); gltfPrimitive.attributes["POSITION"] = gltfModel.accessors.size(); gltfPrimitive.attributes["TEXCOORD_0"] = gltfModel.accessors.size() + 1; - gltfPrimitive.attributes["NORMAL"] = gltfModel.accessors.size() + 2; - gltfPrimitive.attributes["WEIGHTS_0"] = gltfModel.accessors.size() + 3; - gltfPrimitive.attributes["JOINTS_0"] = gltfModel.accessors.size() + 4; + gltfPrimitive.attributes["TEXCOORD_1"] = gltfModel.accessors.size() + 2; + gltfPrimitive.attributes["NORMAL"] = gltfModel.accessors.size() + 3; + gltfPrimitive.attributes["COLOR_0"] = gltfModel.accessors.size() + 6; + gltfPrimitive.attributes["WEIGHTS_0"] = gltfModel.accessors.size() + 7; + gltfPrimitive.attributes["JOINTS_0"] = gltfModel.accessors.size() + 8; gltfPrimitive.mode = TINYGLTF_MODE_TRIANGLES; // Vertices @@ -308,12 +321,19 @@ void MDLPart::exportModel(const QString &fileName) positionAccessor.count = lod.parts[i].num_vertices; positionAccessor.type = TINYGLTF_TYPE_VEC3; - auto &uvAccessor = gltfModel.accessors.emplace_back(); - uvAccessor.bufferView = gltfModel.bufferViews.size(); - uvAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; - uvAccessor.count = lod.parts[i].num_vertices; - uvAccessor.type = TINYGLTF_TYPE_VEC2; - uvAccessor.byteOffset = offsetof(Vertex, uv); + auto &uv0Accessor = gltfModel.accessors.emplace_back(); + uv0Accessor.bufferView = gltfModel.bufferViews.size(); + uv0Accessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; + uv0Accessor.count = lod.parts[i].num_vertices; + uv0Accessor.type = TINYGLTF_TYPE_VEC2; + uv0Accessor.byteOffset = offsetof(Vertex, uv0); + + auto &uv1Accessor = gltfModel.accessors.emplace_back(); + uv1Accessor.bufferView = gltfModel.bufferViews.size(); + uv1Accessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; + uv1Accessor.count = lod.parts[i].num_vertices; + uv1Accessor.type = TINYGLTF_TYPE_VEC2; + uv1Accessor.byteOffset = offsetof(Vertex, uv1); auto &normalAccessor = gltfModel.accessors.emplace_back(); normalAccessor.bufferView = gltfModel.bufferViews.size(); @@ -322,6 +342,27 @@ void MDLPart::exportModel(const QString &fileName) normalAccessor.type = TINYGLTF_TYPE_VEC3; normalAccessor.byteOffset = offsetof(Vertex, normal); + auto &tangent1Accessor = gltfModel.accessors.emplace_back(); + tangent1Accessor.bufferView = gltfModel.bufferViews.size(); + tangent1Accessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; + tangent1Accessor.count = lod.parts[i].num_vertices; + tangent1Accessor.type = TINYGLTF_TYPE_VEC4; + tangent1Accessor.byteOffset = offsetof(Vertex, tangent1); + + auto &tangent2Accessor = gltfModel.accessors.emplace_back(); + tangent2Accessor.bufferView = gltfModel.bufferViews.size(); + tangent2Accessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; + tangent2Accessor.count = lod.parts[i].num_vertices; + tangent2Accessor.type = TINYGLTF_TYPE_VEC4; + tangent2Accessor.byteOffset = offsetof(Vertex, tangent2); + + auto &colorAccessor = gltfModel.accessors.emplace_back(); + colorAccessor.bufferView = gltfModel.bufferViews.size(); + colorAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; + colorAccessor.count = lod.parts[i].num_vertices; + colorAccessor.type = TINYGLTF_TYPE_VEC4; + colorAccessor.byteOffset = offsetof(Vertex, color); + auto &boneWeightAccessor = gltfModel.accessors.emplace_back(); boneWeightAccessor.bufferView = gltfModel.bufferViews.size(); boneWeightAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; @@ -331,7 +372,7 @@ void MDLPart::exportModel(const QString &fileName) auto &boneIdAccessor = gltfModel.accessors.emplace_back(); boneIdAccessor.bufferView = gltfModel.bufferViews.size(); - boneIdAccessor.componentType = TINYGLTF_COMPONENT_TYPE_BYTE; + boneIdAccessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; boneIdAccessor.count = lod.parts[i].num_vertices; boneIdAccessor.type = TINYGLTF_TYPE_VEC4; boneIdAccessor.byteOffset = offsetof(Vertex, bone_id); @@ -378,6 +419,18 @@ void MDLPart::exportModel(const QString &fileName) loader.WriteGltfSceneToFile(&gltfModel, fileName.toStdString(), true, true, false, true); } +RenderModel &MDLPart::getModel(const int index) +{ + return models[index]; +} + +void MDLPart::reloadModel(const int index) +{ + renderer->reloadModel(models[index], 0); + + Q_EMIT modelChanged(); +} + void MDLPart::clear() { models.clear(); diff --git a/parts/mdl/mdlpart.h b/parts/mdl/mdlpart.h index 05308f1..15fb410 100644 --- a/parts/mdl/mdlpart.h +++ b/parts/mdl/mdlpart.h @@ -23,6 +23,8 @@ public: explicit MDLPart(GameData *data, FileCache &cache); void exportModel(const QString &fileName); + RenderModel &getModel(int index); + void reloadModel(int index); int lastX = -1; int lastY = -1; diff --git a/renderer/include/renderer.hpp b/renderer/include/renderer.hpp index e9a4499..ee5dc17 100644 --- a/renderer/include/renderer.hpp +++ b/renderer/include/renderer.hpp @@ -69,6 +69,7 @@ public: void resize(VkSurfaceKHR surface, int width, int height); RenderModel addModel(const physis_MDL &model, int lod); + void reloadModel(RenderModel &model, int lod); RenderTexture addTexture(uint32_t width, uint32_t height, const uint8_t *data, uint32_t data_size); void render(std::vector models); diff --git a/renderer/shaders/mesh.vert b/renderer/shaders/mesh.vert index f132f4e..391db9e 100644 --- a/renderer/shaders/mesh.vert +++ b/renderer/shaders/mesh.vert @@ -4,10 +4,14 @@ #version 450 layout(location = 0) in vec3 inPosition; -layout(location = 1) in vec3 inNormal; -layout(location = 2) in vec2 inUV; -layout(location = 3) in vec4 inBoneWeights; -layout(location = 4) in uvec4 inBoneIds; +layout(location = 1) in vec2 inUV0; +layout(location = 2) in vec2 inUV1; +layout(location = 3) in vec3 inNormal; +layout(location = 4) in uvec4 inTangent1; +layout(location = 5) in uvec4 inTangent2; +layout(location = 6) in vec4 inColor; +layout(location = 7) in vec4 inBoneWeights; +layout(location = 8) in uvec4 inBoneIds; layout(location = 0) out vec3 outNormal; layout(location = 1) out vec3 outFragPos; @@ -42,5 +46,5 @@ void main() { gl_Position = vp * bPos; outNormal = bNor.xyz; outFragPos = vec3(model * vec4(inPosition, 1.0)); - outUV = inUV; + outUV = inUV0; } diff --git a/renderer/shaders/mesh.vert.spv b/renderer/shaders/mesh.vert.spv index 4904d143fe2a58ba19aa17c0d9cf0ea1a385c98e..d1f17c52abba21fc34e770c9e21cae9f4e07afc3 100644 GIT binary patch delta 548 zcmZ9H!AiqW5Jl(ZC9P4i&`l|b8VeS8Ds-VPi*#MAy6&P#p(apUaOXFK;L5Kkp`f3_}#xYIwmEVfKT*RC|*K~uyYCVU|Cu*QjG{t?fdZH#vM<+v0awyZ3jhEB delta 357 zcmZ9GJqp4=5QS&6StUvYE3xsn5D#EwX^~p=0umz#3X&MGv$YK=J%yz4H+mdBf}Qg) z3*v*v@aEgunMe28v8B<@6A3DkP4~#|iBdG^9d;|DNi0PePlOy model.num_lod) - return {}; + reloadModel(renderModel, lod); - for (int i = 0; i < model.lods[lod].num_parts; i++) { + return renderModel; +} + +void Renderer::reloadModel(RenderModel &renderModel, int lod) +{ + if (lod < 0 || lod > renderModel.model.num_lod) + return; + + renderModel.parts.clear(); + + for (int i = 0; i < renderModel.model.lods[lod].num_parts; i++) { RenderPart renderPart; - const physis_Part part = model.lods[lod].parts[i]; + const physis_Part part = renderModel.model.lods[lod].parts[i]; renderPart.materialIndex = part.material_index; @@ -702,8 +711,6 @@ RenderModel Renderer::addModel(const physis_MDL &model, int lod) renderModel.boneInfoBuffer = buffer; renderModel.boneInfoMemory = memory; - - return renderModel; } void Renderer::initPipeline() @@ -729,27 +736,55 @@ void Renderer::initPipeline() positionAttribute.format = VK_FORMAT_R32G32B32_SFLOAT; positionAttribute.offset = offsetof(Vertex, position); - VkVertexInputAttributeDescription uvAttribute = {}; - uvAttribute.format = VK_FORMAT_R32G32_SFLOAT; - uvAttribute.location = 2; - uvAttribute.offset = offsetof(Vertex, uv); + VkVertexInputAttributeDescription uv0Attribute = {}; + uv0Attribute.format = VK_FORMAT_R32G32_SFLOAT; + uv0Attribute.location = 1; + uv0Attribute.offset = offsetof(Vertex, uv0); + + VkVertexInputAttributeDescription uv1Attribute = {}; + uv1Attribute.format = VK_FORMAT_R32G32_SFLOAT; + uv1Attribute.location = 2; + uv1Attribute.offset = offsetof(Vertex, uv1); VkVertexInputAttributeDescription normalAttribute = {}; normalAttribute.format = VK_FORMAT_R32G32B32_SFLOAT; - normalAttribute.location = 1; + normalAttribute.location = 3; normalAttribute.offset = offsetof(Vertex, normal); + VkVertexInputAttributeDescription tangent1Attribute = {}; + tangent1Attribute.format = VK_FORMAT_R8G8B8A8_UINT; + tangent1Attribute.location = 4; + tangent1Attribute.offset = offsetof(Vertex, tangent1); + + VkVertexInputAttributeDescription tangent2Attribute = {}; + tangent2Attribute.format = VK_FORMAT_R8G8B8A8_UINT; + tangent2Attribute.location = 5; + tangent2Attribute.offset = offsetof(Vertex, tangent2); + + VkVertexInputAttributeDescription colorAttribute = {}; + colorAttribute.format = VK_FORMAT_R32G32B32A32_SFLOAT; + colorAttribute.location = 6; + colorAttribute.offset = offsetof(Vertex, color); + VkVertexInputAttributeDescription boneWeightAttribute = {}; boneWeightAttribute.format = VK_FORMAT_R32G32B32A32_SFLOAT; - boneWeightAttribute.location = 3; + boneWeightAttribute.location = 7; boneWeightAttribute.offset = offsetof(Vertex, bone_weight); VkVertexInputAttributeDescription boneIdAttribute = {}; boneIdAttribute.format = VK_FORMAT_R8G8B8A8_UINT; - boneIdAttribute.location = 4; + boneIdAttribute.location = 8; boneIdAttribute.offset = offsetof(Vertex, bone_id); - const std::array attributes = {positionAttribute, normalAttribute, uvAttribute, boneWeightAttribute, boneIdAttribute}; + const std::array attributes = {positionAttribute, + uv0Attribute, + uv1Attribute, + normalAttribute, + tangent1Attribute, + tangent2Attribute, + colorAttribute, + boneWeightAttribute, + boneIdAttribute}; VkPipelineVertexInputStateCreateInfo vertexInputState = {}; vertexInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;