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切换炮塔的材质。
单元测试截图如下:

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