diff --git a/Effects/DeferredPBR_AmbientLightEffect.cs b/Effects/DeferredPBR_AmbientLightEffect.cs new file mode 100644 index 0000000..f1d5e31 --- /dev/null +++ b/Effects/DeferredPBR_AmbientLightEffect.cs @@ -0,0 +1,31 @@ +using Microsoft.Xna.Framework.Graphics; + +namespace Kav +{ + + public class DeferredPBR_AmbientLightEffect : Effect + { + EffectParameter gPositionParam; + EffectParameter gAlbedoParam; + + public Texture2D GPosition { get; set; } + public Texture2D GAlbedo { get; set; } + + public DeferredPBR_AmbientLightEffect(GraphicsDevice graphicsDevice) : base(graphicsDevice, Resources.DeferredPBR_AmbientLightEffect) + { + CacheEffectParameters(); + } + + protected override void OnApply() + { + gPositionParam.SetValue(GPosition); + gAlbedoParam.SetValue(GAlbedo); + } + + void CacheEffectParameters() + { + gPositionParam = Parameters["gPosition"]; + gAlbedoParam = Parameters["gAlbedo"]; + } + } +} diff --git a/Effects/DeferredPBR_DirectionalLightEffect.cs b/Effects/DeferredPBR_DirectionalLightEffect.cs new file mode 100644 index 0000000..fb24e78 --- /dev/null +++ b/Effects/DeferredPBR_DirectionalLightEffect.cs @@ -0,0 +1,152 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Kav +{ + public class DeferredPBR_DirectionalLightEffect : Effect + { + EffectParameter gPositionParam; + EffectParameter gAlbedoParam; + EffectParameter gNormalParam; + EffectParameter gMetallicRoughnessParam; + + EffectParameter shadowMapOneParam; + EffectParameter shadowMapTwoParam; + EffectParameter shadowMapThreeParam; + EffectParameter shadowMapFourParam; + + EffectParameter eyePositionParam; + + EffectParameter directionalLightColorParam; + EffectParameter directionalLightDirectionParam; + + EffectParameter cascadeFarPlanesParam; + + EffectParameter lightSpaceMatrixOneParam; + EffectParameter lightSpaceMatrixTwoParam; + EffectParameter lightSpaceMatrixThreeParam; + EffectParameter lightSpaceMatrixFourParam; + + EffectParameter viewMatrixParam; + + public Texture2D GPosition { get; set; } + public Texture2D GAlbedo { get; set; } + public Texture2D GNormal { get; set; } + public Texture2D GMetallicRoughness { get; set; } + + public Texture2D ShadowMapOne { get; set; } + public Texture2D ShadowMapTwo { get; set; } + public Texture2D ShadowMapThree { get; set; } + public Texture2D ShadowMapFour { get; set; } + + public Vector3 EyePosition { get; set; } + + public Vector3 DirectionalLightDirection { get; set; } + public Vector3 DirectionalLightColor { get; set; } + + public readonly float[] CascadeFarPlanes; + + public Matrix LightSpaceMatrixOne { get; set; } + public Matrix LightSpaceMatrixTwo { get; set; } + public Matrix LightSpaceMatrixThree { get; set; } + public Matrix LightSpaceMatrixFour { get; set; } + + public Matrix ViewMatrix { get; set; } + + public DeferredPBR_DirectionalLightEffect(GraphicsDevice graphicsDevice) : base(graphicsDevice, Resources.DeferredPBR_DirectionalLightEffect) + { + CascadeFarPlanes = new float[4]; + CacheEffectParameters(); + } + + public DeferredPBR_DirectionalLightEffect(DeferredPBR_DirectionalLightEffect cloneSource) : base(cloneSource) + { + GPosition = cloneSource.GPosition; + GAlbedo = cloneSource.GAlbedo; + GNormal = cloneSource.GNormal; + GMetallicRoughness = cloneSource.GMetallicRoughness; + + ShadowMapOne = cloneSource.ShadowMapOne; + ShadowMapTwo = cloneSource.ShadowMapTwo; + ShadowMapThree = cloneSource.ShadowMapThree; + ShadowMapFour = cloneSource.ShadowMapFour; + + EyePosition = cloneSource.EyePosition; + + DirectionalLightDirection = cloneSource.DirectionalLightDirection; + DirectionalLightColor = cloneSource.DirectionalLightColor; + + CascadeFarPlanes = new float[4]; + for (int i = 0 ; i < 4; i++) + { + CascadeFarPlanes[i] = cloneSource.CascadeFarPlanes[i]; + } + + LightSpaceMatrixOne = cloneSource.LightSpaceMatrixOne; + LightSpaceMatrixTwo = cloneSource.LightSpaceMatrixTwo; + LightSpaceMatrixThree = cloneSource.LightSpaceMatrixThree; + LightSpaceMatrixFour = cloneSource.LightSpaceMatrixFour; + + ViewMatrix = cloneSource.ViewMatrix; + } + + public override Effect Clone() + { + return new DeferredPBR_DirectionalLightEffect(this); + } + + protected override void OnApply() + { + gPositionParam.SetValue(GPosition); + gAlbedoParam.SetValue(GAlbedo); + gNormalParam.SetValue(GNormal); + gMetallicRoughnessParam.SetValue(GMetallicRoughness); + + shadowMapOneParam.SetValue(ShadowMapOne); + shadowMapTwoParam.SetValue(ShadowMapTwo); + shadowMapThreeParam.SetValue(ShadowMapThree); + shadowMapFourParam.SetValue(ShadowMapFour); + + eyePositionParam.SetValue(EyePosition); + + directionalLightDirectionParam.SetValue(DirectionalLightDirection); + directionalLightColorParam.SetValue(DirectionalLightColor); + + cascadeFarPlanesParam.SetValue(CascadeFarPlanes); + + lightSpaceMatrixOneParam.SetValue(LightSpaceMatrixOne); + lightSpaceMatrixTwoParam.SetValue(LightSpaceMatrixTwo); + lightSpaceMatrixThreeParam.SetValue(LightSpaceMatrixThree); + lightSpaceMatrixFourParam.SetValue(LightSpaceMatrixFour); + + viewMatrixParam.SetValue(ViewMatrix); + } + + void CacheEffectParameters() + { + gPositionParam = Parameters["gPosition"]; + gAlbedoParam = Parameters["gAlbedo"]; + gNormalParam = Parameters["gNormal"]; + gMetallicRoughnessParam = Parameters["gMetallicRoughness"]; + + shadowMapOneParam = Parameters["shadowMapOne"]; + shadowMapTwoParam = Parameters["shadowMapTwo"]; + shadowMapThreeParam = Parameters["shadowMapThree"]; + shadowMapFourParam = Parameters["shadowMapFour"]; + + eyePositionParam = Parameters["EyePosition"]; + + directionalLightDirectionParam = Parameters["DirectionalLightDirection"]; + directionalLightColorParam = Parameters["DirectionalLightColor"]; + + cascadeFarPlanesParam = Parameters["CascadeFarPlanes"]; + + lightSpaceMatrixOneParam = Parameters["LightSpaceMatrixOne"]; + lightSpaceMatrixTwoParam = Parameters["LightSpaceMatrixTwo"]; + lightSpaceMatrixThreeParam = Parameters["LightSpaceMatrixThree"]; + lightSpaceMatrixFourParam = Parameters["LightSpaceMatrixFour"]; + + viewMatrixParam = Parameters["ViewMatrix"]; + } + } +} diff --git a/Effects/DeferredPBR_PointLightEffect.cs b/Effects/DeferredPBR_PointLightEffect.cs new file mode 100644 index 0000000..6f959ea --- /dev/null +++ b/Effects/DeferredPBR_PointLightEffect.cs @@ -0,0 +1,77 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Kav +{ + public class DeferredPBR_PointLightEffect : Effect + { + EffectParameter gPositionParam; + EffectParameter gAlbedoParam; + EffectParameter gNormalParam; + EffectParameter gMetallicRoughnessParam; + + EffectParameter eyePositionParam; + + EffectParameter pointLightColorParam; + EffectParameter pointLightPositionParam; + + public Texture2D GPosition { get; set; } + public Texture2D GAlbedo { get; set; } + public Texture2D GNormal { get; set; } + public Texture2D GMetallicRoughness { get; set; } + + public Vector3 EyePosition { get; set; } + + public Vector3 PointLightPosition { get; set; } + public Vector3 PointLightColor { get; set; } + + public DeferredPBR_PointLightEffect(GraphicsDevice graphicsDevice) : base(graphicsDevice, Resources.DeferredPBR_PointLightEffect) + { + CacheEffectParameters(); + } + + public DeferredPBR_PointLightEffect(DeferredPBR_PointLightEffect cloneSource) : base(cloneSource) + { + GPosition = cloneSource.GPosition; + GAlbedo = cloneSource.GAlbedo; + GNormal = cloneSource.GNormal; + GMetallicRoughness = cloneSource.GMetallicRoughness; + + EyePosition = cloneSource.EyePosition; + + PointLightPosition = cloneSource.PointLightPosition; + PointLightColor = cloneSource.PointLightColor; + } + + public override Effect Clone() + { + return new DeferredPBR_PointLightEffect(this); + } + + protected override void OnApply() + { + gPositionParam.SetValue(GPosition); + gAlbedoParam.SetValue(GAlbedo); + gNormalParam.SetValue(GNormal); + gMetallicRoughnessParam.SetValue(GMetallicRoughness); + + eyePositionParam.SetValue(EyePosition); + + pointLightPositionParam.SetValue(PointLightPosition); + pointLightColorParam.SetValue(PointLightColor); + } + + void CacheEffectParameters() + { + gPositionParam = Parameters["gPosition"]; + gAlbedoParam = Parameters["gAlbedo"]; + gNormalParam = Parameters["gNormal"]; + gMetallicRoughnessParam = Parameters["gMetallicRoughness"]; + + eyePositionParam = Parameters["EyePosition"]; + + pointLightPositionParam = Parameters["PointLightPosition"]; + pointLightColorParam = Parameters["PointLightColor"]; + } + } +} diff --git a/Effects/FXB/DeferredPBR_AmbientLightEffect.fxb b/Effects/FXB/DeferredPBR_AmbientLightEffect.fxb new file mode 100644 index 0000000..7cb8ee7 Binary files /dev/null and b/Effects/FXB/DeferredPBR_AmbientLightEffect.fxb differ diff --git a/Effects/FXB/DeferredPBR_DirectionalLightEffect.fxb b/Effects/FXB/DeferredPBR_DirectionalLightEffect.fxb new file mode 100644 index 0000000..e47ec40 Binary files /dev/null and b/Effects/FXB/DeferredPBR_DirectionalLightEffect.fxb differ diff --git a/Effects/FXB/DeferredPBR_GBufferEffect.fxb b/Effects/FXB/DeferredPBR_GBufferEffect.fxb index 9b2d684..413c415 100644 Binary files a/Effects/FXB/DeferredPBR_GBufferEffect.fxb and b/Effects/FXB/DeferredPBR_GBufferEffect.fxb differ diff --git a/Effects/FXB/DeferredPBR_PointLightEffect.fxb b/Effects/FXB/DeferredPBR_PointLightEffect.fxb new file mode 100644 index 0000000..e345705 Binary files /dev/null and b/Effects/FXB/DeferredPBR_PointLightEffect.fxb differ diff --git a/Effects/HLSL/DeferredPBR_AmbientLightEffect.fx b/Effects/HLSL/DeferredPBR_AmbientLightEffect.fx new file mode 100644 index 0000000..4144c8d --- /dev/null +++ b/Effects/HLSL/DeferredPBR_AmbientLightEffect.fx @@ -0,0 +1,54 @@ +#include "Macros.fxh" // from FNA + +DECLARE_TEXTURE(gPosition, 0); +DECLARE_TEXTURE(gAlbedo, 1); + +struct VertexInput +{ + float4 Position : POSITION; + float2 TexCoord : TEXCOORD; +}; + +struct PixelInput +{ + float4 Position : SV_POSITION; + float2 TexCoord : TEXCOORD0; +}; + +PixelInput main_vs(VertexInput input) +{ + PixelInput output; + + output.Position = input.Position; + output.TexCoord = input.TexCoord; + + return output; +} + +float4 ComputeColor( + float3 worldPosition, + float3 albedo +) { + float3 color = float3(0.03, 0.03, 0.03) * albedo; + return float4(color, 1.0); +} + +float4 main_ps(PixelInput input) : SV_TARGET0 +{ + float3 worldPosition = SAMPLE_TEXTURE(gPosition, input.TexCoord).rgb; + float3 albedo = SAMPLE_TEXTURE(gAlbedo, input.TexCoord).rgb; + + return ComputeColor( + worldPosition, + albedo + ); +} + +Technique DeferredPBR_Ambient +{ + Pass + { + VertexShader = compile vs_3_0 main_vs(); + PixelShader = compile ps_3_0 main_ps(); + } +} diff --git a/Effects/HLSL/DeferredPBR_DirectionalLightEffect.fx b/Effects/HLSL/DeferredPBR_DirectionalLightEffect.fx new file mode 100644 index 0000000..77a3860 --- /dev/null +++ b/Effects/HLSL/DeferredPBR_DirectionalLightEffect.fx @@ -0,0 +1,190 @@ +#include "Macros.fxh" //from FNA +#include "Lighting.fxh" + +static const int NUM_SHADOW_CASCADES = 4; + +DECLARE_TEXTURE(gPosition, 0); +DECLARE_TEXTURE(gAlbedo, 1); +DECLARE_TEXTURE(gNormal, 2); +DECLARE_TEXTURE(gMetallicRoughness, 3); +DECLARE_TEXTURE(shadowMapOne, 4); +DECLARE_TEXTURE(shadowMapTwo, 5); +DECLARE_TEXTURE(shadowMapThree, 6); +DECLARE_TEXTURE(shadowMapFour, 7); + +BEGIN_CONSTANTS + + float3 EyePosition _ps(c0) _cb(c0); + + float3 DirectionalLightDirection _ps(c1) _cb(c1); + float3 DirectionalLightColor _ps(c2) _cb(c2); + + float CascadeFarPlanes[NUM_SHADOW_CASCADES] _ps(c3) _cb(c3); + +MATRIX_CONSTANTS + + float4x4 LightSpaceMatrixOne _ps(c7) _cb(c7); + float4x4 LightSpaceMatrixTwo _ps(c11) _cb(c11); + float4x4 LightSpaceMatrixThree _ps(c15) _cb(c15); + float4x4 LightSpaceMatrixFour _ps(c19) _cb(c19); + + // used to select shadow cascade + float4x4 ViewMatrix _ps(c23) _cb(c23); + +END_CONSTANTS + +struct VertexInput +{ + float4 Position : POSITION; + float2 TexCoord : TEXCOORD; +}; + +struct PixelInput +{ + float4 Position : SV_POSITION; + float2 TexCoord : TEXCOORD0; +}; + +PixelInput main_vs(VertexInput input) +{ + PixelInput output; + + output.Position = input.Position; + output.TexCoord = input.TexCoord; + + return output; +} + +// Pixel Shader + +float ComputeShadow(float3 positionWorldSpace, float3 N, float3 L) +{ + float bias = 0.005 * tan(acos(dot(N, L))); + bias = clamp(bias, 0, 0.01); + + float4 positionCameraSpace = mul(float4(positionWorldSpace, 1.0), ViewMatrix); + + int shadowCascadeIndex = 0; // 0 is closest + for (int i = 0; i < NUM_SHADOW_CASCADES; i++) + { + if (abs(positionCameraSpace.z) < CascadeFarPlanes[i]) + { + shadowCascadeIndex = i; + break; + } + } + + float4x4 lightSpaceMatrix; + + if (shadowCascadeIndex == 0) + { + lightSpaceMatrix = LightSpaceMatrixOne; + } + else if (shadowCascadeIndex == 1) + { + lightSpaceMatrix = LightSpaceMatrixTwo; + } + else if (shadowCascadeIndex == 2) + { + lightSpaceMatrix = LightSpaceMatrixThree; + } + else + { + lightSpaceMatrix = LightSpaceMatrixFour; + } + + float4 positionLightSpace = mul(float4(positionWorldSpace, 1.0), lightSpaceMatrix); + + // maps to [-1, 1] + float3 projectionCoords = positionLightSpace.xyz / positionLightSpace.w; + + // maps to [0, 1] + projectionCoords.x = (projectionCoords.x * 0.5) + 0.5; + projectionCoords.y = (projectionCoords.y * 0.5) + 0.5; + projectionCoords.y *= -1; + // in XNA clip z is 0 to 1 already + + float inc = 1.0 / 1024.0; + + float shadowFactor = 0; + for (int row = -1; row <= 1; row++) + { + for (int col = -1; col <= 1; col++) + { + float closestDepth; + if (shadowCascadeIndex == 0) + { + closestDepth = SAMPLE_TEXTURE(shadowMapOne, projectionCoords.xy + float2(row, col) * inc).r; + } + else if (shadowCascadeIndex == 1) + { + closestDepth = SAMPLE_TEXTURE(shadowMapTwo, projectionCoords.xy + float2(row, col) * inc).r; + } + else if (shadowCascadeIndex == 2) + { + closestDepth = SAMPLE_TEXTURE(shadowMapThree, projectionCoords.xy + float2(row, col) * inc).r; + } + else + { + closestDepth = SAMPLE_TEXTURE(shadowMapFour, projectionCoords.xy + float2(row, col) * inc).r; + } + shadowFactor += projectionCoords.z - bias > closestDepth ? 1.0 : 0.0; + } + } + + shadowFactor /= 9.0; + + if (projectionCoords.z > 1.0) + { + shadowFactor = 1.0; + } + + return shadowFactor; +} + +float4 ComputeColor( + float3 worldPosition, + float3 worldNormal, + float3 albedo, + float metallic, + float roughness +) { + float3 V = normalize(EyePosition - worldPosition); + float3 N = normalize(worldNormal); + + float3 F0 = float3(0.04, 0.04, 0.04); + F0 = lerp(F0, albedo, metallic); + + float3 L = normalize(DirectionalLightDirection); + float3 radiance = DirectionalLightColor; + + float shadow = ComputeShadow(worldPosition, N, L); + float3 color = ComputeLight(L, radiance, F0, V, N, albedo, metallic, roughness, (1.0 - shadow)); + + return float4(color, 1.0); +} + +float4 main_ps(PixelInput input) : SV_TARGET0 +{ + float3 worldPosition = SAMPLE_TEXTURE(gPosition, input.TexCoord).rgb; + float3 normal = SAMPLE_TEXTURE(gNormal, input.TexCoord).xyz; + float3 albedo = SAMPLE_TEXTURE(gAlbedo, input.TexCoord).rgb; + float2 metallicRoughness = SAMPLE_TEXTURE(gMetallicRoughness, input.TexCoord).rg; + + return ComputeColor( + worldPosition, + normal, + albedo, + metallicRoughness.r, + metallicRoughness.g + ); +} + +Technique DeferredPBR_Directional +{ + Pass + { + VertexShader = compile vs_3_0 main_vs(); + PixelShader = compile ps_3_0 main_ps(); + } +} diff --git a/Effects/HLSL/DeferredPBR_GBufferEffect.fx b/Effects/HLSL/DeferredPBR_GBufferEffect.fx index 63cb2ce..f24da19 100644 --- a/Effects/HLSL/DeferredPBR_GBufferEffect.fx +++ b/Effects/HLSL/DeferredPBR_GBufferEffect.fx @@ -79,10 +79,10 @@ PixelOutput NonePS(PixelInput input) { PixelOutput output; - output.gPosition = float4(input.PositionWorld, 0.0); - output.gNormal = float4(normalize(input.NormalWorld), 0.0); + output.gPosition = float4(input.PositionWorld, 1.0); + output.gNormal = float4(normalize(input.NormalWorld), 1.0); output.gAlbedo = float4(AlbedoValue, 1.0); - output.gMetallicRoughness = float4(MetallicValue, RoughnessValue, 0.0, 0.0); + output.gMetallicRoughness = float4(MetallicValue, RoughnessValue, 0.0, 1.0); return output; } @@ -91,10 +91,10 @@ PixelOutput AlbedoPS(PixelInput input) { PixelOutput output; - output.gPosition = float4(input.PositionWorld, 0.0); - output.gNormal = float4(normalize(input.NormalWorld), 0.0); + output.gPosition = float4(input.PositionWorld, 1.0); + output.gNormal = float4(normalize(input.NormalWorld), 1.0); output.gAlbedo = SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord); - output.gMetallicRoughness = float4(MetallicValue, RoughnessValue, 0.0, 0.0); + output.gMetallicRoughness = float4(MetallicValue, RoughnessValue, 0.0, 1.0); return output; } @@ -103,8 +103,8 @@ PixelOutput MetallicRoughnessPS(PixelInput input) { PixelOutput output; - output.gPosition = float4(input.PositionWorld, 0.0); - output.gNormal = float4(normalize(input.NormalWorld), 0.0); + output.gPosition = float4(input.PositionWorld, 1.0); + output.gNormal = float4(normalize(input.NormalWorld), 1.0); output.gAlbedo = float4(AlbedoValue, 1.0); output.gMetallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord); @@ -115,10 +115,10 @@ PixelOutput NormalPS(PixelInput input) { PixelOutput output; - output.gPosition = float4(input.PositionWorld, 0.0); - output.gNormal = float4(GetNormalFromMap(input.PositionWorld, input.TexCoord, input.NormalWorld), 0.0); + output.gPosition = float4(input.PositionWorld, 1.0); + output.gNormal = float4(GetNormalFromMap(input.PositionWorld, input.TexCoord, input.NormalWorld), 1.0); output.gAlbedo = float4(AlbedoValue, 1.0); - output.gMetallicRoughness = float4(MetallicValue, RoughnessValue, 0.0, 0.0); + output.gMetallicRoughness = float4(MetallicValue, RoughnessValue, 0.0, 1.0); return output; } @@ -127,8 +127,8 @@ PixelOutput AlbedoMetallicRoughnessPS(PixelInput input) { PixelOutput output; - output.gPosition = float4(input.PositionWorld, 0.0); - output.gNormal = float4(normalize(input.NormalWorld), 0.0); + output.gPosition = float4(input.PositionWorld, 1.0); + output.gNormal = float4(normalize(input.NormalWorld), 1.0); output.gAlbedo = SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord); output.gMetallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord); @@ -139,10 +139,10 @@ PixelOutput AlbedoNormalPS(PixelInput input) { PixelOutput output; - output.gPosition = float4(input.PositionWorld, 0.0); - output.gNormal = float4(GetNormalFromMap(input.PositionWorld, input.TexCoord, input.NormalWorld), 0.0); + output.gPosition = float4(input.PositionWorld, 1.0); + output.gNormal = float4(GetNormalFromMap(input.PositionWorld, input.TexCoord, input.NormalWorld), 1.0); output.gAlbedo = SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord); - output.gMetallicRoughness = float4(MetallicValue, RoughnessValue, 0.0, 0.0); + output.gMetallicRoughness = float4(MetallicValue, RoughnessValue, 0.0, 1.0); return output; } @@ -151,8 +151,8 @@ PixelOutput MetallicRoughnessNormalPS(PixelInput input) { PixelOutput output; - output.gPosition = float4(input.PositionWorld, 0.0); - output.gNormal = float4(GetNormalFromMap(input.PositionWorld, input.TexCoord, input.NormalWorld), 0.0); + output.gPosition = float4(input.PositionWorld, 1.0); + output.gNormal = float4(GetNormalFromMap(input.PositionWorld, input.TexCoord, input.NormalWorld), 1.0); output.gAlbedo = float4(AlbedoValue, 1.0); output.gMetallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord); @@ -163,8 +163,8 @@ PixelOutput AlbedoMetallicRoughnessNormalMapPS(PixelInput input) { PixelOutput output; - output.gPosition = float4(input.PositionWorld, 0.0); - output.gNormal = float4(GetNormalFromMap(input.PositionWorld, input.TexCoord, input.NormalWorld), 0.0); + output.gPosition = float4(input.PositionWorld, 1.0); + output.gNormal = float4(GetNormalFromMap(input.PositionWorld, input.TexCoord, input.NormalWorld), 1.0); output.gAlbedo = SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord); output.gMetallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord); diff --git a/Effects/HLSL/DeferredPBR_PointLightEffect.fx b/Effects/HLSL/DeferredPBR_PointLightEffect.fx new file mode 100644 index 0000000..fba3c6e --- /dev/null +++ b/Effects/HLSL/DeferredPBR_PointLightEffect.fx @@ -0,0 +1,91 @@ +#include "Macros.fxh" //from FNA +#include "Lighting.fxh" + +DECLARE_TEXTURE(gPosition, 0); +DECLARE_TEXTURE(gAlbedo, 1); +DECLARE_TEXTURE(gNormal, 2); +DECLARE_TEXTURE(gMetallicRoughness, 3); + +BEGIN_CONSTANTS + + float3 EyePosition _ps(c0) _cb(c0); + + float3 PointLightPosition _ps(c1) _cb(c1); + float3 PointLightColor _ps(c2) _cb(c2); + +MATRIX_CONSTANTS + +END_CONSTANTS + +struct VertexInput +{ + float4 Position : POSITION; + float2 TexCoord : TEXCOORD; +}; + +struct PixelInput +{ + float4 Position : SV_POSITION; + float2 TexCoord : TEXCOORD0; +}; + +PixelInput main_vs(VertexInput input) +{ + PixelInput output; + + output.Position = input.Position; + output.TexCoord = input.TexCoord; + + return output; +} + +// Pixel Shader + +float4 ComputeColor( + float3 worldPosition, + float3 worldNormal, + float3 albedo, + float metallic, + float roughness +) { + float3 V = normalize(EyePosition - worldPosition); + float3 N = normalize(worldNormal); + + float3 F0 = float3(0.04, 0.04, 0.04); + F0 = lerp(F0, albedo, metallic); + + float3 lightDir = PointLightPosition - worldPosition; + float3 L = normalize(lightDir); + float distance = length(lightDir); + float attenuation = 1.0 / (distance * distance); + float3 radiance = PointLightColor * attenuation; + + float3 color = ComputeLight(L, radiance, F0, V, N, albedo, metallic, roughness, 1.0); + + return float4(color, 1.0); +} + +float4 main_ps(PixelInput input) : SV_TARGET0 +{ + float3 worldPosition = SAMPLE_TEXTURE(gPosition, input.TexCoord).rgb; + float3 normal = SAMPLE_TEXTURE(gNormal, input.TexCoord).xyz; + float3 albedo = SAMPLE_TEXTURE(gAlbedo, input.TexCoord).rgb; + float2 metallicRoughness = SAMPLE_TEXTURE(gMetallicRoughness, input.TexCoord).rg; + + return ComputeColor( + worldPosition, + normal, + albedo, + metallicRoughness.r, + metallicRoughness.g + ); +} + +Technique DeferredPBR_Point +{ + Pass + { + VertexShader = compile vs_3_0 main_vs(); + PixelShader = compile ps_3_0 main_ps(); + } +} diff --git a/Effects/HLSL/Lighting.fxh b/Effects/HLSL/Lighting.fxh new file mode 100644 index 0000000..1cc6ae8 --- /dev/null +++ b/Effects/HLSL/Lighting.fxh @@ -0,0 +1,71 @@ +static const float PI = 3.141592653589793; + +float3 FresnelSchlick(float cosTheta, float3 F0) +{ + return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); +} + +float DistributionGGX(float3 N, float3 H, float roughness) +{ + float a = roughness * roughness; + float a2 = a * a; + float NdotH = max(dot(N, H), 0.0); + float NdotH2 = NdotH * NdotH; + + float num = a2; + float denom = (NdotH2 * (a2 - 1.0) + 1.0); + denom = PI * denom * denom; + + return num / denom; +} + +float GeometrySchlickGGX(float NdotV, float roughness) +{ + float r = (roughness + 1.0); + float k = (r * r) / 8.0; + + float num = NdotV; + float denom = NdotV * (1.0 - k) + k; + + return num / denom; +} + +float GeometrySmith(float3 N, float3 V, float3 L, float roughness) +{ + float NdotV = max(dot(N, V), 0.0); + float NdotL = max(dot(N, L), 0.0); + float ggx2 = GeometrySchlickGGX(NdotV, roughness); + float ggx1 = GeometrySchlickGGX(NdotL, roughness); + + return ggx1 * ggx2; +} + +float3 ComputeLight( + float3 L, + float3 radiance, + float3 F0, + float3 V, + float3 N, + float3 albedo, + float metallic, + float roughness, + float shadow +) { + float3 H = normalize(V + L); + + float NDF = DistributionGGX(N, H, roughness); + float G = GeometrySmith(N, V, L, roughness); + float3 F = FresnelSchlick(max(dot(H, V), 0.0), F0); + + float3 numerator = NDF * G * F; + float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0); + float3 specular = numerator / max(denominator, 0.001); + + float3 kS = F; + float3 kD = float3(1.0, 1.0, 1.0) - kS; + + kD *= 1.0 - metallic; + + float NdotL = max(dot(N, L), 0.0); + return (kD * albedo / PI + specular) * radiance * NdotL * shadow; +} diff --git a/Kav.Core.csproj b/Kav.Core.csproj index 7957739..c7c437f 100644 --- a/Kav.Core.csproj +++ b/Kav.Core.csproj @@ -15,6 +15,15 @@ + + Kav.Resources.DeferredPBR_AmbientLightEffect.fxb + + + Kav.Resources.DeferredPBR_PointLightEffect.fxb + + + Kav.Resources.DeferredPBR_DirectionalLightEffect.fxb + Kav.Resources.DeferredPBR_GBufferEffect.fxb diff --git a/Kav.Framework.csproj b/Kav.Framework.csproj index 0557675..a98bb21 100644 --- a/Kav.Framework.csproj +++ b/Kav.Framework.csproj @@ -15,6 +15,15 @@ + + Kav.Resources.DeferredPBR_AmbientLightEffect.fxb + + + Kav.Resources.DeferredPBR_PointLightEffect.fxb + + + Kav.Resources.DeferredPBR_DirectionalLightEffect.fxb + Kav.Resources.DeferredPBR_GBufferEffect.fxb diff --git a/README.md b/README.md index 7a2bff0..bf40a9f 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Essential - [x] Directional lighting - [x] Directional shadow maps - [x] Cascading shadow maps +- [ ] Tone map shader - [ ] Frustum culling - [ ] PCF soft shadowing - [ ] Shadow-casting point lights diff --git a/Renderer.cs b/Renderer.cs index b99fe02..82a9a0f 100644 --- a/Renderer.cs +++ b/Renderer.cs @@ -8,26 +8,32 @@ namespace Kav { private const int MAX_SHADOW_CASCADES = 4; - private GraphicsDevice GraphicsDevice { get; } private int RenderDimensionsX { get; } private int RenderDimensionsY { get; } private VertexBuffer FullscreenTriangle { get; } private int NumShadowCascades { get; } + + private RenderTarget2D ColorRenderTarget { get; } + private RenderTarget2D DirectionalRenderTarget { get; } private RenderTarget2D[] ShadowRenderTargets { get; } private DeferredPBREffect DeferredPBREffect { get; } + private DeferredPBR_AmbientLightEffect DeferredAmbientLightEffect { get; } + private DeferredPBR_PointLightEffect DeferredPointLightEffect { get; } + private DeferredPBR_DirectionalLightEffect DeferredDirectionalLightEffect { get; } private SimpleDepthEffect SimpleDepthEffect { get; } private RenderTarget2D gPosition { get; } private RenderTarget2D gNormal { get; } private RenderTarget2D gAlbedo { get; } private RenderTarget2D gMetallicRoughness { get; } - private RenderTarget2D deferredRenderTarget { get; } private RenderTargetBinding[] GBuffer { get; } + private SpriteBatch SpriteBatch { get; } + public Renderer(GraphicsDevice graphicsDevice, int renderDimensionsX, int renderDimensionsY, int numShadowCascades) { GraphicsDevice = graphicsDevice; @@ -49,6 +55,28 @@ namespace Kav ); } + ColorRenderTarget = new RenderTarget2D( + graphicsDevice, + renderDimensionsX, + renderDimensionsY, + false, + SurfaceFormat.Color, + DepthFormat.None, + 0, + RenderTargetUsage.PreserveContents + ); + + DirectionalRenderTarget = new RenderTarget2D( + graphicsDevice, + renderDimensionsX, + renderDimensionsY, + false, + SurfaceFormat.Color, + DepthFormat.None, + 0, + RenderTargetUsage.PreserveContents + ); + gPosition = new RenderTarget2D( GraphicsDevice, renderDimensionsX, @@ -91,26 +119,21 @@ namespace Kav new RenderTargetBinding(gAlbedo), new RenderTargetBinding(gMetallicRoughness) }; - - deferredRenderTarget = new RenderTarget2D( - GraphicsDevice, - renderDimensionsX, - renderDimensionsY - ); SimpleDepthEffect = new SimpleDepthEffect(GraphicsDevice); DeferredPBREffect = new DeferredPBREffect(GraphicsDevice); + DeferredAmbientLightEffect = new DeferredPBR_AmbientLightEffect(GraphicsDevice); + DeferredPointLightEffect = new DeferredPBR_PointLightEffect(GraphicsDevice); + DeferredDirectionalLightEffect = new DeferredPBR_DirectionalLightEffect(GraphicsDevice); FullscreenTriangle = new VertexBuffer(GraphicsDevice, typeof(VertexPositionTexture), 3, BufferUsage.WriteOnly); FullscreenTriangle.SetData(new VertexPositionTexture[3] { - new VertexPositionTexture(new Vector3(-1, -1, 0), new Vector2(0, 0)), - new VertexPositionTexture(new Vector3(-1, 3, 0), new Vector2(0, -2)), - new VertexPositionTexture(new Vector3(3, -1, 0), new Vector2(2, 0)) + new VertexPositionTexture(new Vector3(-1, -3, 0), new Vector2(0, 2)), + new VertexPositionTexture(new Vector3(-1, 1, 0), new Vector2(0, 0)), + new VertexPositionTexture(new Vector3(3, 1, 0), new Vector2(2, 0)) }); - GraphicsDevice.SetRenderTarget(deferredRenderTarget); - graphicsDevice.Clear(Color.White); - GraphicsDevice.SetRenderTarget(null); + SpriteBatch = new SpriteBatch(graphicsDevice); } public void DeferredRender( @@ -119,7 +142,7 @@ namespace Kav IEnumerable pointLights, DirectionalLight directionalLight ) { - ShadowMapRender(camera, modelTransforms, directionalLight); + // g-buffer pass GraphicsDevice.SetRenderTargets(GBuffer); GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1f, 0); @@ -159,45 +182,54 @@ namespace Kav } } - GraphicsDevice.SetRenderTarget(null); - GraphicsDevice.Clear(Color.CornflowerBlue); + GraphicsDevice.SetRenderTarget(ColorRenderTarget); + GraphicsDevice.Clear(Color.Black); + GraphicsDevice.BlendState = BlendState.Additive; + GraphicsDevice.DepthStencilState = DepthStencilState.None; - DeferredPBREffect.GPosition = gPosition; - DeferredPBREffect.GAlbedo = gAlbedo; - DeferredPBREffect.GNormal = gNormal; - DeferredPBREffect.GMetallicRoughness = gMetallicRoughness; - - DeferredPBREffect.ShadowMapOne = ShadowRenderTargets[0]; - if (NumShadowCascades > 1) + DeferredAmbientLightEffect.GPosition = gPosition; + DeferredAmbientLightEffect.GAlbedo = gAlbedo; + + foreach (var pass in DeferredAmbientLightEffect.CurrentTechnique.Passes) { - DeferredPBREffect.ShadowMapTwo = ShadowRenderTargets[1]; - } - if (NumShadowCascades > 2) - { - DeferredPBREffect.ShadowMapThree = ShadowRenderTargets[2]; - } - if (NumShadowCascades > 3) - { - DeferredPBREffect.ShadowMapFour = ShadowRenderTargets[3]; + pass.Apply(); + GraphicsDevice.SetVertexBuffer(FullscreenTriangle); + GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1); } - DeferredPBREffect.ViewMatrix = camera.View; + DeferredPointLightEffect.EyePosition = camera.Position; - DeferredPBREffect.EyePosition = Matrix.Invert(camera.View).Translation; - - int i = 0; foreach (var pointLight in pointLights) { - if (i > DeferredPBREffect.MaxPointLights) { break; } - DeferredPBREffect.PointLights[i] = pointLight; - i++; + PointLightRender(pointLight); } - DeferredPBREffect.DirectionalLightColor = directionalLight.Color.ToVector3() * directionalLight.Intensity; - DeferredPBREffect.DirectionalLightDirection = directionalLight.Direction; + DirectionalLightRender(camera, modelTransforms, directionalLight); + // return; + // GraphicsDevice.SetRenderTarget(null); + // SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied); + // SpriteBatch.Draw(DirectionalRenderTarget, Vector2.Zero, Color.White); + // SpriteBatch.End(); + GraphicsDevice.SetRenderTarget(null); + GraphicsDevice.Clear(Color.Black); + SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque); + SpriteBatch.Draw(ColorRenderTarget, Vector2.Zero, Color.White); + SpriteBatch.End(); + } - foreach (EffectPass pass in DeferredPBREffect.CurrentTechnique.Passes) + private void PointLightRender(PointLight pointLight) + { + DeferredPointLightEffect.GPosition = gPosition; + DeferredPointLightEffect.GAlbedo = gAlbedo; + DeferredPointLightEffect.GNormal = gNormal; + DeferredPointLightEffect.GMetallicRoughness = gMetallicRoughness; + + DeferredPointLightEffect.PointLightPosition = pointLight.Position; + DeferredPointLightEffect.PointLightColor = + pointLight.Color.ToVector3() * pointLight.Intensity; + + foreach (var pass in DeferredPointLightEffect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.SetVertexBuffer(FullscreenTriangle); @@ -205,12 +237,12 @@ namespace Kav } } - public void ShadowMapRender( - PerspectiveCamera camera, - IEnumerable<(Model, Matrix)> modelTransforms, + private void DirectionalLightRender( + PerspectiveCamera camera, + IEnumerable<(Model, Matrix)> modelTransforms, DirectionalLight directionalLight ) { - // render the individual shadow maps + // render the individual shadow cascades var previousFarPlane = camera.NearPlane; for (var i = 0; i < NumShadowCascades; i++) { @@ -227,10 +259,47 @@ namespace Kav farPlane ); + // TODO: This is tightly coupled to the effect and it sucks RenderShadowMap(shadowCamera, modelTransforms, directionalLight, i); previousFarPlane = farPlane; } + + DeferredDirectionalLightEffect.GPosition = gPosition; + DeferredDirectionalLightEffect.GAlbedo = gAlbedo; + DeferredDirectionalLightEffect.GNormal = gNormal; + DeferredDirectionalLightEffect.GMetallicRoughness = gMetallicRoughness; + + DeferredDirectionalLightEffect.ShadowMapOne = ShadowRenderTargets[0]; + if (NumShadowCascades > 1) + { + DeferredDirectionalLightEffect.ShadowMapTwo = ShadowRenderTargets[1]; + } + if (NumShadowCascades > 2) + { + DeferredDirectionalLightEffect.ShadowMapThree = ShadowRenderTargets[2]; + } + if (NumShadowCascades > 3) + { + DeferredDirectionalLightEffect.ShadowMapFour = ShadowRenderTargets[3]; + } + + DeferredDirectionalLightEffect.DirectionalLightDirection = directionalLight.Direction; + DeferredDirectionalLightEffect.DirectionalLightColor = + directionalLight.Color.ToVector3() * directionalLight.Intensity; + + DeferredDirectionalLightEffect.ViewMatrix = camera.View; + DeferredDirectionalLightEffect.EyePosition = Matrix.Invert(camera.View).Translation; + + GraphicsDevice.SetRenderTarget(ColorRenderTarget); + GraphicsDevice.BlendState = BlendState.Additive; + + foreach (EffectPass pass in DeferredDirectionalLightEffect.CurrentTechnique.Passes) + { + pass.Apply(); + GraphicsDevice.SetVertexBuffer(FullscreenTriangle); + GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1); + } } private void RenderShadowMap( @@ -277,22 +346,22 @@ namespace Kav if (shadowCascadeIndex == 0) { - DeferredPBREffect.LightSpaceMatrixOne = lightSpaceMatrix; + DeferredDirectionalLightEffect.LightSpaceMatrixOne = lightSpaceMatrix; } else if (shadowCascadeIndex == 1) { - DeferredPBREffect.LightSpaceMatrixTwo = lightSpaceMatrix; + DeferredDirectionalLightEffect.LightSpaceMatrixTwo = lightSpaceMatrix; } else if (shadowCascadeIndex == 2) { - DeferredPBREffect.LightSpaceMatrixThree = lightSpaceMatrix; + DeferredDirectionalLightEffect.LightSpaceMatrixThree = lightSpaceMatrix; } else if (shadowCascadeIndex == 3) { - DeferredPBREffect.LightSpaceMatrixFour = lightSpaceMatrix; + DeferredDirectionalLightEffect.LightSpaceMatrixFour = lightSpaceMatrix; } - DeferredPBREffect.CascadeFarPlanes[shadowCascadeIndex] = camera.FarPlane; + DeferredDirectionalLightEffect.CascadeFarPlanes[shadowCascadeIndex] = camera.FarPlane; foreach (var (model, transform) in modelTransforms) { diff --git a/Resources.cs b/Resources.cs index 83d4b04..9ac9358 100644 --- a/Resources.cs +++ b/Resources.cs @@ -4,6 +4,41 @@ namespace Kav { internal class Resources { + public static byte[] DeferredPBR_AmbientLightEffect + { + get + { + if (ambientLightEffect == null) + { + ambientLightEffect = GetResource("DeferredPBR_AmbientLightEffect"); + } + return ambientLightEffect; + } + } + public static byte[] DeferredPBR_PointLightEffect + { + get + { + if (pointLightEffect == null) + { + pointLightEffect = GetResource("DeferredPBR_PointLightEffect"); + } + return pointLightEffect; + } + } + + public static byte[] DeferredPBR_DirectionalLightEffect + { + get + { + if (directionalLightEffect == null) + { + directionalLightEffect = GetResource("DeferredPBR_DirectionalLightEffect"); + } + return directionalLightEffect; + } + } + public static byte[] DeferredPBR_GBufferEffect { get @@ -52,6 +87,9 @@ namespace Kav } } + private static byte[] ambientLightEffect; + private static byte[] pointLightEffect; + private static byte[] directionalLightEffect; private static byte[] gBufferEffect; private static byte[] deferredPBREffect; private static byte[] pbrEffect;