问题
如在教程4-1中的最后所讨论的,一个模型通常包含许多成员,这些成员叫做ModelMeshes。这些ModelMeshes之间的位置联系是包含在Model对象的Bone结构中的。Bone结构定义了ModelMeshes是如何并在哪儿互相联系,每个ModelMesh 相对于parent ModelMesh旋转和/或缩放了多少。
在你可以让模型动起来前,你需要知道ModelMeshes是连接在哪个Bone上,需要可视化Bone的结构。
解决方案
Model是由ModelMeshe组成的。这些ModelMeshe包含了渲染模型确定部分所需的所有数据,包含所有顶点、索引、纹理、effect等,如教程4-1的第二部分所述。但是,ModelMesh 不包含在模型中的位置的信息。例如,一个car模型的右前门ModelMesh包含所有绘制需要的顶点和纹理信息,但它不包含相对于车身中心绘制在哪儿的信息。
这个信息存储在模型的Bones集合中。每个ModelMesh链接到一个Bone,这样XNA可以找到ModelMesh相对于Model中心的位置。一个Bone只是包含一个变换矩阵,保存这个ModelMesh相对于它的parent ModelMesh的位置。例如,车身的ModelMesh指向一个保存0平移的Bone,这是因为车身是车的主体。右前车门的ModelMesh指向一个保存右车门相对于车身中心位置的转换矩阵的Bone。
你还可以加以扩展。一辆高级的汽车模型还包含一个独立的右前车窗ModelMesh,这个ModelMesh也指向一个Bone,这个Bone包含车窗相对于右前车门的位置信息。图4-11显示了这个Bone结构。我在每个名称的后面加了一个B提醒你这些都是Bone对象。要产生这种结构,每个Bone对象都要链接到它的parent Bone和所有它的child Bone。
图4-11 一个汽车模型的Bone层次
在本教程中,你将遍历这个结构并用类似于图4-11的形式将Bone对象写入一个文件。你还要遍历模型的所有ModelMeshes并显示它们链接到哪个Bone。
工作原理
幸运的是,你可以很容易地通过在加载模型后的代码行中设置断点的方法看到模型中定义了哪些ModelMeshe,这一步可以通过点击代码左侧的侧边栏实现,也可以使用如下的代码:
myModel = Content.Load<Model>("tank"); modelTransforms = new Matrix[myModel.Bones.Count]; System.Diagnostics.Debugger.Break();
当运行代码时,程序会在最后一行停止,屏幕的左下角你可以浏览到模型并看到这个模型包含了哪些ModelMeshe,如图4-12所示。
图4-12 浏览实时变量
但你还需要知道每个ModelMesh链接到哪个Bone上,你还想遍历Bone集合获知Bone的层次结构。在本教程中,你将编写一些代码生成一个简单的文本文件,包含Bone结构和ModelMesh-to-Bone之间的联系。
下面的输出清单是DirectX SDK自带的dwarf模型的信息:
Model Bone Information ---------------------- - Name : DXCC_ROOT Index: 0 - Name : null Index: 1 - Name : Drawf Index: 2 - Name : Sword_new Index: 3 - Name : Sword_newShape Index: 4 - Name : backpack_new Index: 5 - Name : backpack_newShape Index: 6 - Name : Body1 Index: 7 - Name : Body1Shape Index: 8 - Name : Armor_new Index: 9 - Name : Armor_newShape Index: 10 - Name : Face_gear_new Index: 11 - Name : Face_gear_newShape Index: 12 - Name : Head_new Index: 13 - Name : Head_newShape Index: 14 - Name : side Index: 15 - Name : front Index: 16 - Name : top Index: 17 - Name : persp Index: 18 Model Mesh Information ---------------------- - ID : 0 Name: Sword_newShape Bone: Sword_newShape (4) - ID : 1 Name: backpack_newShape Bone: backpack_newShape (6) - ID : 2 Name: Body1Shape Bone: Body1Shape (8) - ID : 3 Name: Armor_newShape Bone: Armor_newShape (10) - ID : 4 Name: Face_gear_newShape Bone: Face_gear_newShape (12) - ID : 5 Name: Head_newShape Bone: Head_newShape (14)
在代码底部,你知道这个矮人(dwarf)模型包含六个ModelMeshe,每个ModelMeshe链接到各自的Bone。你可以独立地移动剑和背包,但无法移动手臂和腿,因为手臂和腿不是一个独立的ModelMeshes。
这个模型也不支持基本动画。让我们看一下另一个模型的结构,在XNA Creators Club网站上的一个坦克模型:
Model Bone Information ---------------------- - Name : tank_geo Index: 0 - Name : r_engine_geo Index: 1 - Name : r_back_wheel_geo Index: 2 - Name : r_steer_geo Index: 3 - Name : r_front_wheel_geo Index: 4 - Name : l_engine_geo Index: 5 - Name : l_back_wheel_geo Index: 6 - Name : l_steer_geo Index: 7 - Name : l_front_wheel_geo Index: 8 - Name : turret_geo Index: 9 - Name : canon_geo Index: 10 - Name : hatch_geo Index: 11 Model Mesh Information ---------------------- - ID : 0 Name: r_back_wheel_geo Bone: r_back_wheel_geo (2) - ID : 1 Name: r_front_wheel_geo Bone: r_front_wheel_geo (4) - ID : 2 Name: r_steer_geo Bone: r_steer_geo (3) - ID : 3 Name: r_engine_geo Bone: r_engine_geo (1) - ID : 4 Name: l_back_wheel_geo Bone: l_back_wheel_geo (6) - ID : 5 Name: l_front_wheel_geo Bone: l_front_wheel_geo (8) - ID : 6 Name: l_steer_geo Bone: l_steer_geo (7) - ID : 7 Name: l_engine_geo Bone: l_engine_geo (5) - ID : 8 Name: canon_geo Bone: canon_geo (10) - ID : 9 Name: hatch_geo Bone: hatch_geo (11) - ID : 10 Name: turret_geo Bone: turret_geo (9) - ID : 11 Name: tank_geo Bone: tank_geo (0)
在这个例子中。你会发现几乎每个独立的部分都有一个ModelMesh!轮子有四个ModelMeshes,车身有两个,炮塔一个,炮管一个,甚至连舱盖也有一个。因为所有的ModelMeshes都有各自的Bone,所以它们都可以独立运动!例如,炮管的 ModelMesh链接到Bone 10,改变Bone 10的矩阵就可以改变炮管的位置和旋转,下一个教程中你会学到更多的知识。
收集模型信息
下面的方法可以将模型信息写到一个文件中:
private void WriteModelStructure(Model model) { StreamWriter writer = new StreamWriter("modelStructure.txt"); writer.WriteLine("Model Bone Information"); writer.WriteLine("----------------------"); ModelBone root = model.Root; WriteBone(root, 0, writer); writer.WriteLine(); writer.WriteLine(); writer.WriteLine("Model Mesh Information"); writer.WriteLine("----------------------"); foreach (ModelMesh mesh in model.Meshes) WriteModelMesh(model.Meshes.IndexOf(mesh), mesh, writer); writer.Close(); }
因为你要将文字写到一个文件中,所以需要创建一个StreamWriter,这要求在代码顶部添加System.IO命名空间:
using System.IO;
下一步,在文件中先写一个文件头,然后调用WriteBone方法处理模型的root Bone。这个马上就要创建的方法会写下这个Bone的信息并将这个调用传递到所有child Bone对象,将所有子Bone对象的信息也写入文件。
在写入第二个文件头后,你遍历模型的所有ModelMeshe并将它们传递到WriteModelMesh方法,这个方法会将ModelMesh的信息写到文件中。
WriteBone方法
这个方法写入名称(如果没有指定名称则为null)和索引。最后这个方法对当前Bone的所有child Bone调用自己。通过这种方式,你可以只对模型的root Bone 调用此方法一次,所有链接到这个root Bone的Bone对象都会罗列出来。
private void WriteBone(ModelBone bone, int level, StreamWriter writer) { for (int l = 0; l < level; l++) writer.Write("\t"); writer.Write("- Name : "); if ((bone.Name == "") || (bone.Name == "null")) writer.WriteLine("null"); else writer.WriteLine(bone.Name); for (int l = 0; l < level; l++) writer.Write("\t"); writer.WriteLine(" Index: " + bone.Index); foreach (ModelBone childBone in bone.Children) WriteBone(childBone, level + 1, writer); }
WriteModelMesh方法这个方法写入每个ModelMesh的ID、名称和链接到的Bone,下一个教程你需要这个信息绘制模型动画:
private void WriteModelMesh(int ID, ModelMesh mesh, StreamWriter writer) { writer.WriteLine("- ID : " + ID); writer.WriteLine(" Name: " + mesh.Name); writer.Write(" Bone: " + mesh.ParentBone.Name); writer.WriteLine(" (" + mesh.ParentBone.Index + ")"); }
用法
只需在加载模型时调用WriteModelStructure方法即可:
myModel = Content.Load<Model>("tank"); modelTransforms = new Matrix[myModel.Bones.Count]; WriteModelStructure(myModel);
当运行这个代码时,模型结构会被写到modelStructure.txt文件中,这个文件可以在生成.exe 文件的映射位置找,默认是在\bin\x86\Debug位置中。
代码
WriteModelStructure,WriteBone和WriteModelMesh方法前面已经全部写过了。