Home - 3D IK

Skinned 3D Inverse Kinematics

Anthony Nguyen

Animated Gif of IK arm interaction

This is the Final Project I worked on in CSCE 489, a Special Topics class on Computer Animation taught in Fall 2020 by Dr. Sueda. It's an implementation of inverse kinematics in three dimensions with a ball and socket joint at the shoulder, and a hinge joint at the elbow. The aim was to get inverse kinematics working beyond two dimensions and with more degrees of freedom, while also using the solved angles to change the transforms of the bones for a skinned mesh.

Moving IK from 2D to 3D

This 3D IK problem is solved using a combination of gradient descent with backtracking line search for the general solving, and Newton’s method for refinements. This is an extension of the two dimensional IK solver we implemented as an assignment which only had to deal with a single angle for each segment. With this problem now in three dimensions, the number of angles for each segment depended on the range of motion it would have. The shoulder would have three angles to solve for and the elbow would have an additional angle to consider in this implementation on an arm. The original 2D implementation this is based on used 3x3 transformation matrices when calculating the end location of all the links in order to minimize the distance towards the end-effector. This would not be sufficient in three dimensions, so the first thing to deal with was converting the algorithm to use 4x4 transformation matrices so that there would be a homogeneous w coordinate and all three dimensions would be represented correctly when all the matrices were multiplied to arrive at the final end-effector transform.

Rotation Matrix Setup

m_R0Z << cos(theta(2)), -sin(theta(2)), 0, 0, sin(theta(2)), cos(theta(2)), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1; m_R0Y << cos(theta(1)), 0, sin(theta(1)), 0, 0, 1, 0, 0, -sin(theta(1)), 0, cos(theta(1)), 0, 0, 0, 0, 1; m_R0X << 1, 0, 0, 0, 0, cos(theta(0)), -sin(theta(0)), 0, 0, sin(theta(0)), cos(theta(0)), 0, 0, 0, 0, 1; m_R1Z << cos(theta(3)), -sin(theta(3)), 0, 0, sin(theta(3)), cos(theta(3)), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1;

Endpoint World Space Transform

m_XWorld = m_T0 * m_R0Z * m_R0Y * m_R0X * m_T1 * m_R1Z * m_XLocal;

The derivatives are calculated in the same way as before, just applying them to the bigger matrix. When generating the Hessian matrix, 4x1 blocks are used instead of 3x1 blocks, again, in a similar way to the original 2D implementation. In my implementation, using a rotation order of ZYX worked the best.

Applying 3D IK to Skinned Meshes

Once the IK problem was solved, what was left were a bunch of angles that needed to be used in some fashion. With the skinned mesh implementation we were using, that meant that the new angles needed to be used in the transforms for the upper arm and lower arm bones. This was done by “intercepting” the bone hierarchy at the shoulder, applying the three ZYX shoulder angles to the transform matrix for that bone, and propagating downward. For the elbow, the transform was calculated by translating by the distance between the shoulder and elbow positions from the bind pose before applying the singular Z axis rotation. The bone hierarchy was simplified at the hands to not necessitate any more propagation of transforms, since translation data from the point of “interception” ends up being in correct in the original skin keyframes. However, this propagation can continue by using the keyframe’s quaternion rotations, while translating by the distance between the child and parent bones to maintain the hierarchical spacing.

Image of hand bone weighting

The simplified skinning and bone weighting was accomplished in Blender and exported as an FBX. It was then converted into an OBJ and the correct data format using Dr. Sueda’s fbx-extract program.

End Effector Manipulation

The original 2D IK assignment projected the cursor position into world space to get where to position the end effector, which was intuitive for the two dimensions of a screen, but something I found clunky to work with in three dimensions, even with deprojection. To fix this, I used a manipulation scheme where holding a button and dragging would move the end effector in a specific axis. In this case, holding q would allow X axis translation, w would allow translation on the Y axis, and e would allow translation on the Z axis.

Animated GIF of end effector manipulation

Libraries and Packages Used

  • OpenGL - Graphics library
  • GLFW - Windowing library
  • GLM - Mathematics library for graphics
  • Eigen - Robust matrix library
  • Blender - 3D content authoring tool
  • fbx-extract - Tool to convert .fbx files into .obj files with correct skinning info data
  • Mixamo - Library of models and animations
  • Gatsby.js - Framework for building this static site
  • react-spring - UI animation library used for the parallax effect on this page

Additional Images