#region License // Copyright 2019-2021 Kastellanos Nikolaos // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #endregion using System; using System.IO; using System.Reflection; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace nkast.Aether.Shaders { public class TilemapEffect : Effect, IEffectMatrices { #region Effect Parameters EffectParameter textureParam; EffectParameter textureAtlasParam; EffectParameter mapSizeParam; EffectParameter invAtlasSizeParam; EffectParameter diffuseColorParam; EffectParameter fogColorParam; EffectParameter fogVectorParam; // IEffectMatrices EffectParameter worldViewProjParam; #endregion #region Fields bool fogEnabled; bool vertexColorEnabled; Matrix world = Matrix.Identity; Matrix view = Matrix.Identity; Matrix projection = Matrix.Identity; Matrix worldView; Vector3 diffuseColor = Vector3.One; float alpha = 1; float fogStart = 0; float fogEnd = 1; Vector2 atlasSize; EffectDirtyFlags dirtyFlags = EffectDirtyFlags.All; static readonly string ResourceName = "nkast.Aether.Shaders.Resources.TilemapEffect"; internal static byte[] LoadEffectResource(GraphicsDevice graphicsDevice, string name) { name = GetResourceName(graphicsDevice, name); using (Stream stream = GetAssembly(typeof(TilemapEffect)).GetManifestResourceStream(name)) using (MemoryStream ms = new MemoryStream()) { stream.CopyTo(ms); return ms.ToArray(); } } private static string GetResourceName(GraphicsDevice graphicsDevice, string name) { string platformName = ""; string version = ""; #if XNA platformName = ".xna.WinReach"; #else switch (graphicsDevice.Adapter.Backend) { case GraphicsBackend.DirectX11: platformName = ".dx11.fxo"; break; case GraphicsBackend.OpenGL: case GraphicsBackend.GLES: case GraphicsBackend.WebGL: platformName = ".ogl.fxo"; break; default: throw new NotSupportedException("platform"); } // Detect version version = ".10"; Version kniVersion = GetAssembly(typeof(Effect)).GetName().Version; if (kniVersion.Major == 3) { if (kniVersion.Minor == 9) { version = ".10"; } if (kniVersion.Minor == 10) { version = ".10"; } } #endif return name + platformName + version; } private static Assembly GetAssembly(Type type) { #if W10 return type.GetTypeInfo().Assembly; #else return type.Assembly; #endif } #endregion #region Public Properties public Matrix Projection { get { return projection; } set { projection = value; dirtyFlags |= EffectDirtyFlags.WorldViewProj; } } public Matrix View { get { return view; } set { view = value; dirtyFlags |= EffectDirtyFlags.WorldViewProj | EffectDirtyFlags.Fog; } } public Matrix World { get { return world; } set { world = value; dirtyFlags |= EffectDirtyFlags.WorldViewProj | EffectDirtyFlags.Fog; } } /// /// Gets or sets the Tilemap texture. /// public Texture2D Texture { get { return textureParam.GetValueTexture2D(); } set { textureParam.SetValue(value); } } /// /// Gets or sets the Atlas texture. /// public Texture2D TextureAtlas { get { return textureAtlasParam.GetValueTexture2D(); } set { textureAtlasParam.SetValue(value); } } /// /// Gets or sets the Map size. /// public Vector2 MapSize { get { return mapSizeParam.GetValueVector2(); } set { mapSizeParam.SetValue(value); } } /// /// Gets or sets the Atlas size. /// public Vector2 AtlasSize { get { return atlasSize; } set { atlasSize = value; value.X = 1f/value.X; value.Y = 1f/value.Y; invAtlasSizeParam.SetValue(value); } } /// /// 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 alpha. /// public float Alpha { get { return alpha; } set { alpha = value; dirtyFlags |= EffectDirtyFlags.MaterialColor; } } /// /// Gets or sets the fog enable flag. /// public bool FogEnabled { get { return fogEnabled; } set { if (fogEnabled != value) { fogEnabled = value; dirtyFlags |= EffectDirtyFlags.FogEnable; UpdateCurrentTechnique(); } } } /// /// 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 whether vertex color is enabled. /// public bool VertexColorEnabled { get { return vertexColorEnabled; } set { if (vertexColorEnabled != value) { vertexColorEnabled = value; UpdateCurrentTechnique(); } } } #endregion #region Methods public TilemapEffect(GraphicsDevice graphicsDevice) : base(graphicsDevice, LoadEffectResource(graphicsDevice, ResourceName)) { CacheEffectParameters(null); } public TilemapEffect(GraphicsDevice graphicsDevice, byte[] Bytecode): base(graphicsDevice, Bytecode) { CacheEffectParameters(null); } protected TilemapEffect(TilemapEffect cloneSource) : base(cloneSource) { CacheEffectParameters(cloneSource); fogEnabled = cloneSource.fogEnabled; vertexColorEnabled = cloneSource.vertexColorEnabled; world = cloneSource.world; view = cloneSource.view; projection = cloneSource.projection; diffuseColor = cloneSource.diffuseColor; alpha = cloneSource.alpha; fogStart = cloneSource.fogStart; fogEnd = cloneSource.fogEnd; atlasSize = cloneSource.atlasSize; } public override Effect Clone() { return new TilemapEffect(this); } void CacheEffectParameters(TilemapEffect cloneSource) { textureParam = Parameters["Texture"]; textureAtlasParam = Parameters["TextureAtlas"]; mapSizeParam = Parameters["MapSize"]; invAtlasSizeParam = Parameters["InvAtlasSize"]; diffuseColorParam = Parameters["DiffuseColor"]; fogColorParam = Parameters["FogColor"]; fogVectorParam = Parameters["FogVector"]; // IEffectMatrices worldViewProjParam = Parameters["WorldViewProj"]; } /// /// Lazily computes derived parameter values immediately before applying the effect. /// protected override void OnApply() { // Recompute the world+view+projection matrix or fog vector? dirtyFlags = SetWorldViewProjAndFog(dirtyFlags, ref world, ref view, ref projection, ref worldView, fogEnabled, fogStart, fogEnd, worldViewProjParam, fogVectorParam); // Recompute the diffuse/alpha material color parameter? if ((dirtyFlags & EffectDirtyFlags.MaterialColor) != 0) { diffuseColorParam.SetValue(new Vector4(diffuseColor * alpha, alpha)); dirtyFlags &= ~EffectDirtyFlags.MaterialColor; } } private void UpdateCurrentTechnique() { int shaderIndex = 0; if (!fogEnabled) shaderIndex += 1; if (vertexColorEnabled) shaderIndex += 2; CurrentTechnique = Techniques[shaderIndex]; } /// /// Lazily recomputes the world+view+projection matrix and /// fog vector based on the current effect parameter settings. /// static EffectDirtyFlags SetWorldViewProjAndFog(EffectDirtyFlags dirtyFlags, ref Matrix world, ref Matrix view, ref Matrix projection, ref Matrix worldView, bool fogEnabled, float fogStart, float fogEnd, EffectParameter worldViewProjParam, EffectParameter fogVectorParam) { // Recompute the world+view+projection matrix? if ((dirtyFlags & EffectDirtyFlags.WorldViewProj) != 0) { Matrix worldViewProj; Matrix.Multiply(ref world, ref view, out worldView); Matrix.Multiply(ref worldView, ref projection, out worldViewProj); worldViewProjParam.SetValue(worldViewProj); dirtyFlags &= ~EffectDirtyFlags.WorldViewProj; } if (fogEnabled) { // Recompute the fog vector? if ((dirtyFlags & (EffectDirtyFlags.Fog | EffectDirtyFlags.FogEnable)) != 0) { SetFogVector(ref worldView, fogStart, fogEnd, fogVectorParam); dirtyFlags &= ~(EffectDirtyFlags.Fog | EffectDirtyFlags.FogEnable); } } else { // When fog is disabled, make sure the fog vector is reset to zero. if ((dirtyFlags & EffectDirtyFlags.FogEnable) != 0) { fogVectorParam.SetValue(Vector4.Zero); dirtyFlags &= ~EffectDirtyFlags.FogEnable; } } return dirtyFlags; } /// /// Sets a vector which can be dotted with the object space vertex position to compute fog amount. /// static void SetFogVector(ref Matrix worldView, float fogStart, float fogEnd, EffectParameter fogVectorParam) { if (fogStart == fogEnd) { // Degenerate case: force everything to 100% fogged if start and end are the same. fogVectorParam.SetValue(new Vector4(0, 0, 0, 1)); } else { // We want to transform vertex positions into view space, take the resulting // Z value, then scale and offset according to the fog start/end distances. // Because we only care about the Z component, the shader can do all this // with a single dot product, using only the Z row of the world+view matrix. float scale = 1f / (fogStart - fogEnd); Vector4 fogVector = new Vector4(); fogVector.X = worldView.M13 * scale; fogVector.Y = worldView.M23 * scale; fogVector.Z = worldView.M33 * scale; fogVector.W = (worldView.M43 + fogStart) * scale; fogVectorParam.SetValue(fogVector); } } #endregion enum EffectDirtyFlags { WorldViewProj = 1, //World = 2, //EyePosition = 4, MaterialColor = 8, Fog = 16, FogEnable = 32, //AlphaTest = 64, All = -1 } } }