diff --git a/Effects/PBREffect.cs b/Effects/PBREffect.cs
index 4472bd7..ba73003 100644
--- a/Effects/PBREffect.cs
+++ b/Effects/PBREffect.cs
@@ -5,48 +5,81 @@ namespace Smuggler
 {
     public struct PBRLight
     {
-        public Vector3 direction;
-        public Vector3 colour;
+        public Vector3 position;
+        public Vector3 color;
 
-        public PBRLight(Vector3 direction, Vector3 colour)
+        public PBRLight(Vector3 position, Vector3 colour)
         {
-            this.direction = direction;
-            this.colour = colour;
+            this.position = position;
+            this.color = colour;
+        }
+    }
+
+    public class PBRLightCollection
+    {
+        private readonly Vector3[] positions = new Vector3[4];
+        private readonly Vector3[] colors = new Vector3[4];
+
+        readonly EffectParameter lightPositionsParam;
+        readonly EffectParameter lightColorsParam;
+
+        public PBRLightCollection(EffectParameter lightPositionsParam, EffectParameter lightColorsParam)
+        {
+            this.lightPositionsParam = lightPositionsParam;
+            this.lightColorsParam = lightColorsParam;
+        }
+        
+        public PBRLight this[int i]
+        {
+            get { return new PBRLight(positions[i], colors[i]); }
+            set 
+            {
+                positions[i] = value.position;
+                colors[i] = value.color;
+                lightPositionsParam.SetValue(positions);
+                lightColorsParam.SetValue(colors);
+            }
         }
     }
 
     public class PBREffect : Effect
     {
-        readonly EffectParameter modelParam;
-        readonly EffectParameter viewParam;
-        readonly EffectParameter projectionParam;
-        readonly EffectParameter lightDirParam;
-        readonly EffectParameter lightColourParam;
-        readonly EffectParameter normalScaleParam;
-        readonly EffectParameter emissiveFactorParam;
-        readonly EffectParameter occlusionStrengthParam;
-        readonly EffectParameter metallicRoughnessValuesParam;
-        readonly EffectParameter baseColorFactorParam;
-        readonly EffectParameter cameraLookParam;
+        EffectParameter worldParam;
+        EffectParameter viewParam;
+        EffectParameter projectionParam;
+        EffectParameter worldViewProjectionParam;
+        EffectParameter worldInverseTransposeParam;
 
-        readonly EffectParameter baseColourTextureParam;
-        readonly EffectParameter normalTextureParam;
-        readonly EffectParameter emissionTextureParam;
-        readonly EffectParameter occlusionTextureParam;
-        readonly EffectParameter metallicRoughnessTextureParam;
-        readonly EffectParameter envDiffuseTextureParam;
-        readonly EffectParameter brdfLutTextureParam;
-        readonly EffectParameter envSpecularTextureParam;
+        EffectParameter baseColorTextureParam;
+        EffectParameter normalTextureParam;
+        EffectParameter emissionTextureParam;
+        EffectParameter occlusionTextureParam;
+        EffectParameter metallicRoughnessTextureParam;
+        EffectParameter envDiffuseTextureParam;
+        EffectParameter brdfLutTextureParam;
+        EffectParameter envSpecularTextureParam;
+
+        EffectParameter lightPositionsParam;
+        EffectParameter lightColorsParam;
+
+        EffectParameter albedoParam;
+        EffectParameter metallicParam;
+        EffectParameter roughnessParam;
+        EffectParameter aoParam;
+
+        EffectParameter eyePositionParam;
 
         Matrix world = Matrix.Identity;
         Matrix view = Matrix.Identity;
         Matrix projection = Matrix.Identity;
-        PBRLight light = new PBRLight();
-        float normalScale = 1;
-        Vector3 emissiveFactor;
-        float occlusionStrength;
-        Vector2 metallicRoughnessValue;
-        Vector4 baseColorFactor;
+        PBRLightCollection pbrLightCollection;
+
+        Vector3 albedo;
+        float metallic;
+        float roughness;
+        float ao;
+
+        // FIXME: lazily set properties for performance
 
         public Matrix World
         {
@@ -54,7 +87,9 @@ namespace Smuggler
             set 
             { 
                 world = value;
-                modelParam.SetValue(world); 
+                worldParam.SetValue(world);
+                worldViewProjectionParam.SetValue(world * view * projection);
+                worldInverseTransposeParam.SetValue(Matrix.Transpose(Matrix.Invert(world)));
             }
         }
 
@@ -65,11 +100,8 @@ namespace Smuggler
             { 
                 view = value;
                 viewParam.SetValue(view);
-                cameraLookParam.SetValue(-new Vector3(
-                    view.M13,
-                    view.M23,
-                    view.M33
-                ));
+                worldViewProjectionParam.SetValue(world * view * projection);
+                eyePositionParam.SetValue(Matrix.Invert(view).Translation);
             }
         }
 
@@ -79,75 +111,61 @@ namespace Smuggler
             set 
             { 
                 projection = value;
-                projectionParam.SetValue(value);
+                projectionParam.SetValue(projection);
+                worldViewProjectionParam.SetValue(world * view * projection);
             }
         }
 
-        public PBRLight Light
+        public PBRLightCollection Lights
         {
-            get { return light; }
-            set 
-            { 
-                light = value;
-                lightDirParam.SetValue(light.direction); 
-                lightColourParam.SetValue(light.colour);
-            }
+            get { return pbrLightCollection; }
+            internal set { pbrLightCollection = value; }
         }
 
-        public float NormalScale
+        public Vector3 Albedo
         {
-            get { return normalScale; }
+            get { return albedo; }
             set 
             {
-                normalScale = value;
-                normalScaleParam.SetValue(normalScale); 
+                albedo = value;
+                albedoParam.SetValue(albedo); 
             }
         }
 
-        public Vector3 EmissiveFactor
+        public float Metallic
         {
-            get { return emissiveFactor; }
-            set 
-            { 
-                emissiveFactor = value;
-                emissiveFactorParam.SetValue(emissiveFactor); 
-            }
-        }
-
-        public float OcclusionStrength
-        {
-            get { return occlusionStrength; }
+            get { return metallic; }
             set 
             {
-                occlusionStrength = value;
-                occlusionStrengthParam.SetValue(occlusionStrength); 
+                metallic = value;
+                metallicParam.SetValue(metallic); 
             }
         }
 
-        public Vector2 MetallicRoughnessValue
+        public float Roughness
         {
-            get { return metallicRoughnessValue; }
+            get { return roughness; }
             set
             {
-                metallicRoughnessValue = value;
-                metallicRoughnessValuesParam.SetValue(metallicRoughnessValue);
+                roughness = value;
+                roughnessParam.SetValue(roughness);
             }
         }
 
-        public Vector4 BaseColorFactor
+        public float AO
         {
-            get { return baseColorFactor; }
+            get { return ao; }
             set
             {
-                baseColorFactor = value;
-                baseColorFactorParam.SetValue(baseColorFactor);
+                ao = value;
+                aoParam.SetValue(ao);
             }
         }
 
         public Texture2D BaseColourTexture
         {
-            get { return baseColourTextureParam.GetValueTexture2D(); }
-            set { baseColourTextureParam.SetValue(value); }
+            get { return baseColorTextureParam.GetValueTexture2D(); }
+            set { baseColorTextureParam.SetValue(value); }
         }
 
         public Texture2D NormalTexture
