IK Solver From Scratch In Unity

In this post, we will cover a really simple (CCD) implementation of inverse kinematics.

We’ll structure this with the typical format of waxing poetic, then some code samples and an embedded Unity WebGL demo.

What is Inverse Kinematics?

I’m assuming you’re here because you’re already familiar with the subject. But if not, IK is basically “a way to figure out how to make limbs intentionally reach a specific location”. In this post, we’re specifically focused on 3D simulation and games, but the fundamentals can also be applied to robotics.

Prerequisite Terms

This post has Unity C# code and a Unity demo – so I’m going to assume readers are already familiar with 3D transformations and 3D transform hierarchies. But I’ll do some short descriptions on a few extra niche terms.

Kinematics

The first is “kinematics“. Wikipedia’s definition is:

Kinematics is a subfield of physics, developed in classical mechanics, that describes the motion of points, bodies (objects), and systems of bodies (groups of objects) without considering the forces that cause them to move. Kinematics, as a field of study, is often referred to as the “geometry of motion” and is occasionally seen as a branch of mathematics.

So in a simulation, imagine systems solving the position and rotation of transforms. This will involve various math equations and physics simulations. Basically, ignore the physics simulation portion, or at least ignore the concern that simulated things can physically interact with each other. The rest of what you have is the study of kinematics.
FYI: If you’re concerned with the physics simulation and interaction part, that’s called kinetics.

Forward Kinematics

When modeling an object using transforms, we often have a situation where we have a bunch of sub-objects, which can be parented to each other in a hierarchy. The child object’s transformation will then be dependent on the transformation of its parent. Where objects have a local coordinate position, but their global position depends on both their local position and their parent’s coordinate frame.

An example of evaluating a series of transforms by first evaluating the transform’s parent and then applying its local position and rotation on top.
Note how we must evaluate Node A before we can evaluate the global transformation of its child, Node B.

This is called forward kinematics, where we have a 3D scene and evaluate how everything is placed by evaluating these dependencies in a forward motion (from the root of a system to the end nodes) to see where everything is.

Kinematics Chain

A kinematic chain is a transform hierarchy that goes in a straight line instead of having any branches. We can imagine linkages that go from one transform-origin to another.

Target

The thing we want to move the end of our kinematics chain (the end effector) to.

End Effector

Fancy parlance for the end of the kinematic chain. This is the point on our kinematics chain where we want to touch the target.

See the Wikipedia page for more details. While the term gets its roots in robotics, when talking about IK, it doesn’t have to be specifically for robotics.

Think of this as the “business end” that does work (e.g., mill/drill bit, robotic gripper, hand, probe, motion platform, etc.). And our goal is to move it onto what we want to perform work on.

In this post, I will be shortening the term “end effector” to EE.

Bones/Nodes/Transforms

I’m going to apologize ahead of time for mixing up the terminology. For this topic, I will use the terms bones, nodes, and transforms interchangeably. It’s just going to be way easier for me.

  • Transform tends to be used in a mathematical context.
  • Nodes is more of a comp-sci term for packets of data that can link to each other.
  • Bones is usually used by 3D artists and animators because that’s the term often used in animation packages.

In the end, it’s more-or-less all the same to me.

Doesn’t Unity Already Have IK?

It should be mentioned that Unity’s Mecanim system (the Animator behavior) has built-in IK for humanoid rigs: human-like characters that have a pair of arms and a pair of legs. And I’d recommend using them when you can.

So, why are we discussing the technical foundation to roll out our own IK implementation? There are many types of characters and elements you may want to implement IK for that don’t follow the typical humanoid mold.

For example, your character could have (and be rigged and animated with) even more than a single pair of arms. Perhaps not even human arms. Perhaps not even an even number of arms.
In the examples below, I’m not claiming these did (or didn’t) use IK, just examples off the top of my head of what could be interesting character use cases.

If you’re doing legs, they could have extra pairs of legs, possibly with non-human anatomy.

Or maybe you’re just doing IK on things that simply are not traditional limbs.

Okay, enough of me ripping off images from the internet. I think you get the idea: you’d be missing out on a lot of potential if you limited what you could IK rig in real time if you limit things only to what the Mecanim humanoid solver supports.

