4.7 使用自定义Effects和纹理绘制模型(简单方法)
问题
当你使用教材4-1中的代码将一个模型加载到XNA项目时,你使用的是一个BasicEffect实例。在简单的情况下BasicEffect可以很好地绘制模型,当我们常常想使用一个不同的,自定义的effect绘制一个模型。
解决方案
通过将模型的effect包含在BasicEffect对象中,你可以获取effect的所有信息,诸如纹理和材质等信息。当你将这些属性复制到某个安全的地方后,就可以用你选择的effect覆盖这个effect。
注意:你还可以使用自定义内容处理器更加清晰地完成这个任务,可见教材4-12。
工作原理
首先你需要加载一个模型和自定义effect,两者都需要使用内容管道进行加载:
protected override void LoadContent() { device = graphics.GraphicsDevice; basicEffect = new BasicEffect(device, null); myModel = Content.Load<Model>("tank"); modelTransforms = new Matrix[myModel.Bones.Count]; customEffect = Content.Load<Effect>("bwcolor"); }
本例中的自定义effect会使用灰度值绘制模型(译者注:本例中的灰度实现使用最简单的三个颜色通道求平均值的方法,而不是更好的给绿色更多的权重)。
因为effect是存储在模型的ModelMesh中的,所以你可以简单地使用自定义effect覆盖它们。但是,所有原始effect信息都会丢失!这些信息包括了纹理和材质颜色信息。
在覆盖模型的默认effect前,你想存储这些信息,因为自定义effect需要知道某些信息,例如使用哪个纹理。
你将创建一个方法处理模型和自定义effect,这个方法会使用自定义effect的副本替换模型中的所有effect。最后,存储初始数据,返回初始effect中所有纹理的数组,这样你就可以将它们传递到自定义effect。本例中,所有的effect都会被自定义effect复写,但a small adaptation suffices to override only the effect of a particular ModelMeshPart(译者:?):
private Texture2D[] ChangeEffect(ref Model model, Effect newEffect) { }
因为你想永久地改变模型,所以model参数要使用引用(在第一个变量前加上ref)。否则会使用一个本地副本,而对于这个副本所有改变都不会被调用代码获知。
首先,你要创建一个被原始effect使用的所用纹理的副本:
List<Texture2D> textureList = new List<Texture2D>(); foreach (ModelMesh mesh in model.Meshes) foreach (ModelMeshPart modmeshpart in mesh.MeshParts) { BasicEffect oldEffect = (BasicEffect)modmeshpart.Effect; textureList.Add(oldEffect.Texture); } Texture2D[] textures = textureList.ToArray(); return textures;
你创建了一个集合存储纹理,然后遍历每个ModelMesh的不同ModelMeshParts,每个ModelMeshPart包含指向effect的链接。将这个effect传递到一个BasicEffect对象,这样你就可以访问到它的属性,例如纹理。对于模型中的每个effect都附加上一个纹理集合中的纹理。
收集完所有的纹理后,你将集合转换为一个数组并将这个数组返回。
现在你所做的操作会使用自定义effect的副本覆盖原始effect,所以在第二个循环中添加以下代码:
modmeshpart.Effect = newEffect.Clone(device);
注意:你使用了Clone方法创建了一个effect的副本,你想使用这个副本,否则所有的ModelMeshPart都会共享相同的effect。通过给每个ModelMeshPart施加各自effect的副本,每个ModelMeshPart都能使用不同的纹理。
在LoadContent方法中加载了模型和自定义effect之后调用这个方法:
modelTextures = ChangeEffect(ref myModel, customEffect);
当绘制模型时,你就可以单独定义自定义effect的每个参数了:
int i = 0; Matrix worldMatrix = Matrix.CreateScale(0.01f); myModel.CopyAbsoluteBoneTransformsTo(modelTransforms); foreach (ModelMesh mesh in myModel.Meshes) { foreach (Effect currentEffect in mesh.Effects) { currentEffect.Parameters["xWorld"]. SetValue(modelTransforms[mesh.ParentBone.Index]*worldMatrix); currentEffect.Parameters["xView"].SetValue(fpsCam.ViewMatrix); currentEffect.Parameters["xProjection"].SetValue(fpsCam.ProjectionMatrix); currentEffect.Parameters["xTexture"].SetValue(modelTextures[i++]); } mesh.Draw(); }
请注意你是如何将原始effect信息传递到自定义的effect中的,例如本例中的纹理。
遍历ModelMeshPart而不是Effect
前面的循环中遍历了所有effect,但这样做当你想设置一个特定ModelMeshPart的effect时会出现问题,所以你遍历ModelMesh的ModelMeshPart而不是它的effect:
int i = 0; Matrix worldMatrix = Matrix.CreateScale(0.01f); myModel.CopyAbsoluteBoneTransformsTo(modelTransforms); foreach (ModelMesh mesh in myModel.Meshes) { foreach (ModelMeshPart part in mesh.MeshParts) { Effect currentEffect = part.Effect; currentEffect.Parameters["xWorld"]. SetValue(modelTransforms[mesh.ParentBone.Index] * worldMatrix); currentEffect.Parameters["xView"].SetValue(fpsCam.ViewMatrix); currentEffect.Parameters["xProjection"].SetValue(fpsCam.ProjectionMatrix); currentEffect.Parameters["xTexture"].SetValue(modelTextures[i++]); } mesh.Draw(); }
存储所有原始信息
如果你还想存储的纹理之外的信息,只需简单地将原始effect添加到集合替代纹理:
BasicEffect[] originalEffects;
在ChangeEffect方法中,将原始effect添加到集合中去:
BasicEffect oldEffect = (BasicEffect)modmeshpart.Effect; effectList.Add(oldEffect);
当设置自定义effect的参数时,你可以很容易地访问到初始信息:
currentEffect.Parameters["xTexture"].SetValue(originalEffects[i++].Texture);
代码
在LoadContent方法中,加载模型和自定义effect:
protected override void LoadContent() { device = graphics.GraphicsDevice; basicEffect = new BasicEffect(device, null); cCross = new CoordCross(device); myModel = Content.Load<Model>("tank"); modelTransforms = new Matrix[myModel.Bones.Count]; customEffect = Content.Load<Effect>("bwcolor"); originalEffects = ChangeEffect(ref myModel, customEffect); }
最后一行代码使用自定义effect的副本替换了模型中的effect,是在下列方法中实现的:
private BasicEffect[] ChangeEffect(ref Model model, Effect newEffect) { List<BasicEffect> effectList = new List<BasicEffect>(); foreach (ModelMesh mesh in model.Meshes) foreach (ModelMeshPart modmeshpart in mesh.MeshParts) { BasicEffect oldEffect = (BasicEffect)modmeshpart.Effect; effectList.Add(oldEffect); modmeshpart.Effect = newEffect.Clone(device); } BasicEffect[] effects = effectList.ToArray(); return effects; }
当绘制模型时,你可以像这样访问到存储的信息:
int i = 0; Matrix worldMatrix = Matrix.CreateScale(0.01f); myModel.CopyAbsoluteBoneTransformsTo(modelTransforms); foreach (ModelMesh mesh in myModel.Meshes) { foreach (ModelMeshPart part in mesh.MeshParts) { Effect currentEffect = part.Effect; currentEffect.Parameters["xWorld"]. SetValue(modelTransforms[mesh.ParentBone.Index] * worldMatrix); currentEffect.Parameters["xView"].SetValue(fpsCam.ViewMatrix); currentEffect.Parameters["xProjection"].SetValue(fpsCam.ProjectionMatrix); currentEffect.Parameters["xTexture"].SetValue(originalEffects[i++].Texture); } mesh.Draw(); }
发布时间:2009/6/19 上午10:40:39 阅读次数:5852