@@ -194,66 +212,31 @@ namespace Smuggler
 
         public PBREffect(GraphicsDevice graphicsDevice) : base(graphicsDevice, Resources.PBREffect)
         {
-            modelParam = Parameters["model"];
-            viewParam  = Parameters["view"];
-            projectionParam = Parameters["projection"];
+            CacheEffectParameters(null);
 
-            lightDirParam = Parameters["lightDir"];
-            lightColourParam = Parameters["lightColour"];
-
-            normalScaleParam = Parameters["normalScale"];
-            emissiveFactorParam = Parameters["emissiveFactor"];
-            occlusionStrengthParam = Parameters["occlusionStrength"];
-            metallicRoughnessValuesParam = Parameters["metallicRoughnessValues"];
-            baseColorFactorParam = Parameters["baseColorFactor"];
-            cameraLookParam = Parameters["camera"];
-
-            baseColourTextureParam = Parameters["baseColourTexture"];
-            normalTextureParam = Parameters["normalTexture"];
-            emissionTextureParam = Parameters["emissionTexture"];
-            occlusionTextureParam = Parameters["occlusionTexture"];
-            metallicRoughnessTextureParam = Parameters["metallicRoughnessTexture"];
-            envDiffuseTextureParam = Parameters["envDiffuseTexture"];
-            brdfLutTextureParam = Parameters["brdfLutTexture"];
-            envSpecularTextureParam = Parameters["envSpecularTexture"];
+            pbrLightCollection = new PBRLightCollection(
+                Parameters["LightPositions"],
+                Parameters["LightColors"]
+            );
         }
 
         protected PBREffect(PBREffect cloneSource) : base(cloneSource)
         {
-            modelParam = Parameters["model"];
-            viewParam  = Parameters["view"];
-            projectionParam = Parameters["param"];
-
-            lightDirParam = Parameters["lightDir"];
-            lightColourParam = Parameters["lightColour"];
-
-            normalScaleParam = Parameters["normalScale"];
-            emissiveFactorParam = Parameters["emissiveFactor"];
-            occlusionStrengthParam = Parameters["occlusionStrength"];
-            metallicRoughnessValuesParam = Parameters["metallicRoughnessValues"];
-            baseColorFactorParam = Parameters["baseColorFactor"];
-            cameraLookParam = Parameters["camera"];
-
-            baseColourTextureParam = Parameters["baseColourTexture"];
-            normalTextureParam = Parameters["normalTexture"];
-            emissionTextureParam = Parameters["emissionTexture"];
-            occlusionTextureParam = Parameters["occlusionTexture"];
-            metallicRoughnessTextureParam = Parameters["metallicRoughnessTexture"];
-            envDiffuseTextureParam = Parameters["envDiffuseTexture"];
-            brdfLutTextureParam = Parameters["brdfLutTexture"];
-            envSpecularTextureParam = Parameters["envSpecularTexture"];
+            CacheEffectParameters(cloneSource);
 
             World = cloneSource.World;
             View = cloneSource.View;
             Projection = cloneSource.Projection;
 
-            Light = cloneSource.Light;
+            Lights = new PBRLightCollection(
+                Parameters["LightPositions"],
+                Parameters["LightColors"]
+            );
 
-            NormalScale = cloneSource.normalScale;
-            EmissiveFactor = cloneSource.EmissiveFactor;
-            OcclusionStrength = cloneSource.OcclusionStrength;
-            MetallicRoughnessValue = cloneSource.MetallicRoughnessValue;
-            BaseColorFactor = cloneSource.BaseColorFactor;
+            for (int i = 0; i < 4; i++)
+            {
+                Lights[i] = cloneSource.Lights[i];
+            }
 
             BaseColourTexture = cloneSource.BaseColourTexture;
             NormalTexture = cloneSource.NormalTexture;
@@ -263,6 +246,11 @@ namespace Smuggler
             EnvDiffuseTexture = cloneSource.EnvDiffuseTexture;
             BRDFLutTexture = cloneSource.BRDFLutTexture;
             EnvSpecularTexture = cloneSource.EnvSpecularTexture;
+
+            Albedo = cloneSource.Albedo;
+            Metallic = cloneSource.Metallic;
+            Roughness = cloneSource.Roughness;
+            AO = cloneSource.AO;
         }
 
         public override Effect Clone()
@@ -275,5 +263,33 @@ namespace Smuggler
         {
             base.OnApply();
         }
+
+        void CacheEffectParameters(PBREffect cloneSource)
+        {
+            worldParam = Parameters["World"];
+            viewParam = Parameters["View"];
+            projectionParam = Parameters["Projection"];
+            worldViewProjectionParam = Parameters["WorldViewProjection"];
+            worldInverseTransposeParam = Parameters["WorldInverseTranspose"];
+
+            baseColorTextureParam = Parameters["BaseColorTexture"];
+            normalTextureParam = Parameters["NormalTexture"];
+            emissionTextureParam = Parameters["EmissionTexture"];
+            occlusionTextureParam = Parameters["OcclusionTexture"];
+            metallicRoughnessTextureParam = Parameters["MetallicRoughnessTexture"];
+            envDiffuseTextureParam = Parameters["EnvDiffuseTexture"];
+            brdfLutTextureParam = Parameters["BrdfLutTexture"];
+            envSpecularTextureParam = Parameters["EnvSpecularTexture"];
+
+            lightPositionsParam = Parameters["LightPositions"];
+            lightColorsParam = Parameters["LightColors"];
+
+            albedoParam = Parameters["Albedo"];
+            metallicParam = Parameters["Metallic"];
+            roughnessParam = Parameters["Roughness"];
+            aoParam = Parameters["AO"];
+
+            eyePositionParam = Parameters["EyePosition"];
+        }
     }
 }
diff --git a/Effects/PBREffect.fx b/Effects/PBREffect.fx
index 9d49a36..e26bd49 100644
--- a/Effects/PBREffect.fx
+++ b/Effects/PBREffect.fx
@@ -1,408 +1,159 @@
+#include "Macros.fxh" //from FNA
 
-#include "Macros.fxh"
+static const float PI = 3.141592653589793;
 
-#define NORMALS
-#define UV
+// Transformation Matrices
 
-// A constant buffer that stores the three basic column-major matrices for composing geometry.
-cbuffer ModelViewProjectionConstantBuffer : register(b0)
-{
-    matrix model;
-    matrix view;
-    matrix projection;
-};
+float4x4 World;
+float4x4 View;
+float4x4 Projection;
+
+float4x4 WorldViewProjection;
+float4x3 WorldInverseTranspose;
+
+// Samplers
+
+DECLARE_TEXTURE(BaseColorTexture, 0);
+DECLARE_TEXTURE(NormalTexture, 1);
+DECLARE_TEXTURE(EmissionTexture, 2);
+DECLARE_TEXTURE(OcclusionTexture, 3);
+DECLARE_TEXTURE(MetallicRoughnessTexture, 4);
+DECLARE_CUBEMAP(EnvDiffuseTexture, 8);
+DECLARE_TEXTURE(BrdfLutTexture, 9);
+DECLARE_CUBEMAP(EnvSpecularTexture, 10);
+
+// Light Info
+float3 LightPositions[4];
+float3 LightColors[4];
+
+// PBR Values
+float3 Albedo;
+float Metallic;
+float Roughness;
+float AO;
+
+float3 EyePosition;
 
-// Per-vertex data used as input to the vertex shader.
 struct VertexShaderInput
 {
-    float4 position : POSITION;
-#ifdef NORMALS
-    float3 normal : NORMAL;
-#endif
-#ifdef UV
-    float2 texcoord : TEXCOORD0;
-#endif
+    float4 Position : POSITION;
+    float3 Normal   : NORMAL;
+    float2 TexCoord : TEXCOORD0;
 };
 
