I have some models and skinning matrices for these models that works in WebGL (the animation and skinning deforms correctly). However, I have tried to replicate this using the FBX SDK/similar exporters and have been unable to.
I've tried to generate a nested skeleton, accounting for the object space transform being in world space as joints have inherited transforms but I was unable to make it work this way.
I also considered trying to set the skinning matrices directly using the same code as the WebGL method (animationMatrix * inverseObjectSpaceTransformMatrix) with the vertex shader code but wasn't sure if this was possible - if this method was used, how could it be imported into other programs like blender that doesn't support this? The ideal would be a method that can take the WebGL skinning matrix system and export to a skeleton for use in other programs like blender - using the FBX SDK is just an example of what I'm trying at the moment.
Here's a sample of the data I use to generate the skinning matrices:
b_pedestal:
inverse_object_space_transform:
r=[0.0, 0.0, 0.0, 1.0],
t=[0.0, 0.0, 0.0]
b_pelvis:
inverse_object_space_transform:
r=[0.5, 0.5, 0.5, 0.5],
t=[-1.034437, -0.0118, -0.0]
b_utility:
inverse_object_space_transform:
r=[0.0, 0.0, 0.0, 1.0],
t=[0.0, 0.0, 0.0]
b_l_thigh:
inverse_object_space_transform:
r=[-0.540589, 0.495971, -0.505714, 0.453904],
t=[-0.992615, 0.051638, 0.184923]
b_r_thigh:
inverse_object_space_transform:
r=[-0.505715, -0.453904, 0.540589, 0.495971],
t=[0.992615, -0.051638, -0.184923]
b_spine_1:
inverse_object_space_transform:
r=[0.5, 0.5, 0.5, 0.5],
t=[-1.091035, -0.0118, -0.0]
b_l_calf:
inverse_object_space_transform:
r=[-0.581244, 0.447637, -0.464801, 0.495718],
t=[-0.540767, -0.041656, 0.184923]
b_r_calf:
inverse_object_space_transform:
r=[-0.464801, -0.495718, 0.581244, 0.447637],
t=[0.540767, 0.041655, -0.184923]
b_spine_2:
inverse_object_space_transform:
r=[0.463693, 0.533844, 0.533844, 0.463693],
t=[-1.177534, -0.178358, -0.0]
The WebGL code that generates the skinning matrices:
for (var nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++)
{
var inverseObjectSpaceTransform = inverseObjectSpaceTransforms[nodeIndex];
var translationScale = inverseObjectSpaceTransform.ts;
var scale = translationScale[3];
var rotation = inverseObjectSpaceTransform.r;
var translation = [translationScale[0], translationScale[1], translationScale[2]];
var inverseObjectSpaceTransformSRT = new TransformSRT(scale, rotation, translation);
var inverseObjectSpaceTransformMatrix = mat4.create();
inverseObjectSpaceTransformSRT.setMatrix(inverseObjectSpaceTransformMatrix);
inverseObjectSpaceTransformMatrices.push(inverseObjectSpaceTransformMatrix);
var node = nodes[nodeIndex];
var parentNodeIndex = node.parent_node_index;
parentNodeIndices.push(parentNodeIndex);
}
for (var nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++)
{
var frameMatrix = frameMatrices[nodeIndex];
var animationMatrix = animationMatrices[nodeIndex];
var inverseObjectSpaceTransformMatrix = inverseObjectSpaceTransformMatrices[nodeIndex];
var parentNodeIndex = parentNodeIndices[nodeIndex];
if (parentNodeIndex >= 0)
{
Spasm.assert(parentNodeIndex < nodeIndex);
var parentAnimationMatrix = animationMatrices[parentNodeIndex];
mat4.multiply(animationMatrix, parentAnimationMatrix, frameMatrix);
}
else
{
mat4.copy(animationMatrix, frameMatrix);
}
mat4.multiply(tempMatrix0, animationMatrix, inverseObjectSpaceTransformMatrix);
mat4.transpose(tempMatrix0, tempMatrix0);
transformBuffer.set(tempMatrix0.subarray(0, 12), nodeIndex * 12); // collapsed skinning matrices
}
and the vertex shader:
mat4 get_bone_transform(int bone_index) {
int stride_bone_index = bone_index * 3;
vec4 i0 = u_skinning_matrices[stride_bone_index + 0];
vec4 i1 = u_skinning_matrices[stride_bone_index + 1];
vec4 i2 = u_skinning_matrices[stride_bone_index + 2];
vec4 i3 = vec4(0.0, 0.0, 0.0, 1.0);
mat4 bone_transform = mat4(
vec4(i0.x, i1.x, i2.x, i3.x),
vec4(i0.y, i1.y, i2.y, i3.y),
vec4(i0.z, i1.z, i2.z, i3.z),
vec4(i0.w, i1.w, i2.w, i3.w)
);
return bone_transform;
}
mat4 skinning_transform = (get_bone_transform(blend_indices[0]) * a_blendweight[0]);
skinning_transform += (get_bone_transform(blend_indices[1]) * a_blendweight[1]);
skinning_transform += (get_bone_transform(blend_indices[2]) * a_blendweight[2]);
skinning_transform += (get_bone_transform(blend_indices[3]) * a_blendweight[3]);
mat4 model_view_matrix = u_view_matrix;// * u_model_matrix;
mat4 camera_matrix = u_projection_matrix * model_view_matrix;
vec4 position_transformed = vec4(
(a_position.x * u_position_scale.x) + u_position_offset.x,
(a_position.y * u_position_scale.y) + u_position_offset.y,
(a_position.z * u_position_scale.z) + u_position_offset.z,
1.0
);
vec4 position_skinned = vec4((skinning_transform * position_transformed).xyz, 1.0);
This is my attempt in FBX (python wrapper) to generate a skeleton:
for nodeIndex in range(self.node_count):
nodeatt = fbx.FbxSkeleton.Create(model.scene, f"skeleton_{nodeIndex}")
nodeatt.SetSkeletonType(fbx.FbxSkeleton.eLimbNode)
joint = fbx.FbxNode.Create(model.scene, f"{self.names[nodeIndex]}_{nodeIndex}")
joint.SetNodeAttribute(nodeatt)
bone_nodes.append(joint)
parentNodeIndex = self.mParents[nodeIndex]
if parentNodeIndex >= 0:
bone_nodes[parentNodeIndex].AddChild(bone_nodes[nodeIndex])
else:
# root is first bone always
rootnodeatt = fbx.FbxSkeleton.Create(model.scene, 'rootSkeleton')
rootnodeatt.SetSkeletonType(fbx.FbxSkeleton.eRoot)
root = fbx.FbxNode.Create(model.scene, 'rootNode')
root.AddChild(bone_nodes[0])
root.SetNodeAttribute(rootnodeatt)
model.scene.GetRootNode().AddChild(root)
egt = joint.EvaluateGlobalTransform()
fbx_transform = fbx.FbxAMatrix()
fbx_transform.SetIdentity()
transform = self.mTransforms[nodeIndex]
rotation = transform.get_euler_rotation() # already inverted to be non-inverted object space transform
translation = transform.get_translation()
fbx_transform.SetR(fbx.FbxVector4(rotation[0], rotation[1], rotation[2]))
fbx_transform.SetT(fbx.FbxVector4(translation[0], translation[1], translation[2]))
fix = egt.Inverse() * fbx_transform
joint.LclTranslation.Set(fbx.FbxDouble3(fix.GetT()[0], fix.GetT()[1], fix.GetT()[2]))
joint.LclRotation.Set(fbx.FbxDouble3(fix.GetR()[0], fix.GetR()[1], fix.GetR()[2]))
This is the FBX attempt in blender, where the positions are correct but the bone rotations are not.