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
{
///
/// 定义顶点缓冲绘制的方式。
///
public enum DrawMode
{
///
/// 使用DrawIndexedPrimitives绘制图元
///
Indexed,
///
/// 使用DrawPrimitives绘制图元
///
Primitive,
}
}
文件下载(已下载 1568 次)
发布时间:2010/3/29 上午7:48:53 阅读次数:7639