-// Per-pixel color data passed through the pixel shader.
 struct PixelShaderInput
 {
-    float4 position : SV_POSITION;
-    float3 poswithoutw : POSITION1;
-
-#ifdef NORMALS
-    float3 normal : NORMAL;
-#endif
-
-    float2 texcoord : TEXCOORD0;
+    float4 Position   : SV_POSITION;
+    float2 TexCoord   : TEXCOORD0;
+    float3 PositionWS : TEXCOORD1;
+    float3 NormalWS   : TEXCOORD2;
 };
 
 PixelShaderInput main_vs(VertexShaderInput input)
 {
     PixelShaderInput output;
 
-	// Transform the vertex position into projected space.
-    float4 pos = mul(input.position, model);
-    output.poswithoutw = float3(pos.xyz) / pos.w;
-
-#ifdef NORMALS
-    // If we have normals...
-    output.normal = normalize(mul(float4(input.normal.xyz, 0.0), model));
-#endif
-
-#ifdef UV
-    output.texcoord = input.texcoord;
-#else
-    output.texcoord = float2(0.0f, 0.0f);
-#endif
-
-#ifdef HAS_NORMALS
-#ifdef HAS_TANGENTS
-  vec3 normalW = normalize(vec3(u_ModelMatrix * vec4(a_Normal.xyz, 0.0)));
-  vec3 tangentW = normalize(vec3(u_ModelMatrix * vec4(a_Tangent.xyz, 0.0)));
-  vec3 bitangentW = cross(normalW, tangentW) * a_Tangent.w;
-  v_TBN = mat3(tangentW, bitangentW, normalW);
-#else // HAS_TANGENTS != 1
-  v_Normal = normalize(vec3(u_ModelMatrix * vec4(a_Normal.xyz, 0.0)));
-#endif
-#endif
-
-    // Transform the vertex position into projected space.
-    pos = mul(pos, view);
-    pos = mul(pos, projection);
-    output.position = pos;
+    output.PositionWS = mul(input.Position, World).xyz;
+    output.TexCoord = input.TexCoord;
+    output.NormalWS = normalize(mul(WorldInverseTranspose, input.Normal));
 
+    output.Position = mul(input.Position, WorldViewProjection);
     return output;
 }
 
-//
-// This fragment shader defines a reference implementation for Physically Based Shading of
-// a microfacet surface material defined by a glTF model.
-//
-// References:
-// [1] Real Shading in Unreal Engine 4
-//     http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
-// [2] Physically Based Shading at Disney
-//     http://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf
-// [3] README.md - Environment Maps
-//     https://github.com/KhronosGroup/glTF-WebGL-PBR/#environment-maps
-// [4] "An Inexpensive BRDF Model for Physically based Rendering" by Christophe Schlick
-//     https://www.cs.virginia.edu/~jdl/bib/appearance/analytic%20models/schlick94b.pdf
-
-#define NORMALS
-#define UV
-#define HAS_NORMALS
-// #define USE_IBL
-#define USE_TEX_LOD
-
-DECLARE_TEXTURE(baseColourTexture, 0);
-DECLARE_TEXTURE(normalTexture, 1);
-DECLARE_TEXTURE(emissionTexture, 2);
-DECLARE_TEXTURE(occlusionTexture, 3);
-DECLARE_TEXTURE(metallicRoughnessTexture, 4);
-DECLARE_CUBEMAP(envDiffuseTexture, 8);
-DECLARE_TEXTURE(brdfLutTexture, 9);
-DECLARE_CUBEMAP(envSpecularTexture, 10);
-
-cbuffer cbPerFrame : register(b0)
+float3 FresnelSchlick(float cosTheta, float3 F0)
 {
-    float3 lightDir;
-	float3 lightColour;
-};
-
-cbuffer cbPerObject : register(b1)
-{
-    float normalScale;
-    float3 emissiveFactor;
-    float occlusionStrength;
-    float2 metallicRoughnessValues;
-    float4 baseColorFactor;
-    float3 camera;
-
-    // debugging flags used for shader output of intermediate PBR variables
-    float4 scaleDiffBaseMR;
-    float4 scaleFGDSpec;
-    float4 scaleIBLAmbient;
-};
-
-#ifdef HAS_NORMALS
-#ifdef HAS_TANGENTS
-varying mat3 v_TBN;
-#else
-#endif
-#endif
-
-// Encapsulate the various inputs used by the various functions in the shading equation
-// We store values in this struct to simplify the integration of alternative implementations
-// of the shading terms, outlined in the Readme.MD Appendix.
-struct PBRInfo
-{
-    float NdotL; // cos angle between normal and light direction
-    float NdotV; // cos angle between normal and view direction
-    float NdotH; // cos angle between normal and half vector
-    float LdotH; // cos angle between light direction and half vector
-    float VdotH; // cos angle between view direction and half vector
-    float perceptualRoughness; // roughness value, as authored by the model creator (input to shader)
-    float metalness; // metallic value at the surface
-    float3 reflectance0; // full reflectance color (normal incidence angle)
-    float3 reflectance90; // reflectance color at grazing angle
-    float alphaRoughness; // roughness mapped to a more linear change in the roughness (proposed by [2])
-    float3 diffuseColor; // color contribution from diffuse lighting
-    float3 specularColor; // color contribution from specular lighting
-};
-
-static const float M_PI = 3.141592653589793;
-static const float c_MinRoughness = 0.04;
-
-float4 SRGBtoLINEAR(float4 srgbIn)
-{
-#ifdef MANUAL_SRGB
-#ifdef SRGB_FAST_APPROXIMATION
-    float3 linOut = pow(srgbIn.xyz,float3(2.2, 2.2, 2.2));
-#else //SRGB_FAST_APPROXIMATION
-    float3 bLess = step(float3(0.04045, 0.04045, 0.04045), srgbIn.xyz);
-    float3 linOut = lerp(srgbIn.xyz / float3(12.92, 12.92, 12.92), pow((srgbIn.xyz + float3(0.055, 0.055, 0.055)) / float3(1.055, 1.055, 1.055), float3(2.4, 2.4, 2.4)), bLess);
-#endif //SRGB_FAST_APPROXIMATION
-    return float4(linOut,srgbIn.w);;
-#else //MANUAL_SRGB
-    return srgbIn;
-#endif //MANUAL_SRGB
+    return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
 }
 
-// Find the normal for this fragment, pulling either from a predefined normal map
-// or from the interpolated mesh normal and tangent attributes.
-float3 getNormal(float3 position, float3 normal, float2 uv)
+float DistributionGGX(float3 N, float3 H, float roughness)
 {
-    // Retrieve the tangent space matrix
-#ifndef HAS_TANGENTS
-    float3 pos_dx = ddx(position);
-    float3 pos_dy = ddy(position);
-    float3 tex_dx = ddx(float3(uv, 0.0));
-    float3 tex_dy = ddy(float3(uv, 0.0));
-    float3 t = (tex_dy.y * pos_dx - tex_dx.y * pos_dy) / (tex_dx.x * tex_dy.y - tex_dy.x * tex_dx.y);
+    float a = roughness * roughness;
+    float a2 = a * a;
+    float NdotH = max(dot(N, H), 0.0);
+    float NdotH2 = NdotH * NdotH;
 
-#ifdef HAS_NORMALS
-    float3 ng = normalize(normal);
-#else
-    float3 ng = cross(pos_dx, pos_dy);
-#endif
+    float num = a2;
+    float denom = (NdotH2 * (a2 - 1.0) + 1.0);
+    denom = PI * denom * denom;
 
-    t = normalize(t - ng * dot(ng, t));
-    float3 b = normalize(cross(ng, t));
-    row_major float3x3 tbn = float3x3(t, b, ng);
-
-#else // HAS_TANGENTS
-    mat3 tbn = v_TBN;
-#endif
-
-#ifdef HAS_NORMALMAP
-    float3 n = SAMPLE_TEXTURE(normalTexture, uv).rgb;
-
-    // Need to check the multiplication is equivalent..
-    n = normalize(mul(((2.0 * n - 1.0) * float3(normalScale, normalScale, 1.0)), tbn));
-#else
-    float3 n = tbn[2].xyz;
-#endif
-
-    return n;
+    return num / denom;
 }
 
