34.模型ModelSceneNode.cs类

前面的教程使用的几何体都是手动定义的,通常只能制作简单的模型,复杂的模型通常都是使用诸如3DSMax、Maya等3D建模工具创建的,在简单的情况下,使用XNA自带的BasicEffect可以很好地绘制模型,但要使用的自定义的effect绘制一个模型,需要进行一些工作,基本思路来自于4.7 使用自定义Effects和纹理绘制模型(简单方法),代码如下:

namespace StunEngine.SceneNodes
{
    /// 
    ///  使用Microsoft.Xna.Framework.Graphics.Model定义的几何模型。
    /// 
    public class ModelSceneNode : Renderable3DSceneNode
    {
        #region 成员变量和构造函数

        /// 
        /// 模型
        /// 
        protected Model model;

        /// 
        /// 模型使用的图元数量
        /// 
        private int primitives;

        /// 
        /// 模型名称,包含路径
        /// 
        protected string modelAssetName;

        /// 
        /// 各部件转换矩阵数组
        /// 
        public Matrix[] modelTransforms;

        /// 
        /// 模型的全局包围球
        /// 
        private BoundingSphere originalSphere;

        /// 
        /// 模型的默认材质,只有当模型中某个的ModelMeshPart不包含effect信息时才会使用它
        /// 
        GenericMaterial material;

        /// 
        /// 材质数组,用于保存模型中每个ModelMeshPart的材质
        /// 
        private GenericMaterial[] materials;

        /// 
        /// 模型每秒移动速度
        /// 
        protected float movementSpeed = 60.0f;

        /// 
        /// 模型每秒旋转速度
        /// 
        protected float rotationSpeed = 60.0f;

        /// 
        /// 创建一个ModelSceneNode对象。
        /// 
        /// 引擎
        /// 模型名称
        public ModelSceneNode(StunXnaGE engine, Scene setScene, string modelAssetName)
            : base(engine, setScene)
        {
            this.modelAssetName = modelAssetName;
            material = new GenericMaterial(engine, engine.BaseEffect);
        }

        #endregion

        #region 属性

        /// 
        /// 获取或设置模型移动速度
        /// 
        public float MovementSpeed
        {
            get { return movementSpeed; }
            set { movementSpeed = value; }
        }

        /// 
        /// 获取或设置模型旋转速度
        /// 
        public float RotationSpeed
        {
            get { return rotationSpeed; }
            set { rotationSpeed = value; }
        }

        /// 
        /// 获取模型。
        /// 
        public Model Model { get { return this.model; } }

        /// 
        /// 获取或设置模型的默认材质,只有当模型中某个的ModelMeshPart不包含effect信息时才会使用它
        /// 
        public GenericMaterial Material
        {
            get { return material; }
        }

        /// 
        /// 获取材质数组,这个数组用来保存每个ModelMeshPart的材质
        /// 
        public GenericMaterial[] Materials
        {
            get { return materials; }
        }

        #endregion

        /// 
        /// 加载模型
        /// 
        internal override void LoadContent()
        {
            model = Engine.Content.Load(modelAssetName);
            modelTransforms = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(modelTransforms);
        }

        /// 
        /// 初始化
        /// 
        public override void Initialize()
        {
            base.Initialize();
            this.UpdateOrder = SceneManagement.SceneNodeOrdering.SceneNode.GetValue();

            // 创建一个材质数组保存所有ModelMeshPart的材质
            List mList = new List();

            // 遍历模型的所有ModelMesh
            foreach (ModelMesh mesh in model.Meshes)
            {
                //根据每个ModelMesh的包围球计算模型的全局包围球
                BoundingSphere meshSphere;
                mesh.BoundingSphere.Transform(ref modelTransforms[mesh.ParentBone.Index], out meshSphere);
                BoundingSphere.CreateMerged(ref originalSphere, ref meshSphere, out originalSphere);

                // 遍历当前ModelMesh的所有ModelMeshPart
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                {
                    // 获取模型使用的图元数量
                    primitives += meshPart.PrimitiveCount;

                    // 要支持任意模型,每个MeshPart都需要有一个材质。
                    // 通过BasicEffect对象获取每个MeshPart的材质并将它复制到Materials数组中
                    BasicEffect be = meshPart.Effect as BasicEffect;

                    if (be != null)
                    {
                        GenericMaterial mat = new GenericMaterial(engine, engine.BaseEffect);
                        if (be.TextureEnabled)
                            mat.SetDiffuseTexture(be.Texture);
                        else
                            mat.DiffuseTextureName = null;
                        mat.CurrentTechniqueName = "TexturedLights";
                        mat.DiffuseColor = be.DiffuseColor;
                        mat.SpecularColor = be.SpecularColor;
                        mat.SpecularPower = be.SpecularPower;
                        mat.EmissiveColor = be.EmissiveColor;                        
                        mList.Add(mat);
                    }
                }
            }
            materials = mList.ToArray();
            this.pose.LocalSize = originalSphere.Radius * 2;
        }

