diff --git a/Effects/DiffuseLitSpriteEffect.cs b/Effects/DiffuseLitSpriteEffect.cs
new file mode 100644
index 0000000..35385b6
--- /dev/null
+++ b/Effects/DiffuseLitSpriteEffect.cs
@@ -0,0 +1,154 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Kav
+{
+    public class DiffuseLitSpriteEffect : Effect
+    {
+        EffectParameter textureParam;
+
+        EffectParameter ambientColorParam;
+
+        EffectParameter directionalLightDirectionParam;
+        EffectParameter directionalLightColorParam;
+
+        EffectParameter worldParam;
+        EffectParameter worldViewProjectionParam;
+        EffectParameter worldInverseTransposeParam;
+
+        Texture2D texture;
+
+        Vector3 ambientColor;
+
+        Vector3 directionalLightDirection;
+        Vector3 directionalLightColor;
+
+        Matrix world = Matrix.Identity;
+        Matrix view = Matrix.Identity;
+        Matrix projection = Matrix.Identity;
+
+        EffectDirtyFlags dirtyFlags = EffectDirtyFlags.All;
+
+        public Texture2D Texture
+        {
+            get { return texture; }
+            set
+            {
+                texture = value;
+                textureParam.SetValue(texture);
+            }
+        }
+
+        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 int MaxPointLights { get; } = 8;
+
+        public Vector3 AmbientColor
+        {
+            get { return ambientColor; }
+            set
+            {
+                ambientColor = value;
+                ambientColorParam.SetValue(ambientColor);
+            }
+        }
+
+        public PointLightCollection PointLights { get; private set; }
+
+        public Vector3 DirectionalLightDirection
+        {
+            get { return directionalLightDirection; }
+            set
+            {
+                directionalLightDirection = value;
+                directionalLightDirectionParam.SetValue(directionalLightDirection);
+            }
+        }
+
+        public Vector3 DirectionalLightColor
+        {
+            get { return directionalLightColor; }
+            set
+            {
+                directionalLightColor = value;
+                directionalLightColorParam.SetValue(directionalLightColor);
+            }
+        }
+
+        public DiffuseLitSpriteEffect(GraphicsDevice graphicsDevice) : base(graphicsDevice, Resources.DiffuseLitSpriteEffect)
+        {
+            CacheEffectParameters();
+
+            PointLights = new PointLightCollection(
+                Parameters["PointLightPositions"],
+                Parameters["PointLightColors"],
+                MaxPointLights
+            );
+        }
+
+        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;
+            }
+        }
+
+        private void CacheEffectParameters()
+        {
+            textureParam = Parameters["Texture"];
+
+            worldParam = Parameters["World"];
+            worldViewProjectionParam = Parameters["WorldViewProjection"];
+            worldInverseTransposeParam = Parameters["WorldInverseTranspose"];
+
+            ambientColorParam = Parameters["AmbientColor"];
+
+            directionalLightDirectionParam = Parameters["DirectionalLightDirection"];
+            directionalLightColorParam = Parameters["DirectionalLightColor"];
+        }
+    }
+}
diff --git a/Effects/FXB/DiffuseLitSpriteEffect.fxb b/Effects/FXB/DiffuseLitSpriteEffect.fxb
new file mode 100644
index 0000000..7c6af31
--- /dev/null
+++ b/Effects/FXB/DiffuseLitSpriteEffect.fxb
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bc6de319b583aabd741b9d1012854fd318ee66d28cb4abeaa04ea78058645d25
+size 4288
diff --git a/Effects/HLSL/DiffuseLitSprite.fx b/Effects/HLSL/DiffuseLitSprite.fx
new file mode 100644
index 0000000..163af05
--- /dev/null
+++ b/Effects/HLSL/DiffuseLitSprite.fx
@@ -0,0 +1,90 @@
+#include "Macros.fxh" //from FNA
+
+// Effect applies normalmapped lighting to a 2D sprite.
+
+DECLARE_TEXTURE(Texture, 0);
+DECLARE_TEXTURE(Normal, 1);
+
+BEGIN_CONSTANTS
+
+    float AmbientColor                       _ps(c0)    _cb(c0);
+
+    float3 PointLightPositions[8]            _ps(c1)    _cb(c1);
+    float3 PointLightColors[8]               _ps(c9)    _cb(c9);
+
+    float DirectionalLightDirection          _ps(c17)   _cb(c17);
+    float DirectionalLightColor              _ps(c18)   _cb(c18);
+
+MATRIX_CONSTANTS
+
+    float4x4 WorldInverseTranspose           _ps(c19)   _cb(c19);
+    float4x4 World                  _vs(c0)             _cb(c23);
+    float4x4 WorldViewProjection    _vs(c4)             _cb(c27);
+
+END_CONSTANTS
+
+struct VertexShaderInput
+{
+    float4 Position : POSITION;
+    float2 TexCoord : TEXCOORD0;
+};
+
+struct PixelShaderInput
+{
+    float4 Position   : SV_Position;
+    float2 TexCoord   : TEXCOORD0;
+    float3 PositionWS : TEXCOORD2;
+};
+
+PixelShaderInput main_vs(VertexShaderInput input)
+{
+    PixelShaderInput output;
+
+    output.TexCoord = input.TexCoord;
+    output.PositionWS = mul(input.Position, World).xyz;
+    output.Position = mul(input.Position, WorldViewProjection);
+
+    return output;
+}
+
+float4 main_ps(PixelShaderInput input) : COLOR0
+{
+    // Look up the texture and normalmap values.
+    float4 tex = tex2D(TextureSampler, input.TexCoord);
+    float3 normal = tex2D(NormalSampler, input.TexCoord).xyz;
+    float3 normalWS = mul(normal, (float3x3)WorldInverseTranspose).xyz;
+
+    float3 lightColor = float3(0.0, 0.0, 0.0);
+
+    // point lights
+    for (int i = 0; i < 8; i++)
+    {
+        float3 lightVec = PointLightPositions[i] - input.PositionWS;
+        float distance = length(lightVec);
+
+        float3 lightDir = normalize(lightVec);
+        float diffuse = max(dot(normalWS, lightDir), 0.0);
+        float3 attenuation = 1.0 / (distance * distance);
+
+        lightColor += diffuse * attenuation * PointLightColors[i];
+    }
+
+    // directional light
+    float directionalDiffuse = max(dot(normalWS, DirectionalLightDirection), 0.0);
+    lightColor += directionalDiffuse * DirectionalLightColor;
+
+    // ambient light
+    lightColor += AmbientColor;
+
+    // blend with sample
+    return tex * float4(lightColor, 1.0);
+}
+
+Technique DiffuseLitSprite
+{
+    pass
+    {
+        VertexShader = compile vs_3_0 main_vs();
+        PixelShader = compile ps_3_0 main_ps();
+    }
+}
diff --git a/Kav.Core.csproj b/Kav.Core.csproj
index b52de06..9cb9564 100644
--- a/Kav.Core.csproj
+++ b/Kav.Core.csproj
@@ -49,6 +49,9 @@
     <EmbeddedResource Include="Effects\FXB\SkyboxEffect.fxb">
       <LogicalName>Kav.Resources.SkyboxEffect.fxb</LogicalName>
 		</EmbeddedResource>