-#ifdef USE_IBL
-// Calculation of the lighting contribution from an optional Image Based Light source.
-// Precomputed Environment Maps are required uniform inputs and are computed as outlined in [1].
-// See our README.md on Environment Maps [3] for additional discussion.
-float3 getIBLContribution(PBRInfo pbrInputs, float3 n, float3 reflection)
+float GeometrySchlickGGX(float NdotV, float roughness)
 {
-    float mipCount = 9.0; // resolution of 512x512
-    float lod = (pbrInputs.perceptualRoughness * mipCount);
-  
-    // retrieve a scale and bias to F0. See [1], Figure 3
-    float2 val = float2(pbrInputs.NdotV, 1.0 - pbrInputs.perceptualRoughness);
-    float3 brdf = SRGBtoLINEAR(SAMPLE_TEXTURE(brdfLutTexture, val)).rgb;
+    float r = (roughness + 1.0);
+    float k = (r * r) / 8.0;
 
-    float3 diffuseLight = SRGBtoLINEAR(SAMPLE_CUBEMAP(envDiffuseTexture, n)).rgb;
+    float num = NdotV;
+    float denom = NdotV * (1.0 - k) + k;
 
-#ifdef USE_TEX_LOD
-	float4 reflectionWithLOD = float4(reflection, 0);
-    float3 specularLight = SRGBtoLINEAR(SAMPLE_CUBEMAP_LOD(envSpecularTexture, reflectionWithLOD)).rgb;
-#else
-    float3 specularLight = SRGBtoLINEAR(SAMPLE_CUBEMAP(envSpecularTexture, reflection)).rgb;
-#endif
-
-    float3 diffuse = diffuseLight * pbrInputs.diffuseColor;
-    float3 specular = specularLight * (pbrInputs.specularColor * brdf.x + brdf.y);
-
-    // For presentation, this allows us to disable IBL terms
-    diffuse *= scaleIBLAmbient.x;
-    specular *= scaleIBLAmbient.y;
-
-    return diffuse + specular;
-}
-#endif
-
-// Basic Lambertian diffuse
-// Implementation from Lambert's Photometria https://archive.org/details/lambertsphotome00lambgoog
-// See also [1], Equation 1
-float3 diffuse(PBRInfo pbrInputs)
-{
-    return pbrInputs.diffuseColor / M_PI;
+    return num / denom;
 }
 
-// The following equation models the Fresnel reflectance term of the spec equation (aka F())
-// Implementation of fresnel from [4], Equation 15
-float3 specularReflection(PBRInfo pbrInputs)
+float GeometrySmith(float3 N, float3 V, float3 L, float roughness)
 {
-    return pbrInputs.reflectance0 + (pbrInputs.reflectance90 - pbrInputs.reflectance0) * pow(clamp(1.0 - pbrInputs.VdotH, 0.0, 1.0), 5.0);
+    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;
 }
 
-// This calculates the specular geometric attenuation (aka G()),
-// where rougher material will reflect less light back to the viewer.
-// This implementation is based on [1] Equation 4, and we adopt their modifications to
-// alphaRoughness as input as originally proposed in [2].
-float geometricOcclusion(PBRInfo pbrInputs)
+// The case where we have no texture maps for any PBR data
+float4 None(PixelShaderInput input) : SV_TARGET
 {
-    float NdotL = pbrInputs.NdotL;
-    float NdotV = pbrInputs.NdotV;
-    float r = pbrInputs.alphaRoughness;
+    float3 N = normalize(input.NormalWS);
+    float3 V = normalize(EyePosition - input.PositionWS);
 
-    float attenuationL = 2.0 * NdotL / (NdotL + sqrt(r * r + (1.0 - r * r) * (NdotL * NdotL)));
-    float attenuationV = 2.0 * NdotV / (NdotV + sqrt(r * r + (1.0 - r * r) * (NdotV * NdotV)));
-    return attenuationL * attenuationV;
-}
+    float3 Lo = float3(0.0, 0.0, 0.0);
 
-// The following equation(s) model the distribution of microfacet normals across the area being drawn (aka D())
-// Implementation from "Average Irregularity Representation of a Roughened Surface for Ray Reflection" by T. S. Trowbridge, and K. P. Reitz
-// Follows the distribution function recommended in the SIGGRAPH 2013 course notes from EPIC Games [1], Equation 3.
-float microfacetDistribution(PBRInfo pbrInputs)
-{
-    float roughnessSq = pbrInputs.alphaRoughness * pbrInputs.alphaRoughness;
-    float f = (pbrInputs.NdotH * roughnessSq - pbrInputs.NdotH) * pbrInputs.NdotH + 1.0;
-    return roughnessSq / (M_PI * f * f);
-}
+    for (int i = 0; i < 4; i++)
+    {
+        float3 lightDir = LightPositions[i] - input.PositionWS;
+        float3 L = normalize(lightDir);
+        float3 H = normalize(V + L);
 
-float4 main_ps(PixelShaderInput input) : SV_TARGET
-{
-    // Metallic and Roughness material properties are packed together
-    // In glTF, these factors can be specified by fixed scalar values
-    // or from a metallic-roughness map
-    float perceptualRoughness = metallicRoughnessValues.y;
-    float metallic = metallicRoughnessValues.x;
+        float distance = length(lightDir);
+        float attenuation = 1.0 / (distance * distance);
+        float3 radiance = LightColors[i] * attenuation;
 
-#ifdef HAS_METALROUGHNESSMAP
-    // Roughness is stored in the 'g' channel, metallic is stored in the 'b' channel.
-    // This layout intentionally reserves the 'r' channel for (optional) occlusion map data
-    float4 mrSample = SAMPLE_TEXTURE(metallicRoughnessTexture, input.texcoord);
+        float3 F0 = float3(0.04, 0.04, 0.04);
+        F0 = lerp(F0, Albedo, Metallic);
+        float3 F = FresnelSchlick(max(dot(H, V), 0.0), F0);
 
-	// Had to reverse the order of the channels here - TODO: investigate..
-    perceptualRoughness = mrSample.g * perceptualRoughness;
-    metallic = mrSample.b * metallic;
-#endif
+        float NDF = DistributionGGX(N, H, Roughness);
+        float G = GeometrySmith(N, V, L, Roughness);
 
-    perceptualRoughness = clamp(perceptualRoughness, c_MinRoughness, 1.0);
-    metallic = clamp(metallic, 0.0, 1.0);
+        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);
 
-    // Roughness is authored as perceptual roughness; as is convention,
-    // convert to material roughness by squaring the perceptual roughness [2].
-    float alphaRoughness = perceptualRoughness * perceptualRoughness;
+        float3 kS = F;
+        float3 kD = float3(1.0, 1.0, 1.0) - kS;
 
-    // The albedo may be defined from a base texture or a flat color
+        kD *= 1.0 - Metallic;
 
-#ifdef HAS_BASECOLORMAP
-    float4 baseColor = SRGBtoLINEAR(SAMPLE_TEXTURE(baseColourTexture, input.texcoord)) * baseColorFactor;
-#else
-    float4 baseColor = baseColorFactor;
-#endif
+        float NdotL = max(dot(N, L), 0.0);
+        Lo += (kD * Albedo / PI + specular) * radiance * NdotL;
+    }
 
-    float3 f0 = float3(0.04, 0.04, 0.04);
-    float3 diffuseColor = baseColor.rgb * (float3(1.0, 1.0, 1.0) - f0);
+    float3 ambient = float3(0.03, 0.03, 0.03) * Albedo * AO;
+    float3 color = ambient + Lo;
 
