Merge pull request #16324 from sabrina-shanman/instancing_gltf

(DEV-561) Enable instancing for GLTF static meshes (WIP)
This commit is contained in:
Sabrina Shanman 2019-10-16 17:54:19 -07:00 committed by GitHub
commit f402dbbf2b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 1289 additions and 955 deletions

View file

@ -1485,7 +1485,11 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const
}
for (const QString& modelID : instanceModelIDs) {
// The transform node has the same indexing order as the joints
const uint32_t transformIndex = (uint32_t)modelIDs.indexOf(modelID);
int indexOfModelID = modelIDs.indexOf(modelID);
if (indexOfModelID == -1) {
qCDebug(modelformat) << "Model not in model list: " << modelID;
}
const uint32_t transformIndex = (indexOfModelID == -1) ? 0 : (uint32_t)indexOfModelID;
// accumulate local transforms
glm::mat4 globalTransform = hfmModel.joints[transformIndex].globalTransform;
@ -1585,12 +1589,6 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const
}
}
auto rootJointIndex = modelIDs.indexOf(modelID);
if (rootJointIndex == -1) {
qCDebug(modelformat) << "Model not in model list: " << modelID;
rootJointIndex = 0;
}
// whether we're skinned depends on how many clusters are attached
if (clusterIDs.size() > 0) {
hfm::SkinDeformer skinDeformer;
@ -1603,10 +1601,12 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const
// see http://stackoverflow.com/questions/13566608/loading-skinning-information-from-fbx for a discussion
// of skinning information in FBX
QString jointID = _connectionChildMap.value(clusterID);
hfmCluster.jointIndex = modelIDs.indexOf(jointID);
if (hfmCluster.jointIndex == -1) {
int indexOfJointID = modelIDs.indexOf(jointID);
if (indexOfJointID == -1) {
qCDebug(modelformat) << "Joint not in model list: " << jointID;
hfmCluster.jointIndex = 0;
} else {
hfmCluster.jointIndex = (uint32_t)indexOfJointID;
}
hfmCluster.inverseBindMatrix = glm::inverse(fbxCluster.transformLink) * globalTransform;
@ -1635,7 +1635,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const
// the last cluster is the root cluster
HFMCluster cluster;
cluster.jointIndex = rootJointIndex;
cluster.jointIndex = transformIndex;
clusters.push_back(cluster);
// Skinned mesh instances have a dynamic transform
@ -1676,7 +1676,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const
}
} else {
// this is a no cluster mesh
HFMJoint& joint = hfmModel.joints[rootJointIndex];
HFMJoint& joint = hfmModel.joints[transformIndex];
// Apply geometric offset, if present, by transforming the vertices directly
if (joint.hasGeometricOffset) {

File diff suppressed because it is too large Load diff

View file

@ -38,14 +38,14 @@ struct GLTFAsset {
struct GLTFNode {
QString name;
int camera;
int mesh;
int camera{ -1 };
int mesh{ -1 };
QVector<int> children;
QVector<double> translation;
QVector<double> rotation;
QVector<double> scale;
QVector<double> matrix;
QVector<glm::mat4> transforms;
glm::mat4 transform;
int skin;
QVector<int> skeletons;
QString jointName;
@ -85,6 +85,8 @@ struct GLTFNode {
qCDebug(modelformat) << "skeletons: " << skeletons;
}
}
void normalizeTransform();
};
// Meshes
@ -460,15 +462,56 @@ struct GLTFMaterial {
// Accesors
namespace GLTFAccessorType {
enum Values {
SCALAR = 0,
VEC2,
VEC3,
VEC4,
MAT2,
MAT3,
MAT4
enum Value {
SCALAR = 1,
VEC2 = 2,
VEC3 = 3,
VEC4 = 4,
MAT2 = 5,
MAT3 = 9,
MAT4 = 16
};
inline int count(Value value) {
if (value == MAT2) {
return 4;
}
return (int)value;
}
}
namespace GLTFVertexAttribute {
enum Value {
UNKNOWN = -1,
POSITION = 0,
NORMAL,
TANGENT,
TEXCOORD_0,
TEXCOORD_1,
COLOR_0,
JOINTS_0,
WEIGHTS_0,
};
inline Value fromString(const QString& key) {
if (key == "POSITION") {
return POSITION;
} else if (key == "NORMAL") {
return NORMAL;
} else if (key == "TANGENT") {
return TANGENT;
} else if (key == "TEXCOORD_0") {
return TEXCOORD_0;
} else if (key == "TEXCOORD_1") {
return TEXCOORD_1;
} else if (key == "COLOR_0") {
return COLOR_0;
} else if (key == "JOINTS_0") {
return JOINTS_0;
} else if (key == "WEIGHTS_0") {
return WEIGHTS_0;
}
return UNKNOWN;
}
}
namespace GLTFAccessorComponentType {
enum Values {
@ -760,6 +803,13 @@ struct GLTFFile {
foreach(auto tex, textures) tex.dump();
}
}
void populateMaterialNames();
void sortNodes();
void normalizeNodeTransforms();
private:
void reorderNodes(const std::unordered_map<int, int>& reorderMap);
};
class GLTFSerializer : public QObject, public HFMSerializer {
@ -774,7 +824,7 @@ private:
hifi::URL _url;
hifi::ByteArray _glbBinary;
glm::mat4 getModelTransform(const GLTFNode& node);
const glm::mat4& getModelTransform(const GLTFNode& node);
void getSkinInverseBindMatrices(std::vector<std::vector<float>>& inverseBindMatrixValues);
void generateTargetData(int index, float weight, QVector<glm::vec3>& returnVector);
@ -843,6 +893,9 @@ private:
template <typename T>
bool addArrayFromAccessor(GLTFAccessor& accessor, QVector<T>& outarray);
template <typename T>
bool addArrayFromAttribute(GLTFVertexAttribute::Value vertexAttribute, GLTFAccessor& accessor, QVector<T>& outarray);
void retriangulate(const QVector<int>& in_indices, const QVector<glm::vec3>& in_vertices,
const QVector<glm::vec3>& in_normals, QVector<int>& out_indices,
QVector<glm::vec3>& out_vertices, QVector<glm::vec3>& out_normals);

View file

@ -0,0 +1,66 @@
//
// HFMModelMath.cpp
// model-baker/src/model-baker
//
// Created by Sabrina Shanman on 2019/10/04.
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "HFMModelMath.h"
namespace hfm {
void forEachIndex(const hfm::MeshPart& meshPart, std::function<void(uint32_t)> func) {
for (int i = 0; i <= meshPart.quadIndices.size() - 4; i += 4) {
func((uint32_t)meshPart.quadIndices[i]);
func((uint32_t)meshPart.quadIndices[i+1]);
func((uint32_t)meshPart.quadIndices[i+2]);
func((uint32_t)meshPart.quadIndices[i+3]);
}
for (int i = 0; i <= meshPart.triangleIndices.size() - 3; i += 3) {
func((uint32_t)meshPart.triangleIndices[i]);
func((uint32_t)meshPart.triangleIndices[i+1]);
func((uint32_t)meshPart.triangleIndices[i+2]);
}
}
void thickenFlatExtents(Extents& extents) {
// Add epsilon to extents to compensate for flat plane
extents.minimum -= glm::vec3(EPSILON, EPSILON, EPSILON);
extents.maximum += glm::vec3(EPSILON, EPSILON, EPSILON);
}
void calculateExtentsForShape(hfm::Shape& shape, const std::vector<hfm::Mesh>& meshes, const std::vector<hfm::Joint> joints) {
auto& shapeExtents = shape.transformedExtents;
shapeExtents.reset();
const auto& mesh = meshes[shape.mesh];
const auto& meshPart = mesh.parts[shape.meshPart];
glm::mat4 globalTransform = joints[shape.joint].globalTransform;
forEachIndex(meshPart, [&](int32_t idx){
if (mesh.vertices.size() <= idx) {
return;
}
const glm::vec3& vertex = mesh.vertices[idx];
const glm::vec3 transformedVertex = glm::vec3(globalTransform * glm::vec4(vertex, 1.0f));
shapeExtents.addPoint(transformedVertex);
});
thickenFlatExtents(shapeExtents);
}
void calculateExtentsForModel(Extents& modelExtents, const std::vector<hfm::Shape>& shapes) {
modelExtents.reset();
for (size_t i = 0; i < shapes.size(); ++i) {
const auto& shape = shapes[i];
const auto& shapeExtents = shape.transformedExtents;
modelExtents.addExtents(shapeExtents);
}
}
};

View file

@ -0,0 +1,30 @@
//
// HFMModelMath.h
// model-baker/src/model-baker
//
// Created by Sabrina Shanman on 2019/10/04.
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_hfm_ModelMath_h
#define hifi_hfm_ModelMath_h
#include "HFM.h"
namespace hfm {
void forEachIndex(const hfm::MeshPart& meshPart, std::function<void(uint32_t)> func);
void initializeExtents(Extents& extents);
// This can't be moved to model-baker until
void calculateExtentsForShape(hfm::Shape& shape, const std::vector<hfm::Mesh>& meshes, const std::vector<hfm::Joint> joints);
void calculateExtentsForModel(Extents& modelExtents, const std::vector<hfm::Shape>& shapes);
};
#endif // #define hifi_hfm_ModelMath_h

View file

@ -21,6 +21,7 @@
#include "CalculateBlendshapeNormalsTask.h"
#include "CalculateBlendshapeTangentsTask.h"
#include "PrepareJointsTask.h"
#include "CalculateExtentsTask.h"
#include "BuildDracoMeshTask.h"
#include "ParseFlowDataTask.h"
@ -29,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>, std::vector<hfm::SkinCluster>>;
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>, std::vector<hfm::SkinCluster>, Extents>;
using JobModel = Job::ModelIO<GetModelPartsTask, Input, Output>;
void run(const BakeContextPointer& context, const Input& input, Output& output) {
@ -46,6 +47,7 @@ namespace baker {
output.edit5() = hfmModelIn->shapes;
output.edit6() = hfmModelIn->skinDeformers;
output.edit7() = hfmModelIn->skinClusters;
output.edit8() = hfmModelIn->meshExtents;
}
};
@ -106,7 +108,7 @@ namespace baker {
class BuildModelTask {
public:
using Input = VaryingSet7<hfm::Model::Pointer, std::vector<hfm::Mesh>, std::vector<hfm::Joint>, QMap<int, glm::quat>, QHash<QString, int>, FlowData, std::vector<ShapeVertices>>;
using Input = VaryingSet9<hfm::Model::Pointer, std::vector<hfm::Mesh>, std::vector<hfm::Joint>, QMap<int, glm::quat>, QHash<QString, int>, FlowData, std::vector<ShapeVertices>, std::vector<hfm::Shape>, Extents>;
using Output = hfm::Model::Pointer;
using JobModel = Job::ModelIO<BuildModelTask, Input, Output>;
@ -118,6 +120,8 @@ namespace baker {
hfmModelOut->jointIndices = input.get4();
hfmModelOut->flowData = input.get5();
hfmModelOut->shapeVertices = input.get6();
hfmModelOut->shapes = input.get7();
hfmModelOut->meshExtents = input.get8();
// These depend on the ShapeVertices
// TODO: Create a task for this rather than calculating it here
hfmModelOut->computeKdops();
@ -144,7 +148,8 @@ namespace baker {
const auto jointsIn = modelPartsIn.getN<GetModelPartsTask::Output>(4);
const auto shapesIn = modelPartsIn.getN<GetModelPartsTask::Output>(5);
const auto skinDeformersIn = modelPartsIn.getN<GetModelPartsTask::Output>(6);
const auto deformersIn = modelPartsIn.getN<GetModelPartsTask::Output>(7);
const auto skinClustersIn = modelPartsIn.getN<GetModelPartsTask::Output>(7);
const auto modelExtentsIn = 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.
@ -158,7 +163,7 @@ namespace baker {
// Skinning weight calculations
// NOTE: Due to limitations in the current graphics::MeshPointer representation, the output list of ReweightedDeformers is per-mesh. An element is empty if there are no deformers for the mesh of the same index.
const auto reweightDeformersInputs = ReweightDeformersTask::Input(meshesIn, shapesIn, skinDeformersIn, deformersIn).asVarying();
const auto reweightDeformersInputs = ReweightDeformersTask::Input(meshesIn, shapesIn, skinDeformersIn, skinClustersIn).asVarying();
const auto reweightedDeformers = model.addJob<ReweightDeformersTask>("ReweightDeformers", reweightDeformersInputs);
// Shape vertices are included/rejected based on skinning weight, and thus must use the reweighted deformers.
const auto collectShapeVerticesInputs = CollectShapeVerticesTask::Input(meshesIn, shapesIn, jointsIn, skinDeformersIn, reweightedDeformers).asVarying();
@ -175,6 +180,12 @@ namespace baker {
const auto jointRotationOffsets = jointInfoOut.getN<PrepareJointsTask::Output>(1);
const auto jointIndices = jointInfoOut.getN<PrepareJointsTask::Output>(2);
// Use transform information to compute extents
const auto calculateExtentsInputs = CalculateExtentsTask::Input(modelExtentsIn, meshesIn, shapesIn, jointsOut).asVarying();
const auto calculateExtentsOutputs = model.addJob<CalculateExtentsTask>("CalculateExtents", calculateExtentsInputs);
const auto modelExtentsOut = calculateExtentsOutputs.getN<CalculateExtentsTask::Output>(0);
const auto shapesOut = calculateExtentsOutputs.getN<CalculateExtentsTask::Output>(1);
// Parse material mapping
const auto parseMaterialMappingInputs = ParseMaterialMappingTask::Input(mapping, materialMappingBaseURL).asVarying();
const auto materialMapping = model.addJob<ParseMaterialMappingTask>("ParseMaterialMapping", parseMaterialMappingInputs);
@ -198,7 +209,7 @@ namespace baker {
const auto blendshapesPerMeshOut = model.addJob<BuildBlendshapesTask>("BuildBlendshapes", buildBlendshapesInputs);
const auto buildMeshesInputs = BuildMeshesTask::Input(meshesIn, graphicsMeshes, normalsPerMesh, tangentsPerMesh, blendshapesPerMeshOut).asVarying();
const auto meshesOut = model.addJob<BuildMeshesTask>("BuildMeshes", buildMeshesInputs);
const auto buildModelInputs = BuildModelTask::Input(hfmModelIn, meshesOut, jointsOut, jointRotationOffsets, jointIndices, flowData, shapeVerticesPerJoint).asVarying();
const auto buildModelInputs = BuildModelTask::Input(hfmModelIn, meshesOut, jointsOut, jointRotationOffsets, jointIndices, flowData, shapeVerticesPerJoint, shapesOut, modelExtentsOut).asVarying();
const auto hfmModelOut = model.addJob<BuildModelTask>("BuildModel", buildModelInputs);
output = Output(hfmModelOut, materialMapping, dracoMeshes, dracoErrors, materialList);

View file

@ -0,0 +1,41 @@
//
// CalculateExtentsTask.cpp
// model-baker/src/model-baker
//
// Created by Sabrina Shanman on 2019/10/04.
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "CalculateExtentsTask.h"
#include "hfm/HFMModelMath.h"
void CalculateExtentsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) {
const auto& modelExtentsIn = input.get0();
const auto& meshes = input.get1();
const auto& shapesIn = input.get2();
const auto& joints = input.get3();
auto& modelExtentsOut = output.edit0();
auto& shapesOut = output.edit1();
shapesOut.reserve(shapesIn.size());
for (size_t i = 0; i < shapesIn.size(); ++i) {
shapesOut.push_back(shapesIn[i]);
auto& shapeOut = shapesOut.back();
auto& shapeExtents = shapeOut.transformedExtents;
if (shapeExtents.isValid()) {
continue;
}
hfm::calculateExtentsForShape(shapeOut, meshes, joints);
}
modelExtentsOut = modelExtentsIn;
if (!modelExtentsOut.isValid()) {
hfm::calculateExtentsForModel(modelExtentsOut, shapesOut);
}
}

View file

@ -0,0 +1,29 @@
//
// CalculateExtentsTask.h
// model-baker/src/model-baker
//
// Created by Sabrina Shanman on 2019/10/04.
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_CalculateExtentsTask_h
#define hifi_CalculateExtentsTask_h
#include "Engine.h"
#include "hfm/HFM.h"
// Calculates any undefined extents in the shapes and the model. Precalculated extents will be left alone.
// Bind extents will currently not be calculated
class CalculateExtentsTask {
public:
using Input = baker::VaryingSet4<Extents, std::vector<hfm::Mesh>, std::vector<hfm::Shape>, std::vector<hfm::Joint>>;
using Output = baker::VaryingSet2<Extents, std::vector<hfm::Shape>>;
using JobModel = baker::Job::ModelIO<CalculateExtentsTask, Input, Output>;
void run(const baker::BakeContextPointer& context, const Input& input, Output& output);
};
#endif // hifi_CalculateExtentsTask_h

View file

@ -30,7 +30,7 @@ void CalculateMeshTangentsTask::run(const baker::BakeContextPointer& context, co
// Otherwise confirm if we have the normals and texcoords needed
if (!tangentsIn.empty()) {
tangentsOut = tangentsIn.toStdVector();
} else if (!normals.empty() && mesh.vertices.size() == mesh.texCoords.size()) {
} else if (!normals.empty() && mesh.vertices.size() <= mesh.texCoords.size()) {
tangentsOut.resize(normals.size());
baker::calculateTangents(mesh,
[&mesh, &normals, &tangentsOut](int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec2* outTexCoords, glm::vec3& outNormal) {

View file

@ -0,0 +1,11 @@
set(TARGET_NAME fbx-test)
# This is not a testcase -- just set it up as a regular hifi project
setup_hifi_project(Quick Gui)
setup_memory_debugger()
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
file(GLOB_RECURSE GLB_TEST_FILES "c:/Users/bdavi/git/glTF-Sample-Models/2.0/*.glb")
list(JOIN GLB_TEST_FILES "|" GLB_TEST_FILES)
target_compile_definitions(${TARGET_NAME} PRIVATE -DGLB_TEST_FILES="${GLB_TEST_FILES}")
link_hifi_libraries(shared graphics networking image gpu hfm fbx)
package_libraries_for_deployment()

View file

@ -0,0 +1,77 @@
//
// Created by Bradley Austin Davis on 2018/01/11
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtCore/qglobal.h>
#include <QtCore/QFile>
#include <QtGui/QGuiApplication>
#include <GLTFSerializer.h>
#include <shared/FileUtils.h>
#include <ResourceManager.h>
#include <DependencyManager.h>
#include <Windows.h>
// Currently only used by testing code
inline std::list<std::string> splitString(const std::string& source, const char delimiter = ' ') {
std::list<std::string> result;
size_t start = 0, next;
while (std::string::npos != (next = source.find(delimiter, start))) {
std::string sub = source.substr(start, next - start);
if (!sub.empty()) {
result.push_back(sub);
}
start = next + 1;
}
if (source.size() > start) {
result.push_back(source.substr(start));
}
return result;
}
std::list<std::string> getGlbTestFiles() {
return splitString(GLB_TEST_FILES, '|');
}
QtMessageHandler originalHandler;
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
#if defined(Q_OS_WIN)
OutputDebugStringA(message.toStdString().c_str());
OutputDebugStringA("\n");
#endif
originalHandler(type, context, message);
}
QByteArray readFileBytes(const std::string& filename) {
QFile file(filename.c_str());
file.open(QFile::ReadOnly);
QByteArray result = file.readAll();
file.close();
return result;
}
void processFile(const std::string& filename) {
qDebug() << filename.c_str();
GLTFSerializer().read(readFileBytes(filename), {}, QUrl::fromLocalFile(filename.c_str()));
}
int main(int argc, char** argv) {
QCoreApplication app{ argc, argv };
originalHandler = qInstallMessageHandler(messageHandler);
DependencyManager::set<ResourceManager>(false);
//processFile("c:/Users/bdavi/git/glTF-Sample-Models/2.0/Box/glTF-Binary/Box.glb");
for (const auto& testFile : getGlbTestFiles()) {
processFile(testFile);
}
}