+    <EmbeddedResource Include="Effects\FXB\DiffuseLitSpriteEffect.fxb">
+      <LogicalName>Kav.Resources.DiffuseLitSpriteEffect.fxb</LogicalName>
+		</EmbeddedResource>
     <EmbeddedResource Include="Models\UnitCube.glb">
       <LogicalName>Kav.Resources.UnitCube.glb</LogicalName>
 		</EmbeddedResource>
diff --git a/Kav.Framework.csproj b/Kav.Framework.csproj
index 64058b4..d5c6c57 100644
--- a/Kav.Framework.csproj
+++ b/Kav.Framework.csproj
@@ -49,6 +49,9 @@
     <EmbeddedResource Include="Effects\FXB\SkyboxEffect.fxb">
       <LogicalName>Kav.Resources.SkyboxEffect.fxb</LogicalName>
 		</EmbeddedResource>
+    <EmbeddedResource Include="Effects\FXB\DiffuseLitSpriteEffect.fxb">
+      <LogicalName>Kav.Resources.DiffuseLitSpriteEffect.fxb</LogicalName>
+		</EmbeddedResource>
     <EmbeddedResource Include="Models\UnitCube.glb">
       <LogicalName>Kav.Resources.UnitCube.glb</LogicalName>
 		</EmbeddedResource>
diff --git a/Renderer.cs b/Renderer.cs
index 047c386..23561be 100644
--- a/Renderer.cs
+++ b/Renderer.cs
@@ -33,7 +33,7 @@ namespace Kav
         private LinearDepthEffect LinearDepthEffect { get; }
         private Effect ToneMapEffect { get; }
         private SkyboxEffect SkyboxEffect { get; }
-        private BasicEffect BasicEffect { get; }
+        private DiffuseLitSpriteEffect DiffuseLitSpriteEffect { get; }
 
         private RenderTarget2D gPosition { get; }
         private RenderTarget2D gNormal { get; }
