1

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.

Monteven
  • 11
  • 1
  • 2

0 Answers0