So I have been trying to write an FBX converter for our game engine recently and I have managed to get the meshes exported and coverted properly. But I seem to have a really hard time exporting bone pose and bone animations to our game engine format. The documentation for FBX SDK doesn't seem to help at all and I have been trying to look at the samples, although I don't get the concepts properly, I have managed to make some progress but still the end result does not seem t work. So I would like to explain what I do and hopefully someone can shed some light and provide some direction.
Like any typical game engine here is how my skinning and bone animation system works, to calculate the transform of any bone at any point of time in the animation, I use the following formula:
boneCurrentLocalTransform = boneInterpolatedLocalTransformAtTimeX * inverseBindPoseTransform;
To extract the bone structure, I use FbxScene GetPostCount and GetPose methods and search to find the pose that contains current mesh node. Using the matched pose I build my skeleton hierarchy. At this point I need to assign an inverseBindPoseTransform to each bone in the hierarchy. So what I do is to go through all the clusters in the mesh skin and calculate the inverseBindPose function using the following code I extracted from FBX samples:
FbxAMatrix GetGeometry(FbxNode* pNode)
{
constFbxVector4 lT = pNode->GetGeometricTranslation(FbxNode::eSourcePivot);
constFbxVector4 lR = pNode->GetGeometricRotation(FbxNode::eSourcePivot);
constFbxVector4 lS = pNode->GetGeometricScaling(FbxNode::eSourcePivot);
returnFbxAMatrix(lT, lR, lS);
}
void ComputeClusterDeformation(FbxMesh* pMesh,
FbxCluster* pCluster,
FbxAMatrix& lClusterRelativeInitPosition)
{
FbxAMatrix lReferenceGlobalInitPosition;
FbxAMatrix lClusterGlobalInitPosition;
FbxAMatrix lReferenceGeometry;
pCluster->GetTransformMatrix(lReferenceGlobalInitPosition);
// Multiply lReferenceGlobalInitPosition by Geometric Transformation
lReferenceGeometry = GetGeometry(pMesh->GetNode());
lReferenceGlobalInitPosition *= lReferenceGeometry;
// Get the link initial global position
pCluster->GetTransformLinkMatrix(lClusterGlobalInitPosition);
// Compute the initial position of the link relative to the reference.
lClusterRelativeInitPosition = lClusterGlobalInitPosition.Inverse() * lReferenceGlobalInitPosition;
}
So the inverse bindPose for a cluster would be
FbxAMatrix inversebindPoseMatrix;
ComputeClusterDeformation(m_mesh, cluster, inversebindPoseMatrix);
And then I convert to my own engine matrix format
FbxVector4 translation = inversebindPoseMatrix.GetT();
FbxQuaternion rotation = inversebindPoseMatrix.GetQ();
FbxVector4 scaling = inversebindPoseMatrix.GetS();
Matrix4 translationMatrix;
Matrix4 rotationMatrix;
Matrix4 scaleMatrix;
cml::matrix_translation(
translationMatrix,
Vector3(static_cast<float>(translation[0]), static_cast<float>(translation[1]), static_cast<float>(translation[2])));
cml::matrix_rotation_quaternion(
rotationMatrix,
Quaternion(static_cast<float>(rotation[0]), static_cast<float>(rotation[1]), static_cast<float>(rotation[2]), static_cast<float>(rotation[3])));
cml::matrix_scale(
scaleMatrix,
Vector3(static_cast<float>(scaling[0]), static_cast<float>(scaling[1]),static_cast<float>(scaling[2])));
Matrix4 inverseBindPoseTransform = translationMatrix * rotationMatrix * scaleMatrix;
To extract the animation for each bone at a specific time i use the following code
FbxTime time;
time.SetMilliSeconds(currentTime);
FbxAMatrix transform;
if (isRootBone)
{
transform = linkNode->EvaluateGlobalTransform(time);
}
else
{
transform = linkNode->EvaluateLocalTransform(time);
}
SkeletonAnimationKey keyValue;
// time in seconds
keyValue.m_time = currentTime * 0.001f;
FbxVector4 translation = transform.GetT();
FbxQuaternion rotation = transform.GetQ();
FbxVector4 scaling = transform.GetS();
keyValue.m_position[0] = static_cast<float>(translation[0]);
keyValue.m_position[1] = static_cast<float>(translation[1]);
keyValue.m_position[2] = static_cast<float>(translation[2]);
keyValue.m_rotation[0] = static_cast<float>(rotation[0]);
keyValue.m_rotation[1] = static_cast<float>(rotation[1]);
keyValue.m_rotation[2] = static_cast<float>(rotation[2]);
keyValue.m_rotation[3] = static_cast<float>(rotation[3]);
keyValue.m_rotation.normalize();
keyValue.m_scale[0] = static_cast<float>(scaling[0]);
keyValue.m_scale[1] = static_cast<float>(scaling[1]);
keyValue.m_scale[2] = static_cast<float>(scaling[2]);
Basically this is how the keys are generated for each bone node in the hierarchy. I interpolate between the keys at specific time to generate boneInterpolatedLocalTransformAtTimeX.
Alas after all these effort my mesh is still getting rendered messed up and it seems to be skinning issue. I am wondering if I am not calculating the bone animation transformations or bind poses properly.
One other thing to mention is that I exported vertices from the mesh directly without any manipulation. Basically what ever I get from GetControlPoints method of the mesh, I exported directly without multiplying againts any transformation.
I have also attached the fbx I am using to test + screen shot of the resulting skinned mesh during animation. Thanks in advance.
FBX file: https://drive.google.com/file/d/0B5Eh4ET-2pNQdU96cGFKNlhkeFU/edit?usp=sharing
I have posted the source code to an FBX viewer that performs the skinning. Remember that the bones apply skinning to contol points only. If you split the verts because of non-shared attributes, like texture coordinate seams, you will have to account for that.
https://code.google.com/p/fbxviewer/
You can also draw just the skeleton to see if they are positioned properly.
Can't find what you're looking for? Ask the community or share your knowledge.