From ca6c91446e7a36c72db4f50ba6d7b6ab91a35605 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Sun, 6 Dec 2020 18:52:14 -0800 Subject: [PATCH] new mesh sprite and diffuse lit sprite system --- Effects/DiffuseLitSpriteEffect.cs | 34 +++++-- Effects/FXB/DiffuseLitSpriteEffect.fxb | 4 +- Effects/HLSL/DiffuseLitSprite.fx | 90 ----------------- Effects/HLSL/DiffuseLitSpriteEffect.fx | 134 +++++++++++++++++++++++++ Geometry/MeshSprite.cs | 105 +++++++++++++++++++ Renderer.cs | 93 +++++++++++------ 6 files changed, 327 insertions(+), 133 deletions(-) delete mode 100644 Effects/HLSL/DiffuseLitSprite.fx create mode 100644 Effects/HLSL/DiffuseLitSpriteEffect.fx create mode 100644 Geometry/MeshSprite.cs diff --git a/Effects/DiffuseLitSpriteEffect.cs b/Effects/DiffuseLitSpriteEffect.cs index 35385b6..faf05b3 100644 --- a/Effects/DiffuseLitSpriteEffect.cs +++ b/Effects/DiffuseLitSpriteEffect.cs @@ -5,8 +5,6 @@ namespace Kav { public class DiffuseLitSpriteEffect : Effect { - EffectParameter textureParam; - EffectParameter ambientColorParam; EffectParameter directionalLightDirectionParam; @@ -16,7 +14,12 @@ namespace Kav EffectParameter worldViewProjectionParam; EffectParameter worldInverseTransposeParam; + EffectParameter shaderIndexParam; + Texture2D texture; + Texture2D normal; + + bool normalMapEnabled = false; Vector3 ambientColor; @@ -29,13 +32,16 @@ namespace Kav EffectDirtyFlags dirtyFlags = EffectDirtyFlags.All; - public Texture2D Texture + public bool NormalMapEnabled { - get { return texture; } + get { return normalMapEnabled; } set { - texture = value; - textureParam.SetValue(texture); + if (normalMapEnabled != value) + { + normalMapEnabled = value; + dirtyFlags |= EffectDirtyFlags.ShaderIndex; + } } } @@ -135,12 +141,22 @@ namespace Kav dirtyFlags &= ~EffectDirtyFlags.WorldViewProj; } + + if ((dirtyFlags & EffectDirtyFlags.ShaderIndex) != 0) + { + int shaderIndex = 0; + + if (normalMapEnabled) + { + shaderIndex = 1; + } + + shaderIndexParam.SetValue(shaderIndex); + } } private void CacheEffectParameters() { - textureParam = Parameters["Texture"]; - worldParam = Parameters["World"]; worldViewProjectionParam = Parameters["WorldViewProjection"]; worldInverseTransposeParam = Parameters["WorldInverseTranspose"]; @@ -149,6 +165,8 @@ namespace Kav directionalLightDirectionParam = Parameters["DirectionalLightDirection"]; directionalLightColorParam = Parameters["DirectionalLightColor"]; + + shaderIndexParam = Parameters["ShaderIndex"]; } } } diff --git a/Effects/FXB/DiffuseLitSpriteEffect.fxb b/Effects/FXB/DiffuseLitSpriteEffect.fxb index 7c6af31..73db0a9 100644 --- a/Effects/FXB/DiffuseLitSpriteEffect.fxb +++ b/Effects/FXB/DiffuseLitSpriteEffect.fxb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc6de319b583aabd741b9d1012854fd318ee66d28cb4abeaa04ea78058645d25 -size 4288 +oid sha256:e08585959eff5d0cfa7ec9f5ebd5390281626b535f9170ae11b7f899b4053f2e +size 7124 diff --git a/Effects/HLSL/DiffuseLitSprite.fx b/Effects/HLSL/DiffuseLitSprite.fx deleted file mode 100644 index 163af05..0000000 --- a/Effects/HLSL/DiffuseLitSprite.fx +++ /dev/null @@ -1,90 +0,0 @@ -#include "Macros.fxh" //from FNA - -// Effect applies normalmapped lighting to a 2D sprite. - -DECLARE_TEXTURE(Texture, 0); -DECLARE_TEXTURE(Normal, 1); - -BEGIN_CONSTANTS - - float AmbientColor _ps(c0) _cb(c0); - - float3 PointLightPositions[8] _ps(c1) _cb(c1); - float3 PointLightColors[8] _ps(c9) _cb(c9); - - float DirectionalLightDirection _ps(c17) _cb(c17); - float DirectionalLightColor _ps(c18) _cb(c18); - -MATRIX_CONSTANTS - - float4x4 WorldInverseTranspose _ps(c19) _cb(c19); - float4x4 World _vs(c0) _cb(c23); - float4x4 WorldViewProjection _vs(c4) _cb(c27); - -END_CONSTANTS - -struct VertexShaderInput -{ - float4 Position : POSITION; - float2 TexCoord : TEXCOORD0; -}; - -struct PixelShaderInput -{ - float4 Position : SV_Position; - float2 TexCoord : TEXCOORD0; - float3 PositionWS : TEXCOORD2; -}; - -PixelShaderInput main_vs(VertexShaderInput input) -{ - PixelShaderInput output; - - output.TexCoord = input.TexCoord; - output.PositionWS = mul(input.Position, World).xyz; - output.Position = mul(input.Position, WorldViewProjection); - - return output; -} - -float4 main_ps(PixelShaderInput input) : COLOR0 -{ - // Look up the texture and normalmap values. - float4 tex = tex2D(TextureSampler, input.TexCoord); - float3 normal = tex2D(NormalSampler, input.TexCoord).xyz; - float3 normalWS = mul(normal, (float3x3)WorldInverseTranspose).xyz; - - float3 lightColor = float3(0.0, 0.0, 0.0); - - // point lights - for (int i = 0; i < 8; i++) - { - float3 lightVec = PointLightPositions[i] - input.PositionWS; - float distance = length(lightVec); - - float3 lightDir = normalize(lightVec); - float diffuse = max(dot(normalWS, lightDir), 0.0); - float3 attenuation = 1.0 / (distance * distance); - - lightColor += diffuse * attenuation * PointLightColors[i]; - } - - // directional light - float directionalDiffuse = max(dot(normalWS, DirectionalLightDirection), 0.0); - lightColor += directionalDiffuse * DirectionalLightColor; - - // ambient light - lightColor += AmbientColor; - - // blend with sample - return tex * float4(lightColor, 1.0); -} - -Technique DiffuseLitSprite -{ - pass - { - VertexShader = compile vs_3_0 main_vs(); - PixelShader = compile ps_3_0 main_ps(); - } -} diff --git a/Effects/HLSL/DiffuseLitSpriteEffect.fx b/Effects/HLSL/DiffuseLitSpriteEffect.fx new file mode 100644 index 0000000..c6e18a9 --- /dev/null +++ b/Effects/HLSL/DiffuseLitSpriteEffect.fx @@ -0,0 +1,134 @@ +#include "Macros.fxh" //from FNA + +// Effect applies normalmapped lighting to a 2D sprite. + +DECLARE_TEXTURE(Texture, 0); +DECLARE_TEXTURE(Normal, 1); + +BEGIN_CONSTANTS + + float3 AmbientColor _ps(c0) _cb(c0); + + float3 PointLightPositions[8] _ps(c1) _cb(c1); + float3 PointLightColors[8] _ps(c9) _cb(c9); + + float3 DirectionalLightDirection _ps(c17) _cb(c17); + float3 DirectionalLightColor _ps(c18) _cb(c18); + +MATRIX_CONSTANTS + + float4x4 WorldInverseTranspose _ps(c19) _cb(c19); + float4x4 World _vs(c0) _cb(c23); + float4x4 WorldViewProjection _vs(c4) _cb(c27); + +END_CONSTANTS + +struct VertexShaderInput +{ + float4 Position : POSITION; + float3 Normal : NORMAL; + float2 TexCoord : TEXCOORD0; +}; + +struct PixelShaderInput +{ + float4 Position : SV_Position; + float2 TexCoord : TEXCOORD0; + float3 NormalWS : TEXCOORD1; + float3 PositionWS : TEXCOORD2; +}; + +PixelShaderInput main_vs(VertexShaderInput input) +{ + PixelShaderInput output; + + output.Position = mul(input.Position, WorldViewProjection); + output.TexCoord = input.TexCoord; + output.NormalWS = mul(input.Normal, (float3x3)WorldInverseTranspose).xyz; + output.PositionWS = mul(input.Position, World).xyz; + + return output; +} + +// Easy trick to get tangent-normals to world-space to keep PBR code simplified. +float3 GetNormalFromMap(float3 worldPos, float2 texCoords, float3 normal) +{ + float3 tangentNormal = SAMPLE_TEXTURE(Normal, texCoords).xyz * 2.0 - 1.0; + + float3 Q1 = ddx(worldPos); + float3 Q2 = ddy(worldPos); + float2 st1 = ddx(texCoords); + float2 st2 = ddy(texCoords); + + float3 N = normalize(normal); + float3 T = normalize(Q1*st2.y - Q2*st1.y); + float3 B = -normalize(cross(N, T)); + float3x3 TBN = float3x3(T, B, N); + + return normalize(mul(tangentNormal, TBN)); +} + +float4 LightColor(float3 worldPosition, float3 worldNormal) +{ + float3 lightColor = float3(0.0, 0.0, 0.0); + + // point lights + for (int i = 0; i < 8; i++) + { + float3 lightVec = PointLightPositions[i] - worldPosition; + float distance = length(lightVec); + + float3 lightDir = normalize(lightVec); + float diffuse = max(dot(worldNormal, lightDir), 0.0); + float3 attenuation = 1.0 / (distance * distance); + + lightColor += diffuse * attenuation * PointLightColors[i]; + } + + // directional light + float directionalDiffuse = max(dot(worldNormal, DirectionalLightDirection), 0.0); + lightColor += directionalDiffuse * DirectionalLightColor; + + // ambient light + lightColor += AmbientColor; + + return float4(lightColor, 1.0); +} + +float4 WithoutNormalMap(PixelShaderInput input) : COLOR0 +{ + float4 tex = SAMPLE_TEXTURE(Texture, input.TexCoord); + float3 normalWS = normalize(input.NormalWS); + + return tex * LightColor(input.PositionWS, normalWS); +} + +float4 WithNormalMap(PixelShaderInput input) : COLOR0 +{ + float4 tex = SAMPLE_TEXTURE(Texture, input.TexCoord); + float3 normalWS = GetNormalFromMap(input.PositionWS, input.TexCoord, input.NormalWS); + + return tex * LightColor(input.PositionWS, normalWS); +} + +PixelShader PSArray[2] = +{ + compile ps_3_0 WithoutNormalMap(), + compile ps_3_0 WithNormalMap() +}; + +int PSIndices[2] = +{ + 0, 1 +}; + +int ShaderIndex = 0; + +Technique DiffuseLitSprite +{ + pass + { + VertexShader = compile vs_3_0 main_vs(); + PixelShader = (PSArray[PSIndices[ShaderIndex]]); + } +} diff --git a/Geometry/MeshSprite.cs b/Geometry/MeshSprite.cs new file mode 100644 index 0000000..0d9e29f --- /dev/null +++ b/Geometry/MeshSprite.cs @@ -0,0 +1,105 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Kav +{ + public class MeshSprite + { + private static readonly int PixelScale = 40; + + public IndexBuffer IndexBuffer { get; } + public VertexBuffer VertexBuffer { get; } + + public Texture2D Texture { get; } + public Texture2D Normal { get; } + public SpriteBillboardConstraint BillboardConstraint { get; } + + public MeshSprite( + GraphicsDevice graphicsDevice, + Texture2D texture, + SpriteBillboardConstraint billboardConstraint + ) { + Texture = texture; + Normal = null; + BillboardConstraint = billboardConstraint; + + IndexBuffer = new IndexBuffer( + graphicsDevice, + IndexElementSize.SixteenBits, + 6, + BufferUsage.WriteOnly + ); + IndexBuffer.SetData(GenerateIndexArray()); + + VertexBuffer = new VertexBuffer( + graphicsDevice, + typeof(VertexPositionNormalTexture), + 4, + BufferUsage.WriteOnly + ); + VertexBuffer.SetData(GenerateVertexArray(Texture)); + } + + public MeshSprite( + GraphicsDevice graphicsDevice, + Texture2D texture, + Texture2D normal, + SpriteBillboardConstraint billboardConstraint + ) { + Texture = texture; + Normal = normal; + BillboardConstraint = billboardConstraint; + + IndexBuffer = new IndexBuffer( + graphicsDevice, + IndexElementSize.SixteenBits, + 6, + BufferUsage.WriteOnly + ); + IndexBuffer.SetData(GenerateIndexArray()); + + VertexBuffer = new VertexBuffer( + graphicsDevice, + typeof(VertexPositionNormalTexture), + 4, + BufferUsage.WriteOnly + ); + VertexBuffer.SetData(GenerateVertexArray(Texture)); + } + + private static short[] GenerateIndexArray() + { + short[] result = new short[6]; + result[0] = 0; + result[1] = 1; + result[2] = 2; + result[3] = 1; + result[4] = 3; + result[5] = 2; + return result; + } + + private static VertexPositionNormalTexture[] GenerateVertexArray(Texture2D texture) + { + VertexPositionNormalTexture[] result = new VertexPositionNormalTexture[4]; + + result[0].Position = new Vector3(-texture.Width / 2, texture.Height / 2, 0) / PixelScale; + result[0].Normal = new Vector3(0, 0, -1); + result[0].TextureCoordinate = new Vector2(0, 0); + + result[1].Position = new Vector3(texture.Width / 2, texture.Height / 2, 0) / PixelScale; + result[1].Normal = new Vector3(0, 0, -1); + result[1].TextureCoordinate = new Vector2(1, 0); + + result[2].Position = new Vector3(-texture.Width / 2, -texture.Height / 2, 0) / PixelScale; + result[2].Normal = new Vector3(0, 0, -1); + result[2].TextureCoordinate = new Vector2(0, 1); + + result[3].Position = new Vector3(texture.Width / 2, -texture.Height / 2, 0) / PixelScale; + result[3].Normal = new Vector3(0, 0, -1); + result[3].TextureCoordinate = new Vector2(1, 1); + + return result; + } + } +} diff --git a/Renderer.cs b/Renderer.cs index fc44b55..28d510c 100644 --- a/Renderer.cs +++ b/Renderer.cs @@ -183,7 +183,7 @@ namespace Kav IEnumerable<(Model, Matrix)> modelTransforms, AmbientLight ambientLight, IEnumerable pointLights, - DirectionalLight directionalLight + DirectionalLight? directionalLight ) { GBufferRender(camera, modelTransforms); @@ -199,7 +199,10 @@ namespace Kav PointLightRender(camera, modelTransforms, pointLight); } - DirectionalLightRender(camera, modelTransforms, directionalLight); + if (directionalLight.HasValue) + { + DirectionalLightRender(camera, modelTransforms, directionalLight.Value); + } GraphicsDevice.SetRenderTarget(renderTarget); SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, null, null, null, ToneMapEffect); @@ -213,7 +216,7 @@ namespace Kav IEnumerable<(Model, Matrix)> modelTransforms, AmbientLight ambientLight, IEnumerable pointLights, - DirectionalLight directionalLight, + DirectionalLight? directionalLight, TextureCube skybox ) { GBufferRender(camera, modelTransforms); @@ -230,7 +233,12 @@ namespace Kav { PointLightRender(camera, modelTransforms, pointLight); } - DirectionalLightToonRender(camera, modelTransforms, directionalLight); + + if (directionalLight.HasValue) + { + DirectionalLightToonRender(camera, modelTransforms, directionalLight.Value); + } + SkyboxRender(camera, skybox); GraphicsDevice.SetRenderTarget(renderTarget); @@ -239,16 +247,16 @@ namespace Kav SpriteBatch.End(); } - // billboards sprites into the scene - // FIXME: we can frustum cull the sprites probably - public void BillboardSpriteRender( + // TODO: we could make this a lot more efficient probably + // draws mesh sprites with a forward rendered diffuse lighting technique + public void MeshSpriteRender( RenderTarget2D renderTarget, PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, - IEnumerable sprites, + IEnumerable<(MeshSprite, Matrix)> meshSpriteTransforms, AmbientLight ambientLight, IEnumerable pointLights, - DirectionalLight directionalLight + DirectionalLight? directionalLight ) { GraphicsDevice.SetRenderTarget(ColorRenderTarget); GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1f, 0); @@ -257,16 +265,29 @@ namespace Kav DepthRender(camera, modelTransforms); GraphicsDevice.Clear(ClearOptions.Target, new Color(0, 0, 0, 0), 1f, 0); + GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; + GraphicsDevice.RasterizerState = RasterizerState.CullNone; + GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp; + GraphicsDevice.SamplerStates[1] = SamplerState.PointClamp; + GraphicsDevice.BlendState = BlendState.AlphaBlend; + DiffuseLitSpriteEffect.View = camera.View; DiffuseLitSpriteEffect.Projection = camera.Projection; DiffuseLitSpriteEffect.AmbientColor = ambientLight.Color.ToVector3(); - DiffuseLitSpriteEffect.DirectionalLightDirection = - directionalLight.Direction; - DiffuseLitSpriteEffect.DirectionalLightColor = - directionalLight.Color.ToVector3() * directionalLight.Intensity; + if (directionalLight.HasValue) + { + DiffuseLitSpriteEffect.DirectionalLightDirection = + directionalLight.Value.Direction; + DiffuseLitSpriteEffect.DirectionalLightColor = + directionalLight.Value.Color.ToVector3() * directionalLight.Value.Intensity; + } + else + { + DiffuseLitSpriteEffect.DirectionalLightColor = Vector3.Zero; + } var i = 0; foreach (var pointLight in pointLights) @@ -276,26 +297,28 @@ namespace Kav i += 1; } - foreach (var sprite in sprites) + foreach (var (sprite, transform) in meshSpriteTransforms) { + DiffuseLitSpriteEffect.NormalMapEnabled = sprite.Normal != null; + if (sprite.BillboardConstraint == SpriteBillboardConstraint.None) { - DiffuseLitSpriteEffect.World = sprite.TransformMatrix; + DiffuseLitSpriteEffect.World = transform; } else if (sprite.BillboardConstraint == SpriteBillboardConstraint.Horizontal) { DiffuseLitSpriteEffect.World = Matrix.CreateConstrainedBillboard( - sprite.Position, + transform.Translation, camera.Position, Vector3.Up, camera.Forward, - camera.Position - sprite.Position + camera.Position - transform.Translation ); } else { DiffuseLitSpriteEffect.World = Matrix.CreateConstrainedBillboard( - sprite.Position, + transform.Translation, camera.Position, Vector3.Up, null, @@ -303,24 +326,27 @@ namespace Kav ); } - GraphicsDevice.Textures[1] = sprite.Texture; + GraphicsDevice.Textures[0] = sprite.Texture; + GraphicsDevice.Textures[1] = sprite.Normal; - SpriteBatch.Begin(0, null, null, DepthStencilState.DepthRead, RasterizerState.CullNone, DiffuseLitSpriteEffect); - SpriteBatch.Draw( - sprite.Texture, - Vector2.Zero, - null, - Color.White, - sprite.Rotation, - sprite.Origin, - sprite.Scale / new Vector2(sprite.Texture.Width, -sprite.Texture.Height), - 0, - 0 - ); - SpriteBatch.End(); + GraphicsDevice.SetVertexBuffer(sprite.VertexBuffer); + GraphicsDevice.Indices = sprite.IndexBuffer; + + foreach (var pass in DiffuseLitSpriteEffect.CurrentTechnique.Passes) + { + pass.Apply(); + + GraphicsDevice.DrawIndexedPrimitives( + PrimitiveType.TriangleList, + 0, + 0, + sprite.VertexBuffer.VertexCount, + 0, + 2 + ); + } } - GraphicsDevice.SetRenderTarget(renderTarget); GraphicsDevice.Clear(new Color(0, 0, 0, 0)); SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, null, null); @@ -558,6 +584,7 @@ namespace Kav Deferred_ToonEffect.DitheredShadows = false; Deferred_ToonEffect.EyePosition = camera.Position; + Deferred_ToonEffect.DirectionalLightDirection = directionalLight.Direction; Deferred_ToonEffect.DirectionalLightColor = directionalLight.Color.ToVector3() * directionalLight.Intensity;