The next question is, “what about the other 3rd party stuff, such as the solutions on the asset store?” Sure, if it works just fine, then good enough is good enough. But there’s something enabling about having the intuition to roll out your own solution. So let’s continue.

Cyclic Coordinate Descent

There are many ways IK can be done. Any algorithm that takes a kinematic chain and gets an end effector to reach a target is a form of IK. But we’re specifically going to cover a strategy called Cyclic Coordinate Descent (CCD) because it’s intuitive, direct, and effective.

  • For a certain (arbitrary) number of times (the iteration count):
    • For each node (in the kinematic chain):
      • Rotate the node to make the EE closer to the target.

For each iteration, the EE will get closer to the target. Do enough iterations, and the EE will eventually be close enough to the target that it’s considered at the target. What exactly defines “close enough” depends on your use case.

To figure out how much to rotate a node, we calculate the vectors from that node’s origin to the EE, and the vector from the node’s origin to the target. And then, we figure out what rotation (applied to the node) would be to move the EE so those two vectors overlap. And we rotate the node in that direction.

IK example. Using a greediness of 0.33 and 5 iterations.

In the animation above, we see we’re getting ever-closer to getting the EE on the target, with every node processed and every iteration.

Greediness

There’s also the question of how much to rotate the node. I’m calling this the greediness value because a higher greediness should (in theory) solve the chain faster, but it may not because of edge cases. It may also result in unnatural chain shapes. We can play around with the greediness value and see what works best for an IK chain. This will change the shape of our solved IK chain and will change how fast and how close we solve the chain.

Multiple greediness values – for 5 iterations.

Note what’s going on with the greediness of 1.0. Because it tries to solve the solution immediately, it quickly gets locked in an “uh-oh-spaghettios situation” (or, as adults might call it, a degenerate case or a singularity).

Iterations

There’s also the question of how many times to iterate. This will depend on your needs and your IK chain setup. Something that may have stood out in the previous video examples was that the EE never actually reached the target. The examples get progressively closer with every iteration, but the max limit of five iterations ends before that happens. If it appears we need more processing, we can always raise the limit of maximum allowed iterations.

A few greediness examples with 20 iterations instead of 5.

While additional iterations may result in a closer and more-accurate solution, it comes with the trade-off of spending more effort and time computing the solution.

And, of course, we can always check if the EE is close enough to the target and early exit. Usually, we’ll check this at the end of an iteration. You may have noticed that I could have stopped the bottom left example at around iteration 18, and the bottom right at around iteration 14.

Incremental Greediness

A simple idea is to incrementally increase the greediness as you progress through iterations. That way, it had a chance to evaluate things in a non-greedy way but could hurry up the progress with incremental urgency as we get closer to the end of our allotted iterations.

In the video below, note that greediness values on the right side increase every time we start a new iteration.
The left side contains the same starting greediness as the right but doesn’t increase.

A demo of the greediness incrementally increasing as we get closer to the end.
The top has a starting greediness of 0.25, and the bottom starts with 0.5. The left side has static greediness, while the right will increment every iteration.

Starting Position and Restart

There’s a question of what to do with the IK chain between application frames. We can either leave the rotations where the solver last left the transforms, or we can reset it to some base pose and restart from scratch each time.

If we leave the transforms alone and don’t reset them, we’re essentially performing the IK over multiple frames. If we reset the IK chain to a pose every frame, we can better control what the final solution looks like every frame, but may need to spend more iterations to solve the IK in a single frame.

Sample Code

For our sample IK system, we’ll represent each bone as this Node class shown below. We’ll assume the chain travels along the Z axis. We’re storing the Transform to manipulate, and the length of the bone.

public class Node
{ 
    // The origin on the bone.
    public Transform transform;

    // The length of the bone.
    internal float len;

    public Node(Transform t, float len)
    { 
        this.transform = t;
        this.len = len;
    }
}

An IK chain will be represented as a List of these, where the chain’s root will be the first entry. Each subsequent entry will be a child Transform of the previous entry. The last entry in the list will not be used as a link. Instead, its Transform will be considered the position of the end effector, and its len will be ignored. The code is below.

