AppleSeeker's Tech Blog
Welcome to AppleSeeker's space

模型对象最终包含一系列VertexBuffer和IndexBuffer对象,这些我们将使用它们在屏幕上绘制对象。和从最后一章节的简单立方体示例不同,然而,它很可能该模型将包含多套三角形,所有这些三角形必须要在一起绘制来形成完整的对象。出于这个原因,模型对象包含一个集合的层次结构,正如图所示,必须为了获得绘制顶点数据。

image

包含的模型对象由一个Meshes属性,它提供一个关于ModelMesh对象的集合。每一个ModelMesh代表一组呈现在一个在对象位置的三角形。

有多个模型网格对象的原因是,模型对象能够存储包含部分复杂阶层的几何学。想象一下,例如,你想去渲染一个直升机模型。这里有直升机模型的3个主要部分:主体,身体旋转部分,尾部。螺旋桨是可以移动,和直升机是相关的,以便他们能够旋转。他们可以清楚直升机的一部分,但我们不能纯粹使用直升机的变换矩阵。

XNA允许模型每一个分割块,在XNA的术语中叫bones,可以单独转换。这是为什么模型可以多块存储、渲染的原因。我们将不会探究更多关于bones的细节以及如何在这本书中去使用它们,但在互联网上你应该能够通过搜索引擎找到大量的资料,如果你想更多的了解该内容,那将会给你更多的信息。

包含ModelMesh对象,又是另一个集合,ModelMeshPart对象,包含属性MeshParts。模型网格部分是指多个模型网格收集在一起组成整个几何学的一部分。用这个方法将模型网格分割成多个部分的原因是每一个部分都有不同于其它部分的参数,例如纹理。如果在模型中使用多个纹理,这样可以激活不同的纹理和单独渲染它们。

在我们最终找到的ModelMeshPart类中,IndexBuffer和VertexBuffer都已经隐藏了。除了它们几个其它属性是必不可少,对我们来说能够渲染网状部分:当使用SetVertexBuffer函数,顶点缓冲用来设置到XNA中,VertexOffset是必须的;通过DrawIndexedPrimitives函数,所有NumVertices,StartIndex和PrimitiveCount都是必须的。

这里有2个不同的函数,我们可以用来绘制模型。第一个需要我们依次通过每一个网格部分,告诉每一个网格使用它们本身的效果对象来绘制它们本身。

这些效果对象所提供的模型,使我们不必创建它们,但当然它们不会知道任何关于我们所构建的环境。为了网格部分能够在屏幕上正确的渲染,我们需要给它能够用到的世界、视角和投影矩阵。

在下面代码中,ObjectModel变量包含渲染的模型,效果变量是类级别的BasicEffect,为了所需要所有矩阵预配置,使用mesheffect对象来遍历每一个模型提供的效果对象。

// Build  an array  of  the  absolute  bone transformation  matrices Matrix[] 
boneTransforms = new  Matrix[ObjectModel.Bones.Count]; 
ObjectModel.CopyAbsoluteBoneTransformsTo(boneTransforms);

// Loop for each of  the  meshes  within the  model 
foreach  (ModelMesh   mesh in ObjectModel.Meshes)
{
    // Initialize each of  the  effects within the  mesh 
    foreach  (BasicEffect mesheffect  in mesh.Effects)
    {
        mesheffect.World  = boneTransforms[mesh.ParentBone.Index] * effect.World;
        mesheffect.View  = effect.View;
        mesheffect.Projection = effect.Projection;
    }
    // Draw  the  mesh  (including all  of  its meshparts)
    mesh.Draw();
}

注意:除了绘制模型,此代码还处理模型内的bone的位置。CopyAbsoluteBoneTransformsTo函数使用每一个bone的最终坐标来填充数组,考虑到bone的层级。然后这些转换会结合活动的世界矩阵来为每一个bone决定最终坐标。因为我们的模型不包含bone,转换将不会起任何效果,但代码将会兼容更复杂的模型.

代码会做任何所需要的事让对象出现在屏幕上,但它有一个缺点:因为为了渲染模型使用的效果对象不会有效,我们创建并在我们主要的游戏类中配置,没有一个是在模型的效果对象中出现的我们的效果属性值。正如在之前代码中,效果转换矩阵不必从我们的效果单独拷贝到模型的效果对象中。

