18.Mesh类
本教程开始进入到3D编程阶段,3D比起2D难得可不止1.5倍,要考虑的东西多得多。
要进行3D编程,首先应建立一个相机,这样才能观察3D世界,这一部分的内容已在前面的教程中完成了。然后就是显示3D模型了。模型的创建方法通常有两种:一是自己手动创建模型的顶点、索引、纹理坐标……,但这通常只能用于创建一些诸如立方体、球体、圆柱体等的简单模型;复杂的模型通常都是在诸如3DsMax之类的3D应用程序中创建并导出的。本教程首先讨论简单的自定义模型。
数学知识告诉我们,几何体是由点、线、面构成的,所以要理解下面的知识,你首先应当掌握在XNA中绘制点、线、三角形的方法,这可以参见5.1 绘制三角形,线和点、5.2 在三角形上添加纹理、5.3 使用索引移除冗余顶点、5.4 使用顶点缓冲和索引缓冲将顶点和索引保存在显存中。看懂了这几篇文章,总结一下绘制的流程:
- 定义每个顶点的坐标、颜色、纹理坐标……并创建顶点数组;
- 设置VertexDeclaration(顶点声明),它告知显卡在顶点中包含何种类型的信息;
- 创建索引数组;
- 使用SetData方法将顶点数据和索引数据复制到显存中;
- 设置effect ;
- 在Draw方法中使用DrawIndexedPrimitives或DrawPrimitives方法绘制图元。
看一下以下的代码,这个代码来自于我的StunEngine引擎的0.2版本中Draw方法,绘制了基本地形。这个版本中我还是使用了BasicEffect,并没有像后面的版本中使用了自定义的Effect。展示这个代码的目的是了解一下Draw方法中需要进行的操作,然后将这些操作封装成不同的类。
其中设置顶点数据流、设置索引、设置顶点声明、绘制模型这几个步骤封装到Mesh类中;设置相机的视矩阵和投影矩阵、设置模型的世界矩阵、设置纹理、设置材质封装到Material类中,在设置光照这个步骤中,首先定义光源类Light,然后还要创建管理光源的LightManager类。
//设置相机的视矩阵和投影矩阵 effect.View = scene.Camera.ViewMatrix; effect.Projection = scene.Camera.ProjectionMatrix; //设置模型的世界矩阵 effect.World = pose.WorldMatrix; //设置纹理 effect.Texture = ColorTexture ; effect.TextureEnabled = true; //设置光照 effect.EnableDefaultLighting(); effect.DirectionalLight0.Direction = new Vector3(1, -1, 1); effect.DirectionalLight0.Enabled = true; effect.AmbientLightColor = new Vector3(0.3f, 0.3f, 0.3f); effect.DirectionalLight1.Enabled = false; effect.DirectionalLight2.Enabled = false; //设置材质 effect.SpecularColor = new Vector3(0, 0, 0); //设置顶点数据流 engine.GraphicsDevice.Vertices[0].SetSource(terrainVertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes); //设置索引 engine.GraphicsDevice.Indices = terrainIndexBuffer; //设置顶点声明 engine.GraphicsDevice.VertexDeclaration = myVertexDeclaration; //用BasicEffect绘制地形 effect.Begin(); foreach (EffectPass pass in effect.CurrentTechnique.Passes) pass.Begin(); engine.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleStrip, 0, 0, terrainWidth * terrainHeight, 0, terrainWidth * 2 * (terrainHeight - 1) - 2); pass.End(); } effect.End();
这个教程首先讨论Mesh类,Mesh类与Managed DirectX(以后简称MDX)中的BaseMesh(http://msdn.microsoft.com/en-us/library/bb297081(VS.85).aspx)类相似。
在解决方案中添加一个新的文件夹Rendering,在此命名空间添加一个Mesh类,代码如下:
namespace StunEngine.Rendering { ////// 保存几何数据。 /// Mesh本质上就是包含额外信息的顶点缓冲。 /// public class Mesh { // 顶点缓冲 private VertexBuffer vb; // 索引缓冲 private IndexBuffer ib; // 顶点声明 private VertexDeclaration vxDeclaration; // 绘制模式,分Indexed和Primitive两种 private DrawMode mode; // 图元类型 private PrimitiveType primitiveType; // 顶点数量 internal int verticesCount; // 图元数量 internal int primitiveCount; #region 构造函数 ////// 不使用索引创建一个Mesh对象,自动计算顶点数量和图元数量 /// /// /// /// public Mesh(PrimitiveType primitiveType, VertexBuffer vb, VertexDeclaration vd) { this.verticesCount = vb.SizeInBytes / vd.GetVertexStrideSize(0); this.mode = DrawMode.Primitive; this.primitiveType = primitiveType; this.vb = vb; this.vxDeclaration = vd; this.primitiveCount = CalcPrimitivesCount(); } ////// 创建一个不使用索引的新Mesh,手动指定顶点数量和图元数量 /// /// 图元类型 /// 顶点缓冲 /// 顶点声明 /// 顶点数量 /// 图元数量 public Mesh(PrimitiveType primitiveType, VertexBuffer vb, VertexDeclaration vd, int verticesCount, int primitiveCount) { this.verticesCount = verticesCount; this.mode = DrawMode.Primitive; this.primitiveType = primitiveType; this.vb = vb; this.vxDeclaration = vd; this.primitiveCount = primitiveCount; } ////// 使用索引创建一个Mesh对象,自动计算顶点数量和图元数量 /// /// 图元类型 /// 顶点缓冲 /// 顶点声明 /// 索引缓冲 public Mesh(PrimitiveType primitiveType, VertexBuffer vb, VertexDeclaration vd, IndexBuffer ib) { this.verticesCount = vb.SizeInBytes / vd.GetVertexStrideSize(0); this.mode = DrawMode.Indexed; this.primitiveType = primitiveType; this.vb = vb; this.vxDeclaration = vd; this.ib = ib; this.primitiveCount = CalcPrimitivesCount(); } ////// 创建一个使用索引的新Mesh,手动指定顶点数量和图元数量 /// /// 图元类型 /// 顶点缓冲 /// 顶点声明 /// 索引缓冲 /// 顶点数量 /// 图元数量 public Mesh(PrimitiveType primitiveType, VertexBuffer vb, VertexDeclaration vd, IndexBuffer ib, int verticesCount, int primitiveCount) { this.verticesCount = verticesCount ; this.mode = DrawMode.Indexed; this.primitiveType = primitiveType; this.vb = vb; this.vxDeclaration = vd; this.ib = ib; this.primitiveCount = primitiveCount ; } #endregion #region 属性 ////// 获取存储在mesh中的顶点数量。 /// public int VerticesCount { get { return verticesCount; } } ////// 获取图元数量。 /// public int PrimitivesCount { get { return primitiveCount; } } ////// 获取或设置DrawMode /// public DrawMode DrawMode { get { return mode; } } ////// 顶点缓冲的图元类型 /// public PrimitiveType PrimitiveType { get { return primitiveType; } } ////// 获取顶点缓冲。 /// public VertexBuffer VertexBuffer { get { return vb; } } ////// 获取索引缓冲。 /// public IndexBuffer IndexBuffer { get { return ib; } } ////// 获取顶点声明。 /// public VertexDeclaration VertexDeclaration { get { return vxDeclaration; } } #endregion ////// 计算图元数量 /// private int CalcPrimitivesCount() { int count = verticesCount / 3; //如果不使用索引 if (this.DrawMode == DrawMode.Primitive) { switch (this.PrimitiveType) { //如果为TriangleList则图元数量为顶点数量除3 case PrimitiveType.TriangleList: count = verticesCount / 3; break; //如果为TriangleStrip则图元数量为顶点数量减2 case PrimitiveType.TriangleStrip: count = verticesCount - 2; break; } } //如果使用索引 else { int size = ib.SizeInBytes; int bytesPerIndex = ib.IndexElementSize == IndexElementSize.SixteenBits ? 2 : 4; switch (this.PrimitiveType) { case PrimitiveType.TriangleList: count = size / bytesPerIndex / 3; break; case PrimitiveType.TriangleStrip: count = ((size - 1) / bytesPerIndex / 3) + 1; break; case PrimitiveType.LineList: count = verticesCount - 1; break; } } return count; } ////// 为设备绘制几何体做准备。它必须在Render()之前被调用,设备也可以手动建立。 /// /// public void PrepareRender(GraphicsDevice gd) { gd.VertexDeclaration = vxDeclaration; gd.Vertices[0].SetSource(vb, 0, vxDeclaration.GetVertexStrideSize(0)); if (DrawMode == DrawMode.Indexed) gd.Indices = ib; } ////// 将数据发送到图形设备。在调用这个函数之前设备必须已经准备好(设置数据流,设置顶点声明,索引缓冲等)。 /// /// 图形设备 public void Render(GraphicsDevice gd) { if (DrawMode == DrawMode.Indexed) gd.DrawIndexedPrimitives(primitiveType, 0, 0, verticesCount, 0, primitiveCount); else gd.DrawPrimitives(primitiveType, 0, primitiveCount); } } }
在代码中可以看到有四个构造函数,第一个和第二个的区别在于第一个构造函数会调用类中的私有方法CalcPrimitivesCount()自动计算图元数量。但问题是遇到特殊情况,例如使用TriangleStrip绘制地形时,若使用了退化三角形(又称为Ghost三角形),根据公式计算出的图元数量是不对的,而且通常程序中已经知道了图元数量,所以通常我使用第二个构造函数,这也能稍微提高一点速度。第三个构造函数和第四个的区别是类似的。
代码中还使用了一个DrawMode枚举,它同样定义在Rendering命名空间下,只有两个项,分别表示使用索引和不使用索引。
namespace StunEngine.Rendering { ///文件下载(已下载 1568 次)/// 定义顶点缓冲绘制的方式。 /// public enum DrawMode { ////// 使用DrawIndexedPrimitives绘制图元 /// Indexed, ////// 使用DrawPrimitives绘制图元 /// Primitive, } }
发布时间:2010/3/29 上午7:48:53 阅读次数:6994