Fix model baking to work with new hfm::Shape material definition (1

material per mesh part)
This commit is contained in:
sabrina-shanman 2019-10-28 15:33:31 -07:00
parent eebb9ad51f
commit 9a65d78cdf
5 changed files with 70 additions and 41 deletions

View file

@ -37,10 +37,10 @@ const QByteArray MESH = "Mesh";
void OBJBaker::bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector<hifi::ByteArray>& dracoMeshes, const std::vector<std::vector<hifi::ByteArray>>& dracoMaterialLists) {
// Write OBJ Data as FBX tree nodes
createFBXNodeTree(_rootNode, hfmModel, dracoMeshes[0]);
createFBXNodeTree(_rootNode, hfmModel, dracoMeshes[0], dracoMaterialLists[0]);
}
void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& hfmModel, const hifi::ByteArray& dracoMesh) {
void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& hfmModel, const hifi::ByteArray& dracoMesh, const std::vector<hifi::ByteArray>& dracoMaterialList) {
// Make all generated nodes children of rootNode
rootNode.children = { FBXNode(), FBXNode(), FBXNode() };
FBXNode& globalSettingsNode = rootNode.children[0];
@ -100,24 +100,22 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& h
}
// Generating Objects node's child - Material node
auto& meshParts = hfmModel->meshes[0].parts;
for (auto& meshPart : meshParts) {
// Each material ID should only appear once thanks to deduplication in BuildDracoMeshTask, but we want to make sure they are created in the right order
std::unordered_map<QString, uint32_t> materialIDToIndex;
for (uint32_t materialIndex = 0; materialIndex < hfmModel->materials.size(); ++materialIndex) {
const auto& material = hfmModel->materials[materialIndex];
materialIDToIndex[material.materialID] = materialIndex;
}
// Create nodes for each material in the material list
for (const auto& dracoMaterial : dracoMaterialList) {
const QString materialID = QString(dracoMaterial);
const uint32_t materialIndex = materialIDToIndex[materialID];
const auto& material = hfmModel->materials[materialIndex];
FBXNode materialNode;
materialNode.name = MATERIAL_NODE_NAME;
if (hfmModel->materials.size() == 1) {
// case when no material information is provided, OBJSerializer considers it as a single default material
for (auto& material : hfmModel->materials) {
setMaterialNodeProperties(materialNode, material.name, material, hfmModel);
}
} else {
for (auto& material : hfmModel->materials) {
if (material.name == meshPart.materialID) {
setMaterialNodeProperties(materialNode, meshPart.materialID, material, hfmModel);
break;
}
}
}
setMaterialNodeProperties(materialNode, material.materialID, material, hfmModel);
objectNode.children.append(materialNode);
}

View file

@ -27,7 +27,7 @@ protected:
virtual void bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector<hifi::ByteArray>& dracoMeshes, const std::vector<std::vector<hifi::ByteArray>>& dracoMaterialLists) override;
private:
void createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& hfmModel, const hifi::ByteArray& dracoMesh);
void createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& hfmModel, const hifi::ByteArray& dracoMesh, const std::vector<hifi::ByteArray>& dracoMaterialList);
void setMaterialNodeProperties(FBXNode& materialNode, const QString& materialName, const hfm::Material& material, const hfm::Model::Pointer& hfmModel);
NodeID nextNodeID() { return _nodeID++; }

View file