@@ -160,7 +160,7 @@ namespace Kav
             ToneMapEffect = new Effect(graphicsDevice, Resources.ToneMapEffect);
             Deferred_ToonEffect = new Deferred_ToonEffect(GraphicsDevice);
             SkyboxEffect = new SkyboxEffect(GraphicsDevice);
-            BasicEffect = new BasicEffect(GraphicsDevice);
+            DiffuseLitSpriteEffect = new DiffuseLitSpriteEffect(GraphicsDevice);
 
             FullscreenTriangle = new VertexBuffer(GraphicsDevice, typeof(VertexPositionTexture), 3, BufferUsage.WriteOnly);
             FullscreenTriangle.SetData(new VertexPositionTexture[3] {
@@ -245,7 +245,10 @@ namespace Kav
             RenderTarget2D renderTarget,
             PerspectiveCamera camera,
             IEnumerable<(Model, Matrix)> modelTransforms,
-            IEnumerable<Sprite> sprites
+            IEnumerable<Sprite> sprites,
+            AmbientLight ambientLight,
+            IEnumerable<PointLight> pointLights,
+            DirectionalLight directionalLight
         ) {
             GraphicsDevice.SetRenderTarget(ColorRenderTarget);
             GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1f, 0);
@@ -254,20 +257,31 @@ namespace Kav
             DepthRender(camera, modelTransforms);
             GraphicsDevice.Clear(ClearOptions.Target, new Color(0, 0, 0, 0), 1f, 0);
 
-            BasicEffect.View = camera.View;
-            BasicEffect.Projection = camera.Projection;
-            BasicEffect.TextureEnabled = true;
-            BasicEffect.VertexColorEnabled = true;
+            DiffuseLitSpriteEffect.View = camera.View;
+            DiffuseLitSpriteEffect.Projection = camera.Projection;
+
+            DiffuseLitSpriteEffect.DirectionalLightDirection =
+                directionalLight.Direction;
+            DiffuseLitSpriteEffect.DirectionalLightColor =
+                directionalLight.Color.ToVector3() * directionalLight.Intensity;
+
+            var i = 0;
+            foreach (var pointLight in pointLights)
+            {
+                if (i > DiffuseLitSpriteEffect.MaxPointLights) { break; }
+                DiffuseLitSpriteEffect.PointLights[i] = pointLight;
+                i += 1;
+            }
 
             foreach (var sprite in sprites)
             {
                 if (sprite.BillboardConstraint == SpriteBillboardConstraint.None)
                 {
-                    BasicEffect.World = sprite.TransformMatrix;
+                    DiffuseLitSpriteEffect.World = sprite.TransformMatrix;
                 }
                 else if (sprite.BillboardConstraint == SpriteBillboardConstraint.Horizontal)
                 {
-                    BasicEffect.World = Matrix.CreateConstrainedBillboard(
+                    DiffuseLitSpriteEffect.World = Matrix.CreateConstrainedBillboard(
                         sprite.Position,
                         camera.Position,
                         Vector3.Up,
@@ -277,7 +291,7 @@ namespace Kav
                 }
                 else
                 {
-                    BasicEffect.World = Matrix.CreateConstrainedBillboard(
+                    DiffuseLitSpriteEffect.World = Matrix.CreateConstrainedBillboard(
                         sprite.Position,
                         camera.Position,
                         Vector3.Up,
@@ -286,7 +300,9 @@ namespace Kav
                     );
                 }
 
-                SpriteBatch.Begin(0, null, null, DepthStencilState.DepthRead, RasterizerState.CullNone, BasicEffect);
+                GraphicsDevice.Textures[1] = sprite.Texture;
+
+                SpriteBatch.Begin(0, null, null, DepthStencilState.DepthRead, RasterizerState.CullNone, DiffuseLitSpriteEffect);
                 SpriteBatch.Draw(
                     sprite.Texture,
                     Vector2.Zero,
diff --git a/Resources.cs b/Resources.cs
index fd5daa5..44c28f7 100644
--- a/Resources.cs
+++ b/Resources.cs
@@ -135,6 +135,18 @@ namespace Kav
             }
         }
 
+        public static byte[] DiffuseLitSpriteEffect
+        {
+            get
+            {
+                if (diffuseLitSpriteEffect == null)
+                {
+                    diffuseLitSpriteEffect = GetResource("DiffuseLitSpriteEffect.fxb");
+                }
+                return diffuseLitSpriteEffect;
+            }
+        }
+
         public static byte[] UnitCubeModel
         {
             get
@@ -158,6 +170,7 @@ namespace Kav
         private static byte[] simpleDepthEffect;
         private static byte[] linearDepthEffect;
         private static byte[] skyboxEffect;
+        private static byte[] diffuseLitSpriteEffect;
 
         private static byte[] unitCubeModel;