        /// 
        /// 绘制模型。
        /// 
        ///         
        public override int Draw(GameTime gameTime, bool useReflection)
        {
            GraphicsDevice graphicsDevice = Engine.GraphicsDevice;

            int materialCounter = 0;

            // 遍历模型的所有ModelMesh
            for (int mCounter = 0; mCounter < model.Meshes.Count; mCounter++)
            {
                ModelMesh mesh = model.Meshes[mCounter];
                graphicsDevice.Indices = mesh.IndexBuffer;

                Matrix world = modelTransforms[mesh.ParentBone.Index] * pose.ScaleMatrix * pose.RotationMatrix * pose.TranslateMatrix;

                int count = mesh.MeshParts.Count;
                // 遍历当前ModelMesh的所有ModelMeshPart
                for (int i = 0; i < count; i++)
                {
                    ModelMeshPart part = mesh.MeshParts[i];

                    // 判断使用材质数组中的材质还是默认材质。
                    GenericMaterial m = materialCounter >= materials.Length ? material : materials[materialCounter];
                    materialCounter++;

                    m.SetEffectParameters(ref world, useReflection);

                    Effect effect = m.EffectInstance;
                    effect.Begin();
                    int passesCounte = effect.CurrentTechnique.Passes.Count;
                    for (int j = 0; j < passesCounte; j++)
                    {
                        EffectPass pass = effect.CurrentTechnique.Passes[j];
                        if (part.NumVertices > 0)
                        {
                            pass.Begin();
                            graphicsDevice.VertexDeclaration = part.VertexDeclaration;
                            graphicsDevice.Vertices[0].SetSource(mesh.VertexBuffer, part.StreamOffset, part.VertexStride);
                            graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, part.BaseVertex, 0, part.NumVertices, part.StartIndex, part.PrimitiveCount);
                            pass.End();
                        }
                    }
                    effect.End();
                }
            }
            return primitives;
        }

        #region 单元测试

#if DEBUG
        /// 
        /// 测试ModelSceneNode类
        /// 
        public static void TestModelSceneNode()
        {
            ModelSceneNode sphere = null;
            ModelSceneNode simpleTank = null;
            ModelSceneNode tank = null;
            Vector3 position, scale;
            bool useCustomTexture = true;

            TestGame.Start("测试ModelSceneNode类",
                delegate
                {
                    // 不包含纹理数据,只有一个ModelMesh的球
                    sphere = new ModelSceneNode(TestGame.engine, TestGame.scene, "Models//SphereLowPoly");
                    TestGame.scene.AddNode(sphere);
                    sphere.Materials[0].DiffuseColor = new Vector3(1, 0, 0);
                    position = new Vector3(-8f, 2f, 0f);
                    sphere.Pose.SetPosition(ref position);

                    // 不包含纹理数据,包含3个ModelMesh有层次结构的简单坦克
                    simpleTank = new ModelSceneNode(TestGame.engine, TestGame.scene, "Models//SimpleTank");
                    TestGame.scene.AddNode(simpleTank);
                    scale = new Vector3(0.05f, 0.05f, 0.05f);
                    simpleTank.Pose.SetScale(ref scale);
                    position = new Vector3(0.0f, 1.0f, 0.0f);
                    simpleTank.Pose.SetPosition(ref position);

                    // 包含纹理数据,包含12个ModelMesh有层次结构的复杂坦克
                    tank = new ModelSceneNode(TestGame.engine, TestGame.scene, "Models//tank");
                    TestGame.scene.AddNode(tank);
                    scale = new Vector3(0.01f, 0.01f, 0.01f);
                    tank.Pose.SetScale(ref scale);
                    position = new Vector3(8.0f, 0f, 0.0f);
                    tank.Pose.SetPosition(ref position);

                    TestGame.scene.IsShowMouse = false;
                }, delegate
                {
                    // 按数字1键切换球使用的纹理
                    if (Input.KeyboardKeyJustPressed(Keys.D1))
                    {
                        if (sphere.Materials[0].DiffuseTextureName == null)
                        {
                            sphere.Materials[0].DiffuseTextureName = "Textures\\Grass";
                        }
                        else
                        {
                            sphere.Materials[0].DiffuseTextureName = null;
                        }
                    }
                    
                    // 按数字2键切换简单坦克的炮塔使用的纹理
                    if (Input.KeyboardKeyJustPressed(Keys.D2))
                    {
                        if (simpleTank.Materials[1].DiffuseTextureName ==null)
                        {
                            simpleTank.Materials[1].DiffuseTextureName ="Textures\\Rock";
                        }
                        else
                        {
                            simpleTank.Materials[1].DiffuseTextureName = null;
                        }
                    }

                    // 按数字3键切换复杂坦克的炮塔使用的纹理
                    if (Input.KeyboardKeyJustPressed(Keys.D3))
                    {
                        if (useCustomTexture)
                        {
                           tank.Materials[10].DiffuseTextureName = "Textures\\Rock";
                           useCustomTexture = false ;
                        }
                        else
                        {
                            BasicEffect be = tank.Model .Meshes[10].MeshParts[0].Effect as BasicEffect;
                            tank.Materials[10].SetDiffuseTexture(be.Texture);
                            useCustomTexture = true  ;
                        }
                    }
                });
        }
#endif
        #endregion
    }
}

关键步骤就是将模型的每个ModelMeshPart的材质复制到GenericMaterial材质数组中,这样就可以使用自定义的effect绘制模型了。

在单元测试中使用了三个模型代表了三种典型情况。第一种情况是一个不包含纹理数据,只有一个ModelMesh的球,因为这个球连纹理坐标信息也不包含,所以即使使用了纹理显示也不正常;第二种情况是一个不包含纹理数据,包含3个ModelMesh有层次结构的简单坦克,层次结构分别是车身、炮塔、炮管,这三个ModelMesh的索引都可以在程序的合适位置设置中断看到,对应分别是0、1、2,使用数字键2切换炮塔的材质;第三种情况是一个包含纹理数据,包含12个ModelMesh有层次结构的复杂坦克,其中炮塔的索引为10,使用数字键3切换炮塔的材质。

单元测试截图如下:

单元测试

文件下载(已下载 1168 次)

发布时间:2010/5/10 下午1:12:05  阅读次数:6260

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

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号