XNA Game Engine教程系列9:ComponentPredicate和Materials类

在这个教程中我们将创建一个component predicate类,它用来决定一个组件是否被用于一个行为。这对特殊的绘制过程很有用,例如在创建阴影映射绘制阴影时,我们只需要shadow caster。

另外:很抱歉这个教程不会有很酷的东西,在本章最后你会实现和前面相同的结果,但工作地更好。

// Provides a way for the engine to let the game tell it what to
// draw, instead of the somewhat limited ComponentType
public abstract class ComponentPredicate
{
    // Decides whether the specified component is eligible for the
    // action being done
    public abstract bool IsComponentEligible(Component Component);
}

// Basic ComponentPredicate that uses ComponentType to determine
// what components to use
public class ComponentTypePredicate : ComponentPredicate
{
    // The type of component to accept
    ComponentType Type;

    // Accepts a ComponentType to use to determine what
    // components will be accepted
    public ComponentTypePredicate(ComponentType Type)
    {
        this.Type = Type;
    }

    public override bool IsComponentEligible(Component Component)
    {
        if (Type == ComponentType.Both)
        {
            // If the render type is both, we will use all 2D or
            // 3D components
            if (Component is I2DComponent || Component is I3DComponent)
                return true;
        }
        else if (Type == ComponentType.Component2D)
        {
            // If the render type is 2D, we will only use 2D
            // components
            if (Component is I2DComponent)
                return true;
        }
        else if (Type == ComponentType.Component3D)
        {
            // If the render type is 3D, we will only use 3D
            // components
            if (Component is I3DComponent)
                return true;
        }
        else
        {
            // Otherwise, we will use every component regardless of type
            return true;
        }

        // If the conditions have not been met, we will not use the
        // component
        return false;
    }
}

下面更新GameScreen使用它:

// Draw the screen and its components. Accepts a ComponentPredicate
// to tell us what kind of components to draw.
public virtual void Draw(ComponentPredicate DrawPredicate)
{
    // Temporary list of components to draw
    List<Component> drawing = new List<Component>();

    foreach (Component component in Components.InDrawOrder)
        if (DrawPredicate.IsComponentEligible(component))
            drawing.Add(component);

    // Keep a list of components that are 2D so we can draw them on top
    // of the 3D components
    List<Component> defer2D = new List<Component>();

    foreach (Component component in drawing)
        if (component.Visible && component.Initialized)
            // If the component is visible
            if (component is I2DComponent)
                // If it is 2D, wait to draw
                defer2D.Add(component);
            else
                // otherwise, draw immediately
                component.Draw();

    // Draw 2D components
    foreach (Component component in defer2D)
        component.Draw();
}

最后更新Engine使用它:

// ComponentType to render
public static void Draw(GameTime gameTime, ComponentPredicate DrawPredicate)
{
    // Update the time, create a temp list
    Engine.GameTime = gameTime;
    List<GameScreen> drawing = new List<GameScreen>();

    // Clear the back buffer
    GraphicsDevice.Clear(Color.CornflowerBlue);

    // Populate the temp list if the screen is visible
    foreach (GameScreen screen in GameScreens)
        if (screen.Visible)
            drawing.Add(screen);

    // BlocksDraw and OverrideDrawBlocked logic
    for (int i = GameScreens.Count - 1; i >= 0; i--)
        if (GameScreens[i].BlocksDraw)
        {
            if (i > 0)
                for (int j = i - 1; j >= 0; j--)
                {
                    if (!GameScreens[j].OverrideDrawBlocked)
                        drawing.Remove(GameScreens[j]);
                }

            break;
        }

    // Draw the remaining screens
    foreach (GameScreen screen in drawing)
        if (screen.Initialized)
            screen.Draw(DrawPredicate);
}

public static void Draw(GameTime gameTime, ComponentType DrawType)
{
    Draw(gameTime, new ComponentTypePredicate(DrawType));
}

现在运行程序,你会发现没什么变化。这个教程的第二部分关于材质。一个材质定义了对象如何绘制。它们定义了象specularity(高光),texture(纹理),color(颜色),emmisive light(自发光),bump map(凹凸贴图)等属性。你接下去实现的材质只定义了纹理,但以后会扩展这个类,下面是材质的基类:

// Base Material class keeps track of an effect and handles basic
// properties for it
public class Material : Component
{
    // The effect to handle
    Effect effect;

    public virtual Effect Effect
    {
        get { return effect; }
        set { effect = value; }
    }

    // Sets up the material from the values embedded in a MeshPart
    public virtual Material CreateFromMeshPart(ModelMeshPart MeshPart)
    {
        this.effect = MeshPart.Effect;
        return this;
    }

    // Set World, View, and Projection values
    public virtual void Prepare3DDraw(Matrix World)
    {
        effect.Parameters["World"].SetValue(World);
        effect.Parameters["View"].SetValue(
            Engine.Services.GetService<Camera>().View);
        effect.Parameters["Projection"].SetValue(
            Engine.Services.GetService<Camera>().Projection);
    }
}

接着扩展这个基类实现支持一个叫做TexturedMaterial的纹理:

// Material that handles a texture
public class TexturedMaterial : Material
{
    // The texture to keep track of
    Texture2D texture;

    // Set accesor sets the texture to the underlying effect
    public virtual Texture2D Texture
    {
        get { return texture; }
        set { texture = value; SetTextureToEffect(Effect, texture); }
    }

    public override Effect Effect
    {
        get { return base.Effect; }
        set
        {
            SetTextureToEffect(value, GetTextureFromEffect(base.Effect));
            base.Effect = value;
        }
    }

    // Override gets the texture from the MeshPart
    public override Material CreateFromMeshPart(ModelMeshPart MeshPart)
    {
        base.CreateFromMeshPart(MeshPart);
        this.texture = GetTextureFromEffect(MeshPart.Effect);
        return this;
    }

    // Method to get texture from various types of effect
    internal static Texture2D GetTextureFromEffect(Effect Effect)
    {
        if (Effect == null)
            return null;

        if (Effect is BasicEffect)
            return ((BasicEffect)Effect).Texture;
        else if (Effect.Parameters["Texture"] != null)
            return Effect.Parameters["Texture"].GetValueTexture2D();

        return null;
    }

    // Method to set the texture to various types of effect
    internal static void SetTextureToEffect(Effect Effect, Texture2D Texture)
    {
        if (Effect == null)
            return;

        if (Effect is BasicEffect)
            ((BasicEffect)Effect).Texture = Texture;
        else if (Effect.Parameters["Texture"] != null)
            Effect.Parameters["Texture"].SetValue(Texture);
    }
}

最后制作的材质类叫做MeshPartMaterial。这个类被用于如Actor等类,处理包含在模型中的ModelMeshPart的材质属性。

// Handles setting effect and texture to a MeshPart
public class MeshPartMaterial : TexturedMaterial
{
    ModelMeshPart meshPart;

    public ModelMeshPart MeshPart
    {
        get { return meshPart; }
        set { meshPart = value; value.Effect = Effect; }
    }

    public override Effect Effect
    {
        get { return base.Effect; }
        set { SetTextureToEffect(value, GetTextureFromEffect(base.Effect)); base.Effect = value; MeshPart.Effect = value; }
    }

    public override Material CreateFromMeshPart(ModelMeshPart MeshPart)
    {
        base.CreateFromMeshPart(MeshPart);
        this.meshPart = MeshPart;
        return this;
    }
}

现在我们可以更新Actor类使用这个MeshPartMaterial类:

// In the top of the class:

public Dictionary<ModelMeshPart, Material> Materials = new Dictionary<ModelMeshPart, Material>();
// In Setup()

foreach (ModelMesh mesh in Model.Meshes)
    foreach (ModelMeshPart part in mesh.MeshParts)
        Materials.Add(part, new MeshPartMaterial().CreateFromMeshPart(part));
// In the Draw() method:

// Loop through meshes and effects and set them up to draw
foreach (ModelMesh mesh in model.Meshes)
{
    foreach (ModelMeshPart part in mesh.MeshParts)
        Materials[part].Prepare3DDraw(world);

    // Draw the mesh
    mesh.Draw();
}

