我们的目标是根据骨骼动画来更新模型。看看手上的材料,
1)骨骼动画数据。上一节中我们已经读出了 AnimationSet、Animation和AnimationKey这些动画数据,我们现在要做的就是把它们应用到骨骼上面去。 AnimationSet只是标明了我们要播放的动画名称,关键的处理在Animation和AnimationKey上面。Animation包含了所对应的骨骼名称,下属的AnimationKey包含了坐标变换的类型以及对应的时间戳,我们也把AnimationKey看做一个关键帧。下面要做的就是根据当前时间判断动画落在哪两个关键帧中间,例如key1和key2,然后求出插值系数scaler,
scaler = (当前时间-key1.时间)/(key2.时间-key1.时间)
求出插值系数后,骨骼的当前位置就可以用下面的方法求出,注意各种key类型求插值的方法不一样,
switch( Key的类型 )
{
case 旋转:
...
// 四元数插值
D3DXQUATERNION RotationQuaternion;
D3DXQuaternionSlerp(
&RotationQuaternion,
&pAnimationKey->pQuaternionKeys[Key1].value,
&pAnimationKey->pQuaternionKeys[Key2].value,
Scaler);
// 应用旋转矩阵
D3DXMATRIX RotationMatrix;
D3DXMatrixRotationQuaternion( &RotationMatrix, &RotationQuaternion );
pAnimation->pBone->TransformationMatrix *= RotationMatrix;
break;
case 平移和缩放:
...
// 矢量插值
D3DXVECTOR3 InterpolatedVector =
pAnimationKey->pVectorKeys[Key1].value + Scaler *
( pAnimationKey->pVectorKeys[Key2].value - pAnimationKey->pVectorKeys[Key1].value );
if( pAnimationKey->Type == XAnimationKey::KeyType::Scaling )
{
// 应用缩放矩阵
D3DXMATRIX ScalingMatrix;
D3DXMatrixScaling(
&ScalingMatrix, InterpolatedVector.x, InterpolatedVector.y, InterpolatedVector.z );
pAnimation->pBone->TransformationMatrix *= ScalingMatrix;
}
else
{
// 应用平移矩阵
D3DXMATRIX TranslationMatrix;
D3DXMatrixTranslation(
&TranslationMatrix, InterpolatedVector.x, InterpolatedVector.y, InterpolatedVector.z );
pAnimation->pBone->TransformationMatrix *= TranslationMatrix;
}
break;
case 坐标变换矩阵:
...
// 矩阵插值
D3DXMATRIX TransformMatrix =
pAnimationKey->pMatrixKeys[Key1].value + Scaler *
( pAnimationKey->pMatrixKeys[Key2].value - pAnimationKey->pMatrixKeys[Key1].value );
// 应用坐标变换矩阵
pAnimation->pBone->TransformationMatrix *= TransformMatrix;
break;
} // switch
这样我们就把根据当前时间计算出来的插值坐标变换矩阵应用到骨骼上了。
2) 骨骼数据。在从文件中读出来的时候,我们已经利用pFrameSibling和pFrameFirstChild两个字段构造了一个层次结构。注意骨骼中的TransformationMatrix包含的是当前骨骼相对于父骨骼的坐标变换,应用到mesh上的时候,我们需要的相对于根骨骼的坐标变换。因此我们要做一下处理,简单的一个递归调用,
//-----------------------------------------------------------------------------
// 名称: UpdateHierarchy
// 描述: 计算本节点及所有兄弟、子节点相对于根节点的偏移矩阵
//-----------------------------------------------------------------------------
void UpdateHierarchy( D3DXMATRIX *matTrans = NULL )
{
D3DXMATRIX matIdentity;
// 根节点的偏移矩阵为单位矩阵
if( NULL == matTrans )
{
D3DXMatrixIdentity( &matIdentity );
matTrans = &matIdentity;
}
// 计算偏移矩阵
matCombined = TransformationMatrix * ( *matTrans );
// 更新兄弟节点
if( pFrameSibling )
( ( D3DXFRAME_EX* )pFrameSibling )->UpdateHierarchy( matTrans );
// 更新子节点
if( pFrameFirstChild )
( ( D3DXFRAME_EX* )pFrameFirstChild )->UpdateHierarchy( &matCombined );
}
通过在根骨骼上的一次调用,我们就可以在自定义的matCombined字段中得到各个骨骼相对于根骨骼的坐标变换矩阵.
3) mesh数据。mesh数据相对简单,ID3DXMesh和ID3DXSkinInfo接口为我们了做大部分的工作。不过天底下没有免费的午餐,为了让它们运转起来,我们还是要做一些额外的努力。在步骤1里面我们已经通过插值得到了骨骼当前的坐标变换矩阵,不过这个坐标变换是相对于模型本地坐标的,为了应用到mesh上,我们需要将坐标对齐到mesh的中心,
// 首先bone转换到以mesh中心为坐标原点的坐标系,然后再应用frame的坐标变换矩阵
for( DWORD i=0; i<m_pRootMeshContainer->pSkinInfo->GetNumBones(); i++ )
{
m_pRootMeshContainer->pBoneMatrices[i] =
*( m_pRootMeshContainer->pSkinInfo->GetBoneOffsetMatrix( i ) );
if( m_pRootMeshContainer->ppFrameMatrices[i] )
m_pRootMeshContainer->pBoneMatrices[i] *= *m_pRootMeshContainer->ppFrameMatrices[i];
}
这样所有的数据都准备好了,用ID3DXSkinInfo的方法来更新骨骼关联的每个顶点的坐标,(每个顶点根据关联的所有骨骼的坐标变换矩阵乘以对应的权重再相加来得到最终应用到顶点上的坐标变换矩阵)
m_pRootMeshContainer->pSkinInfo->UpdateSkinnedMesh(
m_pRootMeshContainer->pBoneMatrices, NULL, pSrcVertex, pDestVertex );
到此一切准备都结束了! 绘制Mesh的动作和平常一样,设置材质和纹理,然后调用DrawSubSet方法,这个想来对大家是没有什么难度的事情。怎么样,是不是想从头再看一遍回味一下呢? 呵呵。
(附件里面是测试工程,编译环境为VS.NET2003, DXSDK(October 2004)。大家有什么不明白或者我哪里说错的,欢迎给我写信或者留言。)
本主题包含附件:
sf_20055911366.rar (446304bytes)