/* * Shader.cs * Authors: August Zinsser * * Copyright August Zinsser 2007 * This program is distributed under the terms of the GNU General Public License */ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Reflection; namespace Pina3D { /// /// Holds parameters and makes calls to .fx files for vertex and pixel shaders. To use a shader, call the GetPasses() function /// for a specific shader and then perform rendering between pass.Begin(...) and pass.end(...) flags. /// public class Shader : IDisposable { /// /// An enum for supported techniques for rendering static meshes /// public class StaticTechniques { public const string Blank = "Blank"; public const string TexturedLambert = "TexturedLambert"; public const string DiffuseNormal = "DiffuseNormal"; public const string MultiTexturedLambert = "MultiTexturedLambert"; } protected string mTechniqueName; // Name of the technique to invoke from the .fx file protected Effect mXNAEffect; // Internal XNA Effect Shader protected Material mMaterial; // A reference to the material that holds many shader properties (like color, textures) public List EffectPasses { get { return GenerateEffectPasses(); } } /// /// Create a 3D shader from a given material. /// /// A reference to a material which holds the surface properties to use in shaders public Shader(ref Material materialRef) { // Save the handle to the material mMaterial = materialRef; // Assume the "Blank" technique unless changed later mTechniqueName = StaticTechniques.Blank; // Find the most appropriate XNAEffect PickEffect(); } /// /// Starts the active technique /// public void Begin() { mXNAEffect.Begin(); } /// /// Ends the active technique /// public void End() { mXNAEffect.End(); } /// /// Dispose the internal effect /// public virtual void Dispose() { if (mXNAEffect != null) mXNAEffect.Dispose(); } /// /// Loads and compiles an fx file embedded in this assembly. /// /// Name of the file (with extension), assumed to be in the Shaders folder. /// public static Effect LoadEmbeddedFXFile(string fileName) { string correctFile = "Pina3D.Shaders." + fileName; // Load the .fx files from this assembly's metadata Stream fxStream = null; Assembly assembly = Assembly.GetExecutingAssembly(); string[] resourceNames = assembly.GetManifestResourceNames(); foreach (string resourceName in resourceNames) { if (resourceName.ToLower() == correctFile.ToLower()) { // Attach the stream and extract the fx file fxStream = assembly.GetManifestResourceStream(resourceName); if (fxStream != null) { // Compile the fx code CompiledEffect compiledEffect = Effect.CompileEffectFromFile(fxStream, null, null, CompilerOptions.None, TargetPlatform.Windows); if (!compiledEffect.Success) { string errors = "The fx file " + resourceName + " contains the following errors and cannot be compiled: \n"; errors += compiledEffect.ErrorsAndWarnings + "\n"; throw new InvalidProgramException(errors); } // TODO: Figure out what an effect pool is EffectPool effectPool = new EffectPool(); return new Effect(Pina.Graphics.GraphicsDevice, compiledEffect.GetEffectCode(), CompilerOptions.None, effectPool); } } } throw new FileLoadException("Could not load the embedded fx file " + correctFile); } /// /// Assigns mXNAEffect based on the other variables in this instance /// private void PickEffect() { // Select the appropriate fx file, first by attempting to match the technique name string correctFile = ""; switch (mTechniqueName) { case StaticTechniques.TexturedLambert: case StaticTechniques.MultiTexturedLambert: correctFile = "StaticMesh.fx"; break; } if (correctFile == "") { // Now examine the reference Material if (mMaterial != null) { correctFile = "StaticMesh.fx"; } } // TODO: load the files once, don't reload and recompile it every time a Shader generates effect passes! mXNAEffect = LoadEmbeddedFXFile(correctFile); } /// /// (Re)assigns all the EffectParameters based on the information contained in the reference Material or data internal to this Shader and then /// generates the appropriate EffectPass passes. /// /// private List GenerateEffectPasses() { // Remember which properties are supported bool difMap = mMaterial.UseDiffuseMap; bool norMap = mMaterial.UseNormalMap; bool multiMap = mMaterial.UseMultipleDiffuseMaps; // Query global scene parameters mXNAEffect.Parameters["xWorldMat"].SetValue(Pina.WorldMatrix); mXNAEffect.Parameters["xViewProjMat"].SetValue(Pina.ViewProjectionMatrix); mXNAEffect.Parameters["xLightDir"].SetValue(Pina.GlobalLightDirection); mXNAEffect.Parameters["xLightPower"].SetValue(Pina.GlobalLightPower); mXNAEffect.Parameters["xLightAmbient"].SetValue(Pina.GlobalLightAmbient); mXNAEffect.Parameters["xLightColor"].SetValue(Pina.GlobalLightColor.ToVector4()); // Assign the colors present in the reference material mXNAEffect.Parameters["xAmbientColor"].SetValue(mMaterial.AmbientColor.ToVector4()); mXNAEffect.Parameters["xDiffuseColor"].SetValue(mMaterial.DiffuseColor.ToVector4()); // Assign the effect parameters present in the reference material if (difMap) mXNAEffect.Parameters["xDiffuseMap"].SetValue(mMaterial.DiffuseMap); if (norMap) mXNAEffect.Parameters["xNormalMap"].SetValue(mMaterial.NormalMap); if (multiMap) { mXNAEffect.Parameters["xDiffuseMap1"].SetValue(mMaterial.DiffuseMap1 != null ? mMaterial.DiffuseMap1 : mMaterial.DiffuseMap); mXNAEffect.Parameters["xDiffuseMap2"].SetValue(mMaterial.DiffuseMap2 != null ? mMaterial.DiffuseMap2 : mMaterial.DiffuseMap); mXNAEffect.Parameters["xDiffuseMap3"].SetValue(mMaterial.DiffuseMap3 != null ? mMaterial.DiffuseMap3 : mMaterial.DiffuseMap); } // Select a technique based on the parameters in use if (difMap && !norMap && !multiMap) { mTechniqueName = StaticTechniques.TexturedLambert; } else if (difMap && norMap && !multiMap) { mTechniqueName = StaticTechniques.DiffuseNormal; } else if (multiMap && !norMap) { // TODO (awz): This requires ps2 provide legacy support for ps1 mTechniqueName = StaticTechniques.MultiTexturedLambert; } else { // Not a valid configuration, so just render it blank mTechniqueName = StaticTechniques.Blank; System.Diagnostics.Debug.Assert(false, "Unsupported material options!\n" + mMaterial.UsageSettings); } // Update the effect mXNAEffect.CurrentTechnique = mXNAEffect.Techniques[mTechniqueName]; mXNAEffect.CommitChanges(); // Get the passes List retVal = new List(); for (int i = 0; i < mXNAEffect.CurrentTechnique.Passes.Count; i++) retVal.Add(mXNAEffect.CurrentTechnique.Passes[i]); // Return said passes return retVal; } } }