question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Better understanding of computing skinning matrices

See original GitHub issue

First of all, if this is not related at all to glTF & skinning and is more related to blender then i apologize and this issue can be closed.

I am currently trying to understand how the ‘final joint matrix’ (i.e. the matrix that each influenced vertex is multiplied by) is computed My current knowledge of how it’s calculated: jointMatrix = globalMatrix * inverseBindMatrix where globalMatrix is the joint’s local matrix multiplied by it’s parent global matrix and inverseBindMatrix = invert(globalMatrix)

Now consider a simple setup with just 2 joints: Joint 1: Local matrix = Identity matrix (as i want this joint to have a location of (0, 0, 0), no rotation and a scale of (1, 1, 1) Joint 2: Local matrix = Translation: (100, 50, 50), no rotation, scale(1, 1, 1) of course stored in a matrix but i just wrote it out this way for clarity.

Joint 1 has no parent as it’s the root joint, Joint 2 has Joint 1 as it’s parent

Now as i understand when a joint in rotated it uses it’s local translation as the origin of rotation instead of (0, 0, 0) and here’s where my confusion comes from because the math doesn’t seem to add up

Say Joint 2 was rotated by 45 degrees in the x axis, what i’d expect the final joint matrix to do when influenced vertices are multiplied by it is rotate each vertex 45 degrees in the x axis around the bone’s local translation (i.e. 100, 50, 50 in this case) that seems to be happening when i rotate the bone in blender but the resulting matrix when i do these computations doesn’t seem to take that into account and instead assumes rotation around (0, 0, 0)

Here’s sample code i wrote that would compute the final joint matrix for the above example:

import lombok.Getter;
import lombok.Setter;
import org.joml.Matrix4f;

public class JointTest {
    public static void main(String[] args) {
        Joint joint1 = new Joint();
        // identity matrix as root should be at position (0, 0, 0), have no rotation or scale
        joint1.setLocalMatrix(
                new Matrix4f()
        );
        Joint joint2 = new Joint();
        // joint 2 should have a position of (100, 50, 50) which should be it's rotation origin
        joint2.setLocalMatrix(
                new Matrix4f().translate(100, 50, 50)
        );
        joint2.setParent(joint1);
        joint2.computeAndSetIBM();

        Matrix4f animationMatrix = joint2.getLocalMatrix()
                .rotateX((float) Math.PI / 4f, new Matrix4f());
        joint2.setLocalMatrix(animationMatrix);

        Matrix4f globalMatrix = joint2.computeGlobalMatrix();
        Matrix4f inverseBindMatrix = joint2.getInverseBindMatrix();

        Matrix4f finalJointMatrix = globalMatrix.mul(inverseBindMatrix, new Matrix4f());
        System.out.println(finalJointMatrix);
    }

    @Getter
    @Setter
    private static class Joint {
        // Matrix4f is from JOML (column-major)
        private Matrix4f localMatrix;
        private Matrix4f inverseBindMatrix;
        private Joint parent;

        public void computeAndSetIBM() {
            Matrix4f global = computeGlobalMatrix();
            inverseBindMatrix = global.invert(new Matrix4f());
        }

        public Matrix4f computeGlobalMatrix() {
            Matrix4f globalMatrix = new Matrix4f(localMatrix);
            if (parent != null) {
                return globalMatrix.mul(parent.computeGlobalMatrix(), new Matrix4f());
            }
            return globalMatrix;
        }
    }
}

The result is:

 1.000E+0  0.000E+0  0.000E+0  0.000E+0
 0.000E+0  7.071E-1 -7.071E-1  5.000E+1
 0.000E+0  7.071E-1  7.071E-1 -2.071E+1
 0.000E+0  0.000E+0  0.000E+0  1.000E+0

Which if i understand correctly is not really a 45 degree rotation in the x axis around the origin (100, 50, 50), at least it does not seem correct but please do let me know if im wrong.

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:8 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
javaglcommented, Oct 3, 2022

The globalTransformOfNode refers to the node that the skin is attached to. This is also something that should always be the identity matrix in glTF 2.0.

Trying to figure out solutions by “reverse engineering” the existing code might not be the best approach, because some aspects of the implementation may be confusing, and there are some ‘gotchas’ in the part that renders the glTF. For example, the “default material” in the current state does not include skinning functionality, so by default, you won’t see the skinned result if there is a default material. For PBR materials, it should work, but the PBR implementation itself is far from perfect.

But of course, you may give it a try and see whether you can gain helpful insights from it.

0reactions
Suicolencommented, Oct 3, 2022

I agree with you that it’s not really the best approach, i’ll just take a quick look at it to see if i find anything useful from it. Thank you for all the replies, they actually helped quite a lot. I’ll close this issue as i have no other questions currently.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Chapter 3: Skin
With smooth skinning, each vertex in the mesh can be attached to more than one joint, each attachment affecting the vertex with a...
Read more >
Skinning - M. Cihan ÖZER
Skinning · Specify all DOF values for the skeleton · Recursively traverse through the hierarchy to compute world matrices · Use world matrices...
Read more >
WebGL Skinning
Skinning in graphics is the name given to moving a set of vertices based on the weighted influence of multiple matrices. That's pretty...
Read more >
Basics of Computer Animation—Skinning/Enveloping
The in-betweening, was once a job for apprentice animators. Splines accomplish these tasks automatically. However, the.
Read more >
Skinning
This works/looks better if each vertex corresponds to a fixed position on the skin surface of the character. • If there are m...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found