public static void PerformIK_Cascade(
    List<Node> nodes, 
    Vector3 targ, 
    float rotPercent        = 0.1f, 
    bool incrRaiseRotAmt    = true, 
    int iterCt              = 20, 
    float eps               = 0.001f)
{
    int lastNodeIdx = nodes.Count - 1;
    Transform endEffector = nodes[lastNodeIdx].transform;
    float rotAmt = rotPercent;
    // For each iteration
    for(int i = 0; i < iterCt; ++i)
    { 
        // For each bone in the kinematic chain
        for(int nit = 0; nit < lastNodeIdx; ++nit)
        { 
            
            Vector3 basePos = nodes[nit].transform.position;
            Vector3 EEPos = endEffector.position;

            // Calculate Bone->EE
            Vector3 baseToEE = EEPos - basePos;

            // Calculate Bone->Trg
            Vector3 baseToTarg = targ - basePos;

            // Calculate rotation
            Quaternion rotFromto = Quaternion.FromToRotation(baseToEE, baseToTarg);

            // Calculate partial rotation & apply partial rotation
            Quaternion rotRestrained = Quaternion.Lerp(Quaternion.identity, rotFromto, rotAmt);
            nodes[nit].transform.rotation = rotRestrained * nodes[nit].transform.rotation;
        }

        // If increased greediness, increase to where rotation amount is 
        // close to 1.0 on the final iteration.
        if(incrRaiseRotAmt)
            rotAmt += (1.0f - rotPercent)/(iterCt - 1);

        // Check if our IK is at a solution that's "good enough"
        float distEEtoTarg = (endEffector.position - targ).magnitude;
        if(distEEtoTarg <= eps)
            break;

    }
}

Also, note that technically we didn’t need to store the length of the bone (member len) explicitly. That is implicitly stored as the magnitude of a Transform‘s localPosition. But, while caching a bone’s length wasn’t needed for the IK algorithm, it’s useful for the demo (below).

Demo

An embedded example is below. Source code on GitHub.

Controls

Click and drag the target to move it. Click and drag a blank region to rotate the camera.

  • Running IK: The IK solver will run every frame unless this is toggled off.
  • Increase Greediness: If true, each IK iteration will increase the greediness to solve more aggressively.
  • Resets: Resets the kinematic chain’s transform every frame before solving the IK.
  • Reset Transforms: Manually reset the kinematics chain. Only available if Resets is turned off.
  • Greediness: Changes how aggressively an individual node will try to solve the IK.
  • Its: Change the number of iterations used to solve the IK chain.
  • Cascade/Stepped: Switch between examining two different algorithm approaches. Don’t use Stepped – it’s there for personal testing reasons, but it’s unbridled instability.
  • Nodes: Add, remove and modify nodes in the IK chain. For a listed node, use the slider to change its length, and hit the [x] button to delete it.
  • Restart: Reset the parameters and scene.

When to Apply IK In a MonoBehaviour?

If you’re doing your own IK, your first instinct may be to perform the IK solve in the Update() function of a MonoBehaviour. The problem is that the Update() phase is when a lot of stuff is moving around, possibly even your IK chain bones and your IK target.

Consider running the IK solver in the LateUpdate() pass. That way, you can solve on top of (post-process) any animations and movements previously performed.

End Notes

We’ve covered a very simple introduction, with many things glossed over. Many aspects can be improved or deep-dived into, such as:

  • Speed
  • Stability
  • Properly solved shapes
  • Constraints
  • Trees and cyclical IK structures
  • Alternative IK algorithms and strategies
  • Integrating into a kinetic system (motion planning and controller theory)

This algorithm definitely has more that can be improved in the “properly solved shapes” category, as it often has a bias for certain types of shapes. In the 2D videos with the three bones, we can see that often there’s a pattern of creating a hook at the end that causes (in the next iteration) the root nodes to rotate only slightly to adjust for the end over-shooting. Not only does the hook form, but it’s then a lot of scootching the EE backward in tiny increments. Arguably, a better shape would have been a smooth, uniform arc.

Also, the example assumes each Transform’s rotation can freely swivel freely in any way for any rotation amount. In practice, this is often not going to be the case. Often, certain types of joints will be modeled that have contained rotations.

Hugo Elias

I want to mention that back in the day, a simple and effective reference for IK simulations was Hugo Elias. The website’s gone, but it’s not forgotten, thanks to the Wayback Machine.

Unity WebGL demo compiled with Unity 2018.4.32f1
Tested with Chrome 109.0.5414.75