//-----------------------------------------------------------------------------
// 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();
}
}
}