当设置这些矩阵让模型出现在屏幕的当前位置,这有大量其它属性不会在这拷贝,将因此忽视渲染对象。这些属性包括高亮属性、漫射和放射颜色、Alpha值或更多。

因此,代替方案对我们来说就是使用我们自己的效果对象来渲染模型。这已包含所有我们需要模型来观察的属性,所以我们不必担心在渲染代码中任何处理它们。我们可以简单的递归网格部分,直接渲染它们每一个。

有一个关键部分信息,模型的效果对象包含我们自己的效果对象:为每一个网格部分使用纹理。之前代码中绘制了完全纹理化的对象,即使在代码中没有任何地方提及纹理。我们可以从模型效果中读出纹理并在我们自己的效果中使用纹理来确保当前纹理可以应用于模型的每一个部分。

代码可以可以这么做,然后使用ModelMeshPart对象所提供的信息来构建顶点和索引缓冲,并绘制它们。用这个方式渲染的代码如下所示。

Matrix  initialWorld; 
Matrix[] boneTransforms;

// Store  the  initial world  
matrix initialWorld = effect.World;

// Build  an array  of  the  absolute  bone transformation  
matrices boneTransforms = new  Matrix[ObjectModel.Bones.Count]; 
ObjectModel.CopyAbsoluteBoneTransformsTo(boneTransforms);

// Loop for each mesh
foreach  (ModelMesh   mesh in ObjectModel.Meshes)
{
    // Update the  world  matrix  to  account for the  position of  this bone 
    effect.World = boneTransforms[mesh.ParentBone.Index] * effect.World;

    // Loop for each mesh  part
    foreach  (ModelMeshPart meshpart in mesh.MeshParts)
    {
        // Set the  texture for this meshpart
        SetEffectTexture(effect, ((BasicEffect)meshpart.Effect).Texture);
        // Set the  vertex  and index  buffers 
        effect.GraphicsDevice.SetVertexBuffer(meshpart.VertexBuffer,
        meshpart.VertexOffset);
        effect.GraphicsDevice.Indices = meshpart.IndexBuffer;

        // Draw  the  mesh  part
        foreach  (EffectPass pass in effect.CurrentTechnique.Passes)
        {
            // Apply the  pass
            pass.Apply();
            // Draw  this  meshpart 
            effect.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0,  0,  meshpart.NumVertices,
                meshpart.StartIndex,  meshpart.PrimitiveCount);
        }
    }
}

// Restore  the  initial  world  matrix 
effect.World  = initialWorld;

在这代码里有一些兴趣点。有一些处理是相同的在能够在上一个代码中看到:我们恢复绝对bone数组转换然后我们递归模型的网格集合。在每一个网格,我们不在去更新效果属性,因为这次我们使用我们自己的效果。它已经配置通过使用所有必须的矩阵和其它属性,例如光照、材质等等。我们必须要做的一件事情,然后,就是观察bone的位置。在循环之前,通过从原始的世界矩阵那做一份拷贝,乘以每一个网格的bone位置。

其效果是现在准备去渲染网格,所以我们开发处理它的每一个部分。我们要为每一个部分去做的第一件事情就是去询问它自己的效果对象,去读出所需要的纹理。我们通过这个过程,伴随我们自己的效果对象到一个过程调用,称为SetEffectTexture。这是一个简单的函数,放提供的纹理2D到所提供的效果中,所提供的现在还不存在。

随着所需纹理的设置,代码设置网格部分的VertexBuffer和IndexBuffer到图形设备中。这就已为索引渲染做好准备。

最后网格部分准备好去渲染。正如我们已经准备好了,然后我们为每一个EffectPass循环,渲染索引三角形列表到每一个部分。注意,所有的细节关于DrawIndexedPrimitive所必须的渲染将由网格部分提供,所以这是非常直接的。

最终,完成所有的循环,初始化的世界矩阵将重新存储回我们的效果,重写任何在那所留的bone转换。这将停止意料之外的在渲染过程中缓慢进行的转换。

尽管在代码不是特别复杂,但有一点庞大并且有点理想化,如果我们避免在我们的游戏中去重复每一个对象。幸运的是,游戏框架将帮助我们再次走出这个困扰,让我们看下我们如何能够融入,我们已经添加的功能模型渲染。

posted on 2012-04-05 10:30  AppleSeeker(冯峰)  阅读(426)  评论(0编辑  收藏  举报