Blendshape tangets & normals, cleanup. (#35)
Turns out Maya was always including normals in the FBX export, they were just a bit trickier to get to than originally surmised. We need to go through the proper element access formalities that takes mapping and reference modes into account. Luckily we already have a helper class for this, so let's lean on that.
This commit is contained in:
parent
5786fc9f0b
commit
3eeebf4599
229
src/Fbx2Raw.cpp
229
src/Fbx2Raw.cpp
|
@ -470,151 +470,103 @@ private:
|
||||||
class FbxBlendShapesAccess
|
class FbxBlendShapesAccess
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* A target shape is on a 1:1 basis with the eventual glTF morph target, and is the object which contains the
|
||||||
|
* actual morphed vertex data.
|
||||||
|
*/
|
||||||
struct TargetShape
|
struct TargetShape
|
||||||
{
|
{
|
||||||
TargetShape(
|
explicit TargetShape(const FbxShape *shape, FbxDouble fullWeight) :
|
||||||
double fullWeight,
|
shape(shape),
|
||||||
const std::vector<FbxVector4> &positions,
|
fullWeight(fullWeight),
|
||||||
const std::vector<FbxVector4> &normals,
|
count(shape->GetControlPointsCount()),
|
||||||
const std::vector<FbxVector4> &tangents
|
positions(shape->GetControlPoints()),
|
||||||
) : fullWeight(fullWeight),
|
normals(FbxLayerElementAccess<FbxVector4>(shape->GetElementNormal(), shape->GetElementNormalCount())),
|
||||||
positions(positions),
|
tangents(FbxLayerElementAccess<FbxVector4>(shape->GetElementTangent(), shape->GetElementTangentCount()))
|
||||||
normals(normals),
|
|
||||||
tangents(tangents) {}
|
|
||||||
|
|
||||||
const double fullWeight;
|
|
||||||
const std::vector<FbxVector4> positions;
|
|
||||||
const std::vector<FbxVector4> normals;
|
|
||||||
const std::vector<FbxVector4> tangents;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BlendChannel {
|
|
||||||
explicit BlendChannel(FbxDouble defaultDeform) :
|
|
||||||
defaultDeform(defaultDeform)
|
|
||||||
{}
|
{}
|
||||||
const FbxDouble defaultDeform;
|
|
||||||
|
|
||||||
// one for each FbxShape
|
const FbxShape *shape;
|
||||||
std::vector<TargetShape> targetShapes {};
|
const FbxDouble fullWeight;
|
||||||
|
const unsigned int count;
|
||||||
// always the size of the scene's animation stack; can and will contain nulls
|
const FbxVector4 *positions;
|
||||||
std::vector<FbxAnimCurve *> animations {};
|
const FbxLayerElementAccess<FbxVector4> normals;
|
||||||
|
const FbxLayerElementAccess<FbxVector4> tangents;
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit FbxBlendShapesAccess(const FbxScene *scene, FbxMesh *mesh) :
|
/**
|
||||||
channels(extractChannels(scene, mesh))
|
* A channel collects a sequence (often of length 1) of target shapes.
|
||||||
|
*/
|
||||||
|
struct BlendChannel
|
||||||
|
{
|
||||||
|
BlendChannel(
|
||||||
|
FbxMesh *mesh,
|
||||||
|
const unsigned int blendShapeIx,
|
||||||
|
const unsigned int channelIx,
|
||||||
|
const FbxDouble deformPercent,
|
||||||
|
const std::vector<TargetShape> &targetShapes
|
||||||
|
) : mesh(mesh),
|
||||||
|
blendShapeIx(blendShapeIx),
|
||||||
|
channelIx(channelIx),
|
||||||
|
deformPercent(deformPercent),
|
||||||
|
targetShapes(targetShapes)
|
||||||
|
{}
|
||||||
|
|
||||||
|
FbxAnimCurve *ExtractAnimation(unsigned int animIx) const {
|
||||||
|
FbxAnimStack *stack = mesh->GetScene()->GetSrcObject<FbxAnimStack>(animIx);
|
||||||
|
FbxAnimLayer *layer = stack->GetMember<FbxAnimLayer>(0);
|
||||||
|
return mesh->GetShapeChannel(blendShapeIx, channelIx, layer, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
FbxMesh *const mesh;
|
||||||
|
|
||||||
|
const unsigned int blendShapeIx;
|
||||||
|
const unsigned int channelIx;
|
||||||
|
const std::vector<TargetShape> targetShapes;
|
||||||
|
|
||||||
|
const FbxDouble deformPercent;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit FbxBlendShapesAccess(FbxMesh *mesh) :
|
||||||
|
channels(extractChannels(mesh))
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
size_t GetChannelCount() const { return channels.size(); }
|
size_t GetChannelCount() const { return channels.size(); }
|
||||||
FbxDouble GetDefaultDeform(size_t channelIx) const {
|
const BlendChannel &GetBlendChannel(size_t channelIx) const {
|
||||||
return channels.at(channelIx).defaultDeform;
|
return channels.at(channelIx);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t GetTargetShapeCount(size_t channelIx) const { return channels[channelIx].targetShapes.size(); }
|
size_t GetTargetShapeCount(size_t channelIx) const { return channels[channelIx].targetShapes.size(); }
|
||||||
const TargetShape &GetTargetShape(size_t channelIx, size_t targetShapeIx) const {
|
const TargetShape &GetTargetShape(size_t channelIx, size_t targetShapeIx) const {
|
||||||
return channels.at(channelIx).targetShapes[targetShapeIx];
|
return channels.at(channelIx).targetShapes[targetShapeIx];
|
||||||
}
|
}
|
||||||
size_t GetAnimCount(size_t channelIx) const { return channels.at(channelIx).animations.size(); }
|
|
||||||
FbxAnimCurve *GetAnimation(size_t channelIx, size_t animIx) const {
|
FbxAnimCurve * GetAnimation(size_t channelIx, size_t animIx) const {
|
||||||
return channels.at(channelIx).animations[animIx];
|
return channels.at(channelIx).ExtractAnimation(animIx);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<BlendChannel> extractChannels(const FbxScene *scene, FbxMesh *mesh) const {
|
std::vector<BlendChannel> extractChannels(FbxMesh *mesh) const {
|
||||||
// acquire the regular control points from the mesh
|
std::vector<BlendChannel> channels;
|
||||||
const int controlPointsCount = mesh->GetControlPointsCount();
|
|
||||||
const FbxVector4 *meshPoints = mesh->GetControlPoints();
|
|
||||||
|
|
||||||
// acquire the normals, if they're present & make sure they're well-formed
|
|
||||||
FbxLayerElementArrayTemplate<FbxVector4>* meshNormals = nullptr;
|
|
||||||
if (!mesh->GetNormals(&meshNormals) || meshNormals->GetCount() != controlPointsCount) {
|
|
||||||
meshNormals = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// same, but tangents
|
|
||||||
FbxLayerElementArrayTemplate<FbxVector4>* meshTangents = nullptr;
|
|
||||||
if (!mesh->GetTangents(&meshTangents) || meshTangents->GetCount() != controlPointsCount) {
|
|
||||||
meshTangents = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<BlendChannel> results;
|
|
||||||
for (int shapeIx = 0; shapeIx < mesh->GetDeformerCount(FbxDeformer::eBlendShape); shapeIx++) {
|
for (int shapeIx = 0; shapeIx < mesh->GetDeformerCount(FbxDeformer::eBlendShape); shapeIx++) {
|
||||||
auto *fbxBlendShape = static_cast<FbxBlendShape *>(mesh->GetDeformer(shapeIx, FbxDeformer::eBlendShape));
|
auto *fbxBlendShape = static_cast<FbxBlendShape *>(mesh->GetDeformer(shapeIx, FbxDeformer::eBlendShape));
|
||||||
if (fbxBlendShape == nullptr) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (int channelIx = 0; channelIx < fbxBlendShape->GetBlendShapeChannelCount(); ++channelIx) {
|
for (int channelIx = 0; channelIx < fbxBlendShape->GetBlendShapeChannelCount(); ++channelIx) {
|
||||||
FbxBlendShapeChannel *channel = fbxBlendShape->GetBlendShapeChannel(channelIx);
|
FbxBlendShapeChannel *fbxChannel = fbxBlendShape->GetBlendShapeChannel(channelIx);
|
||||||
unsigned int targetShapeCount = static_cast<unsigned int>(channel->GetTargetShapeCount());
|
|
||||||
if (targetShapeCount < 1) {
|
if (fbxChannel->GetTargetShapeCount() > 0) {
|
||||||
continue;
|
std::vector<TargetShape> targetShapes;
|
||||||
|
const double *fullWeights = fbxChannel->GetTargetShapeFullWeights();
|
||||||
|
for (int targetIx = 0; targetIx < fbxChannel->GetTargetShapeCount(); targetIx ++) {
|
||||||
|
FbxShape *fbxShape = fbxChannel->GetTargetShape(targetIx);
|
||||||
|
targetShapes.push_back(TargetShape(fbxShape, fullWeights[targetIx]));
|
||||||
|
}
|
||||||
|
channels.push_back(BlendChannel(mesh, shapeIx, channelIx, fbxChannel->DeformPercent * 0.01, targetShapes));
|
||||||
}
|
}
|
||||||
BlendChannel shape(channel->DeformPercent * 0.01f);
|
|
||||||
|
|
||||||
std::vector<std::pair<double, FbxShape *>> targetShapes (targetShapeCount);
|
|
||||||
double *fullWeights = channel->GetTargetShapeFullWeights();
|
|
||||||
for (int targetShapeIx = 0; targetShapeIx < targetShapes.size(); targetShapeIx++) {
|
|
||||||
FbxShape *fbxShape = channel->GetTargetShape(targetShapeIx);
|
|
||||||
assert(fbxShape->GetControlPointsCount() == controlPointsCount);
|
|
||||||
|
|
||||||
// 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> positions, normals, tangents;
|
|
||||||
// fetch positions
|
|
||||||
const FbxVector4 *shapePoints = fbxShape->GetControlPoints();
|
|
||||||
for (int pointIx = 0; pointIx < controlPointsCount; pointIx ++) {
|
|
||||||
positions.push_back(shapePoints[pointIx] - meshPoints[pointIx]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if 0 // I've never seen anything but zero normals and tangents come out of this. Revisit later.
|
|
||||||
// maybe fetch normals
|
|
||||||
if (meshNormals) {
|
|
||||||
FbxLayerElementArrayTemplate<FbxVector4>* fbxNormals = nullptr;
|
|
||||||
if (fbxShape->GetNormals(&fbxNormals)) {
|
|
||||||
for (int pointIx = 0; pointIx < controlPointsCount; pointIx ++) {
|
|
||||||
normals.push_back(fbxNormals->GetAt(pointIx) - meshNormals->GetAt(pointIx));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// maybe fetch tangents
|
|
||||||
if (meshTangents) {
|
|
||||||
FbxLayerElementArrayTemplate<FbxVector4>* fbxTangents = nullptr;
|
|
||||||
if (fbxShape->GetTangents(&fbxTangents)) {
|
|
||||||
for (int pointIx = 0; pointIx < controlPointsCount; pointIx ++) {
|
|
||||||
tangents.push_back(fbxTangents->GetAt(pointIx) - meshTangents->GetAt(pointIx));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// finally combine all this into a TargetShape and add it to our work-in-progress BlendChannel
|
|
||||||
shape.targetShapes.push_back(
|
|
||||||
TargetShape(fullWeights[targetShapeIx], positions, normals, tangents)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// go through all the animations in the scene and figure out their relevance to this mesh
|
|
||||||
size_t animationCount = static_cast<size_t>(scene->GetSrcObjectCount<FbxAnimStack>());
|
|
||||||
std::vector<FbxAnimCurve *>animations(animationCount);
|
|
||||||
for (int animIx = 0; animIx < animationCount; animIx++) {
|
|
||||||
auto *pAnimStack = scene->GetSrcObject<FbxAnimStack>(animIx);
|
|
||||||
auto *layer = pAnimStack->GetMember<FbxAnimLayer>(0);
|
|
||||||
if (pAnimStack->GetMemberCount() > 1) {
|
|
||||||
fmt::print("Warning: ignoring animation layers 1+ in stack %s", pAnimStack->GetName());
|
|
||||||
}
|
|
||||||
|
|
||||||
// note that some of these will be null here, which is fine; the critical part is that the
|
|
||||||
// indices maintain parity with the scene-wide animation stack
|
|
||||||
shape.animations.push_back(mesh->GetShapeChannel(shapeIx, channelIx, layer, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
results.push_back(shape);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return results;
|
return channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const std::vector<BlendChannel> channels;
|
const std::vector<BlendChannel> channels;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -667,7 +619,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);
|
const FbxBlendShapesAccess blendShapes(pMesh);
|
||||||
|
|
||||||
if (verboseOutput) {
|
if (verboseOutput) {
|
||||||
fmt::printf(
|
fmt::printf(
|
||||||
|
@ -715,15 +667,15 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std:
|
||||||
|
|
||||||
rawSurface.blendChannels.clear();
|
rawSurface.blendChannels.clear();
|
||||||
std::vector<const FbxBlendShapesAccess::TargetShape *> targetShapes;
|
std::vector<const FbxBlendShapesAccess::TargetShape *> targetShapes;
|
||||||
for (size_t shapeIx = 0; shapeIx < blendShapes.GetChannelCount(); shapeIx ++) {
|
for (size_t channelIx = 0; channelIx < blendShapes.GetChannelCount(); channelIx ++) {
|
||||||
for (size_t targetIx = 0; targetIx < blendShapes.GetTargetShapeCount(shapeIx); targetIx ++) {
|
for (size_t targetIx = 0; targetIx < blendShapes.GetTargetShapeCount(channelIx); targetIx ++) {
|
||||||
const FbxBlendShapesAccess::TargetShape &shape = blendShapes.GetTargetShape(shapeIx, targetIx);
|
const FbxBlendShapesAccess::TargetShape &shape = blendShapes.GetTargetShape(channelIx, targetIx);
|
||||||
targetShapes.push_back(&shape);
|
targetShapes.push_back(&shape);
|
||||||
|
|
||||||
rawSurface.blendChannels.push_back(RawBlendChannel {
|
rawSurface.blendChannels.push_back(RawBlendChannel {
|
||||||
static_cast<float>(blendShapes.GetDefaultDeform(shapeIx)),
|
static_cast<float>(blendShapes.GetBlendChannel(channelIx).deformPercent),
|
||||||
!shape.normals.empty(),
|
shape.normals.LayerPresent(),
|
||||||
!shape.tangents.empty()
|
shape.tangents.LayerPresent(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -828,13 +780,20 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std:
|
||||||
vertex.blendSurfaceIx = rawSurfaceIndex;
|
vertex.blendSurfaceIx = rawSurfaceIndex;
|
||||||
for (const auto *targetShape : targetShapes) {
|
for (const auto *targetShape : targetShapes) {
|
||||||
RawBlendVertex blendVertex;
|
RawBlendVertex blendVertex;
|
||||||
// the morph target positions must be transformed just as with the vertex positions above
|
// the morph target data must be transformed just as with the vertex positions above
|
||||||
blendVertex.position = toVec3f(transform.MultNormalize(targetShape->positions[controlPointIndex]));
|
const FbxVector4 &shapePosition = transform.MultNormalize(targetShape->positions[controlPointIndex]);
|
||||||
if (!targetShape->normals.empty()) {
|
blendVertex.position = toVec3f(shapePosition - fbxPosition);
|
||||||
blendVertex.normal = toVec3f(targetShape->normals[controlPointIndex]);
|
if (targetShape->normals.LayerPresent()) {
|
||||||
|
FbxVector4 normal = targetShape->normals.GetElement(
|
||||||
|
polygonIndex, polygonVertexIndex, controlPointIndex, FbxVector4(0.0f, 0.0f, 0.0f, 0.0f), inverseTransposeTransform, true);
|
||||||
|
normal -= fbxNormal;
|
||||||
|
blendVertex.normal = toVec3f(normal);
|
||||||
}
|
}
|
||||||
if (!targetShape->tangents.empty()) {
|
if (targetShape->tangents.LayerPresent()) {
|
||||||
blendVertex.tangent = toVec4f(targetShape->tangents[controlPointIndex]);
|
FbxVector4 tangent = targetShape->tangents.GetElement(
|
||||||
|
polygonIndex, polygonVertexIndex, controlPointIndex, FbxVector4(0.0f, 0.0f, 0.0f, 0.0f), inverseTransposeTransform, true);
|
||||||
|
tangent -= fbxTangent;
|
||||||
|
blendVertex.tangent = toVec4f(tangent);
|
||||||
}
|
}
|
||||||
vertex.blends.push_back(blendVertex);
|
vertex.blends.push_back(blendVertex);
|
||||||
}
|
}
|
||||||
|
@ -1129,14 +1088,14 @@ static void ReadAnimations(RawModel &raw, FbxScene *pScene)
|
||||||
FbxNodeAttribute *nodeAttr = pNode->GetNodeAttribute();
|
FbxNodeAttribute *nodeAttr = pNode->GetNodeAttribute();
|
||||||
if (nodeAttr != nullptr && nodeAttr->GetAttributeType() == FbxNodeAttribute::EType::eMesh) {
|
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...
|
// it's inelegant to recreate this same access class multiple times, but it's also dirt cheap...
|
||||||
FbxBlendShapesAccess blendShapes(pScene, static_cast<FbxMesh *>(nodeAttr));
|
FbxBlendShapesAccess blendShapes(static_cast<FbxMesh *>(nodeAttr));
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
for (size_t channelIx = 0; channelIx < blendShapes.GetChannelCount(); channelIx++) {
|
for (size_t channelIx = 0; channelIx < blendShapes.GetChannelCount(); channelIx++) {
|
||||||
auto curve = blendShapes.GetAnimation(channelIx, animIx);
|
FbxAnimCurve *curve = blendShapes.GetAnimation(channelIx, animIx);
|
||||||
float influence = (curve != nullptr) ? curve->Evaluate(pTime) : 0; // 0-100
|
float influence = (curve != nullptr) ? curve->Evaluate(pTime) : 0; // 0-100
|
||||||
|
|
||||||
int targetCount = static_cast<int>(blendShapes.GetTargetShapeCount(channelIx));
|
int targetCount = static_cast<int>(blendShapes.GetTargetShapeCount(channelIx));
|
||||||
|
|
Loading…
Reference in New Issue