From 66d4e5bf6e78949f6e5b2fc9eb1983d438047f0b Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Thu, 1 Oct 2020 19:46:25 +0000 Subject: [PATCH] Directional Light + Shadows + Tone Mapping + One Pass Per Light (#2) --- .gitattributes | 1 + Cameras/Camera.cs | 34 +- EffectInterfaces/DirectionalLightEffect.cs | 8 - Effects/DeferredPBREffect.cs | 101 ++++-- Effects/DeferredPBR_AmbientLightEffect.cs | 31 ++ Effects/DeferredPBR_DirectionalLightEffect.cs | 161 +++++++++ Effects/DeferredPBR_PointLightEffect.cs | 77 +++++ Effects/DirectionalLightCollection.cs | 47 --- Effects/FXB/DeferredPBREffect.fxb | Bin 53400 -> 130 bytes .../FXB/DeferredPBR_AmbientLightEffect.fxb | 3 + .../DeferredPBR_DirectionalLightEffect.fxb | 3 + Effects/FXB/DeferredPBR_GBufferEffect.fxb | Bin 7476 -> 129 bytes Effects/FXB/DeferredPBR_PointLightEffect.fxb | 3 + Effects/FXB/GBufferEffect.fxb | Bin 7476 -> 129 bytes Effects/FXB/PBREffect.fxb | Bin 58384 -> 130 bytes Effects/FXB/SimpleDepthEffect.fxb | Bin 624 -> 128 bytes Effects/FXB/ToneMapEffect.fxb | 3 + Effects/HLSL/DeferredPBREffect.fx | 164 ++++++++- .../HLSL/DeferredPBR_AmbientLightEffect.fx | 54 +++ .../DeferredPBR_DirectionalLightEffect.fx | 252 ++++++++++++++ Effects/HLSL/DeferredPBR_GBufferEffect.fx | 40 +-- Effects/HLSL/DeferredPBR_PointLightEffect.fx | 91 +++++ Effects/HLSL/Lighting.fxh | 71 ++++ Effects/HLSL/Macros.fxh | 1 + Effects/HLSL/SimpleDepthEffect.fx | 10 +- Effects/HLSL/ToneMapEffect.fx | 20 ++ Effects/PBREffect.cs | 26 +- Effects/SimpleDepthEffect.cs | 5 +- Kav.Core.csproj | 12 + Kav.Framework.csproj | 12 + Lights/DirectionalLight.cs | 2 +- README.md | 29 ++ Renderer.cs | 315 ++++++++++++++---- Resources.cs | 51 +++ 34 files changed, 1399 insertions(+), 228 deletions(-) create mode 100644 .gitattributes delete mode 100644 EffectInterfaces/DirectionalLightEffect.cs create mode 100644 Effects/DeferredPBR_AmbientLightEffect.cs create mode 100644 Effects/DeferredPBR_DirectionalLightEffect.cs create mode 100644 Effects/DeferredPBR_PointLightEffect.cs delete mode 100644 Effects/DirectionalLightCollection.cs create mode 100644 Effects/FXB/DeferredPBR_AmbientLightEffect.fxb create mode 100644 Effects/FXB/DeferredPBR_DirectionalLightEffect.fxb create mode 100644 Effects/FXB/DeferredPBR_PointLightEffect.fxb create mode 100644 Effects/FXB/ToneMapEffect.fxb create mode 100644 Effects/HLSL/DeferredPBR_AmbientLightEffect.fx create mode 100644 Effects/HLSL/DeferredPBR_DirectionalLightEffect.fx create mode 100644 Effects/HLSL/DeferredPBR_PointLightEffect.fx create mode 100644 Effects/HLSL/Lighting.fxh create mode 100644 Effects/HLSL/ToneMapEffect.fx create mode 100644 README.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..40824ed --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.fxb filter=lfs diff=lfs merge=lfs -text diff --git a/Cameras/Camera.cs b/Cameras/Camera.cs index c0729ee..5462985 100644 --- a/Cameras/Camera.cs +++ b/Cameras/Camera.cs @@ -2,22 +2,32 @@ using Microsoft.Xna.Framework; namespace Kav { - public struct Camera + public struct PerspectiveCamera { - public Matrix Transform { get; } - public Matrix View - { - get - { - return Matrix.CreateLookAt(Transform.Translation, Transform.Translation + Transform.Forward, Transform.Up); - } - } + public Matrix View { get; } public Matrix Projection { get; } - public Camera(Matrix transform, Matrix projection) + public Vector3 Position { get; } + public Vector3 Forward { get; } + public Vector3 Up { get; } + + public float FieldOfView { get; } + public float AspectRatio { get; } + public float NearPlane { get; } + public float FarPlane { get; } + + public PerspectiveCamera(Vector3 position, Vector3 forward, Vector3 up, float fieldOfView, float aspectRatio, float nearPlane, float farPlane) { - Transform = transform; - Projection = projection; + Position = position; + Forward = forward; + Up = up; + View = Matrix.CreateLookAt(Position, Position + Forward, Up); + + FieldOfView = fieldOfView; + AspectRatio = aspectRatio; + NearPlane = nearPlane; + FarPlane = farPlane; + Projection = Matrix.CreatePerspectiveFieldOfView(FieldOfView, AspectRatio, NearPlane, FarPlane); } } } diff --git a/EffectInterfaces/DirectionalLightEffect.cs b/EffectInterfaces/DirectionalLightEffect.cs deleted file mode 100644 index 53ba894..0000000 --- a/EffectInterfaces/DirectionalLightEffect.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Kav -{ - public interface DirectionalLightEffect - { - int MaxDirectionalLights { get; } - DirectionalLightCollection DirectionalLights { get; } - } -} diff --git a/Effects/DeferredPBREffect.cs b/Effects/DeferredPBREffect.cs index 97c8615..181d1ec 100644 --- a/Effects/DeferredPBREffect.cs +++ b/Effects/DeferredPBREffect.cs @@ -10,15 +10,47 @@ namespace Kav EffectParameter gNormalParam; EffectParameter gMetallicRoughnessParam; + EffectParameter shadowMapOneParam; + EffectParameter shadowMapTwoParam; + EffectParameter shadowMapThreeParam; + EffectParameter shadowMapFourParam; + + EffectParameter lightSpaceMatrixOneParam; + EffectParameter lightSpaceMatrixTwoParam; + EffectParameter lightSpaceMatrixThreeParam; + EffectParameter lightSpaceMatrixFourParam; + + EffectParameter viewMatrixParam; + EffectParameter cascadeFarPlanesParam; + + EffectParameter directionalLightColorParam; + EffectParameter directionalLightDirectionParam; + EffectParameter eyePositionParam; + PointLightCollection pointLightCollection; - DirectionalLightCollection directionalLightCollection; 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 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 readonly float[] CascadeFarPlanes; + + public Vector3 DirectionalLightColor { get; set; } + public Vector3 DirectionalLightDirection { get; set; } + public Vector3 EyePosition { get; set; } public int MaxPointLights { get; } = 64; @@ -29,16 +61,10 @@ namespace Kav private set { pointLightCollection = value; } } - public int MaxDirectionalLights { get; } = 4; - - public DirectionalLightCollection DirectionalLights - { - get { return directionalLightCollection; } - private set { directionalLightCollection = value; } - } - public DeferredPBREffect(GraphicsDevice graphicsDevice) : base(graphicsDevice, Resources.DeferredPBREffect) { + CascadeFarPlanes = new float[4]; + CacheEffectParameters(); pointLightCollection = new PointLightCollection( @@ -46,11 +72,6 @@ namespace Kav Parameters["PointLightColors"], MaxPointLights ); - - DirectionalLights = new DirectionalLightCollection( - Parameters["DirectionalLightDirections"], - Parameters["DirectionalLightColors"] - ); } protected DeferredPBREffect(DeferredPBREffect cloneSource) : base(cloneSource) @@ -72,16 +93,6 @@ namespace Kav { PointLights[i] = cloneSource.PointLights[i]; } - - DirectionalLights = new DirectionalLightCollection( - Parameters["DirectionalLightDirections"], - Parameters["DirectionalLightColors"] - ); - - for (int i = 0; i < MaxDirectionalLights; i++) - { - DirectionalLights[i] = cloneSource.DirectionalLights[i]; - } } public override Effect Clone() @@ -96,17 +107,49 @@ namespace Kav gNormalParam.SetValue(GNormal); gMetallicRoughnessParam.SetValue(GMetallicRoughness); + shadowMapOneParam.SetValue(ShadowMapOne); + shadowMapTwoParam.SetValue(ShadowMapTwo); + shadowMapThreeParam.SetValue(ShadowMapThree); + shadowMapFourParam.SetValue(ShadowMapFour); + + lightSpaceMatrixOneParam.SetValue(LightSpaceMatrixOne); + lightSpaceMatrixTwoParam.SetValue(LightSpaceMatrixTwo); + lightSpaceMatrixThreeParam.SetValue(LightSpaceMatrixThree); + lightSpaceMatrixFourParam.SetValue(LightSpaceMatrixFour); + + viewMatrixParam.SetValue(ViewMatrix); + cascadeFarPlanesParam.SetValue(CascadeFarPlanes); + + directionalLightColorParam.SetValue(DirectionalLightColor); + directionalLightDirectionParam.SetValue(DirectionalLightDirection); + eyePositionParam.SetValue(EyePosition); } void CacheEffectParameters() { - gPositionParam = Parameters["gPosition"]; - gAlbedoParam = Parameters["gAlbedo"]; - gNormalParam = Parameters["gNormal"]; - gMetallicRoughnessParam = Parameters["gMetallicRoughness"]; + gPositionParam = Parameters["gPosition"]; + gAlbedoParam = Parameters["gAlbedo"]; + gNormalParam = Parameters["gNormal"]; + gMetallicRoughnessParam = Parameters["gMetallicRoughness"]; - eyePositionParam = Parameters["EyePosition"]; + shadowMapOneParam = Parameters["shadowMapOne"]; + shadowMapTwoParam = Parameters["shadowMapTwo"]; + shadowMapThreeParam = Parameters["shadowMapThree"]; + shadowMapFourParam = Parameters["shadowMapFour"]; + + lightSpaceMatrixOneParam = Parameters["LightSpaceMatrixOne"]; + lightSpaceMatrixTwoParam = Parameters["LightSpaceMatrixTwo"]; + lightSpaceMatrixThreeParam = Parameters["LightSpaceMatrixThree"]; + lightSpaceMatrixFourParam = Parameters["LightSpaceMatrixFour"]; + + viewMatrixParam = Parameters["ViewMatrix"]; + cascadeFarPlanesParam = Parameters["CascadeFarPlanes"]; + + directionalLightDirectionParam = Parameters["DirectionalLightDirection"]; + directionalLightColorParam = Parameters["DirectionalLightColor"]; + + eyePositionParam = Parameters["EyePosition"]; } } } 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..5e1f919 --- /dev/null +++ b/Effects/DeferredPBR_DirectionalLightEffect.cs @@ -0,0 +1,161 @@ +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 shadowMapSizeParam; + + 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 int ShadowMapSize { get; set; } + + 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]; + } + + ShadowMapSize = cloneSource.ShadowMapSize; + + 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); + shadowMapSizeParam.SetValue(ShadowMapSize); + + 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"]; + + shadowMapSizeParam = Parameters["ShadowMapSize"]; + + 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/DirectionalLightCollection.cs b/Effects/DirectionalLightCollection.cs deleted file mode 100644 index 10bcbda..0000000 --- a/Effects/DirectionalLightCollection.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; - -namespace Kav -{ - public class DirectionalLightCollection - { - private readonly Vector3[] directions = new Vector3[4]; - private readonly Vector3[] colors = new Vector3[4]; - private readonly float[] intensities = new float[4]; - - readonly EffectParameter lightDirectionsParam; - readonly EffectParameter lightColorsParam; - - public DirectionalLightCollection(EffectParameter lightDirectionsParam, EffectParameter lightColorsParam) - { - this.lightDirectionsParam = lightDirectionsParam; - this.lightColorsParam = lightColorsParam; - } - - public DirectionalLight this[int i] - { - get - { - var color = colors[i] / intensities[i]; - return new DirectionalLight( - directions[i], - new Color( - color.X, - color.Y, - color.Z, - 1f - ), - intensities[i] - ); - } - set - { - directions[i] = value.Direction; - colors[i] = value.Color.ToVector3() * value.Intensity; - intensities[i] = value.Intensity; - lightDirectionsParam.SetValue(directions); - lightColorsParam.SetValue(colors); - } - } - } -} diff --git a/Effects/FXB/DeferredPBREffect.fxb b/Effects/FXB/DeferredPBREffect.fxb index 10298d5149531440477ff239977961028697e518..1d4d26cc9ff85c859faa1157041406f9ff188d00 100644 GIT binary patch literal 130 zcmWN?OA^8$3;@u5Pr(H&-=v|p0TP56wH?7OJiWfnyW~Ck@ltKhbL?8*`@B8sSpT+XvqQYlF_Hl16o(e3nQCQVq`YeR-lYA8Ss=ikU+@^9Lu(m#Xylf N#bxxbD1Z=g`2p!GCoup3 literal 53400 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 diff --git a/Effects/FXB/DeferredPBR_PointLightEffect.fxb b/Effects/FXB/DeferredPBR_PointLightEffect.fxb new file mode 100644 index 0000000..398f66b --- /dev/null +++ b/Effects/FXB/DeferredPBR_PointLightEffect.fxb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e51395e0f0dd1e1b86272dde1e4fb5aea1fdf9b86399416015ad33cfb7260691 +size 3108 diff --git a/Effects/FXB/GBufferEffect.fxb b/Effects/FXB/GBufferEffect.fxb index 9b2d6844f486894e8a28f804c05221fee8d81938..ad31fdf9431f5cc1eb85c24acbfb120f2a7c4b76 100644 GIT binary patch literal 129 zcmWN?%MrpL5CG6SRnUNe@Lkdk>@LELN=7gTtJimV7r$p8FWJ^Q=b_ZSuiK-}?SK1} z4aZZ>lTlr}7@g#7+4yjzgiwnG(ol^f*x54{48ay854$D{fKDeJV`0PWh-#+fbgji; M7PEg@Mi+zl0pms}XaE2J literal 7476 zcmds6O=w(I6u$4hX)BVR!9iS95J^!%L}{9Y+Q3v}lh%NRCMHuGnP!HWq(*S@ zgf3iUF{q0!x_Dqf>9{E9BDkpw5er?E;-VD65oh5-(3J?5jNfR%7Pd%%bDZ{?;7W4VR7`BD!0E--zIU?CrZ z)FC!LS;vwUj^a54ei+VEu1Crj*?)FC!L z-G1DFejUtywA9(AA5XKOr5$)a20oUXo6P5Drj8fp-#$B=E0>#Hisxq0Dc6NE8}(@# z{uAKLI`vEUI3KM37Jn>>O>{ncz-FFLPUh!xdLC&{6D&)tLaN~$%h*f_Gxv0TX^k?y zco)xuEyTXi{R^iIaUuU(;#|H_DCMW|ly>$}u(Va}Jc9akJHxZ*bER@_tTZ`WE*8p+ ze`$Ad8`|9`?Vg;;y*pAWypx-nn<>mH&NIh$%eI+@%$VYHCzxw;H<*||04A1vH!^y- zRGNHG;{jD=EC_t(Vo#ZHv3j~N)+KIP_sHn*?DWi3t_)dV9kF%6)KVAwFnV@!I#)uO z+=A}=Uf3LW7E!dxxU|9kNIbHSuMEteIfK+dKiWC22f?iCQLs=Sgj1s6xd@INeHn7y z(f6UU1JV%eS;z~Heg*P%NBr$Us+mg2VuqIyN&^DwGRn<}wG4Kb1L}9nEG;z-0yt=ZZ7fOEL%ho;j$h)z>lnK=nqZ z>F|(r;c*NP^AciaDn|A~4>Dp7Q$e|2A!FV!4Icbd2>-xKcWc?a$2+almvTBp;fS*5 z$iDH>c3<01XgNr;9pfLQ{SXaVdylTC(J*UR$DzjUKPt{rBW|Bf*WEN1>1n8|u0 zW+Eror>9P6oVf4Z;l%5;Hyq>iCnp=W6dr=p?~wCg!D#{Vhv19aX4c;4*0zkN$#Pxl z24qqV>{~p=?{4rkN3hj-wCy>!&DxIR!Bo^K>~6m8N9MVTVS&O!2H(~BwjJ&sa6hG| z(yX^PwVV!pb)v!?jGP@R)d1H+zC+%oTrp*gAF#pyZeUy&-w&Q_`i7M4dtIlmeXqgy zX2`PCmAZ!VH_>igmknTp91frlzB0MNL!5~}-=T9+fcRlQ=^vmh`mO6^Khdb}6YSc? zKKSY~+Nx{2$x+NFb>O`%|1o24my-f@IilU{KSoZ*cc2>DXT4m-dYG$texS2i z1JA{4;8U2Ax#|*3Lo|b#tAK|lt_#Uki`LfZUK7XVHsq=%PuTwnN%2T=l=jUGLEgJ{@SfW>@3&a)cF2p)*IVT$X>N5uIC|kLoa~S#`se6>e z81HWUx65hD$!Q|5m%Hf0HNo%T8qv^eVklk{zraKrm&<<2AjUS&&U8-ACwEkwycfMCk%ro~P=VJ=HL+;1ioACt`-VggW;to0W-t5~0 z2%n8|yywKc$jN=&!#>V9=bcE6fve+uFpl$|U`odMEtnYRjQ!*{qQrTGccd#|iSzHZ zMdCcM3AZ^%$~WR>_wDVB@vYA5|A%-7oY8-k<2MuklIP90waMq$hcz*FNj%!P)Bw@O zrEm!X&eHMyr}D>-tTbMW*G8{)96v%|y$3+=0T0>ox-8@ga1D&h9o-(!K6k*)`%r#s znNt(Xw5R6xuYKR&>ervp!-O6s^luV+twne5UZrmwZabP=*!*SM3T`F_v%_PoXQi=6 z9U0FKvR?3B`_yxvfz1aCrL6D=s29YXB}B|b=AEhvR&YPr+^c`)OW26H#!0EnZOF1$ zGiOae=Y0tCm9J9BSr5fF-jfl#lzJkznZF-Ys{uSy_{Y9)1-1Wsfwm@Y^UrsBjajuh zCqypO=TOW?i2D_LCRHK6N9C@IJ~jID+=_2bD|_$}*$*-H8t$P2+)UKCSA!#?`&!3$ zv(-~t$G2^S_{MjySL5AhD%O3i?}+u3*1K$ZM29Y)j&)Q2&R93~?~3)b_TLR1xcKZR p+O9B47%P$k6o|NO^!GTH!Cb|}GTbj)_8aK*bK^k#TE1)G_YchewfX=6 diff --git a/Effects/FXB/PBREffect.fxb b/Effects/FXB/PBREffect.fxb index 1747aa48900f927512bed8397d446fd22572faee..2214837cabe728ec27abfe852d904da5765e8cd1 100644 GIT binary patch literal 130 zcmWN?!4bkB5CFhGRnUL|4mcp)9N`dVR5F5jSiSCNFMiKHUb3xq&RwZ{U$;k{+yC~- z+cBPMo_wgwh|!CjjlkosnPR9VSOcaQ4IZGn9FwyZ$r^MSD*{sh%%lr@YfFlb$wx;i Np_u*4(nlMe_yNlnCgA`8 literal 58384 zcmeI5U5s7Vb>H{QT=EVjnv`Y9QW^mnI0IBEf<$igpa(aiD9V)px@JU;BOnGWnjBdZ zj5x#$sYrkyt`I-;!DyzfTKFLa4hua5WFGvG2P1G{AyA71Z~-GQP@(k$3tSX>FoHZV z01X(L{(pO&|2g}9%y7OfQTIrPcYmzC_S$Rz&)OgB>@!oxHa>d(Sjtyd|8{gg7Sa6g zl>R-T`i0TWu%)@E?mU8?=@gM6(jsc!yQ^&>j`t-Q;*#^e^dtWRzzkiFy20!JD+t>i+7*i_6zaL6l}$ zj?*FaHLG9qaT>p{bl>z9xuyG2r4947Fy?Wp;0=6!P4`z8uU=SQUb^_=%C&F3c6o7a z&B{@pWvYsDq?wT5-{4E=D(Dy$p>bPI=2X#Mp`HeG6ufBS1ZLt-~ zGCc{n>g%|E&Btl{mNFd|{j6@$MF8F^&=_hrcUcL0{*=tuDVJypg1ub|t z4+Wd6l;wM(e@FM{l{?6@6~yhiUGPSR&*_%@=dUbYytaH{bwrl4D6_u6y!kkd-%^%& z(a-7rXG$OC+scC&lzXS(?=&Sh7kv>n;%gU{uPtU9K<4{JIrwmMp)X@3N`&1Y@6IM= z&_jdQx{%fM+dS;`Rn|lPGhYqfr1xq%suY*Td!R`x3ac$Rfc7)<#lTx z_&+56W}WMKc4c{Gb*+*248}^=UfKXgGVe1>tBV(-5!T1M`@kPt-LNhjp>)^tVO@`s znl!?`z|?bZEm{?s26OQe4_bYpg90v2-tjM@{Nc*#@+JKuANBn30P5k{&-J`;`HjU@ zS-8~;m)EYWtSuJxKFI)3U;EWQFqP~5+S1~moLgP_9Qk*U1lK6?Pb#Lpk~q zxf%BuZ=gAM{+ZR)3vXpTDVqMaC|^E*gwZ8Br5+v+bd74Uqvy`QaQV{G#YH_r4X-;j zFC}Gr0S_+laQ?Lmmlju5X7SBz)4?%(%L|*vGdi4ZKBS*J{bKqJ{gOsIe_c1}w9nTQ z3VhuW{eD+>Rm6XwaKDH@BxaAh5~ZI~c)5uGxx(Ks;y+gSP7!}xiuw}Ds;^fR{%#R} zU*Qjm_-%#fP;7m@r0{D+yr=MA7V)1c{EtQaxElNvjjpd{h5v04e^=r6iueZ#-!9_E z)tQgeq4o8o!sm+kI|_flh~H88q2if;S>dZi{5^$#R>bcp{7~@>zM}BAi}(i$-!9^3 zg_~Cj?0i?@zbN7#D15t!pJI^IMHf6(hf(i;AW_$GrOoR8j}cAt3eV{Vb6}U}6x__D8Q5d>+b6Q7f4<_cIq`jZhrd0N5Q}5sBv{})8 zA$?fW-N|XwqPwr5)7W{Frd zo?UtU%97Slb6i?X4;gs6^`ziIF)_scf&nx}D|Mi>yP4~YF zee`}r`XfqTdGJsFdSm0QGk^Mv|N8rX_Ba3ZCDI>N`tSbrhvLy*vUSr((o|2DcRj+> zy(xu-I$4Bn??~!bLf^mFru|-NQXA!v*MZZ4Xw+O zr@C^vW2%Qbrxkx_sLpBe;V$bGJ~}g^CEaz+i+4w9GfIOu`Q$-^{8N&*qcHhV7jkX9 z{rR+U{blhZUvcGa^hkrpbe%@JrdQffI03%7<(n@hD|S7IaHG`I?(mU>kn$qWmM$3o^lzID8=>4vrpCn*yWpd`>B? zjKh~S`hqgTR;9kGZA2&2LfGS!|xY8l`zJWwHIA`E*vI{}ueE*8GI?y4 zO>68ub`X1y?Hjbm{ffBd*^50!2JAD~rGd8j*kjUM?^gDBz8Vp2=YsU@Fuq7LzL4{Q8b8D1z47BGb)qO+Zj!xMblL0!);hN1o>RG>$XB>oPhjgw<8~RsUavU6%!@S(`xvp_PvYlw zL5rWjQmhGLtxI04Ya`zBW_};^n%6h$pLVS@xc*`8@3ll*pV!l|CZpY6|3K^Y5ACq+ zKzE-6`}N>m@O84zXU}XmV*=#LU#N&QvgOFO?ACndh5KXj=6Nc^Ak_X}U_*No1K zkiq*q5e|$c&0D4<2Us4=2ha4grW49*_xb9$(u@yiIyp`nSaF-$=K~(p5&L%Nye%K& z4*R7^GN4!Lt^1{!e-UT?MLu=3{pR_X_t9dXA@(cVHE`^U#GY&H>BQb6dz@YmhYYdz z8hsPkVE)B^TC@#0`@PTA_=z&e9P452GipCK(^SwCW}Q5fVowly;*V(W3O=;8rq9nC za)KH05snD6ryTnS)2XAdZBEF6KIE6H_mEQ^M&Ao{xTA1ehPoe>EGJYZ%}yyV^&%W) zA{=FCOPZa{a&2sc|MCeOf!R0npBvxIzfdEFeKWtQw9R}o(nZ2Ib6gd0J(ep9-^`N= zpV9rDoEE;Bb%pz)t8&`$gl`6)A9U~KH0=<|H}jJ^?N&}3^v(Q3P7B|RHu-Ydhx9A` zN|xRDX6n~P{91?nFP(H;yjq{mufk7bzl@rjuTQGqM&9O1(EO3^r|rMN4}(AFfByLR zsnT~tI{qBdo+^Deq~p)Qhg14)!dH`e0&VTL;dyI)II=A{T?$-JvnMOTM34=Cj-H=G z_ve&#hJEbU`@><+uvPeL$Q$R+nHS9K{Os4jHnN_xUxWQ#Y^(Rv$;X~~j38e+sJNr9 zHeWib!XCG2(7*$p%zI>uGL6$n58G%P%$_+mEaGJzGQ>I*UkdHBUxT)%EOUOeWhbd8 z`U<<2_lI-6jB(I%!#l<&;v2a=ZT`k}cpyK1J=XG+!G0niX)2$2IKPdbfS>fVp5;9% zTI4;Fev$XoyNZiPmKXlycQfynKWWe)qr=cdd^*$Xoso*TWifrByIeoMC;FHA(Mik; z)7!L<@h|e3gRmzY{ZrE@E$WPTJ8z6K#8dCIcy`lCmF?>p=~s+Fv#Jw$BaFN;2FdCw z9AgmkNajn-l^JVXCc;rB!chj<+cFCkeT_fJz9{mPR>hk3MxMX2kAjS}%j;Tvo8XRp z6nH|TaMlOQJ-2fm=$$$wDl#Bn_C3b_&O(-f zI*FqT@ApuiFuua*yLSFi#*u?@oH{6@yvjBr+R-OXI&YsT;Y50CpRZEZvi+fhww&ce zcal|drOth-)3%0scs{AOv?*YPGT4-P)qTIpXzzZ2R(qg14$cVsS)qSjwv~-FSYC(i zt8Jk@)Tdwd6?Z)AkrtSB7#idu&T|**vS>T<5|2KC-$KS#JZtuO-4K62YncYRhG(?T zby9}*#+(_Q`&n%O} zq#x<>g!r!yV3@RHN;3`f-Hz-M`Yds*byIa1I9B^BWJMnn98Yi@&-DJ`SkLF7;MhK- z363ZBxSfOS9mn%|uF&n-hr~Q(r*;xdGM6dm28=u2gN-pL=EjVtlkt9VyhrDojrWYD zjQ7x#b6Dbx_vjs)M%?pD;s-U}BTqSJWxQvse;}>radvpTH@@scLe2@k4tE?z=bvGFIwXW!=6pr<;!;INc zKK8AK^Qn(JUig%z#VhZ3D&PKO zV-B;L*EeNJf6tNljepJh2{zY%^i_U5+hH$pD_ zMw|uvL35_(-!*=t`_%~iM#6k^rU&254;A;Bp7#{Sk912nx3O=dQ5jEr-k203e9x*l;ls-e-EarPQ#}(qm-%Imn8P-R;CNT(e0Y<(|mrX zIg>Nw{u=gujXU9E-<0s2ubm#~!Jjjg#-HbzR(k0ZO7a~+kH4t&3Hdw^{-V++gg`s- z3w7=RzYw+un-_MvpU0wDBlKkh`#o$-?Hkg0kzXQAT#l-Tpn{8WF)$+;bk16fY^Qm4x0 zI5Hm9*nkYP8XFwvJq?{UY|i~?9N6;Q&vb3mX&+-PH@s$YUF@O8nFRK}BOLl6uD1Bj zXCPc&I;qUVX|&rqWR70y869#{#x&At#aQHcnP(YUVU0>I;1_2@z*76J740#%&P%KxaKhZ6%tyB2ISp+?cchb#Zz{&~_FWpzL(xWf!VkWbi+6PI zWPg?{w7ZUgbt#==OqXAc@M`j(wCG48_bnlHr`VMn=$Y487=yY`iTd2T0FOD(Z7xx z&yLe}!eAL5VK*{18@m)(Ku@*{@opUY;)ciYdv9eKzxM{MblTVz^rkj8c7!-~q}*d6 zjQygW?h^%iTRkJjXvi>%VcO|FN5@J0hzC7>CZF}1%6Zg{j=(T^C5GWed1JW55#`*^ z_*ALO^@3;O(12mW@CMuT0p*l-C^)vyW`g4hjt>II!up}$*gl&Hj(3h@?9hJO*V?{k z`&iht#@=J&u=k~1B#gb^FMEs(N%om>#P*mvu*V)}kYg)*JYSF3_SrE0v#-J29$woW z@2SJIl>0P|_XmM5`XTVbyrLO14;5dY3(~j4_#(~YJ#y~t+)u0@;Ir9$a)Pgzv#TG} z7n`r4wmU!kq~U9C=YDpEFRxF$Ua>uE*P^l3u^nga;Ppz(v+1LDz2Y=%waXCpdd2x= zUaVQ#b3bLjx9gD@3!~3t-cMevgCpMZZr4AW*Ej2*cC9qH{$cL#wM1KA@GQ%C{R6G! z$m<{4VcUW3J_+_~Z|8pQjxWYkkB=q3lKQK}7klww!|P%CPx{NZ#~JeX3t!X+eoA~r zT42@T)~>|f3Np2Ljddvftm%a0Z}C;fl~z6vfzfuqhB#|YV>a{1Wx!YKe`)cR(*4Ru zFYlro?Lp*uOEW$aXZ~d$O`WeYG5-p`TC90PuN~#JYhd<8y#E+`I*|`guOpJ`pp3p8 zhW_+_%$=Bju@@Hlfo9Z4gFaagv#(kEV=Cyq2P8#S<_R7CKlsqrnqJ|J>&QtTiH~q} zFz0@FcYG)Q8|1=&!}kim-JJXRFOC1^%W4Gv8@}7dH`=(KR$5=b_TPM{@EzT=C`iBf zZ}8QShQG$&Wc!lBXLWx!r-lE-~R9 zn^6jBzf_ou?*ePDOJ%>1<`kyhCvuv4Al=uL{WSY|;#06=zYINc&S^Wop2lw!jCbn# zX};w~|M@(S9+ju@@xaBlvhmKs-zklFKbOTn!@CNnB||5jqIByY^{iqTPI(uat@Qa+flp z6J#RY{Ym>-|GWK+6X?b3Ec{Pul*SgH#cIDh7uevvgj8_{|Bm8}h7}c69!XKM?()NBZYzu9I-|PdDLniT*(+&{e20GnQM(q}#kfAISJlP{+ z9uvNZ*t^0v2*%=UKYLB^qh8YC8}ERD_|4@|}#&HlMob|Gdsn-5rJbHbh`d%_pwAea35= zoNk`M!*x;yo^cL|ejpt9iGD!ONp;c>^ljZ&4StBzx87@VU(&bKiQkbnbwwj=%dW(; zj!uj(@>_hxch3TI<$G3OE3bi6lxN`=Dl_<=6?1T~5c)>$ey$tLAUor&o{P$MI@S)* z@C>zU3FMABe$4-o+v|+Ti#8$ma2~iN9iJAsqZ~M-4&;KbaZB2~c!OKQ9$P4ny^r#P zIMy6{0LSQ1&sj4*Xf`bVU?_A&oP4cS8yq{0^k`4e7{|^Jc{^F}rR|(bU^y@}m5wOf z7tKG_yaKmvKD=+}4%|lB7PqvAZy-Pe?m`~;lNRp^xW1MrW#HN3mT-yN=)c|LmVN_o zUCCYcF*-4B1Eb~nIq(9O1GgoH-1o*UIy26|Cvj-NcIY$alg6!{uS)58&J$RSGLmly zn}I)glyYwuxA?FoxaGN>;MSM~$2-OCe8y#*Gukem!gg+L2YFV4A7^S}&V}9Nou^?t zsE3JXid%L!^6zCk82YmvZ0!qr@fZk=!p-E z<6rEFvNnl*hKTpo)^_}(-Q7G!vi}i#BBZfqE$7(mk4)9;hP$l;*n1l{{*~XJ1CP`b z>)7r2pdL-Bg5K)}!m%!jJw4_E$b2y0p4-_OBILpcrL}HqzdiS#8Xwd@S0jec5X~v= zhf1~&>OF;hhDe(tN$bh%e^RjaOT`(YIW_zV-Em$hd{Ez3oU-AU3Ln&;*K~iWU!NiR zVV(A4{mvxKH0782xx!rdorDRcejz;|*{EatzNMjqHuv~<<{CfIP_qx~e9>m#o#Wim zxbMz!?nsZzh>wYLM|<%x@!dJjBMCn1_rh5t=H|5zXUlKU$tDi0DHy9x>ybKA`<6Ht z#Mpz4J(Bl+>_>{d80e+5hGuZQi#<8UC+%To8f#?Wmxb{2;>~@TIK?_gDTUio~ zb$#)Q^=Q0*&-*|dl{zlxBctnrJ_#(`@RLgF_~zfC({Rz_ogB)r?}}X}jXl}m5#jb7 zANFS%N!rV8~q(R<_sHe zqZ5yX&`K7`9eon(^w=k(4&vl<6L%jG2OGoRp@SY7`3_wt%NyUJ>!gm-LvLQg$`t2! zBJk(HkM|?GDuaJA!ss7gWUPDH!`A$v5^eT}N*?8t7Jpyqi1;Wiw~u#T;_pcX-a-%I zU!ni(3xY=BT!!`$hcEe#Q?KLbnEvLBS&0W^LN}zxdt9Mo=+QBC`hBGEgS3BZpq4@T z8*scXVe+WE#Bu5K5vxoCUAJgAI@l|Y@%bR@ z1jiE`+ZOL9j!PfVLE_lwnDzt5w%xYJ@$AO8!EF2VyjHE9Y3(s>#HN(?*lDG$A*?&w zW9OS6d0~^84~^PA+gs?JkNtHVyXUxh6UXjCFT2&)caH_+m-ZdoW?Sj{%(E^pMM53~c_d2Cj=vG-t1bU7b-;=^-_z1m~T;4y?cLLT1PXy-Cp+2eWHSjzbv6XQSo zAUhrJ+jlxTy$QZD2KR)oVjlJ@!t z6MXHj{c_(%e};Wh_)zVaW!MhB>@(U7U*o<#H^EmLSgRc7_~^d_S{sSSCsQF^&4YAw!xn7?AQD&rMy3+I+Jd%4UxfnW)Wttg1v`6 z)}4d-F{dMcT4me$R~^s1J^zX}h0?=!vs8N?Zb0(XQBQjJ=%jDG{dKtclfTRTJk1*heGG95(j;%6B43 zi}4Z}L%zXxBFQg(O2ny$^)cft>uf!f&Lr2Pyr!?)*24-fjFw-&7-p&o2Fu()voa-{?by@95_2lKLg#H=0wJ z3m=ZpEJJ)6_i18FyBmxdfI_qc!Q<9Mzqd`_e1n*6tp)a|GAXyvm_oNGGB zzjb7v)8vfP&dxYtpE$pQ?LH#gsQ0%I^~+P!z|e?Q+{Ohi34AP-|r zj0^F;Z<*&X`Q?~GIea9fYd&0gZooJ|J$?_Je9J_;5BiJ~bw!)Df5yq@iWsA$7~TA> z&Kp~vaVp0(%DB!b&p3>q=(zawE2Y(EoNnCpjFaXO6=Uj*`X}a0H?B*EIgNM58N*3u ze~11cE#B4N{TU~8MxDDo8l?P~Oof6-?V(6RlE&=4=noL2l; z?FaVXoPz$CXz}&on>?m@M}#{Hdyg)@!NUB7Im9uQfq#Uj6%JqDu@rt4`jfL$tOI@i zZ0Ic2+sp&m??QIk=d)D&og*;9eiwY8QTXi^Z(y)JGhj^8KeV;0vgGT%myxqn{-&_& z&>n2A10M2gj+~|PIVsNDaIT8^CH46H4c{QXe-CG=V!y9nuOq@2PP@ZaOlKXiCTVR& z>iPL)-cCEYk%_qS6mOqRr;!d8+PLSxQKrm0=(AMDvS1{6<1Cf?R?Vo>{QXMetQ+hz zqYUx(ObX$^&(>$DT*tVxRPI~!za(RpBb>$6ngmi`X?<#!fTuEi~3c!OVLr!BITl=)blrJCS&g4@k- zJD=_8-p^7E+d*tO<2iOC!eIyF9j!q-sAp9)u`g(wcQ4yPpM42C*xDEDpvS=aJ+sCR z5@&u$KK6)s_#0gQVC*3E4ckGmWIq{WerX4dr@fw~n&4^sB5u!V4LA-uV<;Yi>Fvu4UTur8M!s)X`QkAd4IVk@cNZGIeTlHuLB}~r)R15a~~;26>9<3xLzx;$K-Xy-PQ%v zH*P#E{a}oTvA@}_6^KiE4p-McxhlkkJ# zC!_59x_y4CuP_(Bu^BPKH^#U7xE|B5{a}2%kFwi8Qx#qy|2?9*nsjDqb@AfWrIpKP zm%jDd)n`|hS60`O{9&fkbG|Rvb8jvBlX)pu)l|HuHqufw5LeDOwWJx&|H|Uk3(Lz( z7q@$EYWx@dZtm)vemnh$AHFNjQPHyDZ}`PIs&q<^`M@{)@PR39No_5`@Qz)gD&*aPemb?LcOJs&S$mt{H(Ul(=7nA!TePD>v?@6?`!<4o2t)Z8z1-P^Uxzwdf0ymvOcXA5^?I9%o31%PuLcFPb=W*iHC?_`7Ed*W+FB z6S*$pM{8dX@50fSv-Mu>tn?~-l*@!3PGr9LZX74veya#AMtPMB}^b6wbA@9krw=Os(L=Vu>O z?B!F3WRlH}@1hwm$dx3MpEFHrvW#vEaq`*2a{oDvbYybe&y3?`p32tpc2w8koL=hb zwf><9LlfoW`v`45^58#0ChG1e%y%_Mzwg(|n`xkHcm|L0J}LSETFM(QG1l!3FSH3< z4C94$73t)ccmY2pUP1=?LZZOOXBIA$-FaEs&cNg2n`hyF&_rp5&{qR%(FYvW#14f~`9?dW)))PK(yBQNZ5 zU@Ul#e&5fyEqzk0d^{`L-|GOk(3iNiPinVzF4mIZe$*#r+y+L&&XsFV+qsZ|z6?wf zH*QG_-EZcTN_rMs+!4hw;0#gM>$YQiFKf zy-#ZVxH`cT`0-fH`yuxho<@CAgLvA#PilgvmlVToU&igmF zPt6>@w$p>_SI_Co^{dku54M{3mw6gXG+y9a>1a$CT)z&k10uh*C5OKbz`xY40|v*x zct74gsCN8|vskRDgFbvzeYLe6|6MdNk=8b584` z8Zqpj`nu9K^G`_^3IEh_DU0hdl$IWla!#iw6+WZ;J2@@eqG~#PQ&NHerx%z zj(tagt;oj*LVUY^EO0}c@l&w}S@O5<3VselL)?BWwXbshyYXY86Xr8+i{=lFo_6BL zqObRIZc9Vww$5$g@3K#f{c+95uG7c%iP4_YCq~>p4&JR7t@Iq{7wi)=E{Jz?e#{Yx z(Cny(FJbtcR=bBbCECPY!5@0`!X5l0zkL3nsmt_SCgSn@Ils*FnnEJePm+l? z_qpML)o! z#7p>=_J$YQ1TH);w~oEOAYXcFe5ipR`XcZWGW3hS3Eu*Ep}(VTu_hpHd=Q2=m{FY{ zTb~%XWj#~+#8OHXIA$GN;us$im~L@AdQL0!H|i7fGrrl{*yD)v(Ie|#`m4n4;9B@d zmVG+y%qM2tmOimoKAx5B@3ny2pl|J5_@j1@+wJ+ateRzj|J;eTHo79Uq?en;&#uzcRNu2%p$~t^@278#n$9`@}qF z8|M>a{Iid2M&l!KEx2nv$C(G#v*_7tSaic$fHm%BKC-RW1tH()c(|F5jJjDDvo;*} zy~5DJ!TZ7R3+}`ZhFti;^zEX2eWw1^+BaXE&*JLFPZAdMCH?11m>jq^bde_9C;ncQ z`X!w^|IF&@g}0RW@|Qo__-gYl#FLruK~ba9cxQsnCS$z_x^=IT8?lz3_|erhP*8;Vy$@%M(}w+7Kex~(1r<-420%yMdQP)Xb^tmtk%wf*ArJ0X5 zKH50b^wa+(Q)%>h?kBPT-wJDwHrFwue_v7fitg{{v`(6l@6u`iBd3K=(rJIVvbuap z{Wc@Yj{ZaT{kSsU7cRfCxVpCZ(&~lFYgbm*7E@0B+S1~moLgP_<2Y-V-dOv_?|tKm z?Q8z#&EJUT_Vl!@6P7-D^1o{OIRZaJ)WKywQk(sg#O4l^cn>+rN}MfaATFMI>H zv_@OkT}6C4$2I@1+^42wHujA7>j*lnuJ2}q#=I|3La(V3;*YV&l zJ6gwszxJ`}_-v-_YJ)obd>#W$~BTU}Hd_1h+y_SY2H>^vegl(GGC zna?Wl)r%LGudV6aWy?O(s?VB>_C28cx!0H0N>X@AvCRdY>~0!3=cqVz*^S3?v2O1A)R6AYlN+5O**!xB%H9K#a|w z%nZyxx3YlU4^%=iC@YRPjyGWN%}g%JFV0UZQP2p|RPgW#_E8AVNK8pBQgF`CEy&CP zG7Js$3>j7jGBB{RFz_#6U}&&Mwp closestDepth ? 1.0 : 0.0; + } + } + + shadowFactor /= 9.0; + + if (projectionCoords.z > 1.0) + { + shadowFactor = 1.0; + } + + return shadowFactor; + + + // float currentDepth = projectionCoords.z; + + // if (currentDepth > 1.0) + // { + // return 0.0; + // } + + // if (currentDepth - bias > closestDepth) + // { + // return 1.0; + // } + // else + // { + // return 0.0; + // } +} + float3 ComputeLight( - float3 lightDir, + float3 L, float3 radiance, float3 F0, float3 V, float3 N, float3 albedo, float metallic, - float roughness + float roughness, + float shadow ) { - float3 L = normalize(lightDir); float3 H = normalize(V + L); float NDF = DistributionGGX(N, H, roughness); @@ -98,7 +230,7 @@ float3 ComputeLight( kD *= 1.0 - metallic; float NdotL = max(dot(N, L), 0.0); - return (kD * albedo / PI + specular) * radiance * NdotL; + return (kD * albedo / PI + specular) * radiance * NdotL * shadow; } float4 ComputeColor( @@ -120,21 +252,20 @@ float4 ComputeColor( for (int i = 0; i < MAX_POINT_LIGHTS; i++) { float3 lightDir = PointLightPositions[i] - worldPosition; + float3 L = normalize(lightDir); 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); + Lo += ComputeLight(L, radiance, F0, V, N, albedo, metallic, roughness, 1.0); } // directional light - for (int i = 0; i < MAX_DIRECTIONAL_LIGHTS; i++) - { - float3 lightDir = DirectionalLightDirections[i]; - float3 radiance = DirectionalLightColors[i]; + float3 L = normalize(DirectionalLightDirection); + float3 radiance = DirectionalLightColor; - Lo += ComputeLight(lightDir, radiance, F0, V, N, albedo, metallic, roughness); - } + float shadow = ComputeShadow(worldPosition, N, L); + Lo += ComputeLight(L, radiance, F0, V, N, albedo, metallic, roughness, (1.0 - shadow)); float3 ambient = float3(0.03, 0.03, 0.03) * albedo; // * AO; float3 color = ambient + Lo; @@ -148,13 +279,13 @@ float4 ComputeColor( float4 main_ps(PixelInput input) : SV_TARGET0 { - float3 fragPosition = SAMPLE_TEXTURE(gPosition, input.TexCoord).rgb; + 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( - fragPosition, + worldPosition, normal, albedo, metallicRoughness.r, @@ -166,6 +297,7 @@ Technique DeferredPBR { Pass { + VertexShader = compile vs_3_0 main_vs(); PixelShader = compile ps_3_0 main_ps(); } } 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..0982521 --- /dev/null +++ b/Effects/HLSL/DeferredPBR_DirectionalLightEffect.fx @@ -0,0 +1,252 @@ +#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); + + float ShadowMapSize _ps(c7) _cb(c7); + +MATRIX_CONSTANTS + + float4x4 LightSpaceMatrixOne _ps(c8) _cb(c8); + float4x4 LightSpaceMatrixTwo _ps(c12) _cb(c12); + float4x4 LightSpaceMatrixThree _ps(c16) _cb(c16); + float4x4 LightSpaceMatrixFour _ps(c20) _cb(c20); + + // used to select shadow cascade + float4x4 ViewMatrix _ps(c24) _cb(c24); + +END_CONSTANTS + +static float2 poissonDisk[16] = +{ + float2( -0.94201624, -0.39906216 ), + float2( 0.94558609, -0.76890725 ), + float2( -0.094184101, -0.92938870 ), + float2( 0.34495938, 0.29387760 ), + float2( -0.91588581, 0.45771432 ), + float2( -0.81544232, -0.87912464 ), + float2( -0.38277543, 0.27676845 ), + float2( 0.97484398, 0.75648379 ), + float2( 0.44323325, -0.97511554 ), + float2( 0.53742981, -0.47373420 ), + float2( -0.26496911, -0.41893023 ), + float2( 0.79197514, 0.19090188 ), + float2( -0.24188840, 0.99706507 ), + float2( -0.81409955, 0.91437590 ), + float2( 0.19984126, 0.78641367 ), + float2( 0.14383161, -0.14100790 ) +}; + +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 + +// Returns a random number based on a vec3 and an int. +float random(float3 seed, int i){ + float4 seed4 = float4(seed, i); + float dot_product = dot(seed4, float4(12.9898,78.233,45.164,94.673)); + return frac(sin(dot_product) * 43758.5453); +} + +float PoissonCoord(sampler shadowMap, float3 worldPosition, float2 texCoord, float fragmentDepth, float bias) +{ + float visibility = 1.0; + + for (int i = 0; i < 16; i++) + { + int index = int(16.0 * random(floor(worldPosition * 1000.0), i)) % 16; + + if (tex2D(shadowMap, texCoord + poissonDisk[index] / 1024.0).r < fragmentDepth - bias) + { + visibility -= 0.05; + } + } + + return visibility; +} + +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 + + if (projectionCoords.z > 1.0) + { + return 1.0; + } + + float inc = 1.0 / ShadowMapSize; // TODO: shadow map size uniform + + // PCF + Poisson soft shadows + float visibility = 0.0; + // for (int row = -1; row <= 1; row++) + // { + // for (int col = -1; col <= 1; col++) + // { + // if (shadowCascadeIndex == 0) + // { + // visibility += PoissonCoord(SAMPLER(shadowMapOne), positionWorldSpace, projectionCoords.xy + float2(row, col) * inc, projectionCoords.z, bias); + // } + // else if (shadowCascadeIndex == 1) + // { + // visibility += PoissonCoord(SAMPLER(shadowMapTwo), positionWorldSpace, projectionCoords.xy + float2(row, col) * inc, projectionCoords.z, bias); + // } + // else if (shadowCascadeIndex == 2) + // { + // visibility += PoissonCoord(SAMPLER(shadowMapThree), positionWorldSpace, projectionCoords.xy + float2(row, col) * inc, projectionCoords.z, bias); + // } + // else + // { + // visibility += PoissonCoord(SAMPLER(shadowMapFour), positionWorldSpace, projectionCoords.xy + float2(row, col) * inc, projectionCoords.z, bias); + // } + // } + // } + + // visibility /= 9.0; + + if (shadowCascadeIndex == 0) + { + visibility = PoissonCoord(SAMPLER(shadowMapOne), positionWorldSpace, projectionCoords.xy, projectionCoords.z, bias); + } + else if (shadowCascadeIndex == 1) + { + visibility = PoissonCoord(SAMPLER(shadowMapTwo), positionWorldSpace, projectionCoords.xy, projectionCoords.z, bias); + } + else if (shadowCascadeIndex == 2) + { + visibility = PoissonCoord(SAMPLER(shadowMapThree), positionWorldSpace, projectionCoords.xy, projectionCoords.z, bias); + } + else + { + visibility = PoissonCoord(SAMPLER(shadowMapFour), positionWorldSpace, projectionCoords.xy, projectionCoords.z, bias); + } + + return visibility; +} + +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, 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..e57b391 --- /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 visibility +) { + 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 * visibility; +} diff --git a/Effects/HLSL/Macros.fxh b/Effects/HLSL/Macros.fxh index 91d1702..c0aaaac 100644 --- a/Effects/HLSL/Macros.fxh +++ b/Effects/HLSL/Macros.fxh @@ -54,4 +54,5 @@ #define SAMPLE_TEXTURE(Name, texCoord) tex2D(Name##Sampler, texCoord) #define SAMPLE_CUBEMAP(Name, texCoord) texCUBE(Name##Sampler, texCoord) #define SAMPLE_CUBEMAP_LOD(Name, texCoord) texCUBElod(Name##Sampler, texCoord) +#define SAMPLER(Name) Name##Sampler #endif diff --git a/Effects/HLSL/SimpleDepthEffect.fx b/Effects/HLSL/SimpleDepthEffect.fx index 8b19a4e..3598eb5 100644 --- a/Effects/HLSL/SimpleDepthEffect.fx +++ b/Effects/HLSL/SimpleDepthEffect.fx @@ -16,21 +16,29 @@ struct VertexShaderInput struct VertexShaderOutput { float4 Position : SV_Position; + float Depth : TEXCOORD0; }; VertexShaderOutput main_vs(VertexShaderInput input) { VertexShaderOutput output; - output.Position = mul(float4(input.Position.xyz, 1.0), ModelViewProjection); + output.Position = mul(input.Position, ModelViewProjection); + output.Depth = output.Position.z; return output; } +float4 main_ps(VertexShaderOutput input) : SV_TARGET0 +{ + return float4(input.Depth, 0.0, 0.0, 0.0); +} + Technique SimpleDepth { Pass { VertexShader = compile vs_3_0 main_vs(); + PixelShader = compile ps_3_0 main_ps(); } } diff --git a/Effects/HLSL/ToneMapEffect.fx b/Effects/HLSL/ToneMapEffect.fx new file mode 100644 index 0000000..281b714 --- /dev/null +++ b/Effects/HLSL/ToneMapEffect.fx @@ -0,0 +1,20 @@ +sampler TextureSampler : register(s0); + +float4 main_ps(float2 texCoord : TEXCOORD0) : COLOR0 +{ + float3 color = tex2D(TextureSampler, texCoord).xyz; + + 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); +} + +Technique DeferredPBR +{ + Pass + { + PixelShader = compile ps_3_0 main_ps(); + } +} diff --git a/Effects/PBREffect.cs b/Effects/PBREffect.cs index db04a25..2df74cd 100644 --- a/Effects/PBREffect.cs +++ b/Effects/PBREffect.cs @@ -3,7 +3,7 @@ using Microsoft.Xna.Framework.Graphics; namespace Kav { - public class PBREffect : Effect, TransformEffect, PointLightEffect, DirectionalLightEffect + public class PBREffect : Effect, TransformEffect, PointLightEffect { EffectParameter worldParam; EffectParameter worldViewProjectionParam; @@ -31,7 +31,6 @@ namespace Kav Matrix view = Matrix.Identity; Matrix projection = Matrix.Identity; PointLightCollection pointLightCollection; - DirectionalLightCollection directionalLightCollection; Vector3 albedo; float metallic; @@ -84,14 +83,6 @@ namespace Kav private set { pointLightCollection = value; } } - public int MaxDirectionalLights { get; } = 4; - - public DirectionalLightCollection DirectionalLights - { - get { return directionalLightCollection; } - private set { directionalLightCollection = value; } - } - public Vector3 Albedo { get { return albedo; } @@ -204,11 +195,6 @@ namespace Kav Parameters["PositionLightColors"], MaxPointLights ); - - directionalLightCollection = new DirectionalLightCollection( - Parameters["LightDirections"], - Parameters["DirectionLightColors"] - ); } protected PBREffect(PBREffect cloneSource) : base(cloneSource) @@ -230,16 +216,6 @@ namespace Kav PointLights[i] = cloneSource.PointLights[i]; } - DirectionalLights = new DirectionalLightCollection( - Parameters["LightDirections"], - Parameters["DirectionLightColors"] - ); - - for (int i = 0; i < MaxDirectionalLights; i++) - { - DirectionalLights[i] = cloneSource.DirectionalLights[i]; - } - AlbedoTexture = cloneSource.AlbedoTexture; NormalTexture = cloneSource.NormalTexture; EmissionTexture = cloneSource.EmissionTexture; diff --git a/Effects/SimpleDepthEffect.cs b/Effects/SimpleDepthEffect.cs index f68f513..1854e3c 100644 --- a/Effects/SimpleDepthEffect.cs +++ b/Effects/SimpleDepthEffect.cs @@ -42,6 +42,7 @@ namespace Kav dirtyFlags |= EffectDirtyFlags.WorldViewProj; } } + public SimpleDepthEffect(GraphicsDevice graphicsDevice) : base(graphicsDevice, Resources.SimpleDepthEffect) { CacheEffectParameters(); @@ -51,8 +52,8 @@ namespace Kav { if ((dirtyFlags & EffectDirtyFlags.WorldViewProj) != 0) { - Matrix.Multiply(ref model, ref view, out Matrix modelView); - Matrix.Multiply(ref modelView, ref projection, out Matrix worldViewProj); + Matrix.Multiply(ref view, ref projection, out Matrix viewProjection); + Matrix.Multiply(ref model, ref viewProjection, out Matrix worldViewProj); modelViewProjectionParam.SetValue(worldViewProj); diff --git a/Kav.Core.csproj b/Kav.Core.csproj index 7957739..327d7bb 100644 --- a/Kav.Core.csproj +++ b/Kav.Core.csproj @@ -15,9 +15,21 @@ + + Kav.Resources.DeferredPBR_AmbientLightEffect.fxb + + + Kav.Resources.DeferredPBR_PointLightEffect.fxb + + + Kav.Resources.DeferredPBR_DirectionalLightEffect.fxb + Kav.Resources.DeferredPBR_GBufferEffect.fxb + + Kav.Resources.ToneMapEffect.fxb + Kav.Resources.DeferredPBREffect.fxb diff --git a/Kav.Framework.csproj b/Kav.Framework.csproj index 0557675..2b2dd4f 100644 --- a/Kav.Framework.csproj +++ b/Kav.Framework.csproj @@ -15,9 +15,21 @@ + + Kav.Resources.DeferredPBR_AmbientLightEffect.fxb + + + Kav.Resources.DeferredPBR_PointLightEffect.fxb + + + Kav.Resources.DeferredPBR_DirectionalLightEffect.fxb + Kav.Resources.DeferredPBR_GBufferEffect.fxb + + Kav.Resources.ToneMapEffect.fxb + Kav.Resources.DeferredPBREffect.fxb diff --git a/Lights/DirectionalLight.cs b/Lights/DirectionalLight.cs index 31ca4fd..a22ab33 100644 --- a/Lights/DirectionalLight.cs +++ b/Lights/DirectionalLight.cs @@ -12,7 +12,7 @@ namespace Kav { get { - return Matrix.CreateLookAt(-Direction * 100f, Vector3.Zero, Vector3.Up); + return Matrix.CreateLookAt(Direction * 100f, Vector3.Zero, Vector3.Up); } } diff --git a/README.md b/README.md new file mode 100644 index 0000000..8f3190b --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# Kav + +A 3D renderer built on top of FNA. + +## Roadmap + +Essential + +- [x] PBR shading +- [x] Deferred rendering +- [x] Point lighting +- [x] Directional lighting +- [x] Directional shadow maps +- [x] Cascading shadow maps +- [x] Tone map shader +- [x] Poisson soft shadowing +- [ ] Frustum culling +- [ ] Shadow-casting point lights +- [ ] Parabolic lights +- [ ] Skyboxes +- [ ] Screen-space reflection + +Nice-To-Haves + +- [ ] Anti-aliasing +- [ ] Image-based lighting +- [ ] Volumetric lighting +- [ ] Volumetric smoke +- [ ] diff --git a/Renderer.cs b/Renderer.cs index c53f855..63b34e5 100644 --- a/Renderer.cs +++ b/Renderer.cs @@ -6,38 +6,84 @@ namespace Kav { public class Renderer { + private const int MAX_SHADOW_CASCADES = 4; + private int ShadowMapSize { get; } + private GraphicsDevice GraphicsDevice { get; } private int RenderDimensionsX { get; } private int RenderDimensionsY { get; } - private RenderTarget2D DepthRenderTarget { 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 Effect ToneMapEffect { 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 DeferredPBREffect DeferredPBREffect { get; } - private SpriteBatch SpriteBatch { get; } - public Renderer(GraphicsDevice graphicsDevice, int renderDimensionsX, int renderDimensionsY) - { + public Renderer( + GraphicsDevice graphicsDevice, + int renderDimensionsX, + int renderDimensionsY, + int numShadowCascades, + int shadowMapSize + ) { GraphicsDevice = graphicsDevice; RenderDimensionsX = renderDimensionsX; RenderDimensionsY = renderDimensionsY; - DepthRenderTarget = new RenderTarget2D( - GraphicsDevice, + ShadowMapSize = shadowMapSize; + + NumShadowCascades = (int)MathHelper.Clamp(numShadowCascades, 1, MAX_SHADOW_CASCADES); + ShadowRenderTargets = new RenderTarget2D[numShadowCascades]; + + for (var i = 0; i < numShadowCascades; i++) + { + ShadowRenderTargets[i] = new RenderTarget2D( + GraphicsDevice, + ShadowMapSize, + ShadowMapSize, + false, + SurfaceFormat.Single, + DepthFormat.Depth24 + ); + } + + ColorRenderTarget = new RenderTarget2D( + graphicsDevice, renderDimensionsX, renderDimensionsY, false, - SurfaceFormat.HalfSingle, // unused - DepthFormat.Depth24 + SurfaceFormat.Color, + DepthFormat.None, + 0, + RenderTargetUsage.PreserveContents + ); + + DirectionalRenderTarget = new RenderTarget2D( + graphicsDevice, + renderDimensionsX, + renderDimensionsY, + false, + SurfaceFormat.Color, + DepthFormat.None, + 0, + RenderTargetUsage.PreserveContents ); gPosition = new RenderTarget2D( @@ -82,29 +128,33 @@ 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); + DeferredDirectionalLightEffect.ShadowMapSize = ShadowMapSize; + ToneMapEffect = new Effect(graphicsDevice, Resources.ToneMapEffect); - SpriteBatch = new SpriteBatch(GraphicsDevice); + FullscreenTriangle = new VertexBuffer(GraphicsDevice, typeof(VertexPositionTexture), 3, BufferUsage.WriteOnly); + FullscreenTriangle.SetData(new VertexPositionTexture[3] { + 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( - Camera camera, + PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, IEnumerable pointLights, - IEnumerable directionalLights + DirectionalLight directionalLight ) { + // g-buffer pass + GraphicsDevice.SetRenderTargets(GBuffer); GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1f, 0); GraphicsDevice.DepthStencilState = DepthStencilState.Default; @@ -143,53 +193,186 @@ 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.EyePosition = Matrix.Invert(camera.View).Translation; + DeferredAmbientLightEffect.GPosition = gPosition; + DeferredAmbientLightEffect.GAlbedo = gAlbedo; + + foreach (var pass in DeferredAmbientLightEffect.CurrentTechnique.Passes) + { + pass.Apply(); + GraphicsDevice.SetVertexBuffer(FullscreenTriangle); + GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1); + } + + DeferredPointLightEffect.EyePosition = camera.Position; - int i = 0; foreach (var pointLight in pointLights) { - if (i > DeferredPBREffect.MaxPointLights) { break; } - DeferredPBREffect.PointLights[i] = pointLight; - i++; + PointLightRender(pointLight); } - i = 0; - foreach (var directionalLight in directionalLights) - { - if (i > DeferredPBREffect.MaxDirectionalLights) { break; } - DeferredPBREffect.DirectionalLights[i] = directionalLight; - i++; - } + DirectionalLightRender(camera, modelTransforms, directionalLight); + // return; + // GraphicsDevice.SetRenderTarget(null); + // SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied); + // SpriteBatch.Draw(DirectionalRenderTarget, Vector2.Zero, Color.White); + // SpriteBatch.End(); - SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, null, null, DeferredPBREffect); - SpriteBatch.Draw(deferredRenderTarget, Vector2.Zero, Color.White); + GraphicsDevice.SetRenderTarget(null); + GraphicsDevice.Clear(Color.Black); + SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, null, null, null, ToneMapEffect); + SpriteBatch.Draw(ColorRenderTarget, Vector2.Zero, Color.White); SpriteBatch.End(); } - public void Render( - Camera camera, - IEnumerable<(Model, Matrix)> modelTransforms, - IEnumerable pointLights, - IEnumerable directionalLights - ) { - Render(camera.View, camera.Projection, modelTransforms, pointLights, directionalLights); + 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); + GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1); + } } - // for shadow mapping - public void DepthRender(IEnumerable<(Model, Matrix)> modelTransforms, DirectionalLight directionalLight) - { - GraphicsDevice.SetRenderTarget(DepthRenderTarget); - GraphicsDevice.Clear(ClearOptions.DepthBuffer, Color.Black, 1, 0); + private void DirectionalLightRender( + PerspectiveCamera camera, + IEnumerable<(Model, Matrix)> modelTransforms, + DirectionalLight directionalLight + ) { + // render the individual shadow cascades + var previousFarPlane = camera.NearPlane; + for (var i = 0; i < NumShadowCascades; i++) + { + var farPlane = camera.FarPlane / (MathHelper.Max((NumShadowCascades - i - 1) * 2f, 1f)); - SimpleDepthEffect.View = directionalLight.View; - SimpleDepthEffect.Projection = directionalLight.Projection; + // divide the view frustum + var shadowCamera = new PerspectiveCamera( + camera.Position, + camera.Forward, + camera.Up, + camera.FieldOfView, + camera.AspectRatio, + previousFarPlane, + 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( + PerspectiveCamera camera, + IEnumerable<(Model, Matrix)> modelTransforms, + DirectionalLight directionalLight, + int shadowCascadeIndex + ) { + GraphicsDevice.SetRenderTarget(ShadowRenderTargets[shadowCascadeIndex]); + GraphicsDevice.Clear(Color.White); + GraphicsDevice.DepthStencilState = DepthStencilState.Default; + GraphicsDevice.BlendState = BlendState.Opaque; + + var cameraBoundingFrustum = new BoundingFrustum(camera.View * camera.Projection); + Vector3[] frustumCorners = cameraBoundingFrustum.GetCorners(); + + Vector3 frustumCenter = Vector3.Zero; + for (var i = 0; i < frustumCorners.Length; i++) + { + frustumCenter += frustumCorners[i]; + } + frustumCenter /= 8f; + + var lightView = Matrix.CreateLookAt(frustumCenter + directionalLight.Direction, frustumCenter, Vector3.Backward); + + for (var i = 0; i < frustumCorners.Length; i++) + { + frustumCorners[i] = Vector3.Transform(frustumCorners[i], lightView); + } + + BoundingBox lightBox = BoundingBox.CreateFromPoints(frustumCorners); + + SimpleDepthEffect.View = lightView; + SimpleDepthEffect.Projection = Matrix.CreateOrthographicOffCenter( + lightBox.Min.X, + lightBox.Max.X, + lightBox.Min.Y, + lightBox.Max.Y, + -lightBox.Max.Z - 10f, // TODO: near clip plane needs scene AABB info to get rid of this magic value + -lightBox.Min.Z + ); + + var lightSpaceMatrix = SimpleDepthEffect.View * SimpleDepthEffect.Projection; + + if (shadowCascadeIndex == 0) + { + DeferredDirectionalLightEffect.LightSpaceMatrixOne = lightSpaceMatrix; + } + else if (shadowCascadeIndex == 1) + { + DeferredDirectionalLightEffect.LightSpaceMatrixTwo = lightSpaceMatrix; + } + else if (shadowCascadeIndex == 2) + { + DeferredDirectionalLightEffect.LightSpaceMatrixThree = lightSpaceMatrix; + } + else if (shadowCascadeIndex == 3) + { + DeferredDirectionalLightEffect.LightSpaceMatrixFour = lightSpaceMatrix; + } + + DeferredDirectionalLightEffect.CascadeFarPlanes[shadowCascadeIndex] = camera.FarPlane; foreach (var (model, transform) in modelTransforms) { @@ -220,6 +403,15 @@ namespace Kav } } + public void Render( + PerspectiveCamera camera, + IEnumerable<(Model, Matrix)> modelTransforms, + IEnumerable pointLights, + IEnumerable directionalLights + ) { + Render(camera.View, camera.Projection, modelTransforms, pointLights, directionalLights); + } + private void Render( Matrix view, Matrix projection, @@ -254,17 +446,6 @@ namespace Kav } } - if (meshPart.Effect is DirectionalLightEffect directionalLightEffect) - { - int i = 0; - foreach (var directionalLight in directionalLights) - { - if (i > directionalLightEffect.MaxDirectionalLights) { break; } - directionalLightEffect.DirectionalLights[i] = directionalLight; - i++; - } - } - foreach (var pass in meshPart.Effect.CurrentTechnique.Passes) { pass.Apply(); diff --git a/Resources.cs b/Resources.cs index 83d4b04..7791eaa 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 @@ -16,6 +51,18 @@ namespace Kav } } + public static byte[] ToneMapEffect + { + get + { + if (toneMapEffect == null) + { + toneMapEffect = GetResource("ToneMapEffect"); + } + return toneMapEffect; + } + } + public static byte[] DeferredPBREffect { get @@ -52,7 +99,11 @@ namespace Kav } } + private static byte[] ambientLightEffect; + private static byte[] pointLightEffect; + private static byte[] directionalLightEffect; private static byte[] gBufferEffect; + private static byte[] toneMapEffect; private static byte[] deferredPBREffect; private static byte[] pbrEffect; private static byte[] simpleDepthEffect;