4.1 使用BasicEffect类载入模型
问题
因为手动定义一个复杂3D对象的所有点几乎是不可能的,所以这些3D对象应该是由艺术家在3D建模程序中制作,模型可以保存为一个文件。你想从文件加载模型并在场景中绘制这个模型。
解决方案
XNA Framework已经包含了所需方法。XNA提供了一个默认的Model素材管道可以从.x和.fbx文件加载模型。如教程3-1的图片所示,你只需将模型拖动到Solution Explorer的Content文件夹即可,然后将它设置为XNA代码中的一个变量。这个Model变量包含绘制模型的方法。
工作原理
首先将. x或. fbx文件导入到XNA Game Studio中。你可以通过在Windows Explorer中选中这个文件将它拖入到XNA Game Studio的Solution Explorer的Content文件夹中。也可以右击Content文件夹选择Add Existing Item,如图4-1所示,然后浏览到. x或. fbx文件并选择它。
注意:当你点击新添加的模型文件时,在Solution Explorer的右下角Properties框中会显示XNA使用默认的模型导入器和处理器。可见教程4-12至4-16获取如何使用自定义的模型处理器的知识。
添加好模型后,你可以将下面的变量添加到代码中:
Model myModel;
图4-1 将一个模型添加到项目中
然后将模型绑定到这个变量。这一步适合在LoadContent方法中进行:
myModel = Content.Load
注意:这个例子中素材的名称是tank,默认就是模型文件的没有扩展名的名称。你也可以通过选择Solution Explorer中的源文件改变Asset Name属性。
现在你加载了模型,可以通过在Draw 方法中添加以下代码绘制它了,这是将模型绘制到场景所需的所有代码,本教程后面会介绍此代码的背景知识。
//draw model modelTransforms = new Matrix[myModel.Bones.Count]; Matrix worldMatrix = Matrix.CreateScale(0.01f, 0.01f, 0.01f); myModel.CopyAbsoluteBoneTransformsTo(modelTransforms); foreach (ModelMesh mesh in myModel.Meshes) { foreach (BasicEffect effect in mesh.Effects) { effect.EnableDefaultLighting(); effect.World = modelTransforms[mesh.ParentBone.Index] * worldMatrix; effect.View = fpsCam.ViewMatrix; effect.Projection = fpsCam.ProjectionMatrix; } } mesh.Draw();
模型的结构
在大多数情况下,你可以旋转和移动模型的一部分,例如,移动人的手臂。要能实现这个功能,模型应该要被分成几个成员。对每个成员,你需要知道两件事:
- 几何数据:你想知道顶点,顶点包含了组成模型成员的所有三角形顶点的信息,这些信息包括位置,颜色,法线等。
- 成员与它们的父成员如何连接:以人的手臂为例,你要指定手臂是连接在肩膀上的。
每个成员的几何数据以ModelMesh对象的形式存储,而ModelMesh对象是在Model的 ModelMeshCollection中,在这个对象你可以找到它的Meshes属性。成员的位置数据以Bone对象的形式存储,而Bone对象在Model的ModelBoneCollection中,在这个对象中你可以找到模型的Bones集合。每个ModelMesh对象包含指向Bone对象的引用,而一个Bone对象包含指向父Bone的引用,它必须连接到这个父Bone。通过这种方式,你可以将所有Bone对象连接到一起,具体细节可见教程4-8和4-9。在教程4-9中,你可以找到CopyAbsoluteBoneTransformsTo方法的解释。
ModelMeshes和Bones
一个ModelMesh包含模型的一个成员的几何信息,这个成员无法再分成更小的成员。例如,一台笔记本电脑不是一个好的ModelMesh,因为你想翻起/关闭液晶屏,打开/关闭DVD托盘。好的办法是对电脑底座使用一个ModelMesh,液晶屏使用第二个ModelMeshcreen,而DVD托盘使用第三个ModelMesh。
因为所有的ModelMesh都需要他连接到一个Bone上,下一步你的三个ModelMeshes还需要三个Bone。你需要将连接到电脑底座的ModelMesh的Bone作为root Bone,因为电脑底座可以看成电脑的初始位置。连接到液晶屏ModelMesh的表示与底座连接的位置。同理连接DVD托盘的ModelMesh的Bone也要指向root Bone,表示托盘相对于底座的位置。
ModelMeshes和ModelMeshParts
你已经为液晶屏定义了一个ModelMesh和对应的Bone,这样很完美,因为液晶屏不包含可动部分。但是,你可能还想使用一个固定纹理的effect绘制液晶屏的塑料外壳,使用另一个effect绘制LCD,例如,这个效果会从另一张纹理采样颜色或你还想添加一点反光效果。
这就需要用到ModelMeshParts。每个ModelMesh可以包含多个ModelMeshParts,每个ModelMeshParts可以使用不同的纹理,材质或effect进行绘制。这意味着每个ModelMeshPart 都包含各自的几何数据和对应几何数据的effect。
注意:如果你对effect不了解,可以把它们想象成像素的颜色定义,像素是否反光,是否透明,是否从一张图像中获取颜色?可见第6章的例子学习像素如何正确地与光线作用。教程3-13包含了从图像获取颜色的例子。
两个循环嵌套
Model的结构解释了为什么我们需要在绘制模型时使用两个循环。首先,你遍历Model的ModelMeshes,每个ModelMesh包含一个或多个ModelMeshPart,每个ModelMeshPart都有自己的effect。所以,你需要第二个循环遍历当前ModelMesh的所有ModelMeshPart,设置各自effect的参数。但是如果多个ModelMeshParts使用同样的effect,这会导致同样的effect被设置了两次,显然只是浪费时间。要避免这种情况,ModelMesh会通过它的ModelMeshPart保存所有独立的effect,在第二个循环中使用这个effecf集合。
最后,在设置好ModelMesh的所有effect后,你调用ModelMesh对象的Draw方法,这会使用ModelMesh对象的所有ModelMeshParts使用各自的effect进行绘制。
注意:教程4-8的例子中遍历了ModelMesh的ModelMeshParts,而不是effects。
小小的性能提升
前面的代码在每次调用Draw方法时都会重新实例化Bones数组。更好的办法是在加载模型后只实例化并填充数组一次。所以,将以下代码放置到LoadContent方法中:
myModel = content.Load<Model>("tank"); modelTransforms = new Matrix[myModel.Bones.Count];
代码
首先在LoadContent方法中加载Model并初始化Bone数组:
protected override void LoadContent() { device = graphics.GraphicsDevice; basicEffect = new BasicEffect(device, null); myModel = Content.Load<Model>("tank"); modelTransforms = new Matrix[myModel.Bones.Count]; }
然后在Draw 方法中绘制模型:
protected override void Draw(GameTime gameTime) { device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.CornflowerBlue, 1, 0); //draw model Matrix worldMatrix = Matrix.CreateScale(0.01f, 0.01f, 0.01f); myModel.CopyAbsoluteBoneTransformsTo(modelTransforms); foreach (ModelMesh mesh in myModel.Meshes) { foreach (BasicEffect effect in mesh.Effects) { effect.EnableDefaultLighting(); effect.World = modelTransforms[mesh.ParentBone.Index] * worldMatrix; effect.View = fpsCam.ViewMatrix; effect.Projection = fpsCam.ProjectionMatrix; } mesh.Draw(); } base.Draw(gameTime); }
发布时间:2009/6/3 上午10:09:46 阅读次数:7121