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

图4-1 将一个模型添加到项目中

然后将模型绑定到这个变量。这一步适合在LoadContent方法中进行:

myModel = Content.Load("tank");

注意:这个例子中素材的名称是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  阅读次数:7056

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号