diff --git a/Effects/DeferredPBREffect.cs b/Effects/DeferredPBREffect.cs new file mode 100644 index 0000000..04af0e8 --- /dev/null +++ b/Effects/DeferredPBREffect.cs @@ -0,0 +1,88 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Kav +{ + public class DeferredPBREffect : Effect + { + EffectParameter gPositionParam; + EffectParameter gAlbedoParam; + EffectParameter gNormalParam; + EffectParameter gMetallicRoughnessParam; + + EffectParameter eyePositionParam; + PointLightCollection pointLightCollection; + + 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 int MaxPointLights { get; } = 64; + + public PointLightCollection PointLights + { + get { return pointLightCollection; } + private set { pointLightCollection = value; } + } + + public DeferredPBREffect(GraphicsDevice graphicsDevice) : base(graphicsDevice, Resources.DeferredPBREffect) + { + CacheEffectParameters(); + + pointLightCollection = new PointLightCollection( + Parameters["PointLightPositions"], + Parameters["PointLightColors"], + MaxPointLights + ); + } + + protected DeferredPBREffect(DeferredPBREffect cloneSource) : base(cloneSource) + { + GPosition = cloneSource.GPosition; + GAlbedo = cloneSource.GAlbedo; + GNormal = cloneSource.GNormal; + GMetallicRoughness = cloneSource.GMetallicRoughness; + + EyePosition = cloneSource.EyePosition; + + PointLights = new PointLightCollection( + Parameters["LightPositions"], + Parameters["PositionLightColors"], + MaxPointLights + ); + + for (int i = 0; i < MaxPointLights; i++) + { + PointLights[i] = cloneSource.PointLights[i]; + } + } + + public override Effect Clone() + { + return new DeferredPBREffect(this); + } + + protected override void OnApply() + { + gPositionParam.SetValue(GPosition); + gAlbedoParam.SetValue(GAlbedo); + gNormalParam.SetValue(GNormal); + gMetallicRoughnessParam.SetValue(GMetallicRoughness); + + eyePositionParam.SetValue(EyePosition); + } + + void CacheEffectParameters() + { + gPositionParam = Parameters["gPosition"]; + gAlbedoParam = Parameters["gAlbedo"]; + gNormalParam = Parameters["gNormal"]; + gMetallicRoughnessParam = Parameters["gMetallicRoughness"]; + + eyePositionParam = Parameters["EyePosition"]; + } + } +} diff --git a/Effects/FXB/DeferredPBREffect.fxb b/Effects/FXB/DeferredPBREffect.fxb new file mode 100644 index 0000000..b3cda6c Binary files /dev/null and b/Effects/FXB/DeferredPBREffect.fxb differ diff --git a/Effects/HLSL/DeferredPBREffect.fx b/Effects/HLSL/DeferredPBREffect.fx new file mode 100644 index 0000000..9f4e74e --- /dev/null +++ b/Effects/HLSL/DeferredPBREffect.fx @@ -0,0 +1,167 @@ +#include "Macros.fxh" //from FNA + +static const float PI = 3.141592653589793; +static const int MAX_POINT_LIGHTS = 64; + +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 PointLightPositions[MAX_POINT_LIGHTS] _ps(c1) _cb(c1); + float3 PointLightColors[MAX_POINT_LIGHTS] _ps(c65) _cb(c65); + +MATRIX_CONSTANTS + +END_CONSTANTS + +struct PixelInput +{ + float4 Position : SV_POSITION; + float2 TexCoord : TEXCOORD0; +}; + +// Pixel Shader + +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 lightDir, + float3 radiance, + float3 F0, + float3 V, + float3 N, + float3 albedo, + float metallic, + float roughness +) { + float3 L = normalize(lightDir); + 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; +} + +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 Lo = float3(0.0, 0.0, 0.0); + + // point light + for (int i = 0; i < MAX_POINT_LIGHTS; i++) + { + float3 lightDir = PointLightPositions[i] - worldPosition; + float distance = length(lightDir); + float attenuation = 1.0 / (distance * distance); + float3 radiance = PointLightColors[i] * attenuation; + + Lo += ComputeLight(lightDir, radiance, F0, V, N, albedo, metallic, roughness); + } + + // // directional light + // for (int i = 0; i < 4; i++) + // { + // float3 lightDir = LightDirections[i]; + // float3 radiance = DirectionLightColors[i]; + + // Lo += ComputeLight(lightDir, radiance, F0, V, N, albedo, metallic, roughness); + // } + + float3 ambient = float3(0.03, 0.03, 0.03) * albedo; // * AO; + float3 color = ambient + Lo; + + color = color / (color + float3(1.0, 1.0, 1.0)); + float exposureConstant = 1.0 / 2.2; + color = pow(color, float3(exposureConstant, exposureConstant, exposureConstant)); + + return float4(color, 1.0); +} + +float4 main_ps(PixelInput input) : SV_TARGET0 +{ + float3 fragPosition = 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( + fragPosition, + normal, + albedo, + metallicRoughness.r, + metallicRoughness.g + ); +} + +Technique DeferredPBR +{ + Pass + { + PixelShader = compile ps_3_0 main_ps(); + } +} diff --git a/Effects/PBREffect.cs b/Effects/PBREffect.cs index 09e313e..9efe2fe 100644 --- a/Effects/PBREffect.cs +++ b/Effects/PBREffect.cs @@ -3,48 +3,6 @@ using Microsoft.Xna.Framework.Graphics; namespace Kav { - public class PointLightCollection - { - private readonly Vector3[] positions = new Vector3[4]; - private readonly Vector3[] colors = new Vector3[4]; - private readonly float[] intensities = new float[4]; - - readonly EffectParameter lightPositionsParam; - readonly EffectParameter lightColorsParam; - - public PointLightCollection(EffectParameter lightPositionsParam, EffectParameter lightColorsParam) - { - this.lightPositionsParam = lightPositionsParam; - this.lightColorsParam = lightColorsParam; - } - - public PointLight this[int i] - { - get - { - var color = colors[i] / intensities[i]; - return new PointLight( - positions[i], - new Color( - color.X, - color.Y, - color.Z, - 1f - ), - intensities[i] - ); - } - set - { - positions[i] = value.Position; - colors[i] = value.Color.ToVector3() * value.Intensity; - intensities[i] = value.Intensity; - lightPositionsParam.SetValue(positions); - lightColorsParam.SetValue(colors); - } - } - } - public class DirectionalLightCollection { private readonly Vector3[] directions = new Vector3[4]; @@ -285,7 +243,8 @@ namespace Kav pointLightCollection = new PointLightCollection( Parameters["LightPositions"], - Parameters["PositionLightColors"] + Parameters["PositionLightColors"], + MaxPointLights ); directionalLightCollection = new DirectionalLightCollection( @@ -304,7 +263,8 @@ namespace Kav PointLights = new PointLightCollection( Parameters["LightPositions"], - Parameters["PositionLightColors"] + Parameters["PositionLightColors"], + MaxPointLights ); for (int i = 0; i < MaxPointLights; i++) diff --git a/Effects/PointLightCollection.cs b/Effects/PointLightCollection.cs new file mode 100644 index 0000000..708a180 --- /dev/null +++ b/Effects/PointLightCollection.cs @@ -0,0 +1,49 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Kav +{ + public class PointLightCollection + { + private readonly Vector3[] positions; + private readonly Vector3[] colors; + private readonly float[] intensities = new float[4]; + + readonly EffectParameter lightPositionsParam; + readonly EffectParameter lightColorsParam; + + public PointLightCollection(EffectParameter lightPositionsParam, EffectParameter lightColorsParam, int maxLights) + { + this.positions = new Vector3[maxLights]; + this.colors = new Vector3[maxLights]; + this.lightPositionsParam = lightPositionsParam; + this.lightColorsParam = lightColorsParam; + } + + public PointLight this[int i] + { + get + { + var color = colors[i] / intensities[i]; + return new PointLight( + positions[i], + new Color( + color.X, + color.Y, + color.Z, + 1f + ), + intensities[i] + ); + } + set + { + positions[i] = value.Position; + colors[i] = value.Color.ToVector3() * value.Intensity; + intensities[i] = value.Intensity; + lightPositionsParam.SetValue(positions); + lightColorsParam.SetValue(colors); + } + } + } +} diff --git a/Kav.Core.csproj b/Kav.Core.csproj index e143f89..615ab97 100644 --- a/Kav.Core.csproj +++ b/Kav.Core.csproj @@ -18,6 +18,9 @@ Kav.Resources.GBufferEffect.fxb + + Kav.Resources.DeferredPBREffect.fxb + Kav.Resources.PBREffect.fxb diff --git a/Kav.Framework.csproj b/Kav.Framework.csproj index 3d70199..c0f517d 100644 --- a/Kav.Framework.csproj +++ b/Kav.Framework.csproj @@ -18,6 +18,9 @@ Kav.Resources.GBufferEffect.fxb + + Kav.Resources.DeferredPBREffect.fxb + Kav.Resources.PBREffect.fxb diff --git a/Renderer.cs b/Renderer.cs index f31419e..8c0384f 100644 --- a/Renderer.cs +++ b/Renderer.cs @@ -17,10 +17,11 @@ namespace Kav private RenderTarget2D gNormal { get; } private RenderTarget2D gAlbedo { get; } private RenderTarget2D gMetallicRoughness { get; } + private RenderTarget2D deferredRenderTarget { get; } private RenderTargetBinding[] GBuffer { get; } - private GBufferEffect GBufferEffect { get; } + private DeferredPBREffect DeferredPBREffect { get; } private SpriteBatch SpriteBatch { get; } @@ -35,7 +36,7 @@ namespace Kav renderDimensionsX, renderDimensionsY, false, - SurfaceFormat.Color, + SurfaceFormat.HalfSingle, // unused DepthFormat.Depth24 ); @@ -44,8 +45,8 @@ namespace Kav renderDimensionsX, renderDimensionsY, false, - SurfaceFormat.Color, - DepthFormat.None + SurfaceFormat.Vector4, + DepthFormat.Depth24 ); gNormal = new RenderTarget2D( @@ -53,7 +54,7 @@ namespace Kav renderDimensionsX, renderDimensionsY, false, - SurfaceFormat.Color, + SurfaceFormat.Vector4, DepthFormat.None ); @@ -71,7 +72,7 @@ namespace Kav renderDimensionsX, renderDimensionsY, false, - SurfaceFormat.Color, + SurfaceFormat.HalfVector2, DepthFormat.None ); @@ -82,9 +83,23 @@ namespace Kav new RenderTargetBinding(gMetallicRoughness) }; + deferredRenderTarget = new RenderTarget2D( + GraphicsDevice, + renderDimensionsX, + renderDimensionsY + ); + SimpleDepthEffect = new SimpleDepthEffect(GraphicsDevice); + DeferredPBREffect = new DeferredPBREffect(GraphicsDevice); SpriteBatch = new SpriteBatch(GraphicsDevice); + + Texture2D whitePixel = new Texture2D(GraphicsDevice, 1, 1); + whitePixel.SetData(new[] { Color.White }); + + GraphicsDevice.SetRenderTarget(deferredRenderTarget); + graphicsDevice.Clear(Color.White); + GraphicsDevice.SetRenderTarget(null); } public void DeferredRender( @@ -94,7 +109,9 @@ namespace Kav IEnumerable directionalLights ) { GraphicsDevice.SetRenderTargets(GBuffer); - GraphicsDevice.Clear(Color.Black); + GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1f, 0); + GraphicsDevice.DepthStencilState = DepthStencilState.Default; + GraphicsDevice.BlendState = BlendState.Opaque; foreach (var (model, transform) in modelTransforms) { @@ -130,12 +147,24 @@ namespace Kav } GraphicsDevice.SetRenderTarget(null); - GraphicsDevice.Clear(Color.Black); - SpriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null); - SpriteBatch.Draw(gPosition, new Rectangle(0, 0, 640, 360), Color.White); - SpriteBatch.Draw(gAlbedo, new Rectangle(640, 0, 640, 360), Color.White); - SpriteBatch.Draw(gNormal, new Rectangle(0, 360, 640, 360), Color.White); - SpriteBatch.Draw(gMetallicRoughness, new Rectangle(640, 360, 640, 360), Color.White); + GraphicsDevice.Clear(Color.CornflowerBlue); + + DeferredPBREffect.GPosition = gPosition; + DeferredPBREffect.GAlbedo = gAlbedo; + DeferredPBREffect.GNormal = gNormal; + DeferredPBREffect.GMetallicRoughness = gMetallicRoughness; + 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++; + } + + SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, null, null, DeferredPBREffect); + SpriteBatch.Draw(deferredRenderTarget, Vector2.Zero, Color.White); SpriteBatch.End(); } diff --git a/Resources.cs b/Resources.cs index 293307d..5c50646 100644 --- a/Resources.cs +++ b/Resources.cs @@ -15,6 +15,19 @@ namespace Kav return gBufferEffect; } } + + public static byte[] DeferredPBREffect + { + get + { + if (deferredPBREffect == null) + { + deferredPBREffect = GetResource("DeferredPBREffect"); + } + return deferredPBREffect; + } + } + public static byte[] PBREffect { get @@ -40,6 +53,7 @@ namespace Kav } private static byte[] gBufferEffect; + private static byte[] deferredPBREffect; private static byte[] pbrEffect; private static byte[] simpleDepthEffect;