Create ReweightDeformersTask

This commit is contained in:
sabrina-shanman 2019-09-26 17:02:42 -07:00
parent 6234c83a24
commit 2077da6f37
6 changed files with 180 additions and 100 deletions

View file

@ -13,6 +13,7 @@
#include "BakerTypes.h"
#include "ModelMath.h"
#include "ReweightDeformersTask.h"
#include "BuildGraphicsMeshTask.h"
#include "CalculateMeshNormalsTask.h"
#include "CalculateMeshTangentsTask.h"
@ -151,8 +152,13 @@ namespace baker {
const auto calculateBlendshapeTangentsInputs = CalculateBlendshapeTangentsTask::Input(normalsPerBlendshapePerMesh, blendshapesPerMeshIn, meshesIn).asVarying();
const auto tangentsPerBlendshapePerMesh = model.addJob<CalculateBlendshapeTangentsTask>("CalculateBlendshapeTangents", calculateBlendshapeTangentsInputs);
// 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, dynamicTransformsIn, deformersIn).asVarying();
const auto reweightedDeformers = model.addJob<ReweightDeformersTask>("ReweightDeformers", reweightDeformersInputs);
// Build the graphics::MeshPointer for each hfm::Mesh
const auto buildGraphicsMeshInputs = BuildGraphicsMeshTask::Input(meshesIn, url, meshIndicesToModelNames, normalsPerMesh, tangentsPerMesh, shapesIn, dynamicTransformsIn, deformersIn).asVarying();
const auto buildGraphicsMeshInputs = BuildGraphicsMeshTask::Input(meshesIn, url, meshIndicesToModelNames, normalsPerMesh, tangentsPerMesh, shapesIn, dynamicTransformsIn, reweightedDeformers).asVarying();
const auto graphicsMeshes = model.addJob<BuildGraphicsMeshTask>("BuildGraphicsMesh", buildGraphicsMeshInputs);
// Prepare joint information

View file

@ -36,6 +36,14 @@ namespace baker {
using TangentsPerBlendshape = std::vector<std::vector<glm::vec3>>;
using MeshIndicesToModelNames = QHash<int, QString>;
class ReweightedDeformers {
public:
std::vector<uint16_t> indices;
std::vector<uint16_t> weights;
uint16_t weightsPerVertex { 0 };
bool trimmedToMatch { false };
};
};
#endif // hifi_BakerTypes_h

View file

@ -2,8 +2,8 @@
// BuildGraphicsMeshTask.h
// model-baker/src/model-baker
//
// Created by Sabrina Shanman on 2018/12/06.
// Copyright 2018 High Fidelity, Inc.
// Created by Sabrina Shanman on 2019/09/16.
// 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
@ -27,83 +27,7 @@ glm::vec3 normalizeDirForPacking(const glm::vec3& dir) {
return dir;
}
class ReweightedDeformers {
public:
std::vector<uint16_t> indices;
std::vector<uint16_t> weights;
bool trimmedToMatch { false };
};
ReweightedDeformers getReweightedDeformers(size_t numMeshVertices, const hfm::DynamicTransform* dynamicTransform, const std::vector<const hfm::Deformer*> deformers, const uint16_t weightsPerVertex) {
size_t numClusterIndices = numMeshVertices * weightsPerVertex;
ReweightedDeformers reweightedDeformers;
// TODO: Consider having a rootCluster property in the DynamicTransform rather than appending the root to the end of the cluster list.
reweightedDeformers.indices.resize(numClusterIndices, (uint16_t)(deformers.size() - 1));
reweightedDeformers.weights.resize(numClusterIndices, 0);
std::vector<float> weightAccumulators;
weightAccumulators.resize(numClusterIndices, 0.0f);
for (uint16_t i = 0; i < (uint16_t)deformers.size(); ++i) {
const hfm::Deformer& deformer = *deformers[i];
if (deformer.indices.size() != deformer.weights.size()) {
reweightedDeformers.trimmedToMatch = true;
}
size_t numIndicesOrWeights = std::min(deformer.indices.size(), deformer.weights.size());
for (size_t j = 0; j < numIndicesOrWeights; ++j) {
uint32_t index = deformer.indices[j];
float weight = deformer.weights[j];
// look for an unused slot in the weights vector
uint32_t weightIndex = index * weightsPerVertex;
uint32_t lowestIndex = -1;
float lowestWeight = FLT_MAX;
uint16_t k = 0;
for (; k < weightsPerVertex; k++) {
if (weightAccumulators[weightIndex + k] == 0.0f) {
reweightedDeformers.indices[weightIndex + k] = i;
weightAccumulators[weightIndex + k] = weight;
break;
}
if (weightAccumulators[weightIndex + k] < lowestWeight) {
lowestIndex = k;
lowestWeight = weightAccumulators[weightIndex + k];
}
}
if (k == weightsPerVertex && weight > lowestWeight) {
// no space for an additional weight; we must replace the lowest
weightAccumulators[weightIndex + lowestIndex] = weight;
reweightedDeformers.indices[weightIndex + lowestIndex] = i;
}
}
}
// now that we've accumulated the most relevant weights for each vertex
// normalize and compress to 16-bits
for (size_t i = 0; i < numMeshVertices; ++i) {
size_t j = i * weightsPerVertex;
// normalize weights into uint16_t
float totalWeight = 0.0f;
for (size_t k = j; k < j + weightsPerVertex; ++k) {
totalWeight += weightAccumulators[k];
}
const float ALMOST_HALF = 0.499f;
if (totalWeight > 0.0f) {
float weightScalingFactor = (float)(UINT16_MAX) / totalWeight;
for (size_t k = j; k < j + weightsPerVertex; ++k) {
reweightedDeformers.weights[k] = (uint16_t)(weightScalingFactor * weightAccumulators[k] + ALMOST_HALF);
}
} else {
reweightedDeformers.weights[j] = (uint16_t)((float)(UINT16_MAX) + ALMOST_HALF);
}
}
return reweightedDeformers;
}
void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphicsMeshPointer, const baker::MeshNormals& meshNormals, const baker::MeshTangents& meshTangentsIn, const hfm::DynamicTransform* dynamicTransform, const std::vector<const hfm::Deformer*> meshDeformers) {
void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphicsMeshPointer, const baker::MeshNormals& meshNormals, const baker::MeshTangents& meshTangentsIn, uint16_t numDeformerControllers, const baker::ReweightedDeformers reweightedDeformers) {
auto graphicsMesh = std::make_shared<graphics::Mesh>();
// Fill tangents with a dummy value to force tangents to be present if there are normals
@ -162,19 +86,16 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics
// Support for 4 skinning clusters:
// 4 Indices are uint8 ideally, uint16 if more than 256.
const auto clusterIndiceElement = ((meshDeformers.size() < (size_t)UINT8_MAX) ? gpu::Element(gpu::VEC4, gpu::UINT8, gpu::XYZW) : gpu::Element(gpu::VEC4, gpu::UINT16, gpu::XYZW));
const auto clusterIndiceElement = ((numDeformerControllers < (uint16_t)UINT8_MAX) ? gpu::Element(gpu::VEC4, gpu::UINT8, gpu::XYZW) : gpu::Element(gpu::VEC4, gpu::UINT16, gpu::XYZW));
// 4 Weights are normalized 16bits
const auto clusterWeightElement = gpu::Element(gpu::VEC4, gpu::NUINT16, gpu::XYZW);
// Calculate a more condensed view of all the deformer weights
const uint16_t NUM_CLUSTERS_PER_VERT = 4;
ReweightedDeformers reweightedDeformers = getReweightedDeformers(hfmMesh.vertices.size(), dynamicTransform, meshDeformers, NUM_CLUSTERS_PER_VERT);
// Cluster indices and weights must be the same sizes
if (reweightedDeformers.trimmedToMatch) {
HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "BuildGraphicsMeshTask -- The number of indices and weights for a deformer had different sizes and have been trimmed to match");
}
// Record cluster sizes
const size_t numVertClusters = reweightedDeformers.indices.size() / NUM_CLUSTERS_PER_VERT;
const size_t numVertClusters = reweightedDeformers.indices.size() / reweightedDeformers.weightsPerVertex;
const size_t clusterIndicesSize = numVertClusters * clusterIndiceElement.getSize();
const size_t clusterWeightsSize = numVertClusters * clusterWeightElement.getSize();
@ -263,7 +184,7 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics
// Clusters data
if (clusterIndicesSize > 0) {
if (meshDeformers.size() < UINT8_MAX) {
if (numDeformerControllers < (uint16_t)UINT8_MAX) {
// yay! we can fit the clusterIndices within 8-bits
int32_t numIndices = (int32_t)reweightedDeformers.indices.size();
std::vector<uint8_t> packedDeformerIndices;
@ -461,7 +382,7 @@ void BuildGraphicsMeshTask::run(const baker::BakeContextPointer& context, const
const auto& tangentsPerMesh = input.get4();
const auto& shapes = input.get5();
const auto& dynamicTransforms = input.get6();
const auto& deformers = input.get7();
const auto& reweightedDeformersPerMesh = input.get7();
// Currently, there is only (at most) one dynamicTransform per mesh
// An undefined shape.dynamicTransform has the value hfm::UNDEFINED_KEY
@ -478,20 +399,17 @@ void BuildGraphicsMeshTask::run(const baker::BakeContextPointer& context, const
for (int i = 0; i < n; i++) {
graphicsMeshes.emplace_back();
auto& graphicsMesh = graphicsMeshes[i];
const auto& reweightedDeformers = reweightedDeformersPerMesh[i];
auto dynamicTransformIndex = dynamicTransformPerMesh[i];
const hfm::DynamicTransform* dynamicTransform = nullptr;
std::vector<const hfm::Deformer*> meshDeformers;
if (dynamicTransformIndex != hfm::UNDEFINED_KEY) {
dynamicTransform = &dynamicTransforms[dynamicTransformIndex];
for (const auto& deformerIndex : dynamicTransform->deformers) {
const auto& deformer = deformers[deformerIndex];
meshDeformers.push_back(&deformer);
}
uint16_t numDeformerControllers = 0;
if (reweightedDeformers.weightsPerVertex != 0) {
uint32_t dynamicTransformIndex = dynamicTransformPerMesh[i];
const hfm::DynamicTransform& dynamicTransform = dynamicTransforms[dynamicTransformIndex];
numDeformerControllers = (uint16_t)dynamicTransform.deformers.size();
}
// Try to create the graphics::Mesh
buildGraphicsMesh(meshes[i], graphicsMesh, baker::safeGet(normalsPerMesh, i), baker::safeGet(tangentsPerMesh, i), dynamicTransform, meshDeformers);
buildGraphicsMesh(meshes[i], graphicsMesh, baker::safeGet(normalsPerMesh, i), baker::safeGet(tangentsPerMesh, i), numDeformerControllers, reweightedDeformers);
// Choose a name for the mesh
if (graphicsMesh) {

View file

@ -2,8 +2,8 @@
// BuildGraphicsMeshTask.h
// model-baker/src/model-baker
//
// Created by Sabrina Shanman on 2018/12/06.
// Copyright 2018 High Fidelity, Inc.
// Created by Sabrina Shanman on 2019/09/16.
// 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
@ -20,7 +20,7 @@
class BuildGraphicsMeshTask {
public:
using Input = baker::VaryingSet8<std::vector<hfm::Mesh>, hifi::URL, baker::MeshIndicesToModelNames, baker::NormalsPerMesh, baker::TangentsPerMesh, std::vector<hfm::Shape>, std::vector<hfm::DynamicTransform>, std::vector<hfm::Deformer>>;
using Input = baker::VaryingSet8<std::vector<hfm::Mesh>, hifi::URL, baker::MeshIndicesToModelNames, baker::NormalsPerMesh, baker::TangentsPerMesh, std::vector<hfm::Shape>, std::vector<hfm::DynamicTransform>, std::vector<baker::ReweightedDeformers>>;
using Output = std::vector<graphics::MeshPointer>;
using JobModel = baker::Job::ModelIO<BuildGraphicsMeshTask, Input, Output>;

View file

@ -0,0 +1,119 @@
//
// ReweightDeformersTask.h
// model-baker/src/model-baker
//
// Created by Sabrina Shanman on 2019/09/26.
// 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 "ReweightDeformersTask.h"
baker::ReweightedDeformers getReweightedDeformers(size_t numMeshVertices, const hfm::DynamicTransform* dynamicTransform, const std::vector<const hfm::Deformer*> deformers, const uint16_t weightsPerVertex) {
size_t numClusterIndices = numMeshVertices * weightsPerVertex;
baker::ReweightedDeformers reweightedDeformers;
reweightedDeformers.weightsPerVertex = weightsPerVertex;
// TODO: Consider having a rootCluster property in the DynamicTransform rather than appending the root to the end of the cluster list.
reweightedDeformers.indices.resize(numClusterIndices, (uint16_t)(deformers.size() - 1));
reweightedDeformers.weights.resize(numClusterIndices, 0);
std::vector<float> weightAccumulators;
weightAccumulators.resize(numClusterIndices, 0.0f);
for (uint16_t i = 0; i < (uint16_t)deformers.size(); ++i) {
const hfm::Deformer& deformer = *deformers[i];
if (deformer.indices.size() != deformer.weights.size()) {
reweightedDeformers.trimmedToMatch = true;
}
size_t numIndicesOrWeights = std::min(deformer.indices.size(), deformer.weights.size());
for (size_t j = 0; j < numIndicesOrWeights; ++j) {
uint32_t index = deformer.indices[j];
float weight = deformer.weights[j];
// look for an unused slot in the weights vector
uint32_t weightIndex = index * weightsPerVertex;
uint32_t lowestIndex = -1;
float lowestWeight = FLT_MAX;
uint16_t k = 0;
for (; k < weightsPerVertex; k++) {
if (weightAccumulators[weightIndex + k] == 0.0f) {
reweightedDeformers.indices[weightIndex + k] = i;
weightAccumulators[weightIndex + k] = weight;
break;
}
if (weightAccumulators[weightIndex + k] < lowestWeight) {
lowestIndex = k;
lowestWeight = weightAccumulators[weightIndex + k];
}
}
if (k == weightsPerVertex && weight > lowestWeight) {
// no space for an additional weight; we must replace the lowest
weightAccumulators[weightIndex + lowestIndex] = weight;
reweightedDeformers.indices[weightIndex + lowestIndex] = i;
}
}
}
// now that we've accumulated the most relevant weights for each vertex
// normalize and compress to 16-bits
for (size_t i = 0; i < numMeshVertices; ++i) {
size_t j = i * weightsPerVertex;
// normalize weights into uint16_t
float totalWeight = 0.0f;
for (size_t k = j; k < j + weightsPerVertex; ++k) {
totalWeight += weightAccumulators[k];
}
const float ALMOST_HALF = 0.499f;
if (totalWeight > 0.0f) {
float weightScalingFactor = (float)(UINT16_MAX) / totalWeight;
for (size_t k = j; k < j + weightsPerVertex; ++k) {
reweightedDeformers.weights[k] = (uint16_t)(weightScalingFactor * weightAccumulators[k] + ALMOST_HALF);
}
} else {
reweightedDeformers.weights[j] = (uint16_t)((float)(UINT16_MAX) + ALMOST_HALF);
}
}
return reweightedDeformers;
}
void ReweightDeformersTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) {
const uint16_t NUM_WEIGHTS_PER_VERTEX { 4 };
const auto& meshes = input.get0();
const auto& shapes = input.get1();
const auto& dynamicTransforms = input.get2();
const auto& deformers = input.get3();
auto& reweightedDeformers = output;
// Currently, there is only (at most) one dynamicTransform per mesh
// An undefined shape.dynamicTransform has the value hfm::UNDEFINED_KEY
std::vector<uint32_t> dynamicTransformPerMesh;
dynamicTransformPerMesh.resize(meshes.size(), hfm::UNDEFINED_KEY);
for (const auto& shape : shapes) {
uint32_t dynamicTransformIndex = shape.dynamicTransform;
dynamicTransformPerMesh[shape.mesh] = dynamicTransformIndex;
}
reweightedDeformers.reserve(meshes.size());
for (size_t i = 0; i < meshes.size(); ++i) {
const auto& mesh = meshes[i];
uint32_t dynamicTransformIndex = dynamicTransformPerMesh[i];
const hfm::DynamicTransform* dynamicTransform = nullptr;
std::vector<const hfm::Deformer*> meshDeformers;
if (dynamicTransformIndex != hfm::UNDEFINED_KEY) {
dynamicTransform = &dynamicTransforms[dynamicTransformIndex];
for (const auto& deformerIndex : dynamicTransform->deformers) {
const auto& deformer = deformers[deformerIndex];
meshDeformers.push_back(&deformer);
}
}
reweightedDeformers.push_back(getReweightedDeformers((size_t)mesh.vertices.size(), dynamicTransform, meshDeformers, NUM_WEIGHTS_PER_VERTEX));
}
}

View file

@ -0,0 +1,29 @@
//
// ReweightDeformersTask.h
// model-baker/src/model-baker
//
// Created by Sabrina Shanman on 2019/09/26.
// 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_ReweightDeformersTask_h
#define hifi_ReweightDeformersTask_h
#include <hfm/HFM.h>
#include "Engine.h"
#include "BakerTypes.h"
class ReweightDeformersTask {
public:
using Input = baker::VaryingSet4<std::vector<hfm::Mesh>, std::vector<hfm::Shape>, std::vector<hfm::DynamicTransform>, std::vector<hfm::Deformer>>;
using Output = std::vector<baker::ReweightedDeformers>;
using JobModel = baker::Job::ModelIO<ReweightDeformersTask, Input, Output>;
void run(const baker::BakeContextPointer& context, const Input& input, Output& output);
};
#endif // hifi_ReweightDeformersTask_h