第二,修改和更新骨骼层级:
加载完骨骼层级之后,你可以操作它,更改骨骼的方位。你需要创建一个递归函数,按照名字找到相应的Frame数据对象。这个函数如下:
D3DXFRAME_EX *FindFrame(D3DXFRAME_EX *Frame, char *Name)
{
if(Frame && Frame−>Name && Name) {
// 如果名字找到,返回一个Frame指针
if(!strcmp(Frame−>Name, Name)) // strcmp函数比较两个字符串,如果两个字符串相等,返回0
return Frame;
}
// 在sibling frames找匹配的名字
if(Frame && Frame−>pFrameSibling) {
D3DXFRAME_EX *FramePtr = \
FindFrame((D3DXFRAME_EX*)Frame−>pFrameSibling, \
Name);
if(FramePtr)
return FramePtr;
}
// 在child frames找匹配的名字
if(Frame && Frame−>pFrameFirstChild) {
D3DXFRAME_EX *FramePtr = \
FindFrame((D3DXFRAME_EX*)Frame−>pFrameFirstChild, \
Name);
if(FramePtr)
return FramePtr;
}
// 如果没有找到,返回 NULL
return NULL;
}
如果你想找到一个叫“Leg”的Frame,可以把“Leg”传入FindFrame函数,并且提供指向RootFrame的指针:
// pRootframe 为D3DXFRAME_EX root frame 指针
D3DXFRAME_EX *Frame = FindFrame(pRootFrame, "Leg");
if(Frame) {
// 可以在这里做一些处理,比如旋转操作
// 你在这里可以稍微的旋转这个骨头
D3DXMatrixRotationY(&Frame−>TransformationMatrix, 1.57f);
}
一旦你修改变换骨头,你需要更新整个骨骼层级,也就是把变换的组合矩阵存入D3DXFRAME_EX结构的matCombined成员中,用于后面的渲染。下面的函数应该增加到D3DXFRAME_EX结构中,如下:
void UpdateHierarchy(D3DXMATRIX *matTransformation = NULL)
{
D3DXFRAME_EX *pFramePtr;
D3DXMATRIX matIdentity;
// 如果为空,用一个全同矩阵
if(!matTransformation) {
D3DXMatrixIdentity(&matIdentity);
matTransformation = &matIdentity;
}
// 把变换矩阵组合到matCombined中
matCombined = TransformationMatrix * (*matTransformation);
// 更新兄弟层级
if((pFramePtr = (D3DXFRAME_EX*)pFrameSibling))
pFramePtr→UpdateHierarchy(matTransformation);
// 更新孩子层级
if((pFramePtr = (D3DXFRAME_EX*)pFrameFirstChild))
pFramePtr→UpdateHierarchy(&matCombined);
}
现在matCombined储存着每个骨骼相对于原点的变换矩阵,然后只要把各个顶点附在相应的骨骼上,就能渲染了。
第三,使用蒙皮网格:
网格可以分为蒙皮网格(Skin Mesh)和普通网格(Mesh)。蒙皮网格就是具有蒙皮信息的普通网格。为了搞清楚蒙皮网格我们需要介绍相关的三个模版:
template Mesh
{
<3D82AB44-62DA-11CF-AB39-0020AF71E433>
DWORD nVertices; //顶点数
array Vector vertices[nVertices]; //顶点坐标数组
DWORD nFaces; //多边形数
array MeshFace faces[nFaces]; //多边形顶点引索
[...]
}
这个模板存储一个表态的网格和网格的材质。在骨骼蒙皮动画中,整个角色只是一个网格,由蒙皮信息确定网格中的每一个部分如何受到骨骼的影响。网格在内部会分成几个子集,每一个子集将受到一些特定骨骼的影响。
template XSkinMeshHeader
{
< 3CF169CE-FF7C-44ab-93C0-F78F62D172E2 >
WORD nMaxSkinWeightsPerVertex; // 网格中受到骨骼影响的顶点数
WORD nMaxSkinWeightsPerFace; // 网格中受到骨骼影响的多边形数
WORD nBones; // 影响网格顶点的骨骼数量
}
这个模版包含于Mesh模版中。包含关于蒙皮信息的属性。
template SkinWeights
{
< 6F0D123B-BAD2-4167-A0D0-80224F25FABB >
STRING transformNodeName; //骨骼的名字
DWORD nWeights; //附属到该骨骼的顶点数
array DWORD vertexIndices[nWeights]; //附属到该骨骼的顶点引索
array float weights[nWeights]; //相应引索的顶点权值
Matrix4x4 matrixOffset; //相对于骨骼位置的偏移矩阵
}
这个模版也包含于Mesh模版中,真正的蒙皮信息就存储在这里。每一个影响到网格的骨骼在模版中都有实例。例如有12个骨骼影响到网格,Mesh模版里将有12个SkinWeights模版的实例。
蒙皮网格和普通网格的唯一不同点就是看XskinMeshHeader和SkinWeights模版是否存在。如果把这两个模版从任何一个蒙皮网格里面移走的话,就可以得到一个普通网格。在X文件中,我们将会发现一个GUID为TID_D3DRMMesh的模版,这表示模版里面存有一个网格。利用D3D的帮助函数D3DXLoadSkinMeshFromXof将会加载蒙皮网格和其它补充性数据。只需要向它传递一个IDirectXFileData指针,然后它将为你做剩下的事情。现在介绍下D3DXLoadSkinMeshFromXof函数:
HRESULT D3DXLoadSkinMeshFromXof(
LPD3DXFILEDATA pxofMesh, //X文件数据接口
DWORD Options, //加载参数
LPDIRECT3DDEVICE9 pD3DDevice, //使用的三维设备
LPD3DXBUFFER * ppAdjacency, //邻接信息缓冲接口
LPD3DXBUFFER * ppMaterials, //材质缓冲接口
LPD3DXBUFFER * ppEffectInstances, //效果实例接口
DWORD * pMatOut, //材质数
LPD3DXSKININFO * ppSkinInfo, //蒙皮信息接口
LPD3DXMESH * ppMesh //加载的网格模型接口
);
需要特别注意是LPD3DXSKININFO * ppSkinInfo接口,储存着蒙皮信息。
当你加载一个网格,并读取了的这些顶点的权值之后,你可以变换这些顶点去匹配骨骼的方向,使用以下步骤:
1)迭代所有的顶点。为每个顶点进行第2步。
2)对当前顶点连接到的每一个骨头,得到骨头的变换矩阵。
3)对于每个骨头的变换矩阵,用顶点的权值乘以这个变换矩阵然后把这个结果应用到顶点的组合变换矩阵。
4)为每个连接的骨头重复第三步,然后为每个顶点通过第四步重复第二步。当你完成以上步骤,把组合变换矩阵应用到具体的被迭代的顶点(从第一步)。
怎样精确的获得顶点的权值?可以利用ID3DXSkinInfo接口的GetBoneVertexInfluence方法得到这些权值。顶点的权值一般储存在Mesh数据对象的末端。执行完以上步骤后,剩下的仅仅是渲染了。结合前面介绍的计时动画技术,可以把关键帧时间和变换矩阵储存在AnimationSet模版中,利用一些变量不断的修改和更新骨骼层级便能创造出各种动画效果。
3 增加场景数据
在游戏中,仅仅有活灵活现的角色动画是远远不够的。因为你需要让它在具体的场景中尽情地表演,这就需要场景数据。场景数据中最具代表性的就是角色模型在游戏世界中的位置,包围球半径。显然,还有很多其它数据,这些数据都是因场合而异的。正如前面所说,X文件可以用于储存任何数据,包括场景数据。但是用X文件储存场景信息会碰到很多麻烦。比如你很难找到能够把模型转化成附有场景信息的X文件的建模工具。当然,你可以自己开发建模工具的插件。下面我介绍一个简单有效的方式得到场景数据。
很多建模软件都支持导出XML文件。在3DSMAX中,就有这样的插件。完全可以在XML文件中储存场景信息。这样,就需要一个XML文件的读取函数。虽然XML文件较为复杂,但是利用一些帮助库,可以很方便的写出XML文件的读取函数。Tinyxml 就是一个小巧稳定的XML帮助库,在互联网上可以找到很多关于它的信息。我们的目的就是把XML文件中的场景数据根据需要加载到自己定义的数据结构中。
假设定义这样一个简单的数据结构:
struct SMeshSceneInfo //Mesh场景信息,为了访问的便利,不使用链表动态分配内存
{
SMeshSceneInfo()
{
int ID=0;
D3DXVECTOR3 position(0.0f,0.0f,0.0f);
}
string name; //Mesh的名字
int ID; //MeshID
D3DXVECTOR3 position; //Mesh世界坐标
};
接着利用帮助库的函数,在程序的初始化阶段加载场景数据到这个数据结构中。之后在程序里就可以利用这些数据进行相关的操作。
4 简介渐变动画
回到20世纪90年代早期,一种革命性的计算机图形动画技术称之为morphing,就是渐变。它一举成功,成为主流技术,并延用至今。游戏中的渐变技术,最好的例子可能就是ID SOFT的雷神之锤(Quake)。所有这些角色的动画序列由一系列的渐变网格模型构成,一个模型缓慢的改变形状变成第2个模型,第2个模型再改变形状匹配到第3个网络模型上,以此类推。
其实渐变动画的原理很简单,就是差值两个关键帧模型的顶点。第一个关键帧模型叫做源网格模型,第二个关键帧叫做目标网格模型。当从第二个关键帧向第三个关键帧变化时,第二个关键帧网格又变成源网格模型,第三个关键帧变成目标网格模型。
下图显示了随着时间的变化,各网格顶点的变化:
每个顶点都共享源网格模型和目标网格模型中相同的引索。在这里,顶点的次序尤为重要,如果次序错误,将产生奇怪的动画效果。
相比骨骼蒙皮动画,渐变动画的逻辑简单许多,如果掌握了前者,很容易就能实现各种基于渐变动画的效果。
四 结束语
游戏的世界正因为各种动画技术的灵活运用才显得绚丽多彩,有趣迷人。在不久的未来,更棒的动画技术将会普及。比如,在游戏中你是一个身怀绝技的武士,当对手跃起,咆哮着从上方挥刀砍来,你不能简单地按游戏手柄的“挡格”键,而是真正的手握战刀,看准时机把对方的攻击化险为夷。各个动作的计算准确无误,如果你愿意的话,可以如实记录你的行为,立即产生出相同的动画效果。或者你是一个高尔夫球的爱好者,并不是任何地方都有打高尔夫球的场地。没有问题,你同样可以在家里尽情地挥棒击球,享受高夫球的乐趣。相信你可以想象出更多有趣的事情。这不仅仅是想象,而是不久就能体会到的快乐。
相关参考资料:Introduction to 3D Game Programming with DirectX 9.0 by Frank Luna
ISBN:1-55622-913-5 Wordware Publishing © 2003 (388 pages)
Advanced Animation with DirectX by Jim Adams
Copyright © 2003 Premier Press, a division of Course Technology.
Advanced 3D Game Programming with DirectX 9.0 by Peter Walsh
ISBN:1-55622-968-2 (pbk.) Copyright © 2003 Wordware Publishing, Inc.
DirectX 9.0 Programmer's Reference by Microsoft
2005 Microsoft Corporation. All rights reserved.
硬件支持下骨骼蒙皮动画的实现 by Octane3d
0 评论
Recommended Comments
没有要显示的评论。
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new account登录
Already have an account? Sign in here.
现在登录