// Project: XnaGraphicEngine, File: ShaderEffect.cs // Namespace: XnaGraphicEngine.Shaders, Class: ShaderEffect // Path: C:\code\XnaGraphicEngine\Shaders, Author: Abi // Code lines: 904, Size of file: 24,94 KB // Creation date: 07.09.2006 05:56 // Last modified: 05.11.2006 00:32 // Generated with Commenter by abi.exDream.com #region Using directives #if DEBUG //using NUnit.Framework; #endif using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.IO; using System.Text; using XnaGraphicEngine.Graphics; using XnaGraphicEngine.Helpers; using Texture = XnaGraphicEngine.Graphics.Texture; using XnaTexture = Microsoft.Xna.Framework.Graphics.Texture; using Model = XnaGraphicEngine.Graphics.Model; using XnaModel = Microsoft.Xna.Framework.Graphics.Model; using XnaGraphicEngine.Game; #endregion namespace XnaGraphicEngine.Shaders { /// /// Shader effect class. You can either directly use this class by /// providing a fx filename in the constructor or derive from this class /// for special shader functionality (see post screen shaders for a more /// complex example). /// public class ShaderEffect : IGraphicContent { #region Some shaders /// /// Line rendering shader /// public static ShaderEffect lineRendering = new ShaderEffect("LineRendering.fx"); /// /// Normal mapping shader /// public static ShaderEffect normalMapping = new ShaderEffect("NormalMapping.fx"); #endregion #region Variables /// /// Content name for this shader /// private string shaderContentName = ""; /// /// Effect /// protected Effect effect = null; /// /// Effect handles for shaders. /// protected EffectParameter worldViewProj, viewProj, world, viewInverse, lightDir, ambientColor, diffuseColor, specularColor, specularPower, diffuseTexture, normalTexture; #endregion #region Properties /// /// Is this shader valid to render? If not we can't perform any rendering. /// /// Bool public bool Valid { get { return effect != null; } // get } // Valid /// /// Effect /// /// Effect public Effect Effect { get { return effect; } // get } // Effect /// /// Number of techniques /// /// Int public int NumberOfTechniques { get { return effect.Techniques.Count; } // get } // NumberOfTechniques /// /// Get technique /// /// Technique name /// Effect technique public EffectTechnique GetTechnique(string techniqueName) { return effect.Techniques[techniqueName]; } // GetTechnique(techniqueName) /// /// Set value helper to set an effect parameter. /// /// Param /// Set matrix private void SetValue(EffectParameter param, ref Matrix lastUsedMatrix, Matrix newMatrix) { /*obs, always update, matrices change every frame anyway! * matrix compare takes too long, it eats up almost 50% of this method. if (param != null && lastUsedMatrix != newMatrix) */ { lastUsedMatrix = newMatrix; param.SetValue(newMatrix); } // if (param) } // SetValue(param, setMatrix) /// /// Set value helper to set an effect parameter. /// /// Param /// Last used vector /// New vector private void SetValue(EffectParameter param, ref Vector3 lastUsedVector, Vector3 newVector) { if (param != null && lastUsedVector != newVector) { lastUsedVector = newVector; param.SetValue(newVector); } // if (param) } // SetValue(param, lastUsedVector, newVector) /// /// Set value helper to set an effect parameter. /// /// Param /// Last used color /// New color private void SetValue(EffectParameter param, ref Color lastUsedColor, Color newColor) { // Note: This check eats few % of the performance, but the color // often stays the change (around 50%). if (param != null && //slower: lastUsedColor != newColor) lastUsedColor.PackedValue != newColor.PackedValue) { lastUsedColor = newColor; //obs: param.SetValue(ColorHelper.ConvertColorToVector4(newColor)); param.SetValue(newColor.ToVector4()); } // if (param) } // SetValue(param, lastUsedColor, newColor) /// /// Set value helper to set an effect parameter. /// /// Param /// Last used value /// New value private void SetValue(EffectParameter param, ref float lastUsedValue, float newValue) { if (param != null && lastUsedValue != newValue) { lastUsedValue = newValue; param.SetValue(newValue); } // if (param) } // SetValue(param, lastUsedValue, newValue) /// /// Set value helper to set an effect parameter. /// /// Param /// Last used value /// New value private void SetValue(EffectParameter param, ref XnaTexture lastUsedValue, XnaTexture newValue) { if (param != null && lastUsedValue != newValue) { lastUsedValue = newValue; param.SetValue(newValue); } // if (param) } // SetValue(param, lastUsedValue, newValue) protected Matrix lastUsedWorldViewProjMatrix = Matrix.Identity; /// /// Set world view proj matrix /// protected Matrix WorldViewProjMatrix { set { SetValue(worldViewProj, ref lastUsedWorldViewProjMatrix, value); } // set } // WorldViewProjMatrix protected Matrix lastUsedViewProjMatrix = Matrix.Identity; /// /// Set view proj matrix /// protected Matrix ViewProjMatrix { set { SetValue(viewProj, ref lastUsedViewProjMatrix, value); } // set } // ViewProjMatrix //obs: protected Matrix lastUsedWorldMatrix = Matrix.Identity; /// /// Set world matrix /// public Matrix WorldMatrix { set { // This is the most used property here. //obs: SetValue(world, ref lastUsedWorldMatrix, value); /*obs, world matrix ALWAYS changes! and it is always used! if (world != null && lastUsedWorldMatrix != value) { lastUsedWorldMatrix = value; world.SetValue(lastUsedWorldMatrix); } // if (world) */ // Faster, we checked world matrix in constructor. world.SetValue(value); } // set } // WorldMatrix protected Matrix lastUsedInverseViewMatrix = Matrix.Identity; /// /// Set view inverse matrix /// protected Matrix InverseViewMatrix { set { SetValue(viewInverse, ref lastUsedInverseViewMatrix, value); } // set } // InverseViewMatrix protected Vector3 lastUsedLightDir = Vector3.Zero; /// /// Set light direction /// protected Vector3 LightDir { set { // Make sure lightDir is normalized (fx files are optimized // to work with a normalized lightDir vector) value.Normalize(); // Set negative value, shader is optimized not to negate dir! SetValue(lightDir, ref lastUsedLightDir, -value); } // set } // LightDir protected Color lastUsedAmbientColor = ColorHelper.Empty; /// /// Ambient color /// public Color AmbientColor { set { SetValue(ambientColor, ref lastUsedAmbientColor, value); } // set } // AmbientColor protected Color lastUsedDiffuseColor = ColorHelper.Empty; /// /// Diffuse color /// public Color DiffuseColor { set { SetValue(diffuseColor, ref lastUsedDiffuseColor, value); } // set } // DiffuseColor protected Color lastUsedSpecularColor = ColorHelper.Empty; /// /// Specular color /// public Color SpecularColor { set { SetValue(specularColor, ref lastUsedSpecularColor, value); } // set } // SpecularColor private float lastUsedSpecularPower = 0; /// /// SpecularPower for specular color /// public float SpecularPower { set { SetValue(specularPower, ref lastUsedSpecularPower, value); } // set } // SpecularPower protected XnaTexture lastUsedDiffuseTexture = null; /// /// Set diffuse texture /// public Texture DiffuseTexture { set { SetValue(diffuseTexture, ref lastUsedDiffuseTexture, value != null ? value.XnaTexture : null); } // set } // DiffuseTexture protected XnaTexture lastUsedNormalTexture = null; /// /// Set normal texture for normal mapping /// public Texture NormalTexture { set { SetValue(normalTexture, ref lastUsedNormalTexture, value != null ? value.XnaTexture : null); } // set } // NormalTexture #endregion #region Constructor public ShaderEffect(string shaderName) { if (BaseGame.Device == null) throw new NullReferenceException( "XNA device is not initialized, can't create ShaderEffect."); shaderContentName = StringHelper.ExtractFilename(shaderName, true); Load(); BaseGame.RegisterGraphicContentObject(this); } // SimpleShader() #endregion #region Dispose /// /// Dispose /// public virtual void Dispose() { // Dispose shader effect if (effect != null) effect.Dispose(); } // Dispose() #endregion #region Reload effect /// /// Reload effect (can be useful if we change the fx file dynamically). /// public void Load() { /*obs // Dispose old shader if (effect != null) Dispose(); */ // Load shader try { // We have to try, there is no "Exists" method. // We could try to check the xnb filename, but why bother? ^^ effect = BaseGame.Content.Load( Path.Combine(Directories.ContentDirectory, shaderContentName)); } // try #if XBOX360 catch (Exception ex) { Log.Write("Failed to load shader "+shaderContentName+". " + "Error: " + ex.ToString()); // Rethrow error, app can't continue! throw ex; } #else catch { // Try again by loading by filename (only allowed for windows!) // Content file was most likely removed for easier testing :) try { CompiledEffect compiledEffect = Effect.CompileEffectFromFile( Path.Combine("Shaders", shaderContentName + ".fx"), null, null, CompilerOptions.None, TargetPlatform.Windows); effect = new Effect(BaseGame.Device, compiledEffect.GetEffectCode(), CompilerOptions.None, null); } // try catch (Exception ex) { Log.Write("Failed to load shader "+shaderContentName+". " + "Error: " + ex.ToString()); // Rethrow error, app can't continue! throw ex; } // catch } // catch #endif GetParameters(); } // Load() #endregion #region Get parameters /// /// Get parameters, override to support more /// protected virtual void GetParameters() { worldViewProj = effect.Parameters["worldViewProj"]; viewProj = effect.Parameters["viewProj"]; world = effect.Parameters["world"]; viewInverse = effect.Parameters["viewInverse"]; lightDir = effect.Parameters["lightDir"]; ambientColor = effect.Parameters["ambientColor"]; diffuseColor = effect.Parameters["diffuseColor"]; specularColor = effect.Parameters["specularColor"]; specularPower = effect.Parameters["shininess"]; diffuseTexture = effect.Parameters["diffuseTexture"]; normalTexture = effect.Parameters["normalTexture"]; } // GetParameters() #endregion #region SetParameters /// /// Set parameters, this overload sets all material parameters too. /// public virtual void SetParameters(Material setMat) { if (worldViewProj != null) worldViewProj.SetValue(BaseGame.WorldViewProjectionMatrix); if (viewProj != null) viewProj.SetValue(BaseGame.ViewProjectionMatrix); if (world != null) world.SetValue(BaseGame.WorldMatrix); if (viewInverse != null) viewInverse.SetValue(BaseGame.InverseViewMatrix); if (lightDir != null) lightDir.SetValue(BaseGame.LightDirection); // Set all material properties if (setMat != null) { AmbientColor = setMat.ambientColor; DiffuseColor = setMat.diffuseColor; SpecularColor = setMat.specularColor; SpecularPower = setMat.specularPower; DiffuseTexture = setMat.diffuseTexture; NormalTexture = setMat.normalTexture; } // if (setMat) } // SetParameters() /// /// Set parameters, override to set more /// public virtual void SetParameters() { SetParameters(null); } // SetParameters() #endregion #region Update /// /// Update /// public void Update() { effect.CommitChanges(); } // Update() #endregion #region Render /// /// Render /// /// Set matrix /// Technique name /// Render delegate public void Render(Material setMat, string techniqueName, BaseGame.RenderDelegate renderDelegate) { SetParameters(setMat); /*will become important later in the book. // Can we do the requested technique? // For graphic cards not supporting ps2.0, fall back to ps1.1 if (BaseGame.CanUsePS20 == false && techniqueName.EndsWith("20")) // Use same technique without the 20 ending! techniqueName = techniqueName.Substring(0, techniqueName.Length - 2); */ // Start shader effect.CurrentTechnique = effect.Techniques[techniqueName]; effect.Begin(SaveStateMode.None); // Render all passes (usually just one) //foreach (EffectPass pass in effect.CurrentTechnique.Passes) for (int num = 0; num < effect.CurrentTechnique.Passes.Count; num++) { EffectPass pass = effect.CurrentTechnique.Passes[num]; pass.Begin(); renderDelegate(); pass.End(); } // foreach (pass) // End shader effect.End(); } // Render(passName, renderDelegate) /// /// Render /// /// Technique name /// Render delegate public void Render(string techniqueName, BaseGame.RenderDelegate renderDelegate) { Render(null, techniqueName, renderDelegate); } // Render(techniqueName, renderDelegate) #endregion #region Render single pass shader /// /// Render single pass shader, little faster and simpler than /// Render and it just uses the current technique and renderes only /// the first pass (most shaders have only 1 pass anyway). /// Used for MeshRenderManager! /// /// Render delegate public void RenderSinglePassShader( BaseGame.RenderDelegate renderDelegate) { // Start effect (current technique should be set) effect.Begin(SaveStateMode.None); // Start first pass effect.CurrentTechnique.Passes[0].Begin(); // Render renderDelegate(); // End pass and shader effect.CurrentTechnique.Passes[0].End(); effect.End(); } // RenderSinglePassShader(renderDelegate) #endregion } // class ShaderEffect } // namespace XnaGraphicEngine.Shaders