#region File Description //----------------------------------------------------------------------------- // SkinnedEffect.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #endregion #region Using Statements using System; #endregion namespace Microsoft.Xna.Framework.Graphics { /// /// Built-in effect for rendering skinned character models. /// public class SkinnedEffect : Effect, IEffectMatrices, IEffectLights, IEffectFog { public const int MaxBones = 72; #region Effect Parameters EffectParameter textureParam; EffectParameter diffuseColorParam; EffectParameter emissiveColorParam; EffectParameter specularColorParam; EffectParameter specularPowerParam; EffectParameter eyePositionParam; EffectParameter fogColorParam; EffectParameter fogVectorParam; EffectParameter worldParam; EffectParameter worldInverseTransposeParam; EffectParameter worldViewProjParam; EffectParameter bonesParam; EffectParameter shaderIndexParam; #endregion #region Fields bool preferPerPixelLighting; bool oneLight; bool fogEnabled; Matrix world = Matrix.Identity; Matrix view = Matrix.Identity; Matrix projection = Matrix.Identity; Matrix worldView; Vector3 diffuseColor = Vector3.One; Vector3 emissiveColor = Vector3.Zero; Vector3 ambientLightColor = Vector3.Zero; float alpha = 1; DirectionalLight light0; DirectionalLight light1; DirectionalLight light2; float fogStart = 0; float fogEnd = 1; int weightsPerVertex = 4; EffectDirtyFlags dirtyFlags = EffectDirtyFlags.All; #endregion #region Public Properties /// /// Gets or sets the world matrix. /// public Matrix World { get { return world; } set { world = value; dirtyFlags |= EffectDirtyFlags.World | EffectDirtyFlags.WorldViewProj | EffectDirtyFlags.Fog; } } /// /// Gets or sets the view matrix. /// public Matrix View { get { return view; } set { view = value; dirtyFlags |= EffectDirtyFlags.WorldViewProj | EffectDirtyFlags.EyePosition | EffectDirtyFlags.Fog; } } /// /// Gets or sets the projection matrix. /// public Matrix Projection { get { return projection; } set { projection = value; dirtyFlags |= EffectDirtyFlags.WorldViewProj; } } /// /// Gets or sets the material diffuse color (range 0 to 1). /// public Vector3 DiffuseColor { get { return diffuseColor; } set { diffuseColor = value; dirtyFlags |= EffectDirtyFlags.MaterialColor; } } /// /// Gets or sets the material emissive color (range 0 to 1). /// public Vector3 EmissiveColor { get { return emissiveColor; } set { emissiveColor = value; dirtyFlags |= EffectDirtyFlags.MaterialColor; } } /// /// Gets or sets the material specular color (range 0 to 1). /// public Vector3 SpecularColor { get { return specularColorParam.GetValueVector3(); } set { specularColorParam.SetValue(value); } } /// /// Gets or sets the material specular power. /// public float SpecularPower { get { return specularPowerParam.GetValueSingle(); } set { specularPowerParam.SetValue(value); } } /// /// Gets or sets the material alpha. /// public float Alpha { get { return alpha; } set { alpha = value; dirtyFlags |= EffectDirtyFlags.MaterialColor; } } /// /// Gets or sets the per-pixel lighting prefer flag. /// public bool PreferPerPixelLighting { get { return preferPerPixelLighting; } set { if (preferPerPixelLighting != value) { preferPerPixelLighting = value; dirtyFlags |= EffectDirtyFlags.ShaderIndex; } } } /// /// Gets or sets the ambient light color (range 0 to 1). /// public Vector3 AmbientLightColor { get { return ambientLightColor; } set { ambientLightColor = value; dirtyFlags |= EffectDirtyFlags.MaterialColor; } } /// /// Gets the first directional light. /// public DirectionalLight DirectionalLight0 { get { return light0; } } /// /// Gets the second directional light. /// public DirectionalLight DirectionalLight1 { get { return light1; } } /// /// Gets the third directional light. /// public DirectionalLight DirectionalLight2 { get { return light2; } } /// /// Gets or sets the fog enable flag. /// public bool FogEnabled { get { return fogEnabled; } set { if (fogEnabled != value) { fogEnabled = value; dirtyFlags |= EffectDirtyFlags.ShaderIndex | EffectDirtyFlags.FogEnable; } } } /// /// Gets or sets the fog start distance. /// public float FogStart { get { return fogStart; } set { fogStart = value; dirtyFlags |= EffectDirtyFlags.Fog; } } /// /// Gets or sets the fog end distance. /// public float FogEnd { get { return fogEnd; } set { fogEnd = value; dirtyFlags |= EffectDirtyFlags.Fog; } } /// /// Gets or sets the fog color. /// public Vector3 FogColor { get { return fogColorParam.GetValueVector3(); } set { fogColorParam.SetValue(value); } } /// /// Gets or sets the current texture. /// public Texture2D Texture { get { return textureParam.GetValueTexture2D(); } set { textureParam.SetValue(value); } } /// /// Gets or sets the number of skinning weights to evaluate for each vertex (1, 2, or 4). /// public int WeightsPerVertex { get { return weightsPerVertex; } set { if ((value != 1) && (value != 2) && (value != 4)) { throw new ArgumentOutOfRangeException("value"); } weightsPerVertex = value; dirtyFlags |= EffectDirtyFlags.ShaderIndex; } } /// /// Sets an array of skinning bone transform matrices. /// public void SetBoneTransforms(Matrix[] boneTransforms) { if ((boneTransforms == null) || (boneTransforms.Length == 0)) throw new ArgumentNullException("boneTransforms"); if (boneTransforms.Length > MaxBones) throw new ArgumentException(); bonesParam.SetValue(boneTransforms); } /// /// Gets a copy of the current skinning bone transform matrices. /// public Matrix[] GetBoneTransforms(int count) { if (count <= 0 || count > MaxBones) throw new ArgumentOutOfRangeException("count"); Matrix[] bones = bonesParam.GetValueMatrixArray(count); // Convert matrices from 43 to 44 format. for (int i = 0; i < bones.Length; i++) { bones[i].M44 = 1; } return bones; } /// /// This effect requires lighting, so we explicitly implement /// IEffectLights.LightingEnabled, and do not allow turning it off. /// bool IEffectLights.LightingEnabled { get { return true; } set { if (!value) throw new NotSupportedException("SkinnedEffect does not support setting LightingEnabled to false."); } } #endregion #region Methods /// /// Creates a new SkinnedEffect with default parameter settings. /// public SkinnedEffect(GraphicsDevice device) : base(device, Resources.SkinnedEffect) { CacheEffectParameters(null); DirectionalLight0.Enabled = true; SpecularColor = Vector3.One; SpecularPower = 16; Matrix[] identityBones = new Matrix[MaxBones]; for (int i = 0; i < MaxBones; i++) { identityBones[i] = Matrix.Identity; } SetBoneTransforms(identityBones); } /// /// Creates a new SkinnedEffect by cloning parameter settings from an existing instance. /// protected SkinnedEffect(SkinnedEffect cloneSource) : base(cloneSource) { CacheEffectParameters(cloneSource); preferPerPixelLighting = cloneSource.preferPerPixelLighting; fogEnabled = cloneSource.fogEnabled; world = cloneSource.world; view = cloneSource.view; projection = cloneSource.projection; diffuseColor = cloneSource.diffuseColor; emissiveColor = cloneSource.emissiveColor; ambientLightColor = cloneSource.ambientLightColor; alpha = cloneSource.alpha; fogStart = cloneSource.fogStart; fogEnd = cloneSource.fogEnd; weightsPerVertex = cloneSource.weightsPerVertex; } /// /// Creates a clone of the current SkinnedEffect instance. /// public override Effect Clone() { return new SkinnedEffect(this); } /// /// Sets up the standard key/fill/back lighting rig. /// public void EnableDefaultLighting() { AmbientLightColor = EffectHelpers.EnableDefaultLighting(light0, light1, light2); } /// /// Looks up shortcut references to our effect parameters. /// void CacheEffectParameters(SkinnedEffect cloneSource) { textureParam = Parameters["Texture"]; diffuseColorParam = Parameters["DiffuseColor"]; emissiveColorParam = Parameters["EmissiveColor"]; specularColorParam = Parameters["SpecularColor"]; specularPowerParam = Parameters["SpecularPower"]; eyePositionParam = Parameters["EyePosition"]; fogColorParam = Parameters["FogColor"]; fogVectorParam = Parameters["FogVector"]; worldParam = Parameters["World"]; worldInverseTransposeParam = Parameters["WorldInverseTranspose"]; worldViewProjParam = Parameters["WorldViewProj"]; bonesParam = Parameters["Bones"]; shaderIndexParam = Parameters["ShaderIndex"]; light0 = new DirectionalLight(Parameters["DirLight0Direction"], Parameters["DirLight0DiffuseColor"], Parameters["DirLight0SpecularColor"], (cloneSource != null) ? cloneSource.light0 : null); light1 = new DirectionalLight(Parameters["DirLight1Direction"], Parameters["DirLight1DiffuseColor"], Parameters["DirLight1SpecularColor"], (cloneSource != null) ? cloneSource.light1 : null); light2 = new DirectionalLight(Parameters["DirLight2Direction"], Parameters["DirLight2DiffuseColor"], Parameters["DirLight2SpecularColor"], (cloneSource != null) ? cloneSource.light2 : null); } /// /// Lazily computes derived parameter values immediately before applying the effect. /// protected internal override void OnApply() { // Recompute the world+view+projection matrix or fog vector? dirtyFlags = EffectHelpers.SetWorldViewProjAndFog(dirtyFlags, ref world, ref view, ref projection, ref worldView, fogEnabled, fogStart, fogEnd, worldViewProjParam, fogVectorParam); // Recompute the world inverse transpose and eye position? dirtyFlags = EffectHelpers.SetLightingMatrices(dirtyFlags, ref world, ref view, worldParam, worldInverseTransposeParam, eyePositionParam); // Recompute the diffuse/emissive/alpha material color parameters? if ((dirtyFlags & EffectDirtyFlags.MaterialColor) != 0) { EffectHelpers.SetMaterialColor(true, alpha, ref diffuseColor, ref emissiveColor, ref ambientLightColor, diffuseColorParam, emissiveColorParam); dirtyFlags &= ~EffectDirtyFlags.MaterialColor; } // Check if we can use the only-bother-with-the-first-light shader optimization. bool newOneLight = !light1.Enabled && !light2.Enabled; if (oneLight != newOneLight) { oneLight = newOneLight; dirtyFlags |= EffectDirtyFlags.ShaderIndex; } // Recompute the shader index? if ((dirtyFlags & EffectDirtyFlags.ShaderIndex) != 0) { int shaderIndex = 0; if (!fogEnabled) shaderIndex += 1; if (weightsPerVertex == 2) shaderIndex += 2; else if (weightsPerVertex == 4) shaderIndex += 4; if (preferPerPixelLighting) shaderIndex += 12; else if (oneLight) shaderIndex += 6; shaderIndexParam.SetValue(shaderIndex); dirtyFlags &= ~EffectDirtyFlags.ShaderIndex; } } #endregion } }