#region File Description //----------------------------------------------------------------------------- // NormalMappingModelProcessor.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #endregion using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Content.Pipeline.Processors; using Microsoft.Xna.Framework.Content.Pipeline.Graphics; using Microsoft.Xna.Framework.Content.Pipeline; using System.IO; using System.ComponentModel; namespace NormalMappingEffectPipeline { /// /// The NormalMappingModelProcessor is used to change the material/effect applied /// to a model. After going through this processor, the output model will be set /// up to be rendered with NormalMapping.fx. /// [ContentProcessor(DisplayName="Normal Mapping Model Processor")] public class NormalMappingModelProcessor : ModelProcessor { // this constant determines where we will look for the normal map in the opaque // data dictionary. public const string NormalMapKey = "NormalMap"; /// /// We override this property from the base processor and force it to always /// return true: tangent frames are required for normal mapping, so they should /// not be optional. /// [Browsable(false)] public override bool GenerateTangentFrames { get { return true; } set { } } /// /// The user can set this value in the property grid. If it is set, the model /// will use this value for its normal map texture, overriding anything in the /// opaque data. We use the display name and description attributes to control /// how the property appears in the UI. /// [DisplayName("Normal Map Texture")] [Description("If set, this file will be used as the normal map on the model, " + "overriding anything found in the opaque data.")] [DefaultValue("")] public string NormalMapTexture { get { return normalMapTexture; } set { normalMapTexture = value; } } private string normalMapTexture; String directory; public override ModelContent Process(NodeContent input, ContentProcessorContext context) { if (input == null) { throw new ArgumentNullException("input"); } directory = Path.GetDirectoryName(input.Identity.SourceFilename); LookUpNormalMapAndAddToTextures(input); return base.Process(input, context); } /// /// Looks into the OpaqueData property on the "mesh" object, and looks for a /// a string containing the path to the normal map. The normal map is added /// to the Textures collection for each of the mesh's materials. /// private void LookUpNormalMapAndAddToTextures(NodeContent node) { MeshContent mesh = node as MeshContent; if (mesh != null) { string pathToNormalMap; // if NormalMapTexture hasn't been set in the UI, we'll try to look up // the normal map using the opaque data. if (String.IsNullOrEmpty(NormalMapTexture)) { pathToNormalMap = mesh.OpaqueData.GetValue(NormalMapKey, null); } // However, if the NormalMapTexture is set, we'll use that value for // ever mesh in the scene. else { pathToNormalMap = NormalMapTexture; } if (pathToNormalMap == null) { throw new InvalidContentException("the normal map is missing!"); } pathToNormalMap = Path.Combine(directory, pathToNormalMap); foreach (GeometryContent geometry in mesh.Geometry) { geometry.Material.Textures.Add(NormalMapKey, new ExternalReference(pathToNormalMap)); } } foreach (NodeContent child in node.Children) { LookUpNormalMapAndAddToTextures(child); } } // acceptableVertexChannelNames are the inputs that the normal map effect // expects. The NormalMappingModelProcessor overrides ProcessVertexChannel // to remove all vertex channels which don't have one of these four // names. static IList acceptableVertexChannelNames = new string[] { VertexChannelNames.TextureCoordinate(0), VertexChannelNames.Normal(0), VertexChannelNames.Binormal(0), VertexChannelNames.Tangent(0) }; /// /// As an optimization, ProcessVertexChannel is overriden to remove data which /// is not used by the vertex shader. /// /// the geometry object which contains the /// vertex channel /// the index of the vertex channel /// to operate on /// the context that the processor is operating /// under. in most cases, this parameter isn't necessary; but could /// be used to log a warning that a channel had been removed. protected override void ProcessVertexChannel(GeometryContent geometry, int vertexChannelIndex, ContentProcessorContext context) { String vertexChannelName = geometry.Vertices.Channels[vertexChannelIndex].Name; // if this vertex channel has an acceptable names, process it as normal. if (acceptableVertexChannelNames.Contains(vertexChannelName)) { base.ProcessVertexChannel(geometry, vertexChannelIndex, context); } // otherwise, remove it from the vertex channels; it's just extra data // we don't need. else { geometry.Vertices.Channels.Remove(vertexChannelName); } } protected override MaterialContent ConvertMaterial(MaterialContent material, ContentProcessorContext context) { EffectMaterialContent normalMappingMaterial = new EffectMaterialContent(); normalMappingMaterial.Effect = new ExternalReference (Path.Combine(directory, "NormalMapping.fx")); OpaqueDataDictionary processorParameters = new OpaqueDataDictionary(); processorParameters["ColorKeyColor"] = this.ColorKeyColor; processorParameters["ColorKeyEnabled"] = this.ColorKeyEnabled; processorParameters["TextureFormat"] = this.TextureFormat; processorParameters["GenerateMipmaps"] = this.GenerateMipmaps; processorParameters["ResizeTexturesToPowerOfTwo"] = this.ResizeTexturesToPowerOfTwo; // copy the textures in the original material to the new normal mapping // material. this way the diffuse texture is preserved. The // PreprocessSceneHierarchy function has already added the normal map // texture to the Textures collection, so that will be copied as well. foreach (KeyValuePair> texture in material.Textures) { normalMappingMaterial.Textures.Add(texture.Key, texture.Value); } // and convert the material using the NormalMappingMaterialProcessor, // who has something special in store for the normal map. return context.Convert (normalMappingMaterial, typeof(NormalMappingMaterialProcessor).Name, processorParameters); } } }