本文由哈利_蜘蛛侠原创,转载请注明出处!有问题请联系2024958085@qq.com
这一次我们继续来讲述Jim Adams 老哥的RPG编程书籍第二版第二章的第13节:Working with Meshes (使用网格模型)。这一节分为四个小节,我们列在下面:
1、 The .X Files (.X文件)
2、 The .X File Format (.X文件的格式)
3、 Creating .X Meshes (创建.X网格模型)
4、 Parsing .X Files
原文翻译:
===============================================================================
2.13 Working with Meshes (使用网格模型)
在其最底层,Direct3D并不使用网格模型,而是只使用多边形。但是D3DX通过给你能够包含和渲染这样的网格模型的一系列对象,而向Direct3D系统中增加了使用网格模型的功能。
在其最底层,网格模型是由可能上千个顶点和多边形构成的,这都需要复杂的操作。幸运的是,Direct3D 附带了一种与生俱来的3-D文件格式来储存描述一个3-D模型的信息,包括(但不限于)顶点、面、法向量和纹理数据。这个文件格式被称为.X。
2.13.1 The .X Files (.X文件)
一个.X文件(通过它的扩展名——.X,而进行识别)是归属于微软的一种高度通用的3-D模型储存格式。(对不起,我们不会被Mulder和Scully所拜访。——这里作者小小地幽默了一下:Mulder和Scully是美国科幻剧《The X Files》的主角。)它是由模板驱动的,并且完全可扩展,这意味着你可以使用它来满足所有的文件储存需求。对于我们的情况,我正在谈论的文件存储是你的3-D网格模型数据。
虽然在现在我可以继续谈论有关.X文件的格式的复杂的细节,但是这样做的话,这些只有最hard-core(不知道怎么翻译好)的程序员和图形艺术家才会使用的信息会让本章内容不堪重负的。让我们面对它吧——努力地手动编辑一个包含了上千个顶点的网格模型是可笑的。为什么在你可以使用一个类似trueSpace或MilkShape 3D的程序在一个用对户友好环境中来设计你的网格模型的时候,却要去做那种令人退缩的繁重工作呢?那就是我所刚才的话的意思!
注意
===============================================================================
如果你真的对.X文件格式的复杂细节感兴趣,你应该查查我的书《Advanced Animation with DirectX》。在此书中,我说明了如何在你的项目中使用.X文件的细节,从使用标准的DirectX数据对象到创建你自己的数据集——全都在那儿!查阅附录A,“参考文献”,来获得更多信息。(参考文献这一部分我今后是不会翻译的。)
===============================================================================
相反,我只会给你快速浏览一下使用.X文件格式化的方法;然后你就可以转移到好的事情上了,也就是在你的游戏中使用模型!
2.13.2 The .X File Format (.X文件的格式)
一个.X文件,通过其.X扩展名而被识别,是高度通用的(versatile)。它可以是基于文本的,以使得编辑更加容易;也可以是基于二进制的,以使得文件更小,并且更容易免受偷窥者的干扰。.X的整个格式是基于模板的,这与C的结构体相似。
为了读取并使用一个.X文件,你采用一些个COM对象来剖析(parsethrough)在这个.X文件中遇到的每个数据对象。数据对象以一个字节数组(arrayof bytes)的形式被传递给你;你只需要将这个数组转型为一个可以使用的结构体,就可以轻易地读取包含在该对象中的数据了。
根据储存在.X文件中的数据不同,这些对象也会不同。在这里,这些对象代表网格模型以及与网格有关的数据(例如骨骼结构(bone structures),叫做帧继承(frame hierarchies),以及动画数据)。剖析这些对象、载入网格数据、建立动画表以及构造帧继承,这些都是你的工作。
我会回到整个的网格和动画的事情上的。首先呢,我想要谈谈我刚才提过的至关重要的帧继承。
Using a Frame Hierarchy (使用帧继承)
你使用一个帧模板(frame template),也叫做frame of reference 模板,来将一个或多个数据对象(通常是网格模型)组合在一起以便更好地操控。你还可以创建一个单个的网格,并使用多个帧来包含网格引用(references),这让你可以多次使用一个网格。
举例来说,如果你有一个台球的模型。因为要设定15个球,所以你创建15个帧,每一帧包含原始的球网格模型的一个引用。从此时起,你可以(通过使用一个帧变换矩阵对象)将每一帧摆放在台球桌上,使得每一个网格实例与帧一起运动(move with the frame,不知道翻译得对不对)。
本质上来说,你从单独的一个网格模型中创建了15个台球网格实例。除了创建一个网格模型的多个实例以外,你还可以使用它们来创建一个帧继承(frame hierarchy)。帧继承定义了一个场景的结构或者网格的组织(grouping of meshes)。当一个帧运动时,所有嵌入其内的帧也会移动。
举例来说,将一副人类骨骼想象成一个帧的集合(如图2.24所示)。在继承的顶部是胸部。从胸部往下,你可以将每一根骨头与前面一根骨头连在一起 —— 也就是说,胸部连到臀部,臀部连到腿部,腿部连到脚。往相反的方向走(向上),你发现胸部连接到手臂,而手臂连接到手。这个排序会一直进行下去,直到所有的骨头以这样或那样的方法连回到胸部。
到了这里,你有了一个根帧(root frame),它正是胸部。根没有父帧(parent frames),这表示它是继承的顶部,并且不属于另外一帧。与其他帧相连的帧称为子帧(child frames)(也叫做结点(nodes))。例如,上臂是下臂的父亲,而手是下臂的孩子。从继承中往上,手、下臂和上臂都是胸部的孩子。
当一个帧运动的时候,其所有的子帧也会运动。如果你移动你的上臂,你的下臂和手也会运动。另一方面(无意使用双关语——原文On the other hand,而我们也在谈论hand,所以作者提醒读者不要联想到双关语),如果你移动你的手,metaphorically speaking (不知道是什么意思),只有你的手会移动位置,因为它没有子帧(下臂是手的父帧)。
使用高级网格和动画的技术时,帧继承是非常重要的。事实上,为了使用蒙皮网格等技术(关于蒙皮网格的更多信息,请参见本章稍后的“Meshes with D3DX”这一节),帧继承是必备知识。
使用帧继承的另外一个理由是对场景的某部分进行孤立。这样,你可以通过移动特定的帧来改变场景的一小部分,而让场景的其余部分维持原样。例如,如果你有一个代表你的房子的帧,以及一个代表一扇门的帧,你可以在不影响房子的帧的情况下改变门的帧。
2.13.3 Creating .X Meshes (创建.X网格模型)
你可以通过很多种方式创建你自己的.X文件。微软写了一些你可以用在3D Studio Max 或Maya 等建模程序中的导出器。如果你买不起这些酷炫的建模程序的话,你可以手动地创建模型(通过手动地将每个顶点和多边形的数据输入一个文本.X文件中),或者使用一个低成本的替代程序,例如MilkShape 3D,它是由Mete Ciragan开发的低面数建模器。
MilkShape 3D是为了创建游戏Half-Life (《半条命》)的模型而产生的建模软件,但是它的应用范围已经变得广泛得多了。MilkShape 3D现在支持多种模型格式(主要用于游戏),但是它仍然是一款有用的程序。
你知道这扯得有点远了,所以我就直言不讳了(原文比较难翻译:You know this is getting somewhere, so I’ll go ahead andlet the cat out of the bag)—— MilkShape 3D有一个你可以使用的.X文件导出器,它是你最喜欢的作家(对,没错,正是在下!)编写的。登陆MilkShape 3-D的官方网站(位于http://www.swissquake.ch/chumbalum-soft)来下载它吧。
除了使用第三方的程序来为你创建.X文件以外,你唯一剩下的选择就是手动创建了。然而,因为你自己去创建模型不是最佳的选择,所以我不会在此书中讲述这个话题。
2.13.4 Parsing .X Files (剖析.X文件)
本章以及本书之后的部分,手动地剖析一个.X文件以提取出重要信息将会变成必行之事。为了剖析一个.X文件,你使用一组IDirectXFile对象,它们的工作是打开一个.X文件并枚举(enumerating through)文件的数据对象,从而将其以一种容易访问的方式送给你。
注意
===============================================================================
为了使用IDirectXFile组件,你必须包含进dxfile.h、rmxfguid.h和rmxftmpl.h头文件。另外,你必须将dxguid.lib 和d3dxof.lib 库文件链接到你的项目中。
===============================================================================
剖析一个.X文件实际上并不像它一眼看上去那么难。诀窍就是扫描整个对象的继承来寻找你想要使用的数据对象——一般来说,是代表网格模型和帧的对象。最难的部分是要记住,物体可以嵌套在另外的物体之中,所以你也许遇到的不是对象,而是对象的引用(这些引用需要被解析 (resolved) 以便访问原始的对象数据)。图2.25说明了数据对象的组织方式。
下列代码打开一个.X文件,并剖析包含在其中的对象。记住,这些函数在本章(以及本书其余部分)中的学习中是非常必要的,所以暂时不用担心太多关于使用它们的事情。
BOOL ParseXFile(char *Filename)
{
IDirectXFile *pDXFile = NULL;
IDirectXFileEnumObject *pDXEnum = NULL;
IDirectXFileData *pDXData = NULL;
// Create the .X file object
if(FAILED(DirectXFileCreate(&pDXFile)))
return FALSE;
// Register the templates in use
// Use the standard retained mode templates from Direct3D
if(FAILED(pDXFile->RegisterTemplates((LPVOID) \
D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES))) {
pDXFile->Release();
return FALSE;
}
// Create an enumeration object
if(FAILED(pDXFile->CreateEnumObject((LPVOID)Filename, \
DXFILELOAD_FROMFILE, &pDXEnum))) {
pDXFile->Release();
return FALSE;
}
// Enumerate all top-level objects
while(SUCCEEDED(pDXEnum->GetNextDataObject(&pDXData))) {
ParseXFileData(pDXData);
ReleaseCOM(pDXData);
}
// Release objects
ReleaseCOM(pDXEnum);
ReleaseCOM(pDXFile);
// Return a success
return TRUE;
}
void ParseXFileData(IDirectXFileData *pData)
{
IDirectXFileObject *pSubObj = NULL;
IDirectXFileData *pSubData = NULL;
IDirectXFileDataReference *pDataRef = NULL;
const GUID *pType = NULL;
char *pName = NULL;
DWORD dwSize;
char *pBuffer;
// Get the object type
if(FAILED(pData->GetType(&pType)))
return;
// Get the object name (if any)
if(FAILED(pData->GetName(NULL, &dwSize)))
return;
if(dwSize) {
if((pName = new char[dwSize]) != NULL)
pData->GetName(pName, &dwSize);
}
// Give a default name if none found
if(pName == NULL) {
if((pName = new char[9]) == NULL)
return;
strcpy(pName, “Template”);
}
// See what the object was and deal with it
// This is where you’ll jump in with your own code
// Scan for embedded objects
while(SUCCEEDED(pData->GetNextObject(&pSubObj))) {
// Process embedded references
if(SUCCEEDED(pSubObj->QueryInterface( \
IID_IDirectXFileDataReference, (void**)&pDataRef))) {
if(SUCCEEDED(pDataRef->Resolve(&pSubData))) {
ParseXFileData(pSubData);
ReleaseCOM(pSubData);
}
ReleaseCOM(pDataRef);
}
// Process non-referenced embedded objects
if(SUCCEEDED(pSubObj->QueryInterface( \
IID_IDirectXFileData, (void**)&pSubData))) {
ParseXFileData(pSubData);
ReleaseCOM(pSubData);
}
ReleaseCOM(pSubObj);
}
// Release name buffer
delete[] pName;
}
ParseXFile 和ParseXFileData 函数一起合作来剖析.X文件中的每一个数据对象。ParseXFile 函数打开该.X文件并对其进行枚举,寻找继承中最顶部的对象。当每一个对象被发现时,它被传递到ParseXFileData函数中。
ParseXFileData 函数处理数据对象的数据。它首先得到对象的类型以及对象实例的名字(如果有的话)。从中你可以处理对象的数据,然后让该函数使用递归来枚举所有的子对象。这个过程会一直持续到所有的对象被处理完为止。
你只需要用你想要处理的.X文件的名字来调用ParseXFile 函数,然后这两个函数就会处理剩下的事情了。你会在本章稍后的“Skinned Meshes”这一节中学会如何让这两个函数好好发挥作用的。
===============================================================================
好啦,这一节其实是介绍性的,更刺激的事情在后面呢!而且,事实上,要弄懂关于在DirectX程序中使用网格模型的事情,单靠这一章的内容是远远不够的。本书后面的部分还会花上不少篇幅来讲述其方方面面。另外,“龙书”第二版的第14章-第16章也是非常值得参考和借鉴的内容!
此外,关于我们使用的建模软件,我这里稍微提一下。虽然作者说我们自己去建模型不是最好的选择(也就是说,作为游戏程序员,不应该同时兼任建模者的角色),不过对于我们这些基本上是孤军奋战的人来说,自己做一些简单的模型,然后用在自己的Direct3D 程序中难道不是一件令人兴奋的事情吗?当然,你可以选择从网上下载一些免费的(免费也分好几种,有“用于任意用途的免费”和“用于非商业用途的免费”等等)、可以转换成.X文件的3D模型,不过自己做一个模型不是更令人期待吗?
除了3D Studio Max 和Maya 这样的大型而昂贵的3D建模软件外,作者还提到了另外两款小巧的、便宜的、但是功能也不弱的3D建模软件——trueSpace 3D和MilkShape 3D,尤其是偏爱后者。不过我下载了MilkShape 3D后发现用起来还是不太方便的,而且由于体积小,所以功能还是比较有限的(另外,它还是一个低面数建模软件,没法制作面数太多的模型)。
在这里小编向大家强烈推荐一款免费开源的3D建模软件,它的名字叫做Blender。引用一下百度百科的介绍:
Blender 是一个开源的跨平台全能三维动画制作软件,提供从建模、动画、材质、渲染、到音频处理和视频剪辑等一系列动画短片制作解决方案,以及绿屏抠像、摄像机反向跟踪、遮罩处理、后期结点合成等高级影视解决方案。还内置有卡通描边(FreeStyle)和基于 GPU 技术 Cycles 渲染器。拥有方便在不同工作下使用的多种用户界面,Blender 以 Python 为内建脚本,支持多种第三方渲染器,同时还内建游戏引擎。
基本上,3D Studio Max能做到的事情,Blender也能做到,甚至能够做得更快更好。另外这款软件非常小巧,功能最强大的最新版的体积也只有不到60M。用它制作出来的3D模型是完全可以放心使用的,你不必担心版权问题(不过用它附带的游戏引擎制作出来的游戏还是要遵循跟开源软件相关的协议的)!下面是某位大神做出来的一个模型的渲染后的效果图:
然后关于教程,这里推荐《blender权威指南》,是中国人写的,据说写得不错,网上有电子扫描版。
好啦,这一期就到这里啦,咱们下期再见!