First functional morph shape export.

- This does not support progressive morphs. Still evaluating whether
  that's required functionality. It's really just a little bit of more
  work. An example implementation is available in the SDK samples.

- We're blithely ignoring NORMALs and TANGENTs. This almost certainly
  has to change.
This commit is contained in:
Par Winzell 2017-10-29 01:59:51 -07:00
parent b1076fa373
commit 77470e236e
8 changed files with 183 additions and 20 deletions

View File

@ -449,6 +449,91 @@ private:
std::vector<Vec4f> vertexJointWeights; std::vector<Vec4f> vertexJointWeights;
}; };
class FbxBlendShapesAccess
{
public:
struct Shape {
explicit Shape(const std::vector<FbxVector4> &offsets, FbxFloat defaultDeform, const std::vector<FbxAnimCurve *>animations) :
offsets(offsets),
defaultDeform(defaultDeform),
animations(animations)
{}
const std::vector<FbxVector4> offsets;
const FbxDouble defaultDeform;
const std::vector<FbxAnimCurve *> animations;
};
explicit FbxBlendShapesAccess(const FbxScene *scene, FbxMesh *mesh) :
shapes(extractShapes(scene, mesh))
{ }
size_t GetShapeCount() const { return shapes.size(); }
FbxDouble GetDefaultDeform(size_t shapeIx) const {
return shapes.at(shapeIx).defaultDeform;
}
FbxVector4 GetPosition(size_t shapeIx, size_t vertexIx) const {
return shapes.at(shapeIx).offsets[vertexIx];
}
size_t GetAnimCount(size_t shapeIx) const { return shapes.at(shapeIx).animations.size(); }
FbxAnimCurve * GetAnimation(size_t shapeIx, size_t animIx) const {
return shapes.at(shapeIx).animations[animIx];
}
private:
std::vector<Shape> extractShapes(const FbxScene *scene, FbxMesh *mesh) const {
std::vector<Shape> results;
// acquire the regular control points from the mesh
const FbxVector4 *meshPoints = mesh->GetControlPoints();
for (int shapeIx = 0; shapeIx < mesh->GetDeformerCount(FbxDeformer::eBlendShape); shapeIx++) {
auto *blendShape = dynamic_cast<FbxBlendShape *>(mesh->GetDeformer(shapeIx, FbxDeformer::eBlendShape));
if (blendShape == nullptr) {
continue;
}
for (int channelIx = 0; channelIx < blendShape->GetBlendShapeChannelCount(); ++channelIx) {
FbxBlendShapeChannel *channel = blendShape->GetBlendShapeChannel(channelIx);
if (channel->GetTargetShapeCount() != 1) {
if (channel->GetTargetShapeCount() > 1) {
fmt::print("Warning: Blend Shape %s is a progressive morph; this is not supported.\n", channel->GetName());
}
continue;
}
FbxShape *fbxShape = channel->GetTargetShape(0);
assert(fbxShape->GetControlPointsCount() == mesh->GetControlPointsCount());
// glTF morph target positions are *mutation vectors* to be added (by weight) to the regular mesh positions.
// FBX blend shape control points, on the other hand are final positions.
// So we must do a little subtracting.
std::vector<FbxVector4> offsets;
auto shapePoints = fbxShape->GetControlPoints();
for (int pointIx = 0; pointIx < mesh->GetControlPointsCount(); pointIx ++) {
offsets.push_back(shapePoints[pointIx] - meshPoints[pointIx]);
}
size_t animationCount = static_cast<size_t>(scene->GetSrcObjectCount<FbxAnimStack>());
std::vector<FbxAnimCurve *>animations(animationCount);
for (int j = 0; j < animationCount; j++) {
auto *pAnimStack = scene->GetSrcObject<FbxAnimStack>(j);
auto *layer = pAnimStack->GetMember<FbxAnimLayer>(0);
if (pAnimStack->GetMemberCount() > 1) {
fmt::print("Warning: ignoring animation layers 1+ in stack %s", pAnimStack->GetName());
}
FbxAnimCurve *curve = mesh->GetShapeChannel(shapeIx, channelIx, layer, true);
// note that curve can be null here, which is fine
animations[j] = curve;
}
results.push_back(Shape(offsets, channel->DeformPercent * 0.01f, animations));
}
}
return results;
}
const std::vector<Shape> shapes;
};
static bool TriangleTexturePolarity(const Vec2f &uv0, const Vec2f &uv1, const Vec2f &uv2) static bool TriangleTexturePolarity(const Vec2f &uv0, const Vec2f &uv1, const Vec2f &uv2)
{ {
const Vec2f d0 = uv1 - uv0; const Vec2f d0 = uv1 - uv0;
@ -482,7 +567,7 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std:
{ {
FbxGeometryConverter meshConverter(pScene->GetFbxManager()); FbxGeometryConverter meshConverter(pScene->GetFbxManager());
meshConverter.Triangulate(pNode->GetNodeAttribute(), true); meshConverter.Triangulate(pNode->GetNodeAttribute(), true);
const FbxMesh *pMesh = pNode->GetMesh(); FbxMesh *pMesh = pNode->GetMesh();
const char *meshName = (pNode->GetName()[0] != '\0') ? pNode->GetName() : pMesh->GetName(); const char *meshName = (pNode->GetName()[0] != '\0') ? pNode->GetName() : pMesh->GetName();
const int rawSurfaceIndex = raw.AddSurface(meshName, pNode->GetName()); const int rawSurfaceIndex = raw.AddSurface(meshName, pNode->GetName());
@ -496,6 +581,7 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std:
const FbxLayerElementAccess<FbxVector2> uvLayer1(pMesh->GetElementUV(1), pMesh->GetElementUVCount()); const FbxLayerElementAccess<FbxVector2> uvLayer1(pMesh->GetElementUV(1), pMesh->GetElementUVCount());
const FbxSkinningAccess skinning(pMesh, pScene, pNode); const FbxSkinningAccess skinning(pMesh, pScene, pNode);
const FbxMaterialsAccess materials(pMesh, textureLocations); const FbxMaterialsAccess materials(pMesh, textureLocations);
const FbxBlendShapesAccess blendShapes(pScene, pMesh);
if (verboseOutput) { if (verboseOutput) {
fmt::printf( fmt::printf(
@ -541,6 +627,11 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std:
rawSurface.jointGeometryMaxs.emplace_back(-FLT_MAX, -FLT_MAX, -FLT_MAX); rawSurface.jointGeometryMaxs.emplace_back(-FLT_MAX, -FLT_MAX, -FLT_MAX);
} }
rawSurface.defaultShapeDeforms.clear();
for (int shapeIx = 0; shapeIx < blendShapes.GetShapeCount(); shapeIx ++) {
rawSurface.defaultShapeDeforms.push_back(blendShapes.GetDefaultDeform(shapeIx));
}
int polygonVertexIndex = 0; int polygonVertexIndex = 0;
for (int polygonIndex = 0; polygonIndex < pMesh->GetPolygonCount(); polygonIndex++) { for (int polygonIndex = 0; polygonIndex < pMesh->GetPolygonCount(); polygonIndex++) {
@ -641,6 +732,12 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std:
rawSurface.bounds.AddPoint(vertex.position); rawSurface.bounds.AddPoint(vertex.position);
for (int shapeIx = 0; shapeIx < blendShapes.GetShapeCount(); shapeIx ++) {
// extract this vertex' position in this blend shape, transform it as per above, and add it to the vector
const Vec3f rawPos = toVec3(transform.MultNormalize(blendShapes.GetPosition(shapeIx, controlPointIndex)));
vertex.shapePositions.push_back(rawPos);
}
if (skinning.IsSkinned()) { if (skinning.IsSkinned()) {
const int jointIndices[FbxSkinningAccess::MAX_WEIGHTS] = { const int jointIndices[FbxSkinningAccess::MAX_WEIGHTS] = {
vertex.jointIndices[0], vertex.jointIndices[0],
@ -846,12 +943,12 @@ static void ReadAnimations(RawModel &raw, FbxScene *pScene)
{ {
FbxTime::EMode eMode = FbxTime::eFrames24; FbxTime::EMode eMode = FbxTime::eFrames24;
const int animationCount = pScene->GetSrcObjectCount<FbxAnimStack>(); const int animationCount = pScene->GetSrcObjectCount<FbxAnimStack>();
for (int i = 0; i < animationCount; i++) { for (int animIx = 0; animIx < animationCount; animIx++) {
FbxAnimStack *pAnimStack = pScene->GetSrcObject<FbxAnimStack>(i); FbxAnimStack *pAnimStack = pScene->GetSrcObject<FbxAnimStack>(animIx);
FbxString animStackName = pAnimStack->GetName(); FbxString animStackName = pAnimStack->GetName();
if (verboseOutput) { if (verboseOutput) {
fmt::printf("animation %d: %s (%d%%)", i, (const char *) animStackName, 0); fmt::printf("animation %d: %s (%d%%)", animIx, (const char *) animStackName, 0);
} }
pScene->SetCurrentAnimationStack(pAnimStack); pScene->SetCurrentAnimationStack(pAnimStack);
@ -884,10 +981,20 @@ static void ReadAnimations(RawModel &raw, FbxScene *pScene)
bool hasTranslation = false; bool hasTranslation = false;
bool hasRotation = false; bool hasRotation = false;
bool hasScale = false; bool hasScale = false;
bool hasMorphs = false;
RawChannel channel; RawChannel channel;
channel.nodeIndex = raw.GetNodeByName(pNode->GetName()); channel.nodeIndex = raw.GetNodeByName(pNode->GetName());
std::vector<FbxAnimCurve *> shapeAnimCurves;
FbxNodeAttribute *nodeAttr = pNode->GetNodeAttribute();
if (nodeAttr != nullptr && nodeAttr->GetAttributeType() == FbxNodeAttribute::EType::eMesh) {
// it's inelegant to recreate this same access class multiple times, but it's also dirt cheap...
FbxBlendShapesAccess blendShapes(pScene, dynamic_cast<FbxMesh *>(nodeAttr));
for (size_t shapeIx = 0; shapeIx < blendShapes.GetShapeCount(); shapeIx ++) {
shapeAnimCurves.push_back(blendShapes.GetAnimation(shapeIx, animIx));
}
}
for (FbxLongLong frameIndex = firstFrameIndex; frameIndex <= lastFrameIndex; frameIndex++) { for (FbxLongLong frameIndex = firstFrameIndex; frameIndex <= lastFrameIndex; frameIndex++) {
FbxTime pTime; FbxTime pTime;
pTime.SetFrame(frameIndex, eMode); pTime.SetFrame(frameIndex, eMode);
@ -915,9 +1022,15 @@ static void ReadAnimations(RawModel &raw, FbxScene *pScene)
channel.translations.push_back(toVec3f(localTranslation)); channel.translations.push_back(toVec3f(localTranslation));
channel.rotations.push_back(toQuatf(localRotation)); channel.rotations.push_back(toQuatf(localRotation));
channel.scales.push_back(toVec3f(localScale)); channel.scales.push_back(toVec3f(localScale));
for (auto curve : shapeAnimCurves) {
float weight = curve->Evaluate(pTime) * 0.01; // [0, 100] in FBX, [0, 1] in glTF
hasMorphs |= (fabs(weight) > epsilon);
channel.weights.push_back(weight);
}
} }
if (hasTranslation || hasRotation || hasScale) { if (hasTranslation || hasRotation || hasScale || hasMorphs) {
if (!hasTranslation) { if (!hasTranslation) {
channel.translations.clear(); channel.translations.clear();
} }
@ -927,16 +1040,20 @@ static void ReadAnimations(RawModel &raw, FbxScene *pScene)
if (!hasScale) { if (!hasScale) {
channel.scales.clear(); channel.scales.clear();
} }
if (!hasMorphs) {
channel.weights.clear();
}
animation.channels.emplace_back(channel); animation.channels.emplace_back(channel);
totalSizeInBytes += channel.translations.size() * sizeof(channel.translations[0]) + totalSizeInBytes += channel.translations.size() * sizeof(channel.translations[0]) +
channel.rotations.size() * sizeof(channel.rotations[0]) + channel.rotations.size() * sizeof(channel.rotations[0]) +
channel.scales.size() * sizeof(channel.scales[0]); channel.scales.size() * sizeof(channel.scales[0]) +
channel.weights.size() * sizeof(channel.weights[0]);
} }
if (verboseOutput) { if (verboseOutput) {
fmt::printf("\ranimation %d: %s (%d%%)", i, (const char *) animStackName, nodeIndex * 100 / nodeCount); fmt::printf("\ranimation %d: %s (%d%%)", animIx, (const char *) animStackName, nodeIndex * 100 / nodeCount);
} }
} }
@ -944,7 +1061,7 @@ static void ReadAnimations(RawModel &raw, FbxScene *pScene)
if (verboseOutput) { if (verboseOutput) {
fmt::printf( fmt::printf(
"\ranimation %d: %s (%d channels, %3.1f MB)\n", i, (const char *) animStackName, "\ranimation %d: %s (%d channels, %3.1f MB)\n", animIx, (const char *) animStackName,
(int) animation.channels.size(), (float) totalSizeInBytes * 1e-6f); (int) animation.channels.size(), (float) totalSizeInBytes * 1e-6f);
} }
} }

View File

@ -333,15 +333,15 @@ ModelData *Raw2Gltf(
fmt::printf("Animation '%s' has %lu channels:\n", animation.name.c_str(), animation.channels.size()); fmt::printf("Animation '%s' has %lu channels:\n", animation.name.c_str(), animation.channels.size());
} }
for (size_t j = 0; j < animation.channels.size(); j++) { for (size_t channelIx = 0; channelIx < animation.channels.size(); channelIx++) {
const RawChannel &channel = animation.channels[j]; const RawChannel &channel = animation.channels[channelIx];
const RawNode &node = raw.GetNode(channel.nodeIndex); const RawNode &node = raw.GetNode(channel.nodeIndex);
if (verboseOutput) { if (verboseOutput) {
fmt::printf( fmt::printf(
" Channel %lu (%s) has translations/rotations/scales: [%lu, %lu, %lu]\n", " Channel %lu (%s) has translations/rotations/scales/weights: [%lu, %lu, %lu, %lu]\n",
j, node.name.c_str(), channel.translations.size(), channelIx, node.name.c_str(), channel.translations.size(), channel.rotations.size(),
channel.rotations.size(), channel.scales.size()); channel.scales.size(), channel.weights.size());
} }
NodeData &nDat = require(nodesByName, node.name); NodeData &nDat = require(nodesByName, node.name);
@ -354,6 +354,9 @@ ModelData *Raw2Gltf(
if (!channel.scales.empty()) { if (!channel.scales.empty()) {
aDat.AddNodeChannel(nDat, *gltf->AddAccessorAndView(buffer, GLT_VEC3F, channel.scales), "scale"); aDat.AddNodeChannel(nDat, *gltf->AddAccessorAndView(buffer, GLT_VEC3F, channel.scales), "scale");
} }
if (!channel.weights.empty()) {
aDat.AddNodeChannel(nDat, *gltf->AddAccessorAndView(buffer, {CT_FLOAT, 1, "SCALAR"}, channel.weights), "weights");
}
} }
} }
@ -496,7 +499,7 @@ ModelData *Raw2Gltf(
mesh = meshIter->second.get(); mesh = meshIter->second.get();
} else { } else {
auto meshPtr = gltf->meshes.hold(new MeshData(rawSurface.name)); auto meshPtr = gltf->meshes.hold(new MeshData(rawSurface.name, rawSurface.defaultShapeDeforms));
meshByNodeName[nodeName] = meshPtr; meshByNodeName[nodeName] = meshPtr;
meshNode.SetMesh(meshPtr->ix); meshNode.SetMesh(meshPtr->ix);
mesh = meshPtr.get(); mesh = meshPtr.get();
@ -565,6 +568,21 @@ ModelData *Raw2Gltf(
accessor->min = toStdVec(rawSurface.bounds.min); accessor->min = toStdVec(rawSurface.bounds.min);
accessor->max = toStdVec(rawSurface.bounds.max); accessor->max = toStdVec(rawSurface.bounds.max);
for (int ii = 0; ii < rawSurface.defaultShapeDeforms.size(); ii ++) {
auto bufferView = gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER);
std::vector<Vec3f> result;
Bounds<float, 3> shapeBounds;
for (int jj = 0; jj < surfaceModel.GetVertexCount(); jj ++) {
const Vec3f &position = surfaceModel.GetVertex(jj).shapePositions[ii];
shapeBounds.AddPoint(position);
result.push_back(position);
}
accessor = gltf->AddAccessorWithView(*bufferView, GLT_VEC3F, result);
accessor->min = toStdVec(shapeBounds.min);
accessor->max = toStdVec(shapeBounds.max);
primitive->AddTarget(*accessor);
}
} }
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_NORMAL) != 0) { if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_NORMAL) != 0) {
const AttributeDefinition<Vec3f> ATTR_NORMAL("NORMAL", &RawVertex::normal, const AttributeDefinition<Vec3f> ATTR_NORMAL("NORMAL", &RawVertex::normal,

View File

@ -37,7 +37,8 @@ bool RawVertex::operator==(const RawVertex &other) const
(uv1 == other.uv1) && (uv1 == other.uv1) &&
(jointIndices == other.jointIndices) && (jointIndices == other.jointIndices) &&
(jointWeights == other.jointWeights) && (jointWeights == other.jointWeights) &&
(polarityUv0 == other.polarityUv0); (polarityUv0 == other.polarityUv0) &&
(shapePositions == other.shapePositions);
} }
size_t RawVertex::Difference(const RawVertex &other) const size_t RawVertex::Difference(const RawVertex &other) const

View File

@ -46,6 +46,8 @@ struct RawVertex
Vec4i jointIndices { 0, 0, 0, 0 }; Vec4i jointIndices { 0, 0, 0, 0 };
Vec4f jointWeights { 0.0f }; Vec4f jointWeights { 0.0f };
std::vector<Vec3f> shapePositions { };
bool polarityUv0; bool polarityUv0;
bool pad1; bool pad1;
bool pad2; bool pad2;
@ -166,6 +168,7 @@ struct RawSurface
std::vector<Vec3f> jointGeometryMins; std::vector<Vec3f> jointGeometryMins;
std::vector<Vec3f> jointGeometryMaxs; std::vector<Vec3f> jointGeometryMaxs;
std::vector<Mat4f> inverseBindMatrices; std::vector<Mat4f> inverseBindMatrices;
std::vector<float> defaultShapeDeforms;
bool discrete; bool discrete;
bool skinRigid; bool skinRigid;
}; };
@ -176,6 +179,7 @@ struct RawChannel
std::vector<Vec3f> translations; std::vector<Vec3f> translations;
std::vector<Quatf> rotations; std::vector<Quatf> rotations;
std::vector<Vec3f> scales; std::vector<Vec3f> scales;
std::vector<float> weights;
}; };
struct RawAnimation struct RawAnimation

View File

@ -10,9 +10,10 @@
#include "MeshData.h" #include "MeshData.h"
#include "PrimitiveData.h" #include "PrimitiveData.h"
MeshData::MeshData(std::string name) MeshData::MeshData(const std::string &name, const std::vector<float> &weights)
: Holdable(), : Holdable(),
name(std::move(name)) name(name),
weights(weights)
{ {
} }
@ -22,8 +23,12 @@ json MeshData::serialize() const
for (const auto &primitive : primitives) { for (const auto &primitive : primitives) {
jsonPrimitivesArray.push_back(*primitive); jsonPrimitivesArray.push_back(*primitive);
} }
return { json result = {
{ "name", name }, { "name", name },
{ "primitives", jsonPrimitivesArray } { "primitives", jsonPrimitivesArray }
}; };
if (!weights.empty()) {
result["weights"] = weights;
}
return result;
} }

View File

@ -20,7 +20,7 @@
struct MeshData : Holdable struct MeshData : Holdable
{ {
explicit MeshData(std::string name); MeshData(const std::string &name, const std::vector<float> &weights);
void AddPrimitive(std::shared_ptr<PrimitiveData> primitive) void AddPrimitive(std::shared_ptr<PrimitiveData> primitive)
{ {
@ -30,6 +30,7 @@ struct MeshData : Holdable
json serialize() const override; json serialize() const override;
const std::string name; const std::string name;
const std::vector<float> weights;
std::vector<std::shared_ptr<PrimitiveData>> primitives; std::vector<std::shared_ptr<PrimitiveData>> primitives;
}; };

View File

@ -39,6 +39,11 @@ void PrimitiveData::NoteDracoBuffer(const BufferViewData &data)
dracoBufferView = data.ix; dracoBufferView = data.ix;
} }
void PrimitiveData::AddTarget(const AccessorData &positions)
{
targetPositionAccessors.push_back(positions.ix);
}
void to_json(json &j, const PrimitiveData &d) { void to_json(json &j, const PrimitiveData &d) {
j = { j = {
{ "material", d.material }, { "material", d.material },
@ -48,7 +53,15 @@ void to_json(json &j, const PrimitiveData &d) {
if (d.indices >= 0) { if (d.indices >= 0) {
j["indices"] = d.indices; j["indices"] = d.indices;
} }
if (!d.targetPositionAccessors.empty()) {
json targets {};
for (int ii = 0; ii < d.targetPositionAccessors.size(); ii ++) {
targets.push_back({
{ "POSITION", d.targetPositionAccessors[ii] }
});
}
j["targets"] = targets;
}
if (!d.dracoAttributes.empty()) { if (!d.dracoAttributes.empty()) {
j["extensions"] = { j["extensions"] = {
{ KHR_DRACO_MESH_COMPRESSION, { { KHR_DRACO_MESH_COMPRESSION, {

View File

@ -31,6 +31,8 @@ struct PrimitiveData
void AddAttrib(std::string name, const AccessorData &accessor); void AddAttrib(std::string name, const AccessorData &accessor);
void AddTarget(const AccessorData &positions);
template<class T> template<class T>
void AddDracoAttrib(const AttributeDefinition<T> attribute, const std::vector<T> &attribArr) void AddDracoAttrib(const AttributeDefinition<T> attribute, const std::vector<T> &attribArr)
{ {
@ -59,6 +61,8 @@ struct PrimitiveData
const unsigned int material; const unsigned int material;
const MeshMode mode; const MeshMode mode;
std::vector<int> targetPositionAccessors { };
std::map<std::string, int> attributes; std::map<std::string, int> attributes;
std::map<std::string, int> dracoAttributes; std::map<std::string, int> dracoAttributes;