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 下午1:45:13 阅读次数:6874