18.Mesh类

本教程开始进入到3D编程阶段,3D比起2D难得可不止1.5倍,要考虑的东西多得多。

要进行3D编程,首先应建立一个相机,这样才能观察3D世界,这一部分的内容已在前面的教程中完成了。然后就是显示3D模型了。模型的创建方法通常有两种:一是自己手动创建模型的顶点、索引、纹理坐标……,但这通常只能用于创建一些诸如立方体、球体、圆柱体等的简单模型;复杂的模型通常都是在诸如3DsMax之类的3D应用程序中创建并导出的。本教程首先讨论简单的自定义模型。

数学知识告诉我们,几何体是由点、线、面构成的,所以要理解下面的知识,你首先应当掌握在XNA中绘制点、线、三角形的方法,这可以参见5.1 绘制三角形,线和点5.2 在三角形上添加纹理5.3 使用索引移除冗余顶点5.4 使用顶点缓冲和索引缓冲将顶点和索引保存在显存中。看懂了这几篇文章,总结一下绘制的流程:

  1. 定义每个顶点的坐标、颜色、纹理坐标……并创建顶点数组;
  2. 设置VertexDeclaration(顶点声明),它告知显卡在顶点中包含何种类型的信息;
  3. 创建索引数组;
  4. 使用SetData方法将顶点数据和索引数据复制到显存中;
  5. 设置effect ;
  6. 在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  阅读次数:6994

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

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号