循环坐标下降(CCD)算法中对骨骼动画中膝盖等关节的特殊处理

循环坐标下降(CCD)算法中对骨骼动画中膝盖等关节的特殊处理

         最近研究循环坐标下降(cyclic coordinate decentCCD)算法,发现它在处理人物的某些关节上不起作用。CCD算法的原始算法针对的是多个骨骼、多个关节的IK解算处理,但是对于人体骨骼有着特殊的构造,使用CCD算法不能正确地反映这些构造,所以我们必须对这些构造进行特殊处理。

       那么人体骨骼的构造特殊性在哪儿呢?这里我们暂且不探讨骨骼扭转的情况,仅仅探讨骨骼旋转的情况。我们发现人体的一些部位有三个自由度(3 Dimensions of Freedom3DoF),而另一些部位只有一个自由度(1DoF),比如说我们的肩膀关节,它就有三个自由度,而肘部关节只有一个自由度;同理髋关节(如果可能的话)有三个自由度,而膝关节只有一个自由度。

了解这些很重要,因为我们要对CCD算法的结果进行进一步的限制。一般来说,普通的骨骼(比如说头发骨骼),当对末端关节进行移动以靠近固定关节时,二维的情况有两个解,三维的情况就有无穷多个解,解集会形成一个圆圈。我们只能看到我们的小腿相对于大腿顺着弯曲,却无法想象小腿相对大腿反着弯曲。这就是必须要限制的原因。

如果不限制的话,就会出现这样的情况:

循环坐标下降(CCD)算法中对骨骼动画中膝盖等关节的特殊处理_第1张图片

循环坐标下降(CCD)算法中对骨骼动画中膝盖等关节的特殊处理_第2张图片

正常情况是这样的:

 循环坐标下降(CCD)算法中对骨骼动画中膝盖等关节的特殊处理_第3张图片

       那么如何对CCD算法进行限制呢?下面以膝盖为例。首先在预处理阶段判断骨骼的关节处是否为膝盖,保存为一个bool值,以后这样的骨骼进行单独处理。处理的方法是这样的:不以向量叉积求出的旋转轴进行旋转,而是以仅仅以X轴进行旋转。下面是改进后的代码:

/*---------------------------------------------------------------------------*/
void MMDRenderHandler::CalculateInverseKinematics( void )// 计算反向运动学
{
    // 遍历每个IK
    foreach ( const IK& _IK, m_IKs )
    {
        Bone& destBone = m_Bones[_IK.destIndex];        // 一般是IK骨骼
        Bone& targetBone = m_Bones[_IK.targetIndex];    // 一般是与IK一端连接的骨骼

        for ( int i = 0; i < _IK.iteration; ++i )
        {
            for ( int j = 0; j < _IK.bones.size( ); ++j )
            {
                quint16 index = _IK.bones[j];
                Bone& joint = m_Bones[index];

                CCDIKSolve( joint,
                            targetBone,
                            destBone,
                            _IK.limitAngle,
                            i );

                CalculateBonesFinalPos( index );
            }
            if ( qFuzzyIsNull( ( targetBone.finalPos -
                                 destBone.finalPos ).SquareLength( ) ) )
            {
                break;// 目的达到了,结束迭代
            }
        }
    }
}
/*---------------------------------------------------------------------------*/
void MMDRenderHandler::CCDIKSolve( Bone& joint, // 想象成肘部
                                   Bone& target, // 目标位置
                                   Bone& end, // 末端效应器
                                   float limitAngle,// 单位限制角度
                                   int iterNum )// 迭代次数
{  
    // 使用循环坐标下降算法(cyclic coordinate decent,CCD)

    // 计算在绝对旋转后的连接点和目标位置以及末端效应器的相对位置
    Vector3F absJoint2End = end.finalPos - joint.finalPos;
    Vector3F absJoint2Target = target.finalPos - joint.finalPos;

    Quaternion invRotation = joint.absRotation.Conjugate( );// 求出四元数的共轭四元数

    // 转为本地坐标系(平移因素在第一阶段已剔除)
    Vector3F localJoint2End = invRotation.RotatedVector( absJoint2End );
    Vector3F localJoint2Target = invRotation.RotatedVector( absJoint2Target );

    // 计算应该旋转的角度
    float deltaAngle = acosf( Vector3F::DotProduct(
                                  localJoint2End.Normalized( ),
                                  localJoint2Target.Normalized( ) ) );

    if ( std::isnan( deltaAngle ) ||
         qFuzzyIsNull( deltaAngle ) )// 角度计算出错或角度太小(一般是向量太接近)
    {
        return;// 不处理,直接返回
    }

    // 限制角度为[-limitAngle, limitAngle]
    deltaAngle = qBound( -limitAngle, deltaAngle, limitAngle );

    // 求出旋转轴
    Vector3F rotateAxis = Vector3F::CrossProduct( localJoint2Target,
                                                  localJoint2End );

    // 构造旋转四元数
    Quaternion deltaRotation = Quaternion::FromRotation(
                rotateAxis, deltaAngle );

    if ( joint.isXContraint )// 连接点的骨骼是膝盖,则限定仅绕X轴旋转
    {
        float curYaw, curPitch, curRoll;
        float deltaYaw, deltaPitch, deltaRoll;

        if ( iterNum == 0 )// 第一次迭代仅仅绕着X轴旋转
        {
            deltaRotation = Quaternion::FromRotation(
                        Vector3F( 1.0f, 0.0f, 0.0f ),
                        fabsf( deltaAngle ) );
        }
        else
        {
            deltaRotation.ToEuler( deltaYaw, deltaPitch, deltaRoll );
            joint.rotation.ToEuler( curYaw, curPitch, curRoll );

            if ( std::isnan( deltaYaw ) ||
                 qFuzzyIsNull( deltaYaw ) )// 角度计算出错或角度太小(一般是向量太接近)
            {
                return;// 不处理,直接返回
            }

            // 限制前滚角为[-0.002f - curYaw, M_PI - curYaw]
            deltaYaw = qBound( -0.002f - curYaw, deltaYaw, float( M_PI ) - curYaw );

            // 进一步限制
            deltaYaw = qBound( -limitAngle, deltaYaw, limitAngle );

            deltaRotation = Quaternion::FromEuler( deltaYaw, 0.0f, 0.0f );
        }
    }

    joint.rotation *= deltaRotation;
    joint.absRotation = m_Bones[joint.parent].absRotation * joint.rotation;
}

演示程序下载地址:这里

你可能感兴趣的:(qt,OpenGL,骨骼动画)