//----------------------------------------------------------------------------- // ShaderEffect.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.IO; using System.Text; using RacingGame.Graphics; using RacingGame.Helpers; using Texture = RacingGame.Graphics.Texture; using XnaTexture = Microsoft.Xna.Framework.Graphics.Texture; namespace RacingGame.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 : IDisposable { /// /// Line rendering shader /// public static ShaderEffect lineRendering = new ShaderEffect("LineRendering.fx"); /// /// Simple shader with just per pixel lighting for testing. /// public static ShaderEffect lighting = new ShaderEffect("LightingShader.fx"); /// /// Normal mapping shader for simple objects and the landscape rendering. /// public static ShaderEffect normalMapping = new ShaderEffect("NormalMapping.fx"); /// /// Landscape normal mapping shader for the landscape rendering with /// detail texture support, everything else should use normalMapping. /// public static ShaderEffect landscapeNormalMapping = new ShaderEffect("LandscapeNormalMapping.fx"); /// /// Shadow mapping shader /// public static ShadowMapShader shadowMapping = new ShadowMapShader(); /// /// Content name for this shader /// private string shaderContentName = ""; /// /// Effect /// protected Effect effect = null; /// /// Effect handles for shaders. /// protected EffectParameter worldViewProj, viewProj, world, viewInverse, projection, lightDir, ambientColor, diffuseColor, specularColor, specularPower, alphaFactor, scale, diffuseTexture, normalTexture, heightTexture, reflectionCubeTexture, detailTexture, parallaxAmount, carHueColorChange; /// /// Is this shader valid to render? If not we can't perform any rendering. /// /// Bool public bool Valid { get { return effect != null; } } /// /// Effect /// /// Effect public Effect Effect { get { return effect; } } /// /// Number of techniques /// /// Int public int NumberOfTechniques { get { return effect.Techniques.Count; } } /// /// Get technique /// /// Technique name /// Effect technique public EffectTechnique GetTechnique(string techniqueName) { return effect.Techniques[techniqueName]; } /// /// World parameter /// /// Effect parameter public EffectParameter WorldParameter { get { return world; } } /// /// Set value helper to set an effect parameter. /// /// Param /// Set matrix private static void SetValue(EffectParameter param, ref Matrix lastUsedMatrix, Matrix newMatrix) { // Always update, matrices change every frame anyway! lastUsedMatrix = newMatrix; param.SetValue(newMatrix); } /// /// Set value helper to set an effect parameter. /// /// Param /// Last used vector /// New vector private static void SetValue(EffectParameter param, ref Vector3 lastUsedVector, Vector3 newVector) { if (param != null && lastUsedVector != newVector) { lastUsedVector = newVector; param.SetValue(newVector); } } /// /// Set value helper to set an effect parameter. /// /// Param /// Last used color /// New color private static 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; param.SetValue(newColor.ToVector4()); } } /// /// Set value helper to set an effect parameter. /// /// Param /// Last used value /// New value private static void SetValue(EffectParameter param, ref float lastUsedValue, float newValue) { if (param != null && lastUsedValue != newValue) { lastUsedValue = newValue; param.SetValue(newValue); } } /// /// Set value helper to set an effect parameter. /// /// Param /// Last used value /// New value private static void SetValue(EffectParameter param, ref XnaTexture lastUsedValue, XnaTexture newValue) { if (param != null && lastUsedValue != newValue) { lastUsedValue = newValue; param.SetValue(newValue); } } protected Matrix lastUsedWorldViewProjMatrix = Matrix.Identity; /// /// Set world view proj matrix /// protected Matrix WorldViewProjMatrix { get { // Note: Only implemented for stupid FxCop rule, // you should never "get" a shader texture this way! return lastUsedWorldViewProjMatrix; } set { SetValue(worldViewProj, ref lastUsedWorldViewProjMatrix, value); } } protected Matrix lastUsedViewProjMatrix = Matrix.Identity; /// /// Set view proj matrix /// protected Matrix ViewProjMatrix { get { // Note: Only implemented for stupid FxCop rule, // you should never "get" a shader texture this way! return lastUsedViewProjMatrix; } set { SetValue(viewProj, ref lastUsedViewProjMatrix, value); } } /// /// Set world matrix /// public Matrix WorldMatrix { get { // Note: Only implemented for stupid FxCop rule, // you should never "get" a shader texture this way! return Matrix.Identity;//makes REALLY no sense! } set { // Faster, we checked world matrix in constructor. if (world != null) world.SetValue(value); } } protected Matrix lastUsedInverseViewMatrix = Matrix.Identity; /// /// Set view inverse matrix /// protected Matrix InverseViewMatrix { get { // Note: Only implemented for stupid FxCop rule, // you should never "get" a shader texture this way! return lastUsedInverseViewMatrix; } set { viewInverse.SetValue (value.Translation); //SetValue(viewInverse, ref lastUsedInverseViewMatrix, value); } } protected Matrix lastUsedProjectionMatrix = Matrix.Identity; /// /// Set projection matrix /// protected Matrix ProjectionMatrix { get { // Note: Only implemented for stupid FxCop rule, // you should never "get" a shader texture this way! return lastUsedProjectionMatrix; } set { SetValue(projection, ref lastUsedProjectionMatrix, value); } } protected Vector3 lastUsedLightDir = Vector3.Zero; /// /// Set light direction /// protected Vector3 LightDir { get { // Note: Only implemented for stupid FxCop rule, // you should never "get" a shader texture this way! return lastUsedLightDir; } 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); } } protected Color lastUsedAmbientColor = ColorHelper.Empty; /// /// Ambient color /// public Color AmbientColor { get { // Note: Only implemented for stupid FxCop rule, // you should never "get" a shader texture this way! return lastUsedAmbientColor; } set { SetValue(ambientColor, ref lastUsedAmbientColor, value); } } protected Color lastUsedDiffuseColor = ColorHelper.Empty; /// /// Diffuse color /// public Color DiffuseColor { get { // Note: Only implemented for stupid FxCop rule, // you should never "get" a shader texture this way! return lastUsedDiffuseColor; } set { SetValue(diffuseColor, ref lastUsedDiffuseColor, value); } } protected Color lastUsedSpecularColor = ColorHelper.Empty; /// /// Specular color /// public Color SpecularColor { get { // Note: Only implemented for stupid FxCop rule, // you should never "get" a shader texture this way! return lastUsedSpecularColor; } set { SetValue(specularColor, ref lastUsedSpecularColor, value); } } private float lastUsedSpecularPower = 0; /// /// SpecularPower for specular color /// public float SpecularPower { get { // Note: Only implemented for stupid FxCop rule, // you should never "get" a shader texture this way! return lastUsedSpecularPower; } set { SetValue(specularPower, ref lastUsedSpecularPower, value); } } private float lastUsedAlphaFactor = 0; /// /// Alpha factor /// /// Float public float AlphaFactor { get { // Note: Only implemented for stupid FxCop rule, // you should never "get" a shader texture this way! return lastUsedAlphaFactor; } set { SetValue(alphaFactor, ref lastUsedAlphaFactor, value); } } protected XnaTexture lastUsedDiffuseTexture = null; /// /// Set diffuse texture /// public Texture DiffuseTexture { get { // Note: Only implemented for stupid FxCop rule, // you should never "get" a shader texture this way! return null;//makes no sense! } set { SetValue(diffuseTexture, ref lastUsedDiffuseTexture, value != null ? value.XnaTexture : null); } } protected XnaTexture lastUsedNormalTexture = null; /// /// Set normal texture for normal mapping /// public Texture NormalTexture { get { // Note: Only implemented for stupid FxCop rule, // you should never "get" a shader texture this way! return null;//makes no sense! } set { SetValue(normalTexture, ref lastUsedNormalTexture, value != null ? value.XnaTexture : null); } } protected XnaTexture lastUsedHeightTexture = null; /// /// Set height texture for parallax mapping /// public Texture HeightTexture { get { // Note: Only implemented for stupid FxCop rule, // you should never "get" a shader texture this way! return null;//makes no sense! } set { SetValue(heightTexture, ref lastUsedHeightTexture, value != null ? value.XnaTexture : null); } } protected TextureCube lastUsedReflectionCubeTexture = null; /// /// Set reflection cube map texture for reflection stuff. /// public TextureCube ReflectionCubeTexture { get { return lastUsedReflectionCubeTexture; } set { if (reflectionCubeTexture != null && lastUsedReflectionCubeTexture != value) { lastUsedReflectionCubeTexture = value; reflectionCubeTexture.SetValue(value); } } } protected XnaTexture lastUsedDetailTexture = null; /// /// Set height texture for parallax mapping /// public Texture DetailTexture { get { // Note: Only implemented for stupid FxCop rule, // you should never "get" a shader texture this way! return null;//makes no sense! } set { SetValue(detailTexture, ref lastUsedDetailTexture, value != null ? value.XnaTexture : null); } } protected float lastUsedParallaxAmount = -1.0f; /// /// Parallax amount for parallax and offset shaders. /// public float ParallaxAmount { get { // Note: Only implemented for stupid FxCop rule, // you should never "get" a shader texture this way! return lastUsedParallaxAmount; } set { SetValue(parallaxAmount, ref lastUsedParallaxAmount, value); } } protected Color lastUsedCarHueColorChange = ColorHelper.Empty; /// /// Shadow car color for the special ShadowCar shader. /// public Color CarHueColorChange { get { // Note: Only implemented for stupid FxCop rule, // you should never "get" a shader texture this way! return lastUsedCarHueColorChange; } set { SetValue(carHueColorChange, ref lastUsedCarHueColorChange, value); } } [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] public ShaderEffect(string shaderName) { if (BaseGame.Device == null) throw new InvalidOperationException( "XNA device is not initialized, can't create ShaderEffect."); shaderContentName = Path.GetFileNameWithoutExtension(shaderName); Reload(); } /// /// Dispose /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Dispose /// /// Disposing protected virtual void Dispose(bool disposing) { if (disposing) { // Dispose shader effect if (effect != null) effect.Dispose(); } } /// /// Reload effect (can be useful if we change the fx file dynamically). /// public void Reload() { // Load shader effect = BaseGame.Content.Load( Path.Combine(Directories.ContentDirectory, "Shaders", shaderContentName)); // Reset and get all available parameters. // This is especially important for derived classes. ResetParameters(); GetParameters(); SetParameterDefaultValues(); } /// /// Reset parameters /// protected virtual void ResetParameters() { lastUsedInverseViewMatrix = Matrix.Identity; lastUsedAmbientColor = ColorHelper.Empty; lastUsedDiffuseTexture = null; } protected virtual void SetParameterDefaultValues() { } /// /// 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"]; projection = effect.Parameters["projection"]; lightDir = effect.Parameters["lightDir"]; ambientColor = effect.Parameters["ambientColor"]; diffuseColor = effect.Parameters["diffuseColor"]; specularColor = effect.Parameters["specularColor"]; specularPower = effect.Parameters["specularPower"]; alphaFactor = effect.Parameters["alphaFactor"]; // Default alpha factor to 1.0f for hotels and stuff AlphaFactor = 1.0f; scale = effect.Parameters["scale"]; diffuseTexture = effect.Parameters["diffuseTexture"]; normalTexture = effect.Parameters["normalTexture"]; heightTexture = effect.Parameters["heightTexture"]; reflectionCubeTexture = effect.Parameters["reflectionCubeTexture"]; detailTexture = effect.Parameters["detailTexture"]; parallaxAmount = effect.Parameters["parallaxAmount"]; carHueColorChange = effect.Parameters["carHueColorChange"]; } /// /// 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) { if (viewInverse.ParameterClass == EffectParameterClass.Matrix) viewInverse.SetValue(BaseGame.InverseViewMatrix); else viewInverse.SetValue(BaseGame.InverseViewMatrix.Translation); } if (lightDir != null) lightDir.SetValue(BaseGame.LightDirection); // Set the reflection cube texture only once if (lastUsedReflectionCubeTexture == null && reflectionCubeTexture != null) { ReflectionCubeTexture = BaseGame.UI.SkyCubeMapTexture; } // 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; HeightTexture = setMat.heightTexture; ParallaxAmount = setMat.parallaxAmount; DetailTexture = setMat.detailTexture; } } /// /// Set parameters, override to set more /// public virtual void SetParameters() { SetParameters(null); } /// /// Set parameters, this overload sets all material parameters too. /// public virtual void SetParametersOptimizedGeneral() { 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.Translation); if (lightDir != null) lightDir.SetValue(BaseGame.LightDirection); // Set the reflection cube texture only once if (lastUsedReflectionCubeTexture == null && reflectionCubeTexture != null) { ReflectionCubeTexture = BaseGame.UI.SkyCubeMapTexture; } // lastUsed parameters for colors and textures are not used, // but we overwrite the values in SetParametersOptimized. // We fix this by clearing all lastUsed values we will use later. lastUsedAmbientColor = ColorHelper.Empty; lastUsedDiffuseColor = ColorHelper.Empty; lastUsedSpecularColor = ColorHelper.Empty; lastUsedDiffuseTexture = null; lastUsedNormalTexture = null; } /// /// Set parameters, this overload sets all material parameters too. /// public virtual void SetParametersOptimized(Material setMat) { if (setMat == null) throw new ArgumentNullException("setMat"); // No need to set world matrix, will be done later in mesh rendering // in the MeshRenderManager. All the rest is set with help of the // SetParametersOptimizedGeneral above. // Only update ambient, diffuse, specular and the textures, the rest // will not change for a material change in MeshRenderManager. ambientColor.SetValue(setMat.ambientColor.ToVector4()); diffuseColor.SetValue(setMat.diffuseColor.ToVector4()); specularColor.SetValue(setMat.specularColor.ToVector4()); if (setMat.diffuseTexture != null) diffuseTexture.SetValue(setMat.diffuseTexture.XnaTexture); if (setMat.normalTexture != null) normalTexture.SetValue(setMat.normalTexture.XnaTexture); } /// /// Update /// public void Update() { for (int num = 0; num < effect.CurrentTechnique.Passes.Count; num++) { effect.CurrentTechnique.Passes[num].Apply(); } } /// /// Render /// /// Set matrix /// Pass name /// Render delegate public void Render(Material setMat, string techniqueName, BaseGame.RenderHandler renderCode) { if (techniqueName == null) throw new ArgumentNullException("techniqueName"); if (renderCode == null) throw new ArgumentNullException("renderCode"); SetParameters(setMat); // Start shader effect.CurrentTechnique = effect.Techniques[techniqueName]; // 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.Apply(); renderCode(); } } /// /// Render /// /// Technique name /// Render delegate public void Render(string techniqueName, BaseGame.RenderHandler renderDelegate) { Render(null, techniqueName, renderDelegate); } /// /// Render single pass shader /// /// Render delegate public void RenderSinglePassShader( BaseGame.RenderHandler renderCode) { if (renderCode == null) throw new ArgumentNullException("renderCode"); // Start effect (current technique should be set) // Start first pass effect.CurrentTechnique.Passes[0].Apply(); // Render renderCode(); } } }