Inverse Kinematics
Inverse Kinematics
逆向运动学是处理一个特殊节点怎么移动到一个世界坐标的位置上的过程。
Understand how CCD IK works
CCD是循环旋转下降(Cyclic Coordinate Descent),这个算法有三个基础概念:
- Goal:目标的世界坐标点
- Ik chain:所有需要移动和选择的节点列表
- End effector:节点列表末端
这个算法原理很简单,它是一个迭代算法。最开始迭代从节点列表的倒数第二个节点开始,倒数第一个是End effector
。我们来解释一次迭代的过程:
- 求当前节点到目标点的向量:
Joint to Target
- 求当前节点到末端节点的向量:
Joint to Effector
- 旋转
Joint to Effector
向量与Jointt to Target
同方向
我们可以看这个图比较清晰,这里画出来两次迭代:
我们需要多次迭代,直到effector
离目标点足够接近。
Implement a CCD solver
迭代的次数需要固定,不能让他无线循环。
Understand how FABRIK works
FABRILK是正反向运动学(Forward And Backward Reaching Inverse Kinematics),这个算法我们只用位置来计算,不像CCD使用旋转。同样的也需要三个参数:
- Goal:目标的世界坐标点
- Ik chain:所有需要移动和选择的节点列表
- End effector:节点列表末端
这个算法分成两部分:
- 反向迭代:先把最后一个节点开始移动到目标节点位置,然后从后到前依次移动列表中的节点,移动的长度为两节点之间的长度,也就是骨骼的长度;移动的方向是当前节点到下一个节点的方向。
- 正向迭代:先把头节点移动到之前反向迭代的位置,然后从前到后依次移动列表中的节点,移动的长度为两节点之间的长度,也就是骨骼的长度;移动的方向是当前节点到上一个节点的位置。
我们可以看这个图,前两步骤是反向迭代,后三步是正向迭代:
这个算法对于人体动画生成会更加的自然。
Ball-and-socket constraints
这个办法是对旋转角度的限制,父节点旋转和子节点旋转的角度不能超过多少,超过了就以限制角度为准。形象点说就是跟肩膀关节一样是一样的。像是下面这张图一样,黑色的关节只能在白色这个关节凹陷处旋转。

以下是伪代码:
void ApplyBallSocketConstraint(int i, float limit)
{
quat parentRot = i == 0 ? mOffset.rotation :GetWorldTransform(i - 1).rotation;
quat thisRot = GetWorldTransform(i).rotation;
vec3 parentDir = parentRot * vec3(0, 0, 1);
vec3 thisDir = thisRot * vec3(0, 0, 1);
float angle = ::angle(parentDir, thisDir);
if (angle > limit * QUAT_DEG2RAD)
{
vec3 correction = crHoss(parentDir, thisDir);
quat worldSpaceRotation = parentRot *angleAxis(limit * QUAT_DEG2RAD, correction);mChain[i].rotation = worldSpaceRotation * inverse(parentRot);
}
}
Hinge constraints
这个限制是只允许旋转沿着一个特殊的轴的旋转,这个像肘和膝盖,跟我们生活中的铰链一样。
所以我们只需知道当前节点与父节点的世界空间旋转,用旋转四元数乘以轴法线,并找到两者之间的一个四元数,这个就是我们需要的旋转的量,以下是伪代码:
void ApplyHingeSocketConstraint(int i, vec3axis)
{
Transform joint = GetWorldTransform(i);
Transform parent = GetWorldTransform(i -1);
vec3 currentHinge = joint.rotation * axis;
vec3 desiredHinge = parent.rotation * axis;
mChain[i].rotation = mChain[i].rotation * fromToRotation(currentHinge, desiredHinge);
}
Understand where and how IK solvers fit into an animation pipeline
有两种常使用IK的地方:手的位置和脚的位置。
有两个常见的问题在我们做IK解决方案时:
- What happens if the up motion is too far away?
- At what point in the animation cycle can we interpolate between pinned and non-pinned positions?
我们举一个简单的计算脚部IK的例子:
- 从髋关节(Hip)出射出一条射线A到脚踝(Ankle)
- 射线碰撞到物体,碰撞点作为IK节点列表的当前目标点
- 没有发生碰撞,脚踝就作为当前目标点
- 同样从髋关节(Hip)出射出一条射线B,不对射线B做长度限制
- 射线检测碰到物体,碰撞点作为未来的目标点
- 没有发生碰撞,把未来的目标点作为当前的目标点
如果我们单纯直接使用目标点
- 只用当前目标点:角色的脚可能会突然弹到地面
- 只用未来目标点:角色的脚只会在地面拖着
所以我们需要做插值:
- 当角色的脚在碰撞点上,使用使用未来目标点
- 当角色的脚在碰撞点下,使用当前目标点
Finding the foot goals
我们从髋关节(hip)下面一点发出直的射线到角色脚踝下面一点。射线的方向是髋关节到脚踝。

如果有碰撞,碰撞点就为目标点,没有目标点就是脚踝的位置。需要注意的是,我们改变的位置是脚踝而不是脚底板,所以如果有碰撞点,需要把目标点上移一段距离,如图 :

Interpolating the foot goals
对于插值,我们需要知道是对什么插值,是对那两个值插值,是对动画切片中的腿的位置和IK射线检测计算出来腿的位置做插值,首先需要知道腿当前是属于那个阶段:
- 在地面上
- 被提起来
- 悬挂
- 正在被放置
不同的阶段使用插值比例是不一样的,如果当前腿的位置是在地面上,我们就直接可以使用动画切片的位置;如果腿不在地面上,我们就需要让腿回来地面上,这时就需要使用IK计算出来腿的位置在哪里。
这样一来我们就可以清晰知道插值的边界是哪里,当脚离开地面数值就为0,然后脚在地面时数值就为1。这个脚的位置数据是直接使用的动画切片的位置来做判断。我们可以使用一个曲线来线性来画出来,把当前动画化切片的播放时间单位话,整个切片时间就是0到1.
Vertical character placement
IK拉伸和普通动画切片的比较:
在垂直方向,需要把脚稍微向下一段距离,这样可以让IK系统避免拉伸。
IK Pass
复制当前姿势的节点到IK解决系统中。对于每一只腿,复制髋关节的世界坐标到IK系统的根节点里。复制膝盖的本地坐标到节点1。复制脚踝的本地坐标到节点2。然后运行IK解决。