1
Fork 0
mirror of https://github.com/redstrate/Novus.git synced 2025-06-08 06:07:46 +00:00

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.
This commit is contained in:
Joshua Goins 2023-12-09 14:49:31 -05:00
parent be5625c1b8
commit c7b6dd076c
9 changed files with 217 additions and 33 deletions

View file

@ -45,6 +45,8 @@ private Q_SLOTS:
void reloadGear(); void reloadGear();
private: private:
void importModel(const QString &filename);
std::optional<GearInfo> currentGear; std::optional<GearInfo> currentGear;
Race currentRace = Race::Hyur; Race currentRace = Race::Hyur;

View file

@ -11,6 +11,7 @@
#include "filecache.h" #include "filecache.h"
#include "magic_enum.hpp" #include "magic_enum.hpp"
#include "tiny_gltf.h"
SingleGearView::SingleGearView(GameData *data, FileCache &cache, QWidget *parent) SingleGearView::SingleGearView(GameData *data, FileCache &cache, QWidget *parent)
: QWidget(parent) : QWidget(parent)
@ -88,6 +89,21 @@ SingleGearView::SingleGearView(GameData *data, FileCache &cache, QWidget *parent
importButton = new QPushButton(QStringLiteral("Import...")); importButton = new QPushButton(QStringLiteral("Import..."));
importButton->setIcon(QIcon::fromTheme(QStringLiteral("document-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); topControlLayout->addWidget(importButton);
exportButton = new QPushButton(QStringLiteral("Export...")); exportButton = new QPushButton(QStringLiteral("Export..."));
@ -297,4 +313,75 @@ QString SingleGearView::getLoadedGearPath() const
return gearView->getLoadedGearPath(); 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<Vertex> 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" #include "moc_singlegearview.cpp"

2
extern/libphysis vendored

@ -1 +1 @@
Subproject commit 14fa0367302aa2b3ef9a8adba5cbe61f81a1f672 Subproject commit 66f64ee7ee510c2e2b30800e1f94b77a21bc0cf0

View file

@ -199,6 +199,9 @@ void MDLPart::exportModel(const QString &fileName)
tinygltf::Model gltfModel; tinygltf::Model gltfModel;
gltfModel.asset.generator = "Novus"; 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(); auto &gltfSkeletonNode = gltfModel.nodes.emplace_back();
gltfSkeletonNode.name = skeleton->root_bone->name; gltfSkeletonNode.name = skeleton->root_bone->name;
@ -230,11 +233,19 @@ void MDLPart::exportModel(const QString &fileName)
auto &real_bone = skeleton->bones[real_bone_id]; auto &real_bone = skeleton->bones[real_bone_id];
if (real_bone.parent_bone != nullptr) { if (real_bone.parent_bone != nullptr) {
bool found = false;
for (int k = 0; k < model.num_affected_bones; k++) { for (int k = 0; k < model.num_affected_bones; k++) {
if (strcmp(model.affected_bone_names[k], real_bone.parent_bone->name) == 0) { 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 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 { } else {
gltfSkeletonNode.children.push_back(i + 1); 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++) { for (int i = 0; i < lod.num_parts; i++) {
gltfSkeletonNode.children.push_back(gltfModel.nodes.size());
auto &gltfNode = gltfModel.nodes.emplace_back(); auto &gltfNode = gltfModel.nodes.emplace_back();
;
gltfNode.name = models[0].name.toStdString() + " Part " + std::to_string(i) + ".0"; gltfNode.name = models[0].name.toStdString() + " Part " + std::to_string(i) + ".0";
gltfNode.skin = 0; gltfNode.skin = 0;
gltfSkeletonNode.children.push_back(gltfModel.nodes.size());
gltfNode.mesh = gltfModel.meshes.size(); gltfNode.mesh = gltfModel.meshes.size();
auto &gltfMesh = gltfModel.meshes.emplace_back(); auto &gltfMesh = gltfModel.meshes.emplace_back();
@ -295,9 +306,11 @@ void MDLPart::exportModel(const QString &fileName)
auto &gltfPrimitive = gltfMesh.primitives.emplace_back(); auto &gltfPrimitive = gltfMesh.primitives.emplace_back();
gltfPrimitive.attributes["POSITION"] = gltfModel.accessors.size(); gltfPrimitive.attributes["POSITION"] = gltfModel.accessors.size();
gltfPrimitive.attributes["TEXCOORD_0"] = gltfModel.accessors.size() + 1; gltfPrimitive.attributes["TEXCOORD_0"] = gltfModel.accessors.size() + 1;
gltfPrimitive.attributes["NORMAL"] = gltfModel.accessors.size() + 2; gltfPrimitive.attributes["TEXCOORD_1"] = gltfModel.accessors.size() + 2;
gltfPrimitive.attributes["WEIGHTS_0"] = gltfModel.accessors.size() + 3; gltfPrimitive.attributes["NORMAL"] = gltfModel.accessors.size() + 3;
gltfPrimitive.attributes["JOINTS_0"] = gltfModel.accessors.size() + 4; 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; gltfPrimitive.mode = TINYGLTF_MODE_TRIANGLES;
// Vertices // Vertices
@ -308,12 +321,19 @@ void MDLPart::exportModel(const QString &fileName)
positionAccessor.count = lod.parts[i].num_vertices; positionAccessor.count = lod.parts[i].num_vertices;
positionAccessor.type = TINYGLTF_TYPE_VEC3; positionAccessor.type = TINYGLTF_TYPE_VEC3;
auto &uvAccessor = gltfModel.accessors.emplace_back(); auto &uv0Accessor = gltfModel.accessors.emplace_back();
uvAccessor.bufferView = gltfModel.bufferViews.size(); uv0Accessor.bufferView = gltfModel.bufferViews.size();
uvAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; uv0Accessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
uvAccessor.count = lod.parts[i].num_vertices; uv0Accessor.count = lod.parts[i].num_vertices;
uvAccessor.type = TINYGLTF_TYPE_VEC2; uv0Accessor.type = TINYGLTF_TYPE_VEC2;
uvAccessor.byteOffset = offsetof(Vertex, uv); 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(); auto &normalAccessor = gltfModel.accessors.emplace_back();
normalAccessor.bufferView = gltfModel.bufferViews.size(); normalAccessor.bufferView = gltfModel.bufferViews.size();
@ -322,6 +342,27 @@ void MDLPart::exportModel(const QString &fileName)
normalAccessor.type = TINYGLTF_TYPE_VEC3; normalAccessor.type = TINYGLTF_TYPE_VEC3;
normalAccessor.byteOffset = offsetof(Vertex, normal); 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(); auto &boneWeightAccessor = gltfModel.accessors.emplace_back();
boneWeightAccessor.bufferView = gltfModel.bufferViews.size(); boneWeightAccessor.bufferView = gltfModel.bufferViews.size();
boneWeightAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; boneWeightAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
@ -331,7 +372,7 @@ void MDLPart::exportModel(const QString &fileName)
auto &boneIdAccessor = gltfModel.accessors.emplace_back(); auto &boneIdAccessor = gltfModel.accessors.emplace_back();
boneIdAccessor.bufferView = gltfModel.bufferViews.size(); 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.count = lod.parts[i].num_vertices;
boneIdAccessor.type = TINYGLTF_TYPE_VEC4; boneIdAccessor.type = TINYGLTF_TYPE_VEC4;
boneIdAccessor.byteOffset = offsetof(Vertex, bone_id); 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); 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() void MDLPart::clear()
{ {
models.clear(); models.clear();

View file

@ -23,6 +23,8 @@ public:
explicit MDLPart(GameData *data, FileCache &cache); explicit MDLPart(GameData *data, FileCache &cache);
void exportModel(const QString &fileName); void exportModel(const QString &fileName);
RenderModel &getModel(int index);
void reloadModel(int index);
int lastX = -1; int lastX = -1;
int lastY = -1; int lastY = -1;

View file

@ -69,6 +69,7 @@ public:
void resize(VkSurfaceKHR surface, int width, int height); void resize(VkSurfaceKHR surface, int width, int height);
RenderModel addModel(const physis_MDL &model, int lod); 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); RenderTexture addTexture(uint32_t width, uint32_t height, const uint8_t *data, uint32_t data_size);
void render(std::vector<RenderModel> models); void render(std::vector<RenderModel> models);

View file

@ -4,10 +4,14 @@
#version 450 #version 450
layout(location = 0) in vec3 inPosition; layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal; layout(location = 1) in vec2 inUV0;
layout(location = 2) in vec2 inUV; layout(location = 2) in vec2 inUV1;
layout(location = 3) in vec4 inBoneWeights; layout(location = 3) in vec3 inNormal;
layout(location = 4) in uvec4 inBoneIds; 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 = 0) out vec3 outNormal;
layout(location = 1) out vec3 outFragPos; layout(location = 1) out vec3 outFragPos;
@ -42,5 +46,5 @@ void main() {
gl_Position = vp * bPos; gl_Position = vp * bPos;
outNormal = bNor.xyz; outNormal = bNor.xyz;
outFragPos = vec3(model * vec4(inPosition, 1.0)); outFragPos = vec3(model * vec4(inPosition, 1.0));
outUV = inUV; outUV = inUV0;
} }

Binary file not shown.

View file

@ -638,13 +638,22 @@ RenderModel Renderer::addModel(const physis_MDL &model, int lod)
RenderModel renderModel; RenderModel renderModel;
renderModel.model = model; renderModel.model = model;
if (lod < 0 || lod > model.num_lod) reloadModel(renderModel, lod);
return {};
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; 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; renderPart.materialIndex = part.material_index;
@ -702,8 +711,6 @@ RenderModel Renderer::addModel(const physis_MDL &model, int lod)
renderModel.boneInfoBuffer = buffer; renderModel.boneInfoBuffer = buffer;
renderModel.boneInfoMemory = memory; renderModel.boneInfoMemory = memory;
return renderModel;
} }
void Renderer::initPipeline() void Renderer::initPipeline()
@ -729,27 +736,55 @@ void Renderer::initPipeline()
positionAttribute.format = VK_FORMAT_R32G32B32_SFLOAT; positionAttribute.format = VK_FORMAT_R32G32B32_SFLOAT;
positionAttribute.offset = offsetof(Vertex, position); positionAttribute.offset = offsetof(Vertex, position);
VkVertexInputAttributeDescription uvAttribute = {}; VkVertexInputAttributeDescription uv0Attribute = {};
uvAttribute.format = VK_FORMAT_R32G32_SFLOAT; uv0Attribute.format = VK_FORMAT_R32G32_SFLOAT;
uvAttribute.location = 2; uv0Attribute.location = 1;
uvAttribute.offset = offsetof(Vertex, uv); uv0Attribute.offset = offsetof(Vertex, uv0);
VkVertexInputAttributeDescription uv1Attribute = {};
uv1Attribute.format = VK_FORMAT_R32G32_SFLOAT;
uv1Attribute.location = 2;
uv1Attribute.offset = offsetof(Vertex, uv1);
VkVertexInputAttributeDescription normalAttribute = {}; VkVertexInputAttributeDescription normalAttribute = {};
normalAttribute.format = VK_FORMAT_R32G32B32_SFLOAT; normalAttribute.format = VK_FORMAT_R32G32B32_SFLOAT;
normalAttribute.location = 1; normalAttribute.location = 3;
normalAttribute.offset = offsetof(Vertex, normal); 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 = {}; VkVertexInputAttributeDescription boneWeightAttribute = {};
boneWeightAttribute.format = VK_FORMAT_R32G32B32A32_SFLOAT; boneWeightAttribute.format = VK_FORMAT_R32G32B32A32_SFLOAT;
boneWeightAttribute.location = 3; boneWeightAttribute.location = 7;
boneWeightAttribute.offset = offsetof(Vertex, bone_weight); boneWeightAttribute.offset = offsetof(Vertex, bone_weight);
VkVertexInputAttributeDescription boneIdAttribute = {}; VkVertexInputAttributeDescription boneIdAttribute = {};
boneIdAttribute.format = VK_FORMAT_R8G8B8A8_UINT; boneIdAttribute.format = VK_FORMAT_R8G8B8A8_UINT;
boneIdAttribute.location = 4; boneIdAttribute.location = 8;
boneIdAttribute.offset = offsetof(Vertex, bone_id); 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 = {}; VkPipelineVertexInputStateCreateInfo vertexInputState = {};
vertexInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;