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的材质 ListmList = 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 阅读次数:6282