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

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

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号