-    diffuseColor *= 1.0 - metallic;
-
-    float3 specularColor = lerp(f0, baseColor.rgb, metallic);
-
-    // Compute reflectance.
-    float reflectance = max(max(specularColor.r, specularColor.g), specularColor.b);
-
-    // For typical incident reflectance range (between 4% to 100%) set the grazing reflectance to 100% for typical fresnel effect.
-    // For very low reflectance range on highly diffuse objects (below 4%), incrementally reduce grazing reflecance to 0%.
-    float reflectance90 = clamp(reflectance * 25.0, 0.0, 1.0);
-    float3 specularEnvironmentR0 = specularColor.rgb;
-    float3 specularEnvironmentR90 = float3(1.0, 1.0, 1.0) * reflectance90;
-
-    float3 n = getNormal(input.poswithoutw, input.normal, input.texcoord); // normal at surface point
-    float3 v = normalize(camera - input.poswithoutw); // Vector from surface point to camera
-    
-    float3 l = normalize(lightDir); // Vector from surface point to light
-    float3 h = normalize(l + v); // Half vector between both l and v
-    float3 reflection = -normalize(reflect(v, n));
-
-    float NdotL = clamp(dot(n, l), 0.001, 1.0);
-    float NdotV = abs(dot(n, v)) + 0.001;
-    float NdotH = clamp(dot(n, h), 0.0, 1.0);
-    float LdotH = clamp(dot(l, h), 0.0, 1.0);
-    float VdotH = clamp(dot(v, h), 0.0, 1.0);
-
-    PBRInfo pbrInputs;
-    pbrInputs.NdotL = NdotL;
-    pbrInputs.NdotV = NdotV;
-    pbrInputs.NdotH = NdotH;
-    pbrInputs.LdotH = LdotH;
-    pbrInputs.VdotH = VdotH;
-    pbrInputs.perceptualRoughness = perceptualRoughness;
-    pbrInputs.metalness = metallic;
-    pbrInputs.reflectance0 = specularEnvironmentR0;
-    pbrInputs.reflectance90 = specularEnvironmentR90;
-    pbrInputs.alphaRoughness = alphaRoughness;
-    pbrInputs.diffuseColor = diffuseColor;
-    pbrInputs.specularColor = specularColor;
-
-    // Calculate the shading terms for the microfacet specular shading model
-    float3 F = specularReflection(pbrInputs);
-    
-    float G = geometricOcclusion(pbrInputs);
-    float D = microfacetDistribution(pbrInputs);
-
-    // Calculation of analytical lighting contribution
-    float3 diffuseContrib = (1.0 - F) * diffuse(pbrInputs);
-    float3 specContrib = F * G * D / (4.0 * NdotL * NdotV);
-    float3 color = NdotL * lightColour * (diffuseContrib + specContrib);
-
-    
-    // Calculate lighting contribution from image based lighting source (IBL)
-#ifdef USE_IBL
-    color += getIBLContribution(pbrInputs, n, reflection);
-#endif
-
-    // Apply optional PBR terms for additional (optional) shading
-#ifdef HAS_OCCLUSIONMAP
-    float ao = SAMPLE_TEXTURE(occlusionTexture, input.texcoord).r;
-    color = lerp(color, color * ao, occlusionStrength);
-#endif
-
-#ifdef HAS_EMISSIVEMAP
-    float3 emissive = SRGBtoLINEAR(SAMPLE_TEXTURE(emissionTexture, input.texcoord)).rgb * emissiveFactor;
-    color += emissive;
-#endif
-
-    // This section uses lerp to override final color for reference app visualization
-    // of various parameters in the lighting equation.
-    color = lerp(color, F, scaleFGDSpec.x);
-    color = lerp(color, float3(G, G, G), scaleFGDSpec.y);
-    color = lerp(color, float3(D, D, D), scaleFGDSpec.z);
-    color = lerp(color, specContrib, scaleFGDSpec.w);
-    color = lerp(color, diffuseContrib, scaleDiffBaseMR.x);
-    color = lerp(color, baseColor.rgb, scaleDiffBaseMR.y);
-    color = lerp(color, float3(metallic, metallic, metallic), scaleDiffBaseMR.z);
-    color = lerp(color, float3(perceptualRoughness, perceptualRoughness, perceptualRoughness), scaleDiffBaseMR.w);
+    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 PBR
 {
-	Pass pass1
-	{
-		VertexShader = compile vs_3_0 main_vs();
-		PixelShader = compile ps_3_0 main_ps();
-	}
+    Pass Pass1
+    {
+        VertexShader = compile vs_3_0 main_vs();
+        PixelShader = compile ps_3_0 None();
+    }
 }
