I'm trying to implement skinning using skeletal animations stored in a Collada file, and while I managed to load it and render the model without skinning correctly, I can't figure out why when I apply my skinning algorithm all the parts get bunched up at the model's feet, or extremely deformed. The entire project is stored on GitHub for reference (the skinning branch).
I believe the vertex shader is correct since if I pass identity transforms to the bones I get my default pose model, it's calculating the bone transforms based on the skeletal animation in the .dae
file that's somehow broken. This is what my problem looks like, versus how the model looks like in the default pose:
I believe my problem is somewhere while applying the recursive bone transforms:
public void Update(double deltaSec)
{
if (CurrentAnimationName is null) return;
var anim = animations[CurrentAnimationName];
currentAnimationSec = (currentAnimationSec + deltaSec) % anim.Duration.TotalSeconds;
void calculateBoneTransforms(BoneNode boneNode, Matrix4x4 parentTransform)
{
var bone = anim.Bones.FirstOrDefault(b => b.Id == boneNode.Id);
var nodeTransform = bone?[TimeSpan.FromSeconds(currentAnimationSec)] ?? boneNode.Transform;
var globalTransform = parentTransform * nodeTransform;
if (boneNode.Id >= 0)
for (int meshIdx = 0; meshIdx < perMeshData.Length; ++meshIdx)
perMeshData[meshIdx].FinalBoneMatrices[boneNode.Id] = globalTransform * perMeshData[meshIdx].boneOffsetMatrices[boneNode.Id];
foreach (var child in boneNode.Children)
calculateBoneTransforms(child, globalTransform);
}
calculateBoneTransforms(rootBoneNode, Matrix4x4.Identity);
}
Or when building the recursive structure of bone data with their transforms:
BoneNode visitTransforms(Node node, Matrix4x4 mat)
{
var boneNode = new BoneNode
{
Children = new BoneNode[node.ChildCount],
Id = boneIds.TryGetValue(node.Name, out var id) ? id : -1,
Transform = Matrix4x4.Transpose(node.Transform.ToNumerics()),
};
mat = node.Transform.ToNumerics() * mat;
foreach (var meshIndex in node.MeshIndices)
transformsDictionary[scene.Meshes[meshIndex]] = mat;
int childIdx = 0;
foreach (var child in node.Children)
boneNode.Children[childIdx++] = visitTransforms(child, mat);
return boneNode;
}
rootBoneNode = visitTransforms(scene.RootNode, Matrix4x4.Identity);
I believe the bone to vertex weights are gathered and uploaded to the shader correctly, and that the final bone array uniform is uploaded correctly (but maybe not calculated correctly). I'm also not sure of the order of matrix multiplications and whether or not to transpose anything when uploading to the shader, though I've tried it both ways every attempt.
If anyone runs into a similar issue, my problem was that my keyframe bone transforms were being transposed compared to how the rest of the chain of transforms were calculated, so when I multiplied them everything went crazy. So, keep track of what matrices are left-handed and which are right-handed!