现在运行程序你可以发射一个盒子,没什么新的东西,但至少工作正常,以后我们会实现更高级的东西。

现在对Terrain进行同样的操作:

// Material for a Terrain object
public class TerrainMaterial : TexturedMaterial
{
    public TerrainMaterial(Texture2D Texture)
        : base()
    {
        this.Effect = new BasicEffect(Engine.GraphicsDevice, null);
        this.Texture = Texture;

        SetupEffect();
    }

    // Setup BasicEffect
    private void SetupEffect()
    {
        BasicEffect basicEffect = (BasicEffect)this.Effect;

        basicEffect.Texture = Texture;
        basicEffect.TextureEnabled = true;
        basicEffect.EnableDefaultLighting();
        basicEffect.DirectionalLight0.Direction = new Vector3(1, -1, 1);
        basicEffect.DirectionalLight0.Enabled = true;
        basicEffect.AmbientLightColor = new Vector3(0.3f, 0.3f, 0.3f);
        basicEffect.DirectionalLight1.Enabled = false;
        basicEffect.DirectionalLight2.Enabled = false;
        basicEffect.SpecularColor = new Vector3(0, 0, 0);
    }

    public override void Prepare3DDraw(Matrix World)
    {
        base.Prepare3DDraw(World);

        Engine.GraphicsDevice.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
        Engine.GraphicsDevice.SamplerStates[0].AddressV = TextureAddressMode.Wrap;
    }
}

这个类所做的就是跟踪纹理并创建effect,以后我们会扩展为多纹理,现在改变terrain的代码:

// Replaces Effect and Texture properties

// Material
public TerrainMaterial Material;
// Remove the following from Setup()

basicEffect = new BasicEffect(Engine.GraphicsDevice, null);
SetupEffect();

// Add to Setup()

Material = new TerrainMaterial(Texture);
private float[,] CreateTerrain(Texture2D heightMap, Texture2D texture)

// Becomes

private float[,] CreateTerrain(Texture2D heightMap)

// Remove from CreateTerrain()

this.texture = texture;
// Update the Draw() method

// Draw the terrain
public override void Draw()
{
    // Require the camera
    Camera camera = Engine.Services.GetService<Camera>();
    if (camera == null)
        throw new Exception("The engine services does not contain a "
        + "camera service. The terrain requires a camera to draw.");

    // Set effect values
    Material.Prepare3DDraw(MathUtil.CreateWorldMatrix(
        Position, Rotation, Scale));

    // Get width and height
    int width = heightData.GetLength(0);
    int height = heightData.GetLength(1);

    // Terrain uses different vertex winding than normal models, 
    // so set the new one
    Engine.GraphicsDevice.RenderState.CullMode = CullMode.CullClockwiseFace;

    // Start the effect
    Material.Effect.Begin();

    // For each pass..
    foreach (EffectPass pass in Material.Effect.CurrentTechnique.Passes)
    {
        // Begin the pass
        pass.Begin();

        // Draw the terrain vertices and indices
        Engine.GraphicsDevice.Vertices[0].SetSource(terrainVertexBuffer, 0, 
            VertexPositionNormalTexture.SizeInBytes);
        Engine.GraphicsDevice.Indices = terrainIndexBuffer;
        Engine.GraphicsDevice.VertexDeclaration = myVertexDeclaration;
        Engine.GraphicsDevice.DrawIndexedPrimitives(
            Microsoft.Xna.Framework.Graphics.PrimitiveType.TriangleStrip, 
            0, 0, width * height, 0, width * 2 * (height - 1) - 2);

        // End the pass
        pass.End();
    }

    // End the effect
    Material.Effect.End();

    // Set the vertex winding back
    Engine.GraphicsDevice.RenderState.CullMode = 
        CullMode.CullCounterClockwiseFace;
}

现在再次运行程序,和以前一样没什么变化,我们会在以后做出更酷的效果。


发布时间:2009/2/10 13:45:13  阅读次数:6284

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

沪ICP备18037240号-1

沪公网安备 31011002002865号