@ -30,7 +30,7 @@ namespace baker {
class GetModelPartsTask {
public:
using Input = hfm::Model::Pointer;
using Output = VaryingSet8<std::vector<hfm::Mesh>, hifi::URL, baker::MeshIndicesToModelNames, baker::BlendshapesPerMesh, std::vector<hfm::Joint>, std::vector<hfm::Shape>, std::vector<hfm::SkinDeformer>, Extents>;
using Output = VaryingSet9<std::vector<hfm::Mesh>, hifi::URL, baker::MeshIndicesToModelNames, baker::BlendshapesPerMesh, std::vector<hfm::Joint>, std::vector<hfm::Shape>, std::vector<hfm::SkinDeformer>, Extents, std::vector<hfm::Material>>;
using JobModel = Job::ModelIO<GetModelPartsTask, Input, Output>;
void run(const BakeContextPointer& context, const Input& input, Output& output) {
@ -47,6 +47,7 @@ namespace baker {
output.edit5() = hfmModelIn->shapes;
output.edit6() = hfmModelIn->skinDeformers;
output.edit7() = hfmModelIn->meshExtents;
output.edit8() = hfmModelIn->materials;
}
};
@ -170,6 +171,7 @@ namespace baker {
const auto shapesIn = modelPartsIn.getN<GetModelPartsTask::Output>(5);
const auto skinDeformersIn = modelPartsIn.getN<GetModelPartsTask::Output>(6);
const auto modelExtentsIn = modelPartsIn.getN<GetModelPartsTask::Output>(7);
const auto materialsIn = modelPartsIn.getN<GetModelPartsTask::Output>(8);
// Calculate normals and tangents for meshes and blendshapes if they do not exist
// Note: Normals are never calculated here for OBJ models. OBJ files optionally define normals on a per-face basis, so for consistency normals are calculated beforehand in OBJSerializer.
@ -214,7 +216,7 @@ namespace baker {
// TODO: Tangent support (Needs changes to FBXSerializer_Mesh as well)
// NOTE: Due to an unresolved linker error, BuildDracoMeshTask is not functional on Android
// TODO: Figure out why BuildDracoMeshTask.cpp won't link with draco on Android
const auto buildDracoMeshInputs = BuildDracoMeshTask::Input(meshesIn, normalsPerMesh, tangentsPerMesh).asVarying();
const auto buildDracoMeshInputs = BuildDracoMeshTask::Input(shapesOut, meshesIn, materialsIn, normalsPerMesh, tangentsPerMesh).asVarying();
const auto buildDracoMeshOutputs = model.addJob<BuildDracoMeshTask>("BuildDracoMesh", buildDracoMeshInputs);
const auto dracoMeshes = buildDracoMeshOutputs.getN<BuildDracoMeshTask::Output>(0);
const auto dracoErrors = buildDracoMeshOutputs.getN<BuildDracoMeshTask::Output>(1);

View file

@ -39,19 +39,47 @@
#include "ModelMath.h"
#ifndef Q_OS_ANDROID
std::vector<hifi::ByteArray> createMaterialList(const hfm::Mesh& mesh) {
std::vector<hifi::ByteArray> materialList;
for (const auto& meshPart : mesh.parts) {
auto materialID = QVariant(meshPart.materialID).toByteArray();
const auto materialIt = std::find(materialList.cbegin(), materialList.cend(), materialID);
if (materialIt == materialList.cend()) {
materialList.push_back(materialID);
void reindexMaterials(const std::vector<uint32_t>& originalMaterialIndices, std::vector<uint32_t>& materials, std::vector<uint16_t>& materialIndices) {
materialIndices.resize(originalMaterialIndices.size());
for (size_t i = 0; i < originalMaterialIndices.size(); ++i) {
uint32_t material = originalMaterialIndices[i];
auto foundMaterial = std::find(materials.cbegin(), materials.cend(), material);
if (foundMaterial == materials.cend()) {
materials.push_back(material);
materialIndices[i] = (uint16_t)(materials.size() - 1);
} else {
materialIndices[i] = (uint16_t)(foundMaterial - materials.cbegin());
}
}
return materialList;
}
std::tuple<std::unique_ptr<draco::Mesh>, bool> createDracoMesh(const hfm::Mesh& mesh, const std::vector<glm::vec3>& normals, const std::vector<glm::vec3>& tangents, const std::vector<hifi::ByteArray>& materialList) {
void createMaterialLists(const std::vector<hfm::Shape>& shapes, const std::vector<hfm::Mesh>& meshes, const std::vector<hfm::Material>& hfmMaterials, std::vector<std::vector<hifi::ByteArray>>& materialIndexLists, std::vector<std::vector<uint16_t>>& partMaterialIndicesPerMesh) {
std::vector<std::vector<uint32_t>> materialsPerMesh;
for (const auto& mesh : meshes) {
materialsPerMesh.emplace_back(mesh.parts.size(), hfm::UNDEFINED_KEY);
}
for (const auto& shape : shapes) {
materialsPerMesh[shape.mesh][shape.meshPart] = shape.material;
}
materialIndexLists.resize(materialsPerMesh.size());
partMaterialIndicesPerMesh.resize(materialsPerMesh.size());
for (size_t i = 0; i < materialsPerMesh.size(); ++i) {
const std::vector<uint32_t>& materials = materialsPerMesh[i];
std::vector<uint32_t> uniqueMaterials;
reindexMaterials(materials, uniqueMaterials, partMaterialIndicesPerMesh[i]);
materialIndexLists[i].reserve(uniqueMaterials.size());
for (const uint32_t material : uniqueMaterials) {
const auto& hfmMaterial = hfmMaterials[material];
materialIndexLists[i].push_back(QVariant(hfmMaterial.materialID).toByteArray());
}
}
}
std::tuple<std::unique_ptr<draco::Mesh>, bool> createDracoMesh(const hfm::Mesh& mesh, const std::vector<glm::vec3>& normals, const std::vector<glm::vec3>& tangents, const std::vector<uint16_t>& partMaterialIndices) {
Q_ASSERT(normals.size() == 0 || (int)normals.size() == mesh.vertices.size());
Q_ASSERT(mesh.colors.size() == 0 || mesh.colors.size() == mesh.vertices.size());
Q_ASSERT(mesh.texCoords.size() == 0 || mesh.texCoords.size() == mesh.vertices.size());
@ -122,11 +150,9 @@ std::tuple<std::unique_ptr<draco::Mesh>, bool> createDracoMesh(const hfm::Mesh&
auto partIndex = 0;
draco::FaceIndex face;
uint16_t materialID;
for (auto& part : mesh.parts) {
auto materialIt = std::find(materialList.cbegin(), materialList.cend(), QVariant(part.materialID).toByteArray());
materialID = (uint16_t)(materialIt - materialList.cbegin());
uint16_t materialID = partMaterialIndices[partIndex];
auto addFace = [&](const QVector<int>& indices, int index, draco::FaceIndex face) {
int32_t idx0 = indices[index];
@ -214,30 +240,33 @@ void BuildDracoMeshTask::run(const baker::BakeContextPointer& context, const Inp
#ifdef Q_OS_ANDROID
qCWarning(model_baker) << "BuildDracoMesh is disabled on Android. Output meshes will be empty.";
#else
const auto& meshes = input.get0();
const auto& normalsPerMesh = input.get1();
const auto& tangentsPerMesh = input.get2();
const auto& shapes = input.get0();
const auto& meshes = input.get1();
const auto& materials = input.get2();
const auto& normalsPerMesh = input.get3();
const auto& tangentsPerMesh = input.get4();
auto& dracoBytesPerMesh = output.edit0();
auto& dracoErrorsPerMesh = output.edit1();
auto& materialLists = output.edit2();
std::vector<std::vector<uint16_t>> partMaterialIndicesPerMesh;
createMaterialLists(shapes, meshes, materials, materialLists, partMaterialIndicesPerMesh);
dracoBytesPerMesh.reserve(meshes.size());
// vector<bool> is an exception to the std::vector conventions as it is a bit field
// So a bool reference to an element doesn't work
dracoErrorsPerMesh.resize(meshes.size());
materialLists.reserve(meshes.size());
for (size_t i = 0; i < meshes.size(); i++) {
const auto& mesh = meshes[i];
const auto& normals = baker::safeGet(normalsPerMesh, i);
const auto& tangents = baker::safeGet(tangentsPerMesh, i);
dracoBytesPerMesh.emplace_back();
auto& dracoBytes = dracoBytesPerMesh.back();
materialLists.push_back(createMaterialList(mesh));
const auto& materialList = materialLists.back();
const auto& partMaterialIndices = partMaterialIndicesPerMesh[i];
bool dracoError;
std::unique_ptr<draco::Mesh> dracoMesh;
std::tie(dracoMesh, dracoError) = createDracoMesh(mesh, normals, tangents, materialList);
std::tie(dracoMesh, dracoError) = createDracoMesh(mesh, normals, tangents, partMaterialIndices);
dracoErrorsPerMesh[i] = dracoError;
if (dracoMesh) {

View file

@ -33,7 +33,7 @@ public:
class BuildDracoMeshTask {
public:
using Config = BuildDracoMeshConfig;
using Input = baker::VaryingSet3<std::vector<hfm::Mesh>, baker::NormalsPerMesh, baker::TangentsPerMesh>;
using Input = baker::VaryingSet5<std::vector<hfm::Shape>, std::vector<hfm::Mesh>, std::vector<hfm::Material>, baker::NormalsPerMesh, baker::TangentsPerMesh>;
using Output = baker::VaryingSet3<std::vector<hifi::ByteArray>, std::vector<bool>, std::vector<std::vector<hifi::ByteArray>>>;
using JobModel = baker::Job::ModelIO<BuildDracoMeshTask, Input, Output, Config>;