diff --git a/Effects/DeferredPBREffect.cs b/Effects/DeferredPBREffect.cs
new file mode 100644
index 0000000..97c8615
--- /dev/null
+++ b/Effects/DeferredPBREffect.cs
@@ -0,0 +1,112 @@
+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;
+        DirectionalLightCollection directionalLightCollection;
+
+        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 int MaxDirectionalLights { get; } = 4;
+
+        public DirectionalLightCollection DirectionalLights
+        {
+            get { return directionalLightCollection; }
+            private set { directionalLightCollection = value; }
+        }
+
+        public DeferredPBREffect(GraphicsDevice graphicsDevice) : base(graphicsDevice, Resources.DeferredPBREffect)
+        {
+            CacheEffectParameters();
+
+            pointLightCollection = new PointLightCollection(
+                Parameters["PointLightPositions"],
+                Parameters["PointLightColors"],
+                MaxPointLights
+            );
+
+            DirectionalLights = new DirectionalLightCollection(
+                Parameters["DirectionalLightDirections"],
+                Parameters["DirectionalLightColors"]
+            );
+        }
+
+        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];
+            }
+
+            DirectionalLights = new DirectionalLightCollection(
+                Parameters["DirectionalLightDirections"],
+                Parameters["DirectionalLightColors"]
+            );
+
+            for (int i = 0; i < MaxDirectionalLights; i++)
+            {
+                DirectionalLights[i] = cloneSource.DirectionalLights[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/DeferredPBR_GBufferEffect.cs b/Effects/DeferredPBR_GBufferEffect.cs
new file mode 100644
index 0000000..26a9c8f
--- /dev/null
+++ b/Effects/DeferredPBR_GBufferEffect.cs
@@ -0,0 +1,241 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Kav
+{
+    public class DeferredPBR_GBufferEffect : Effect, TransformEffect
+    {
+        EffectParameter worldParam;
+        EffectParameter worldViewProjectionParam;
+        EffectParameter worldInverseTransposeParam;
+
+        EffectParameter albedoTextureParam;
+        EffectParameter normalTextureParam;
+        EffectParameter metallicRoughnessTextureParam;
+
+        EffectParameter albedoParam;
+        EffectParameter metallicParam;
+        EffectParameter roughnessParam;
+
+        EffectParameter shaderIndexParam;
+
+        Matrix world = Matrix.Identity;
+        Matrix view = Matrix.Identity;
+        Matrix projection = Matrix.Identity;
+
+        Vector3 albedo;
+        float metallic;
+        float roughness;
+
+        bool albedoTextureEnabled = false;
+        bool metallicRoughnessMapEnabled = false;
+        bool normalMapEnabled = false;
+
+        EffectDirtyFlags dirtyFlags = EffectDirtyFlags.All;
+
+        public Matrix World
+        {
+            get { return world; }
+            set
+            {
+                world = value;
+                dirtyFlags |= EffectDirtyFlags.World | EffectDirtyFlags.WorldViewProj;
+            }
+        }
+
+        public Matrix View
+        {
+            get { return view; }
+            set
+            {
+                view = value;
+                dirtyFlags |= EffectDirtyFlags.WorldViewProj | EffectDirtyFlags.EyePosition;
+            }
+        }
+
+        public Matrix Projection
+        {
+            get { return projection; }
+            set
+            {
+                projection = value;
+                dirtyFlags |= EffectDirtyFlags.WorldViewProj;
+            }
+        }
+
+        public Vector3 Albedo
+        {
+            get { return albedo; }
+            set
+            {
+                albedo = value;
+                albedoParam.SetValue(albedo);
+            }
+        }
+
+        public float Metallic
+        {
+            get { return metallic; }
+            set
+            {
+                metallic = value;
+                metallicParam.SetValue(metallic);
+            }
+        }
+
+        public float Roughness
+        {
+            get { return roughness; }
+            set
+            {
+                roughness = value;
+                roughnessParam.SetValue(roughness);
+            }
+        }
+
+        public Texture2D AlbedoTexture
+        {
+            get { return albedoTextureParam.GetValueTexture2D(); }
+            set
+            {
+                albedoTextureParam.SetValue(value);
+                albedoTextureEnabled = value != null;
+                dirtyFlags |= EffectDirtyFlags.ShaderIndex;
+            }
+        }
+
+        public Texture2D NormalTexture
+        {
+            get { return normalTextureParam.GetValueTexture2D(); }
+            set
+            {
+                normalTextureParam.SetValue(value);
+                normalMapEnabled = value != null;
+                dirtyFlags |= EffectDirtyFlags.ShaderIndex;
+            }
+        }
+
+        public Texture2D MetallicRoughnessTexture
+        {
+            get { return metallicRoughnessTextureParam.GetValueTexture2D(); }
+            set
+            {
+                metallicRoughnessTextureParam.SetValue(value);
+                metallicRoughnessMapEnabled = value != null;
+                dirtyFlags |= EffectDirtyFlags.ShaderIndex;
+            }
+        }
+
+        public DeferredPBR_GBufferEffect(GraphicsDevice graphicsDevice) : base(graphicsDevice, Resources.DeferredPBR_GBufferEffect)
+        {
+            CacheEffectParameters();
+        }
+
+        protected DeferredPBR_GBufferEffect(DeferredPBR_GBufferEffect cloneSource) : base(cloneSource)
+        {
+            CacheEffectParameters();
+
+            World = cloneSource.World;
+            View = cloneSource.View;
+            Projection = cloneSource.Projection;
+
+            AlbedoTexture = cloneSource.AlbedoTexture;
+            NormalTexture = cloneSource.NormalTexture;
+            MetallicRoughnessTexture = cloneSource.MetallicRoughnessTexture;
+
+            Albedo = cloneSource.Albedo;
+            Metallic = cloneSource.Metallic;
+            Roughness = cloneSource.Roughness;
+        }
+
+        public override Effect Clone()
+        {
+            return new DeferredPBR_GBufferEffect(this);
+        }
+
+        protected override void OnApply()
+        {
+            if ((dirtyFlags & EffectDirtyFlags.World) != 0)
+            {
+                worldParam.SetValue(world);
+
+                Matrix.Invert(ref world, out Matrix worldInverse);
+                Matrix.Transpose(ref worldInverse, out Matrix worldInverseTranspose);
+                worldInverseTransposeParam.SetValue(worldInverseTranspose);
+
+                dirtyFlags &= ~EffectDirtyFlags.World;
+            }
+
+            if ((dirtyFlags & EffectDirtyFlags.WorldViewProj) != 0)
+            {
+                Matrix.Multiply(ref world, ref view, out Matrix worldView);
+                Matrix.Multiply(ref worldView, ref projection, out Matrix worldViewProj);
+                worldViewProjectionParam.SetValue(worldViewProj);
+
+                dirtyFlags &= ~EffectDirtyFlags.WorldViewProj;
+            }
+
+            if ((dirtyFlags & EffectDirtyFlags.EyePosition) != 0)
+            {
+                Matrix.Invert(ref view, out Matrix inverseView);
+
+                dirtyFlags &= ~EffectDirtyFlags.EyePosition;
+            }
+
+            if ((dirtyFlags & EffectDirtyFlags.ShaderIndex) != 0)
+            {
+                int shaderIndex = 0;
+
+                if (albedoTextureEnabled && metallicRoughnessMapEnabled && normalMapEnabled)
+                {
+                    shaderIndex = 7;
+                }
+                else if (metallicRoughnessMapEnabled && normalMapEnabled)
+                {
+                    shaderIndex = 6;
+                }
+                else if (albedoTextureEnabled && normalMapEnabled)
+                {
+                    shaderIndex = 5;
+                }
+                else if (albedoTextureEnabled && metallicRoughnessMapEnabled)
+                {
+                    shaderIndex = 4;
+                }
+                else if (normalMapEnabled)
+                {
+                    shaderIndex = 3;
+                }
+                else if (metallicRoughnessMapEnabled)
+                {
+                    shaderIndex = 2;
+                }
+                else if (albedoTextureEnabled)
+                {
+                    shaderIndex = 1;
+                }
+
+                shaderIndexParam.SetValue(shaderIndex);
+
+                dirtyFlags &= ~EffectDirtyFlags.ShaderIndex;
+            }
+        }
+
+        void CacheEffectParameters()
+        {
+            worldParam = Parameters["World"];
+            worldViewProjectionParam = Parameters["WorldViewProjection"];
+            worldInverseTransposeParam = Parameters["WorldInverseTranspose"];
+
+            albedoTextureParam = Parameters["AlbedoTexture"];
+            normalTextureParam = Parameters["NormalTexture"];
+            metallicRoughnessTextureParam = Parameters["MetallicRoughnessTexture"];
+
+            albedoParam = Parameters["AlbedoValue"];
+            metallicParam = Parameters["MetallicValue"];
+            roughnessParam = Parameters["RoughnessValue"];
+
+            shaderIndexParam = Parameters["ShaderIndex"];
+        }
+    }
+}
diff --git a/Effects/DirectionalLightCollection.cs b/Effects/DirectionalLightCollection.cs
new file mode 100644
index 0000000..10bcbda
--- /dev/null
+++ b/Effects/DirectionalLightCollection.cs
@@ -0,0 +1,47 @@
+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
new file mode 100644
index 0000000..10298d5
Binary files /dev/null and b/Effects/FXB/DeferredPBREffect.fxb differ
diff --git a/Effects/FXB/DeferredPBR_GBufferEffect.fxb b/Effects/FXB/DeferredPBR_GBufferEffect.fxb
new file mode 100644
index 0000000..9b2d684
Binary files /dev/null and b/Effects/FXB/DeferredPBR_GBufferEffect.fxb differ
diff --git a/Effects/FXB/GBufferEffect.fxb b/Effects/FXB/GBufferEffect.fxb
new file mode 100644
index 0000000..9b2d684
Binary files /dev/null and b/Effects/FXB/GBufferEffect.fxb differ
diff --git a/Effects/HLSL/DeferredPBREffect.fx b/Effects/HLSL/DeferredPBREffect.fx
new file mode 100644
index 0000000..a49f27a
--- /dev/null
+++ b/Effects/HLSL/DeferredPBREffect.fx
@@ -0,0 +1,171 @@
+#include "Macros.fxh" //from FNA
+
+static const float PI = 3.141592653589793;
+static const int MAX_POINT_LIGHTS = 64;
+static const int MAX_DIRECTIONAL_LIGHTS = 4;
+
+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);
+
+    float3 DirectionalLightDirections[MAX_DIRECTIONAL_LIGHTS]  _ps(c129)   _cb(c129);
+    float3 DirectionalLightColors[MAX_DIRECTIONAL_LIGHTS]      _ps(c133)   _cb(c133);
+
+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 < MAX_DIRECTIONAL_LIGHTS; i++)
+    {
+        float3 lightDir = DirectionalLightDirections[i];
+        float3 radiance = DirectionalLightColors[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/HLSL/DeferredPBR_GBufferEffect.fx b/Effects/HLSL/DeferredPBR_GBufferEffect.fx
new file mode 100644
index 0000000..63cb2ce
--- /dev/null
+++ b/Effects/HLSL/DeferredPBR_GBufferEffect.fx
@@ -0,0 +1,203 @@
+#include "Macros.fxh"
+
+DECLARE_TEXTURE(AlbedoTexture, 0);
+DECLARE_TEXTURE(NormalTexture, 1);
+DECLARE_TEXTURE(MetallicRoughnessTexture, 2);
+
+BEGIN_CONSTANTS
+
+    float3 AlbedoValue                         _ps(c0)    _cb(c0);
+    float MetallicValue                        _ps(c1)    _cb(c1);
+    float RoughnessValue                       _ps(c2)    _cb(c2);
+
+MATRIX_CONSTANTS
+
+    float4x4 World                  _vs(c0)               _cb(c7);
+    float4x4 WorldInverseTranspose  _vs(c4)               _cb(c11);
+    float4x4 WorldViewProjection    _vs(c8)               _cb(c15);
+
+END_CONSTANTS
+
+struct VertexInput
+{
+    float4 Position : POSITION;
+    float3 Normal   : NORMAL;
+    float2 TexCoord : TEXCOORD0;
+};
+
+struct PixelInput
+{
+    float4 Position      : SV_POSITION;
+    float3 PositionWorld : TEXCOORD0;
+    float3 NormalWorld   : TEXCOORD1;
+    float2 TexCoord      : TEXCOORD2;
+};
+
+struct PixelOutput
+{
+    float4 gPosition          : COLOR0;
+    float4 gNormal            : COLOR1;
+    float4 gAlbedo            : COLOR2;
+    float4 gMetallicRoughness : COLOR3;
+};
+
+// Vertex Shader
+
+PixelInput main_vs(VertexInput input)
+{
+    PixelInput output;
+
+    output.PositionWorld = mul(input.Position, World).xyz;
+    output.NormalWorld = mul(input.Normal, (float3x3)WorldInverseTranspose).xyz;
+    output.TexCoord = input.TexCoord;
+    output.Position = mul(input.Position, WorldViewProjection);
+
+    return output;
+}
+
+// Pixel Shaders
+
+// Easy trick to get tangent-normals to world-space to keep PBR code simplified.
+float3 GetNormalFromMap(float3 worldPos, float2 texCoords, float3 normal)
+{
+    float3 tangentNormal = SAMPLE_TEXTURE(NormalTexture, texCoords).xyz * 2.0 - 1.0;
+
+    float3 Q1  = ddx(worldPos);
+    float3 Q2  = ddy(worldPos);
+    float2 st1 = ddx(texCoords);
+    float2 st2 = ddy(texCoords);
+
+    float3 N   = normalize(normal);
+    float3 T  = normalize(Q1*st2.y - Q2*st1.y);
+    float3 B  = -normalize(cross(N, T));
+    float3x3 TBN = float3x3(T, B, N);
+
+    return normalize(mul(tangentNormal, TBN));
+}
+
+PixelOutput NonePS(PixelInput input)
+{
+    PixelOutput output;
+
+    output.gPosition = float4(input.PositionWorld, 0.0);
+    output.gNormal = float4(normalize(input.NormalWorld), 0.0);
+    output.gAlbedo = float4(AlbedoValue, 1.0);
+    output.gMetallicRoughness = float4(MetallicValue, RoughnessValue, 0.0, 0.0);
+
+    return output;
+}
+
+PixelOutput AlbedoPS(PixelInput input)
+{
+    PixelOutput output;
+
+    output.gPosition = float4(input.PositionWorld, 0.0);
+    output.gNormal = float4(normalize(input.NormalWorld), 0.0);
+    output.gAlbedo = SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord);
+    output.gMetallicRoughness = float4(MetallicValue, RoughnessValue, 0.0, 0.0);
+
+    return output;
+}
+
+PixelOutput MetallicRoughnessPS(PixelInput input)
+{
+    PixelOutput output;
+
+    output.gPosition = float4(input.PositionWorld, 0.0);
+    output.gNormal = float4(normalize(input.NormalWorld), 0.0);
+    output.gAlbedo = float4(AlbedoValue, 1.0);
+    output.gMetallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord);
+
+    return output;
+}
+
+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.gAlbedo = float4(AlbedoValue, 1.0);
+    output.gMetallicRoughness = float4(MetallicValue, RoughnessValue, 0.0, 0.0);
+
+    return output;
+}
+
+PixelOutput AlbedoMetallicRoughnessPS(PixelInput input)
+{
+    PixelOutput output;
+
+    output.gPosition = float4(input.PositionWorld, 0.0);
+    output.gNormal = float4(normalize(input.NormalWorld), 0.0);
+    output.gAlbedo = SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord);
+    output.gMetallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord);
+
+    return output;
+}
+
+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.gAlbedo = SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord);
+    output.gMetallicRoughness = float4(MetallicValue, RoughnessValue, 0.0, 0.0);
+
+    return output;
+}
+
+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.gAlbedo = float4(AlbedoValue, 1.0);
+    output.gMetallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord);
+
+    return output;
+}
+
+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.gAlbedo = SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord);
+    output.gMetallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord);
+
+    return output;
+}
+
+PixelShader PSArray[8] =
+{
+    compile ps_3_0 NonePS(),
+
+    compile ps_3_0 AlbedoPS(),
+    compile ps_3_0 MetallicRoughnessPS(),
+    compile ps_3_0 NormalPS(),
+
+    compile ps_3_0 AlbedoMetallicRoughnessPS(),
+    compile ps_3_0 AlbedoNormalPS(),
+    compile ps_3_0 MetallicRoughnessNormalPS(),
+
+    compile ps_3_0 AlbedoMetallicRoughnessNormalMapPS()
+};
+
+int PSIndices[8] =
+{
+    0, 1, 2, 3, 4, 5, 6, 7
+};
+
+int ShaderIndex = 0;
+
+Technique GBuffer
+{
+    Pass
+    {
+        VertexShader = compile vs_3_0 main_vs();
+        PixelShader = (PSArray[PSIndices[ShaderIndex]]);
+    }
+}
diff --git a/Effects/PBREffect.cs b/Effects/PBREffect.cs
index 09e313e..db04a25 100644
--- a/Effects/PBREffect.cs
+++ b/Effects/PBREffect.cs
@@ -3,90 +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];
-        private readonly Vector3[] colors = new Vector3[4];
-        private readonly float[] intensities = new float[4];
-
-        readonly EffectParameter lightPositionsParam;
-        readonly EffectParameter lightColorsParam;
-
-        public DirectionalLightCollection(EffectParameter lightPositionsParam, EffectParameter lightColorsParam)
-        {
-            this.lightPositionsParam = lightPositionsParam;
-            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;
-                lightPositionsParam.SetValue(directions);
-                lightColorsParam.SetValue(colors);
-            }
-        }
-    }
-
     public class PBREffect : Effect, TransformEffect, PointLightEffect, DirectionalLightEffect
     {
         EffectParameter worldParam;
@@ -285,7 +201,8 @@ namespace Kav
 
             pointLightCollection = new PointLightCollection(
                 Parameters["LightPositions"],
-                Parameters["PositionLightColors"]
+                Parameters["PositionLightColors"],
+                MaxPointLights
             );
 
             directionalLightCollection = new DirectionalLightCollection(
@@ -304,7 +221,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..53c459b
--- /dev/null
+++ b/Effects/PointLightCollection.cs
@@ -0,0 +1,50 @@
+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;
+
+        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.intensities = new float[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.csproj b/Kav.Core.csproj
similarity index 63%
rename from Kav.csproj
rename to Kav.Core.csproj
index 4497042..7957739 100644
--- a/Kav.csproj
+++ b/Kav.Core.csproj
@@ -11,10 +11,16 @@
 
   
     
-    
+    
   
 
   
+    
+      Kav.Resources.DeferredPBR_GBufferEffect.fxb
+    
+    
+      Kav.Resources.DeferredPBREffect.fxb
+    
     
       Kav.Resources.PBREffect.fxb
 		
diff --git a/Kav.Framework.csproj b/Kav.Framework.csproj
new file mode 100644
index 0000000..0557675
--- /dev/null
+++ b/Kav.Framework.csproj
@@ -0,0 +1,32 @@
+
+
+  
+    netstandard2.0
+    Kav
+    Evan Hemsley
+    Evan Hemsley 2020
+    true
+    Kav
+  
+
+  
+    
+    
+  
+
+  
+    
+      Kav.Resources.DeferredPBR_GBufferEffect.fxb
+    
+    
+      Kav.Resources.DeferredPBREffect.fxb
+    
+    
+      Kav.Resources.PBREffect.fxb
+		
+    
+      Kav.Resources.SimpleDepthEffect.fxb
+		
+  
+
+
diff --git a/Loaders/ModelLoader.cs b/Loaders/ModelLoader.cs
index fc5b206..4e908a0 100644
--- a/Loaders/ModelLoader.cs
+++ b/Loaders/ModelLoader.cs
@@ -15,7 +15,7 @@ namespace Kav
 
                 foreach (var meshPartData in meshData.MeshParts)
                 {
-                    var effect = new Kav.PBREffect(
+                    var effect = new Kav.DeferredPBR_GBufferEffect(
                         graphicsDevice
                     )
                     {
diff --git a/Renderer.cs b/Renderer.cs
index 77037b3..c53f855 100644
--- a/Renderer.cs
+++ b/Renderer.cs
@@ -13,6 +13,18 @@ namespace Kav
         private RenderTarget2D DepthRenderTarget { 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 DeferredPBREffect DeferredPBREffect { get; }
+
+        private SpriteBatch SpriteBatch { get; }
+
         public Renderer(GraphicsDevice graphicsDevice, int renderDimensionsX, int renderDimensionsY)
         {
             GraphicsDevice = graphicsDevice;
@@ -24,11 +36,141 @@ namespace Kav
                 renderDimensionsX,
                 renderDimensionsY,
                 false,
-                SurfaceFormat.Color,
+                SurfaceFormat.HalfSingle, // unused
                 DepthFormat.Depth24
             );
 
+            gPosition = new RenderTarget2D(
+                GraphicsDevice,
+                renderDimensionsX,
+                renderDimensionsY,
+                false,
+                SurfaceFormat.Vector4,
+                DepthFormat.Depth24
+            );
+
+            gNormal = new RenderTarget2D(
+                GraphicsDevice,
+                renderDimensionsX,
+                renderDimensionsY,
+                false,
+                SurfaceFormat.Vector4,
+                DepthFormat.None
+            );
+
+            gAlbedo = new RenderTarget2D(
+                GraphicsDevice,
+                renderDimensionsX,
+                renderDimensionsY,
+                false,
+                SurfaceFormat.Color,
+                DepthFormat.None
+            );
+
+            gMetallicRoughness = new RenderTarget2D(
+                GraphicsDevice,
+                renderDimensionsX,
+                renderDimensionsY,
+                false,
+                SurfaceFormat.HalfVector2,
+                DepthFormat.None
+            );
+
+            GBuffer = new RenderTargetBinding[4] {
+                new RenderTargetBinding(gPosition),
+                new RenderTargetBinding(gNormal),
+                new RenderTargetBinding(gAlbedo),
+                new RenderTargetBinding(gMetallicRoughness)
+            };
+            
+            deferredRenderTarget = new RenderTarget2D(
+                GraphicsDevice,
+                renderDimensionsX,
+                renderDimensionsY
+            );
+
             SimpleDepthEffect = new SimpleDepthEffect(GraphicsDevice);
+            DeferredPBREffect = new DeferredPBREffect(GraphicsDevice);
+
+            SpriteBatch = new SpriteBatch(GraphicsDevice);
+
+            GraphicsDevice.SetRenderTarget(deferredRenderTarget);
+            graphicsDevice.Clear(Color.White);
+            GraphicsDevice.SetRenderTarget(null);
+        }
+
+        public void DeferredRender(
+            Camera camera,
+            IEnumerable<(Model, Matrix)> modelTransforms,
+            IEnumerable pointLights,
+            IEnumerable directionalLights
+        ) {
+            GraphicsDevice.SetRenderTargets(GBuffer);
+            GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1f, 0);
+            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
+            GraphicsDevice.BlendState = BlendState.Opaque;
+
+            foreach (var (model, transform) in modelTransforms)
+            {
+                foreach (var modelMesh in model.Meshes)
+                {
+                    foreach (var meshPart in modelMesh.MeshParts)
+                    {
+                        GraphicsDevice.SetVertexBuffer(meshPart.VertexBuffer);
+                        GraphicsDevice.Indices = meshPart.IndexBuffer;
+
+                        if (meshPart.Effect is TransformEffect transformEffect)
+                        {
+                            transformEffect.World = transform;
+                            transformEffect.View = camera.View;
+                            transformEffect.Projection = camera.Projection;
+                        }
+
+                        foreach (var pass in meshPart.Effect.CurrentTechnique.Passes)
+                        {
+                            pass.Apply();
+
+                            GraphicsDevice.DrawIndexedPrimitives(
+                                PrimitiveType.TriangleList,
+                                0,
+                                0,
+                                meshPart.VertexBuffer.VertexCount,
+                                0,
+                                meshPart.Triangles.Length
+                            );
+                        }
+                    }
+                }
+            }
+
+            GraphicsDevice.SetRenderTarget(null);
+            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++;
+            }
+
+            i = 0;
+            foreach (var directionalLight in directionalLights)
+            {
+                if (i > DeferredPBREffect.MaxDirectionalLights) { break; }
+                DeferredPBREffect.DirectionalLights[i] = directionalLight;
+                i++;
+            }
+
+            SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, null, null, DeferredPBREffect);
+            SpriteBatch.Draw(deferredRenderTarget, Vector2.Zero, Color.White);
+            SpriteBatch.End();
         }
 
         public void Render(
diff --git a/Resources.cs b/Resources.cs
index 6ff3806..83d4b04 100644
--- a/Resources.cs
+++ b/Resources.cs
@@ -4,6 +4,30 @@ namespace Kav
 {
     internal class Resources
     {
+        public static byte[] DeferredPBR_GBufferEffect
+        {
+            get
+            {
+                if (gBufferEffect == null)
+                {
+                    gBufferEffect = GetResource("DeferredPBR_GBufferEffect");
+                }
+                return gBufferEffect;
+            }
+        }
+
+        public static byte[] DeferredPBREffect
+        {
+            get
+            {
+                if (deferredPBREffect == null)
+                {
+                    deferredPBREffect = GetResource("DeferredPBREffect");
+                }
+                return deferredPBREffect;
+            }
+        }
+
         public static byte[] PBREffect
         {
             get
@@ -28,6 +52,8 @@ namespace Kav
             }
         }
 
+        private static byte[] gBufferEffect;
+        private static byte[] deferredPBREffect;
         private static byte[] pbrEffect;
         private static byte[] simpleDepthEffect;