您的位置:首页 > 编程语言 > Qt开发

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

2013-11-03 22:54 363 查看

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

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

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

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

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





正常情况是这样的:

 


       那么如何对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 骨骼动画
相关文章推荐