diff --git a/Effects/PBREffect.fxb b/Effects/PBREffect.fxb
index c6d92be..bcc23fa 100644
Binary files a/Effects/PBREffect.fxb and b/Effects/PBREffect.fxb differ
diff --git a/Effects/ReferencePBREffect.fx b/Effects/ReferencePBREffect.fx
new file mode 100644
index 0000000..61609b9
--- /dev/null
+++ b/Effects/ReferencePBREffect.fx
@@ -0,0 +1,410 @@
+
+#include "Macros.fxh"
+
+#define NORMALS
+#define UV
+#define HAS_BASECOLORMAP
+
+// A constant buffer that stores the three basic column-major matrices for composing geometry.
+cbuffer ModelViewProjectionConstantBuffer : register(b0)
+{
+    matrix model;
+    matrix view;
+    matrix projection;
+};
+
+// Per-vertex data used as input to the vertex shader.
+struct VertexShaderInput
+{
+    float4 position : POSITION;
+#ifdef NORMALS
+    float3 normal : NORMAL;
+#endif
+#ifdef UV
+    float2 texcoord : TEXCOORD0;
+#endif
+};
+
+// Per-pixel color data passed through the pixel shader.
+struct PixelShaderInput
+{
+    float4 position : SV_POSITION;
+    float4 positionWS : TEXCOORD1;
+    float3 normalWS : TEXCOORD2;
+
+#ifdef NORMALS
+    float3 normal : NORMAL;
+#endif
+
+    float2 texcoord : TEXCOORD0;
+};
+
+PixelShaderInput main_vs(VertexShaderInput input)
+{
+    PixelShaderInput output;
+
+	// Transform the vertex position into projected space.
+    float4 pos = mul(input.position, model);
+
+#ifdef NORMALS
+    // If we have normals...
+    output.normal = normalize(mul(float4(input.normal.xyz, 0.0), model));
+#endif
+
+#ifdef UV
+    output.texcoord = input.texcoord;
+#else
+    output.texcoord = float2(0.0f, 0.0f);
+#endif
+
+#ifdef HAS_NORMALS
+#ifdef HAS_TANGENTS
+  vec3 normalW = normalize(vec3(u_ModelMatrix * vec4(a_Normal.xyz, 0.0)));
+  vec3 tangentW = normalize(vec3(u_ModelMatrix * vec4(a_Tangent.xyz, 0.0)));
+  vec3 bitangentW = cross(normalW, tangentW) * a_Tangent.w;
+  v_TBN = mat3(tangentW, bitangentW, normalW);
+#else // HAS_TANGENTS != 1
+  v_Normal = normalize(vec3(u_ModelMatrix * vec4(a_Normal.xyz, 0.0)));
+#endif
+#endif
+
+    // Transform the vertex position into projected space.
+    pos = mul(pos, view);
+    pos = mul(pos, projection);
+    output.position = pos;
+
+    return output;
+}
+
+//
+// This fragment shader defines a reference implementation for Physically Based Shading of
+// a microfacet surface material defined by a glTF model.
+//
+// References:
+// [1] Real Shading in Unreal Engine 4
+//     http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
+// [2] Physically Based Shading at Disney
+//     http://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf
+// [3] README.md - Environment Maps
+//     https://github.com/KhronosGroup/glTF-WebGL-PBR/#environment-maps
+// [4] "An Inexpensive BRDF Model for Physically based Rendering" by Christophe Schlick
+//     https://www.cs.virginia.edu/~jdl/bib/appearance/analytic%20models/schlick94b.pdf
+
+#define NORMALS
+#define UV
+#define HAS_NORMALS
+// #define USE_IBL
+#define USE_TEX_LOD
+
+DECLARE_TEXTURE(baseColourTexture, 0);
+DECLARE_TEXTURE(normalTexture, 1);
+DECLARE_TEXTURE(emissionTexture, 2);
+DECLARE_TEXTURE(occlusionTexture, 3);
+DECLARE_TEXTURE(metallicRoughnessTexture, 4);
+DECLARE_CUBEMAP(envDiffuseTexture, 8);
+DECLARE_TEXTURE(brdfLutTexture, 9);
+DECLARE_CUBEMAP(envSpecularTexture, 10);
+
+cbuffer cbPerFrame : register(b0)
+{
+    float3 lightDir;
+	float3 lightColour;
+};
+
+cbuffer cbPerObject : register(b1)
+{
+    float normalScale;
+    float3 emissiveFactor;
+    float occlusionStrength;
+    float2 metallicRoughnessValues;
+    float4 baseColorFactor;
+    float3 camera;
+
+    // debugging flags used for shader output of intermediate PBR variables
+    float4 scaleDiffBaseMR;
+    float4 scaleFGDSpec;
+    float4 scaleIBLAmbient;
+};
+
+#ifdef HAS_NORMALS
+#ifdef HAS_TANGENTS
+varying mat3 v_TBN;
+#else
+#endif
+#endif
+
+// Encapsulate the various inputs used by the various functions in the shading equation
+// We store values in this struct to simplify the integration of alternative implementations
+// of the shading terms, outlined in the Readme.MD Appendix.
+struct PBRInfo
+{
+    float NdotL; // cos angle between normal and light direction
+    float NdotV; // cos angle between normal and view direction
+    float NdotH; // cos angle between normal and half vector
+    float LdotH; // cos angle between light direction and half vector
+    float VdotH; // cos angle between view direction and half vector
+    float perceptualRoughness; // roughness value, as authored by the model creator (input to shader)
+    float metalness; // metallic value at the surface
+    float3 reflectance0; // full reflectance color (normal incidence angle)
+    float3 reflectance90; // reflectance color at grazing angle
+    float alphaRoughness; // roughness mapped to a more linear change in the roughness (proposed by [2])
+    float3 diffuseColor; // color contribution from diffuse lighting
+    float3 specularColor; // color contribution from specular lighting
+};
+
+static const float M_PI = 3.141592653589793;
+static const float c_MinRoughness = 0.04;
+
+float4 SRGBtoLINEAR(float4 srgbIn)
+{
+#ifdef MANUAL_SRGB
+#ifdef SRGB_FAST_APPROXIMATION
+    float3 linOut = pow(srgbIn.xyz,float3(2.2, 2.2, 2.2));
+#else //SRGB_FAST_APPROXIMATION
+    float3 bLess = step(float3(0.04045, 0.04045, 0.04045), srgbIn.xyz);
+    float3 linOut = lerp(srgbIn.xyz / float3(12.92, 12.92, 12.92), pow((srgbIn.xyz + float3(0.055, 0.055, 0.055)) / float3(1.055, 1.055, 1.055), float3(2.4, 2.4, 2.4)), bLess);
+#endif //SRGB_FAST_APPROXIMATION
+    return float4(linOut,srgbIn.w);;
+#else //MANUAL_SRGB
+    return srgbIn;
+#endif //MANUAL_SRGB
+}
+
+// Find the normal for this fragment, pulling either from a predefined normal map
+// or from the interpolated mesh normal and tangent attributes.
+float3 getNormal(float3 position, float3 normal, float2 uv)
+{
+    // Retrieve the tangent space matrix
+#ifndef HAS_TANGENTS
+    float3 pos_dx = ddx(position);
+    float3 pos_dy = ddy(position);
+    float3 tex_dx = ddx(float3(uv, 0.0));
+    float3 tex_dy = ddy(float3(uv, 0.0));
+    float3 t = (tex_dy.y * pos_dx - tex_dx.y * pos_dy) / (tex_dx.x * tex_dy.y - tex_dy.x * tex_dx.y);
+
+#ifdef HAS_NORMALS
+    float3 ng = normalize(normal);
+#else
+    float3 ng = cross(pos_dx, pos_dy);
+#endif
+
+    t = normalize(t - ng * dot(ng, t));
+    float3 b = normalize(cross(ng, t));
+    row_major float3x3 tbn = float3x3(t, b, ng);
+
+#else // HAS_TANGENTS
+    mat3 tbn = v_TBN;
+#endif
+
+#ifdef HAS_NORMALMAP
+    float3 n = SAMPLE_TEXTURE(normalTexture, uv).rgb;
+
+    // Need to check the multiplication is equivalent..
+    n = normalize(mul(((2.0 * n - 1.0) * float3(normalScale, normalScale, 1.0)), tbn));
+#else
+    float3 n = tbn[2].xyz;
+#endif
+
+    return n;
+}
+
+#ifdef USE_IBL
+// Calculation of the lighting contribution from an optional Image Based Light source.
+// Precomputed Environment Maps are required uniform inputs and are computed as outlined in [1].
+// See our README.md on Environment Maps [3] for additional discussion.
+float3 getIBLContribution(PBRInfo pbrInputs, float3 n, float3 reflection)
+{
+    float mipCount = 9.0; // resolution of 512x512
+    float lod = (pbrInputs.perceptualRoughness * mipCount);
+  
+    // retrieve a scale and bias to F0. See [1], Figure 3
+    float2 val = float2(pbrInputs.NdotV, 1.0 - pbrInputs.perceptualRoughness);
+    float3 brdf = SRGBtoLINEAR(SAMPLE_TEXTURE(brdfLutTexture, val)).rgb;
+
+    float3 diffuseLight = SRGBtoLINEAR(SAMPLE_CUBEMAP(envDiffuseTexture, n)).rgb;
+
+#ifdef USE_TEX_LOD
+	float4 reflectionWithLOD = float4(reflection, 0);
+    float3 specularLight = SRGBtoLINEAR(SAMPLE_CUBEMAP_LOD(envSpecularTexture, reflectionWithLOD)).rgb;
+#else
+    float3 specularLight = SRGBtoLINEAR(SAMPLE_CUBEMAP(envSpecularTexture, reflection)).rgb;
+#endif
+
+    float3 diffuse = diffuseLight * pbrInputs.diffuseColor;
+    float3 specular = specularLight * (pbrInputs.specularColor * brdf.x + brdf.y);
+
+    // For presentation, this allows us to disable IBL terms
+    diffuse *= scaleIBLAmbient.x;
+    specular *= scaleIBLAmbient.y;
+
+    return diffuse + specular;
+}
+#endif
+
+// Basic Lambertian diffuse
+// Implementation from Lambert's Photometria https://archive.org/details/lambertsphotome00lambgoog
+// See also [1], Equation 1
+float3 diffuse(PBRInfo pbrInputs)
+{
+    return pbrInputs.diffuseColor / M_PI;
+}
+
+// The following equation models the Fresnel reflectance term of the spec equation (aka F())
+// Implementation of fresnel from [4], Equation 15
+float3 specularReflection(PBRInfo pbrInputs)
+{
+    return pbrInputs.reflectance0 + (pbrInputs.reflectance90 - pbrInputs.reflectance0) * pow(clamp(1.0 - pbrInputs.VdotH, 0.0, 1.0), 5.0);
+}
+
+// This calculates the specular geometric attenuation (aka G()),
+// where rougher material will reflect less light back to the viewer.
+// This implementation is based on [1] Equation 4, and we adopt their modifications to
+// alphaRoughness as input as originally proposed in [2].
+float geometricOcclusion(PBRInfo pbrInputs)
+{
+    float NdotL = pbrInputs.NdotL;
+    float NdotV = pbrInputs.NdotV;
+    float r = pbrInputs.alphaRoughness;
+
+    float attenuationL = 2.0 * NdotL / (NdotL + sqrt(r * r + (1.0 - r * r) * (NdotL * NdotL)));
+    float attenuationV = 2.0 * NdotV / (NdotV + sqrt(r * r + (1.0 - r * r) * (NdotV * NdotV)));
+    return attenuationL * attenuationV;
+}
+
+// The following equation(s) model the distribution of microfacet normals across the area being drawn (aka D())
+// Implementation from "Average Irregularity Representation of a Roughened Surface for Ray Reflection" by T. S. Trowbridge, and K. P. Reitz
+// Follows the distribution function recommended in the SIGGRAPH 2013 course notes from EPIC Games [1], Equation 3.
+float microfacetDistribution(PBRInfo pbrInputs)
+{
+    float roughnessSq = pbrInputs.alphaRoughness * pbrInputs.alphaRoughness;
+    float f = (pbrInputs.NdotH * roughnessSq - pbrInputs.NdotH) * pbrInputs.NdotH + 1.0;
+    return roughnessSq / (M_PI * f * f);
+}
+
+float4 main_ps(PixelShaderInput input) : SV_TARGET
+{
+    // Metallic and Roughness material properties are packed together
+    // In glTF, these factors can be specified by fixed scalar values
+    // or from a metallic-roughness map
+    float perceptualRoughness = metallicRoughnessValues.y;
+    float metallic = metallicRoughnessValues.x;
+
+#ifdef HAS_METALROUGHNESSMAP
+    // Roughness is stored in the 'g' channel, metallic is stored in the 'b' channel.
+    // This layout intentionally reserves the 'r' channel for (optional) occlusion map data
+    float4 mrSample = SAMPLE_TEXTURE(metallicRoughnessTexture, input.texcoord);
+
+	// Had to reverse the order of the channels here - TODO: investigate..
+    perceptualRoughness = mrSample.g * perceptualRoughness;
+    metallic = mrSample.b * metallic;
+#endif
+
+    perceptualRoughness = clamp(perceptualRoughness, c_MinRoughness, 1.0);
+    metallic = clamp(metallic, 0.0, 1.0);
+
+    // Roughness is authored as perceptual roughness; as is convention,
+    // convert to material roughness by squaring the perceptual roughness [2].
+    float alphaRoughness = perceptualRoughness * perceptualRoughness;
+
+    // The albedo may be defined from a base texture or a flat color
+
+#ifdef HAS_BASECOLORMAP
+    float4 baseColor = SRGBtoLINEAR(SAMPLE_TEXTURE(baseColourTexture, input.texcoord)) * baseColorFactor;
+#else
+    float4 baseColor = baseColorFactor;
+#endif
+
+    float3 f0 = float3(0.04, 0.04, 0.04);
+    float3 diffuseColor = baseColor.rgb * (float3(1.0, 1.0, 1.0) - f0);
+
+    diffuseColor *= 1.0 - metallic;
+
+    float3 specularColor = lerp(f0, baseColor.rgb, metallic);
+
+    // Compute reflectance.
+    float reflectance = max(max(specularColor.r, specularColor.g), specularColor.b);
+
+    // For typical incident reflectance range (between 4% to 100%) set the grazing reflectance to 100% for typical fresnel effect.
+    // For very low reflectance range on highly diffuse objects (below 4%), incrementally reduce grazing reflecance to 0%.
+    float reflectance90 = clamp(reflectance * 25.0, 0.0, 1.0);
+    float3 specularEnvironmentR0 = specularColor.rgb;
+    float3 specularEnvironmentR90 = float3(1.0, 1.0, 1.0) * reflectance90;
+
+    float3 n = getNormal(input.poswithoutw, input.normal, input.texcoord); // normal at surface point
+    float3 v = normalize(camera - input.poswithoutw); // Vector from surface point to camera
+    
+    float3 l = normalize(lightDir); // Vector from surface point to light
+    float3 h = normalize(l + v); // Half vector between both l and v
+    float3 reflection = -normalize(reflect(v, n));
+
+    float NdotL = clamp(dot(n, l), 0.001, 1.0);
+    float NdotV = abs(dot(n, v)) + 0.001;
+    float NdotH = clamp(dot(n, h), 0.0, 1.0);
+    float LdotH = clamp(dot(l, h), 0.0, 1.0);
+    float VdotH = clamp(dot(v, h), 0.0, 1.0);
+
+    PBRInfo pbrInputs;
+    pbrInputs.NdotL = NdotL;
+    pbrInputs.NdotV = NdotV;
+    pbrInputs.NdotH = NdotH;
+    pbrInputs.LdotH = LdotH;
+    pbrInputs.VdotH = VdotH;
+    pbrInputs.perceptualRoughness = perceptualRoughness;
+    pbrInputs.metalness = metallic;
+    pbrInputs.reflectance0 = specularEnvironmentR0;
+    pbrInputs.reflectance90 = specularEnvironmentR90;
+    pbrInputs.alphaRoughness = alphaRoughness;
+    pbrInputs.diffuseColor = diffuseColor;
+    pbrInputs.specularColor = specularColor;
+
+    // Calculate the shading terms for the microfacet specular shading model
+    float3 F = specularReflection(pbrInputs);
+    
+    float G = geometricOcclusion(pbrInputs);
+    float D = microfacetDistribution(pbrInputs);
+
+    // Calculation of analytical lighting contribution
+    float3 diffuseContrib = (1.0 - F) * diffuse(pbrInputs);
+    float3 specContrib = F * G * D / (4.0 * NdotL * NdotV);
+    float3 color = NdotL * lightColour * (diffuseContrib + specContrib);
+
+    
+    // Calculate lighting contribution from image based lighting source (IBL)
+#ifdef USE_IBL
+    color += getIBLContribution(pbrInputs, n, reflection);
+#endif
+
+    // Apply optional PBR terms for additional (optional) shading
+#ifdef HAS_OCCLUSIONMAP
+    float ao = SAMPLE_TEXTURE(occlusionTexture, input.texcoord).r;
+    color = lerp(color, color * ao, occlusionStrength);
+#endif
+
+#ifdef HAS_EMISSIVEMAP
+    float3 emissive = SRGBtoLINEAR(SAMPLE_TEXTURE(emissionTexture, input.texcoord)).rgb * emissiveFactor;
+    color += emissive;
+#endif
+
+    // This section uses lerp to override final color for reference app visualization
+    // of various parameters in the lighting equation.
+    color = lerp(color, F, scaleFGDSpec.x);
+    color = lerp(color, float3(G, G, G), scaleFGDSpec.y);
+    color = lerp(color, float3(D, D, D), scaleFGDSpec.z);
+    color = lerp(color, specContrib, scaleFGDSpec.w);
+    color = lerp(color, diffuseContrib, scaleDiffBaseMR.x);
+    color = lerp(color, baseColor.rgb, scaleDiffBaseMR.y);
+    color = lerp(color, float3(metallic, metallic, metallic), scaleDiffBaseMR.z);
+    color = lerp(color, float3(perceptualRoughness, perceptualRoughness, perceptualRoughness), scaleDiffBaseMR.w);
+
+    //return float4(baseColor.xyz, 1.0);
+    return float4(color, 1.0);
+}
+
+Technique PBR
+{
+	Pass pass1
+	{
+		VertexShader = compile vs_3_0 main_vs();
+		PixelShader = compile ps_3_0 main_ps();
+	}
+}
diff --git a/Importer.cs b/Importer.cs
index ce4b783..7f1b75c 100644
--- a/Importer.cs
+++ b/Importer.cs
@@ -141,98 +141,113 @@ namespace Smuggler
 
             if (primitive.Material != null)
             {
-                var normalChannel = primitive.Material.FindChannel("Normal");
-                if (normalChannel.HasValue)
+                //var normalChannel = primitive.Material.FindChannel("Normal");
+                //if (normalChannel.HasValue)
+                //{
+                //    if (normalChannel.Value.Texture != null)
+                //    {
+                //        effect.NormalTexture = Texture2D.FromStream(
+                //            graphicsDevice,
+                //            normalChannel.Value.Texture.PrimaryImage.Content.Open()
+                //        );
+                //    }
+
+                //    effect.NormalScale = normalChannel.Value.Parameter.X;
+                //}
+
+                //var occlusionChannel = primitive.Material.FindChannel("Occlusion");
+                //if (occlusionChannel.HasValue)
+                //{
+                //    if (occlusionChannel.Value.Texture != null)
+                //    {
+                //        effect.OcclusionTexture = Texture2D.FromStream(
+                //            graphicsDevice,
+                //            occlusionChannel.Value.Texture.PrimaryImage.Content.Open()
+                //        );
+                //    }
+
+                //    effect.OcclusionStrength = occlusionChannel.Value.Parameter.X;
+                //}
+
+                //var emissiveChannel = primitive.Material.FindChannel("Emissive");
+                //if (emissiveChannel.HasValue)
+                //{
+                //    if (emissiveChannel.Value.Texture != null)
+                //    {
+                //        effect.EmissionTexture = Texture2D.FromStream(
+                //            graphicsDevice,
+                //            emissiveChannel.Value.Texture.PrimaryImage.Content.Open()
+                //        );
+                //    }
+
+                //    var parameter = emissiveChannel.Value.Parameter;
+
+                //    effect.EmissiveFactor = new Vector3(
+                //        parameter.X,
+                //        parameter.Y,
+                //        parameter.Z
+                //    );
+                //}
+
+                var albedoChannel = primitive.Material.FindChannel("BaseColor");
+                if (albedoChannel.HasValue)
                 {
-                    if (normalChannel.Value.Texture != null)
-                    {
-                        effect.NormalTexture = Texture2D.FromStream(
-                            graphicsDevice,
-                            normalChannel.Value.Texture.PrimaryImage.Content.Open()
-                        );
-                    }
+                    //if (albedoChannel.Value.Texture != null)
+                    //{
+                    //    effect.BaseColourTexture = Texture2D.FromStream(
+                    //        graphicsDevice,
+                    //        albedoChannel.Value.Texture.PrimaryImage.Content.Open()
+                    //    );
+                    //}
 
-                    effect.NormalScale = normalChannel.Value.Parameter.X;
-                }
+                    var parameter = albedoChannel.Value.Parameter;
 
-                var occlusionChannel = primitive.Material.FindChannel("Occlusion");
-                if (occlusionChannel.HasValue)
-                {
-                    if (occlusionChannel.Value.Texture != null)
-                    {
-                        effect.OcclusionTexture = Texture2D.FromStream(
-                            graphicsDevice,
-                            occlusionChannel.Value.Texture.PrimaryImage.Content.Open()
-                        );
-                    }
-
-                    effect.OcclusionStrength = occlusionChannel.Value.Parameter.X;
-                }
-
-                var emissiveChannel = primitive.Material.FindChannel("Emissive");
-                if (emissiveChannel.HasValue)
-                {
-                    if (emissiveChannel.Value.Texture != null)
-                    {
-                        effect.EmissionTexture = Texture2D.FromStream(
-                            graphicsDevice,
-                            emissiveChannel.Value.Texture.PrimaryImage.Content.Open()
-                        );
-                    }
-
-                    var parameter = emissiveChannel.Value.Parameter;
-
-                    effect.EmissiveFactor = new Vector3(
+                    effect.Albedo = new Vector3(
                         parameter.X,
                         parameter.Y,
                         parameter.Z
                     );
                 }
 
-                var baseColorChannel = primitive.Material.FindChannel("BaseColor");
-                if (baseColorChannel.HasValue)
-                {
-                    if (baseColorChannel.Value.Texture != null)
-                    {
-                        effect.BaseColourTexture = Texture2D.FromStream(
-                            graphicsDevice,
-                            baseColorChannel.Value.Texture.PrimaryImage.Content.Open()
-                        );
-                    }
-
-                    var parameter = baseColorChannel.Value.Parameter;
-
-                    effect.BaseColorFactor = new Vector4(
-                        parameter.X,
-                        parameter.Y,
-                        parameter.Z,
-                        parameter.W
-                    );
-                }
-
                 var metallicRoughnessChannel = primitive.Material.FindChannel("MetallicRoughness");
                 if (metallicRoughnessChannel.HasValue)
                 {
-                    if (metallicRoughnessChannel.Value.Texture != null)
-                    {
-                        effect.MetallicRoughnessTexture = Texture2D.FromStream(
-                            graphicsDevice,
-                            metallicRoughnessChannel.Value.Texture.PrimaryImage.Content.Open()
-                        );
-                    }
+                    //if (metallicRoughnessChannel.Value.Texture != null)
+                    //{
+                    //    effect.MetallicRoughnessTexture = Texture2D.FromStream(
+                    //        graphicsDevice,
+                    //        metallicRoughnessChannel.Value.Texture.PrimaryImage.Content.Open()
+                    //    );
+                    //}
 
                     var parameter = metallicRoughnessChannel.Value.Parameter;
 
-                    effect.MetallicRoughnessValue = new Vector2(
-                        parameter.X,
-                        parameter.Y
-                    );
+                    effect.Metallic = parameter.X;
+                    effect.Roughness = parameter.Y;
                 }
             }
 
-            effect.Light = new PBRLight(
-                new Vector3(0.5f, 0.5f, -0.5f), 
-                new Vector3(10f, 10f, 10f)
+            effect.Albedo = new Vector3(0.5f, 0, 0);
+            effect.AO = 1f;
+
+            effect.Lights[0] = new PBRLight(
+                new Vector3(-10f, 10f, 10f),
+                new Vector3(300f, 300f, 300f)
+            );
+
+            effect.Lights[1] = new PBRLight(
+                new Vector3(10f, 10f, 10f),
+                new Vector3(300f, 300f, 300f)
+            );
+
+            effect.Lights[2] = new PBRLight(
+                new Vector3(-10f, -10f, 10f),
+                new Vector3(300f, 300f, 300f)
+            );
+
+            effect.Lights[3] = new PBRLight(
+                new Vector3(10f, -10f, 10f), 
+                new Vector3(300f, 300f, 300f)
             );
 
             /* FIXME: how to load cube maps from GLTF? */
diff --git a/Smuggler.csproj b/Smuggler.csproj
index 59eef25..e5628ef 100644
--- a/Smuggler.csproj
+++ b/Smuggler.csproj
@@ -8,6 +8,7 @@
     <Copyright>Cassandra Lugo and Evan Hemsley 2020</Copyright>
     <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
     <AssemblyName>Smuggler</AssemblyName>
+    <Platforms>AnyCPU;x86</Platforms>
   </PropertyGroup>
 
   <ItemGroup>
@@ -15,7 +16,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <ProjectReference Include="..\FNA\FNA.Core.csproj"/>
+    <ProjectReference Include="..\FNA\FNA.Core.csproj" />
   </ItemGroup>
 
   <ItemGroup>