diff --git a/interface/resources/shaders/directional_light.frag b/interface/resources/shaders/directional_light.frag
new file mode 100644
index 0000000000..7fb5d83719
--- /dev/null
+++ b/interface/resources/shaders/directional_light.frag
@@ -0,0 +1,26 @@
+#version 120
+
+//
+//  directional_light.frag
+//  fragment shader
+//
+//  Created by Andrzej Kapolka on 9/3/14.
+//  Copyright 2014 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+// the diffuse texture
+uniform sampler2D diffuseMap;
+
+// the normal texture
+uniform sampler2D normalMap;
+
+void main(void) {
+    // compute the base color based on OpenGL lighting model
+    vec4 normal = texture2D(normalMap, gl_TexCoord[0].st);
+    gl_FragColor = vec4((texture2D(diffuseMap, gl_TexCoord[0].st) * (gl_FrontLightModelProduct.sceneColor +
+        gl_FrontLightProduct[0].ambient + gl_FrontLightProduct[0].diffuse * max(0.0, dot(normal * 2.0 -
+            vec4(1.0, 1.0, 1.0, 2.0), gl_LightSource[0].position)))).rgb, normal.a);
+}
diff --git a/interface/resources/shaders/metavoxel_heightfield_cascaded_shadow_map.frag b/interface/resources/shaders/directional_light_cascaded_shadow_map.frag
similarity index 50%
rename from interface/resources/shaders/metavoxel_heightfield_cascaded_shadow_map.frag
rename to interface/resources/shaders/directional_light_cascaded_shadow_map.frag
index 059a4e0296..d16467520e 100644
--- a/interface/resources/shaders/metavoxel_heightfield_cascaded_shadow_map.frag
+++ b/interface/resources/shaders/directional_light_cascaded_shadow_map.frag
@@ -1,10 +1,10 @@
 #version 120
 
 //
-//  metavoxel_heightfield.frag
+//  directional_light.frag
 //  fragment shader
 //
-//  Created by Andrzej Kapolka on 7/28/14.
+//  Created by Andrzej Kapolka on 9/3/14.
 //  Copyright 2014 High Fidelity, Inc.
 //
 //  Distributed under the Apache License, Version 2.0.
@@ -14,6 +14,12 @@
 // the diffuse texture
 uniform sampler2D diffuseMap;
 
+// the normal texture
+uniform sampler2D normalMap;
+
+// the depth texture
+uniform sampler2D depthMap;
+
 // the shadow texture
 uniform sampler2DShadow shadowMap;
 
@@ -21,28 +27,39 @@ uniform sampler2DShadow shadowMap;
 uniform vec3 shadowDistances;
 
 // the inverse of the size of the shadow map
-const float shadowScale = 1.0 / 2048.0;
+uniform float shadowScale;
 
-// the interpolated position
-varying vec4 position;
+// the distance to the near clip plane
+uniform float near;
 
-// the interpolated normal
-varying vec4 normal;
+// scale factor for depth: (far - near) / far
+uniform float depthScale;
+
+// offset for depth texture coordinates
+uniform vec2 depthTexCoordOffset;
+
+// scale for depth texture coordinates
+uniform vec2 depthTexCoordScale;
 
 void main(void) {
+    // compute the view space position using the depth
+    float z = near / (texture2D(depthMap, gl_TexCoord[0].st).r * depthScale - 1.0);
+    vec4 position = vec4((depthTexCoordOffset + gl_TexCoord[0].st * depthTexCoordScale) * z, z, 1.0);
+
     // compute the index of the cascade to use and the corresponding texture coordinates
     int shadowIndex = int(dot(step(vec3(position.z), shadowDistances), vec3(1.0, 1.0, 1.0)));
     vec3 shadowTexCoord = vec3(dot(gl_EyePlaneS[shadowIndex], position), dot(gl_EyePlaneT[shadowIndex], position),
         dot(gl_EyePlaneR[shadowIndex], position));
     
-    // compute the base color based on OpenGL lighting model
-    float diffuse = dot(normalize(normal), gl_LightSource[0].position);
+    // compute the color based on OpenGL lighting model, use the alpha from the normal map
+    vec4 normal = texture2D(normalMap, gl_TexCoord[0].st);
+    float diffuse = dot(normal * 2.0 - vec4(1.0, 1.0, 1.0, 2.0), gl_LightSource[0].position);
     float facingLight = step(0.0, diffuse) * 0.25 *
         (shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, -shadowScale, 0.0)).r +
         shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, shadowScale, 0.0)).r +
         shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, -shadowScale, 0.0)).r +
         shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, shadowScale, 0.0)).r);
-    vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
-        gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
-    gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st);
+    vec4 baseColor = texture2D(diffuseMap, gl_TexCoord[0].st) * (gl_FrontLightModelProduct.sceneColor +
+        gl_FrontLightProduct[0].ambient + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
+    gl_FragColor = vec4(baseColor.rgb, normal.a);
 }
diff --git a/interface/resources/shaders/directional_light_shadow_map.frag b/interface/resources/shaders/directional_light_shadow_map.frag
new file mode 100644
index 0000000000..d4ac254fc5
--- /dev/null
+++ b/interface/resources/shaders/directional_light_shadow_map.frag
@@ -0,0 +1,60 @@
+#version 120
+
+//
+//  directional_light.frag
+//  fragment shader
+//
+//  Created by Andrzej Kapolka on 9/3/14.
+//  Copyright 2014 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+// the diffuse texture
+uniform sampler2D diffuseMap;
+
+// the normal texture
+uniform sampler2D normalMap;
+
+// the depth texture
+uniform sampler2D depthMap;
+
+// the shadow texture
+uniform sampler2DShadow shadowMap;
+
+// the inverse of the size of the shadow map
+uniform float shadowScale;
+
+// the distance to the near clip plane
+uniform float near;
+
+// scale factor for depth: (far - near) / far
+uniform float depthScale;
+
+// offset for depth texture coordinates
+uniform vec2 depthTexCoordOffset;
+
+// scale for depth texture coordinates
+uniform vec2 depthTexCoordScale;
+
+void main(void) {
+    // compute the view space position using the depth
+    float z = near / (texture2D(depthMap, gl_TexCoord[0].st).r * depthScale - 1.0);
+    vec4 position = vec4((depthTexCoordOffset + gl_TexCoord[0].st * depthTexCoordScale) * z, z, 1.0);
+    
+    // compute the corresponding texture coordinates
+    vec3 shadowTexCoord = vec3(dot(gl_EyePlaneS[0], position), dot(gl_EyePlaneT[0], position), dot(gl_EyePlaneR[0], position));
+    
+    // compute the color based on OpenGL lighting model, use the alpha from the normal map
+    vec4 normal = texture2D(normalMap, gl_TexCoord[0].st);
+    float diffuse = dot(normal * 2.0 - vec4(1.0, 1.0, 1.0, 2.0), gl_LightSource[0].position);
+    float facingLight = step(0.0, diffuse) * 0.25 *
+        (shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, -shadowScale, 0.0)).r +
+        shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, shadowScale, 0.0)).r +
+        shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, -shadowScale, 0.0)).r +
+        shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, shadowScale, 0.0)).r);
+    vec4 baseColor = texture2D(diffuseMap, gl_TexCoord[0].st) * (gl_FrontLightModelProduct.sceneColor +
+        gl_FrontLightProduct[0].ambient + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
+    gl_FragColor = vec4(baseColor.rgb, normal.a);
+}
diff --git a/interface/resources/shaders/metavoxel_heightfield.frag b/interface/resources/shaders/metavoxel_heightfield.frag
deleted file mode 100644
index f99a0a6403..0000000000
--- a/interface/resources/shaders/metavoxel_heightfield.frag
+++ /dev/null
@@ -1,25 +0,0 @@
-#version 120
-
-//
-//  metavoxel_heightfield.frag
-//  fragment shader
-//
-//  Created by Andrzej Kapolka on 7/28/14.
-//  Copyright 2014 High Fidelity, Inc.
-//
-//  Distributed under the Apache License, Version 2.0.
-//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-
-// the diffuse texture
-uniform sampler2D diffuseMap;
-
-// the interpolated normal
-varying vec4 normal;
-
-void main(void) {
-    // compute the base color based on OpenGL lighting model
-    vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
-        gl_FrontLightProduct[0].diffuse * max(0.0, dot(normalize(normal), gl_LightSource[0].position)));
-    gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st);
-}
diff --git a/interface/resources/shaders/metavoxel_heightfield.vert b/interface/resources/shaders/metavoxel_heightfield.vert
deleted file mode 100644
index 70cf3f9419..0000000000
--- a/interface/resources/shaders/metavoxel_heightfield.vert
+++ /dev/null
@@ -1,51 +0,0 @@
-#version 120
-
-//
-//  metavoxel_heighfield.vert
-//  vertex shader
-//
-//  Created by Andrzej Kapolka on 7/28/14.
-//  Copyright 2014 High Fidelity, Inc.
-//
-//  Distributed under the Apache License, Version 2.0.
-//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-
-// the height texture
-uniform sampler2D heightMap;
-
-// the distance between height points in texture space
-uniform float heightScale;
-
-// the scale between height and color textures
-uniform float colorScale;
-
-// the interpolated position
-varying vec4 position;
-
-// the interpolated normal
-varying vec4 normal;
-
-void main(void) {
-    // transform and store the normal for interpolation
-    vec2 heightCoord = gl_MultiTexCoord0.st;
-    float deltaX = texture2D(heightMap, heightCoord - vec2(heightScale, 0.0)).r -
-        texture2D(heightMap, heightCoord + vec2(heightScale, 0.0)).r;
-    float deltaZ = texture2D(heightMap, heightCoord - vec2(0.0, heightScale)).r -
-        texture2D(heightMap, heightCoord + vec2(0.0, heightScale)).r;
-    normal = normalize(gl_ModelViewMatrix * vec4(deltaX, heightScale, deltaZ, 0.0));
-    
-    // add the height to the position
-    float height = texture2D(heightMap, heightCoord).r;
-    position = gl_ModelViewMatrix * (gl_Vertex + vec4(0.0, height, 0.0, 0.0));
-    gl_Position = gl_ProjectionMatrix * position;
-    
-    // the zero height should be invisible
-    gl_FrontColor = vec4(1.0, 1.0, 1.0, step(height, 0.0));
-    
-    // pass along the scaled/offset texture coordinates
-    gl_TexCoord[0] = (gl_MultiTexCoord0 - vec4(heightScale, heightScale, 0.0, 0.0)) * colorScale;
-    
-    // and the shadow texture coordinates
-    gl_TexCoord[1] = vec4(dot(gl_EyePlaneS[0], position), dot(gl_EyePlaneT[0], position), dot(gl_EyePlaneR[0], position), 1.0);
-}
diff --git a/interface/resources/shaders/metavoxel_heightfield_base.frag b/interface/resources/shaders/metavoxel_heightfield_base.frag
index 9b64a59e6f..a4523ced23 100644
--- a/interface/resources/shaders/metavoxel_heightfield_base.frag
+++ b/interface/resources/shaders/metavoxel_heightfield_base.frag
@@ -14,7 +14,11 @@
 // the diffuse texture
 uniform sampler2D diffuseMap;
 
+// the interpolated normal
+varying vec4 normal;
+
 void main(void) {
     // compute the base color based on OpenGL lighting model
-    gl_FragColor = gl_Color * texture2D(diffuseMap, gl_TexCoord[0].st);
+    gl_FragData[0] = gl_Color * texture2D(diffuseMap, gl_TexCoord[0].st);
+    gl_FragData[1] = normalize(normal) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0);
 }
diff --git a/interface/resources/shaders/metavoxel_heightfield_base.vert b/interface/resources/shaders/metavoxel_heightfield_base.vert
index 3e4b081d6f..f097426e13 100644
--- a/interface/resources/shaders/metavoxel_heightfield_base.vert
+++ b/interface/resources/shaders/metavoxel_heightfield_base.vert
@@ -20,9 +20,20 @@ uniform float heightScale;
 // the scale between height and color textures
 uniform float colorScale;
 
+// the interpolated normal
+varying vec4 normal;
+
 void main(void) {
+    // transform and store the normal for interpolation
+    vec2 heightCoord = gl_MultiTexCoord0.st;
+    float deltaX = texture2D(heightMap, heightCoord - vec2(heightScale, 0.0)).r -
+        texture2D(heightMap, heightCoord + vec2(heightScale, 0.0)).r;
+    float deltaZ = texture2D(heightMap, heightCoord - vec2(0.0, heightScale)).r -
+        texture2D(heightMap, heightCoord + vec2(0.0, heightScale)).r;
+    normal = normalize(gl_ModelViewMatrix * vec4(deltaX, heightScale, deltaZ, 0.0));
+    
     // add the height to the position
-    float height = texture2D(heightMap, gl_MultiTexCoord0.st).r;
+    float height = texture2D(heightMap, heightCoord).r;
     gl_Position = gl_ModelViewProjectionMatrix * (gl_Vertex + vec4(0.0, height, 0.0, 0.0));
     
     // the zero height should be invisible
diff --git a/interface/resources/shaders/metavoxel_heightfield_light.frag b/interface/resources/shaders/metavoxel_heightfield_light.frag
deleted file mode 100644
index ce3f23e142..0000000000
--- a/interface/resources/shaders/metavoxel_heightfield_light.frag
+++ /dev/null
@@ -1,21 +0,0 @@
-#version 120
-
-//
-//  metavoxel_heightfield_light.frag
-//  fragment shader
-//
-//  Created by Andrzej Kapolka on 8/20/14.
-//  Copyright 2014 High Fidelity, Inc.
-//
-//  Distributed under the Apache License, Version 2.0.
-//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-
-// the interpolated normal
-varying vec4 normal;
-
-void main(void) {
-    // compute the base color based on OpenGL lighting model
-    gl_FragColor = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
-        gl_FrontLightProduct[0].diffuse * max(0.0, dot(normalize(normal), gl_LightSource[0].position)));
-}
diff --git a/interface/resources/shaders/metavoxel_heightfield_light.vert b/interface/resources/shaders/metavoxel_heightfield_light.vert
deleted file mode 100644
index 228d575b81..0000000000
--- a/interface/resources/shaders/metavoxel_heightfield_light.vert
+++ /dev/null
@@ -1,45 +0,0 @@
-#version 120
-
-//
-//  metavoxel_heighfield_light.vert
-//  vertex shader
-//
-//  Created by Andrzej Kapolka on 8/20/14.
-//  Copyright 2014 High Fidelity, Inc.
-//
-//  Distributed under the Apache License, Version 2.0.
-//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-
-// the height texture
-uniform sampler2D heightMap;
-
-// the distance between height points in texture space
-uniform float heightScale;
-
-// the interpolated position
-varying vec4 position;
-
-// the interpolated normal
-varying vec4 normal;
-
-void main(void) {
-    // transform and store the normal for interpolation
-    vec2 heightCoord = gl_MultiTexCoord0.st;
-    float deltaX = texture2D(heightMap, heightCoord - vec2(heightScale, 0.0)).r -
-        texture2D(heightMap, heightCoord + vec2(heightScale, 0.0)).r;
-    float deltaZ = texture2D(heightMap, heightCoord - vec2(0.0, heightScale)).r -
-        texture2D(heightMap, heightCoord + vec2(0.0, heightScale)).r;
-    normal = normalize(gl_ModelViewMatrix * vec4(deltaX, heightScale, deltaZ, 0.0));
-    
-    // add the height to the position
-    float height = texture2D(heightMap, heightCoord).r;
-    position = gl_ModelViewMatrix * (gl_Vertex + vec4(0.0, height, 0.0, 0.0));
-    gl_Position = gl_ProjectionMatrix * position;
-    
-    // the zero height should be invisible
-    gl_FrontColor = vec4(1.0, 1.0, 1.0, step(height, 0.0));
-    
-    // and the shadow texture coordinates
-    gl_TexCoord[1] = vec4(dot(gl_EyePlaneS[0], position), dot(gl_EyePlaneT[0], position), dot(gl_EyePlaneR[0], position), 1.0);
-}
diff --git a/interface/resources/shaders/metavoxel_heightfield_light_cascaded_shadow_map.frag b/interface/resources/shaders/metavoxel_heightfield_light_cascaded_shadow_map.frag
deleted file mode 100644
index 73382eb83c..0000000000
--- a/interface/resources/shaders/metavoxel_heightfield_light_cascaded_shadow_map.frag
+++ /dev/null
@@ -1,44 +0,0 @@
-#version 120
-
-//
-//  metavoxel_heightfield_light_cascaded_shadow_map.frag
-//  fragment shader
-//
-//  Created by Andrzej Kapolka on 8/20/14.
-//  Copyright 2014 High Fidelity, Inc.
-//
-//  Distributed under the Apache License, Version 2.0.
-//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-
-// the shadow texture
-uniform sampler2DShadow shadowMap;
-
-// the distances to the cascade sections
-uniform vec3 shadowDistances;
-
-// the inverse of the size of the shadow map
-const float shadowScale = 1.0 / 2048.0;
-
-// the interpolated position
-varying vec4 position;
-
-// the interpolated normal
-varying vec4 normal;
-
-void main(void) {
-    // compute the index of the cascade to use and the corresponding texture coordinates
-    int shadowIndex = int(dot(step(vec3(position.z), shadowDistances), vec3(1.0, 1.0, 1.0)));
-    vec3 shadowTexCoord = vec3(dot(gl_EyePlaneS[shadowIndex], position), dot(gl_EyePlaneT[shadowIndex], position),
-        dot(gl_EyePlaneR[shadowIndex], position));
-    
-    // compute the base color based on OpenGL lighting model
-    float diffuse = dot(normalize(normal), gl_LightSource[0].position);
-    float facingLight = step(0.0, diffuse) * 0.25 *
-        (shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, -shadowScale, 0.0)).r +
-        shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, shadowScale, 0.0)).r +
-        shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, -shadowScale, 0.0)).r +
-        shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, shadowScale, 0.0)).r);
-    gl_FragColor = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
-        gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
-}
diff --git a/interface/resources/shaders/metavoxel_heightfield_light_shadow_map.frag b/interface/resources/shaders/metavoxel_heightfield_light_shadow_map.frag
deleted file mode 100644
index 4f2df8958b..0000000000
--- a/interface/resources/shaders/metavoxel_heightfield_light_shadow_map.frag
+++ /dev/null
@@ -1,33 +0,0 @@
-#version 120
-
-//
-//  metavoxel_heightfield_light_shadow_map.frag
-//  fragment shader
-//
-//  Created by Andrzej Kapolka on 8/20/14.
-//  Copyright 2014 High Fidelity, Inc.
-//
-//  Distributed under the Apache License, Version 2.0.
-//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-
-// the shadow texture
-uniform sampler2DShadow shadowMap;
-
-// the inverse of the size of the shadow map
-const float shadowScale = 1.0 / 2048.0;
-
-// the interpolated normal
-varying vec4 normal;
-
-void main(void) {
-    // compute the base color based on OpenGL lighting model
-    float diffuse = dot(normalize(normal), gl_LightSource[0].position);
-    float facingLight = step(0.0, diffuse) * 0.25 *
-        (shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, -shadowScale, 0.0)).r +
-        shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, shadowScale, 0.0)).r +
-        shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, -shadowScale, 0.0)).r +
-        shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, shadowScale, 0.0)).r);
-    gl_FragColor = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
-        gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
-}
diff --git a/interface/resources/shaders/metavoxel_heightfield_shadow_map.frag b/interface/resources/shaders/metavoxel_heightfield_shadow_map.frag
deleted file mode 100644
index bf319462d3..0000000000
--- a/interface/resources/shaders/metavoxel_heightfield_shadow_map.frag
+++ /dev/null
@@ -1,37 +0,0 @@
-#version 120
-
-//
-//  metavoxel_heightfield.frag
-//  fragment shader
-//
-//  Created by Andrzej Kapolka on 7/28/14.
-//  Copyright 2014 High Fidelity, Inc.
-//
-//  Distributed under the Apache License, Version 2.0.
-//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-
-// the diffuse texture
-uniform sampler2D diffuseMap;
-
-// the shadow texture
-uniform sampler2DShadow shadowMap;
-
-// the inverse of the size of the shadow map
-const float shadowScale = 1.0 / 2048.0;
-
-// the interpolated normal
-varying vec4 normal;
-
-void main(void) {
-    // compute the base color based on OpenGL lighting model
-    float diffuse = dot(normalize(normal), gl_LightSource[0].position);
-    float facingLight = step(0.0, diffuse) * 0.25 *
-        (shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, -shadowScale, 0.0)).r +
-        shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, shadowScale, 0.0)).r +
-        shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, -shadowScale, 0.0)).r +
-        shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, shadowScale, 0.0)).r);
-    vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
-        gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
-    gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st);
-}
diff --git a/interface/resources/shaders/metavoxel_voxel_base.frag b/interface/resources/shaders/metavoxel_voxel_base.frag
new file mode 100644
index 0000000000..72eca6b25d
--- /dev/null
+++ b/interface/resources/shaders/metavoxel_voxel_base.frag
@@ -0,0 +1,21 @@
+#version 120
+
+//
+//  metavoxel_voxel_base.frag
+//  fragment shader
+//
+//  Created by Andrzej Kapolka on 9/4/14.
+//  Copyright 2014 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+// the interpolated normal
+varying vec4 normal;
+
+void main(void) {
+    // store the interpolated color and normal
+    gl_FragData[0] = gl_Color;
+    gl_FragData[1] = normalize(normal) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0);
+}
diff --git a/interface/resources/shaders/metavoxel_voxel_base.vert b/interface/resources/shaders/metavoxel_voxel_base.vert
new file mode 100644
index 0000000000..4fe5802462
--- /dev/null
+++ b/interface/resources/shaders/metavoxel_voxel_base.vert
@@ -0,0 +1,26 @@
+#version 120
+
+//
+//  metavoxel_voxel_base.vert
+//  vertex shader
+//
+//  Created by Andrzej Kapolka on 9/4/14.
+//  Copyright 2014 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+// the interpolated normal
+varying vec4 normal;
+
+void main(void) {
+    // transform and store the normal for interpolation
+    normal = vec4(normalize(gl_NormalMatrix * gl_Normal), 0.0);
+    
+    // use the fixed-function position
+    gl_Position = ftransform();
+    
+    // copy the color for interpolation
+    gl_FrontColor = vec4(gl_Color.rgb, 0.0);
+}
diff --git a/interface/resources/shaders/metavoxel_voxel_splat.frag b/interface/resources/shaders/metavoxel_voxel_splat.frag
new file mode 100644
index 0000000000..66ce2721ac
--- /dev/null
+++ b/interface/resources/shaders/metavoxel_voxel_splat.frag
@@ -0,0 +1,29 @@
+#version 120
+
+//
+//  metavoxel_voxel_splat.frag
+//  fragment shader
+//
+//  Created by Andrzej Kapolka on 9/4/14.
+//  Copyright 2014 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+// the number of splats per pass
+const int SPLAT_COUNT = 4;
+
+// the splat textures
+uniform sampler2D diffuseMaps[SPLAT_COUNT];
+
+// alpha values for the four splat textures
+varying vec4 alphaValues;
+
+void main(void) {
+    // blend the splat textures
+    gl_FragColor = (texture2D(diffuseMaps[0], gl_TexCoord[0].st) * alphaValues.x +
+        texture2D(diffuseMaps[1], gl_TexCoord[1].st) * alphaValues.y +
+        texture2D(diffuseMaps[2], gl_TexCoord[2].st) * alphaValues.z +
+        texture2D(diffuseMaps[3], gl_TexCoord[3].st) * alphaValues.w);
+}
diff --git a/interface/resources/shaders/metavoxel_voxel_splat.vert b/interface/resources/shaders/metavoxel_voxel_splat.vert
new file mode 100644
index 0000000000..150a9e7d2e
--- /dev/null
+++ b/interface/resources/shaders/metavoxel_voxel_splat.vert
@@ -0,0 +1,62 @@
+#version 120
+
+//
+//  metavoxel_voxel_splat.vert
+//  vertex shader
+//
+//  Created by Andrzej Kapolka on 9/4/14.
+//  Copyright 2014 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+// the splat textures scales on the S axis
+uniform vec4 splatTextureScalesS;
+
+// the splat texture scales on the T axis
+uniform vec4 splatTextureScalesT;
+
+// the lower bounds of the values corresponding to the splat textures
+uniform vec4 textureValueMinima;
+
+// the upper bounds of the values corresponding to the splat textures
+uniform vec4 textureValueMaxima;
+
+// the materials to apply to the vertex
+attribute vec4 materials;
+
+// the weights of each material
+attribute vec4 materialWeights;
+
+// alpha values for the four splat textures
+varying vec4 alphaValues;
+
+void main(void) {
+    // use the fixed-function position
+    gl_Position = ftransform();
+    
+    // pass along the scaled/offset texture coordinates
+    vec4 textureSpacePosition = vec4(gl_Vertex.xz, 0.0, 1.0);
+    gl_TexCoord[0] = textureSpacePosition * vec4(splatTextureScalesS[0], splatTextureScalesT[0], 0.0, 1.0);
+    gl_TexCoord[1] = textureSpacePosition * vec4(splatTextureScalesS[1], splatTextureScalesT[1], 0.0, 1.0);
+    gl_TexCoord[2] = textureSpacePosition * vec4(splatTextureScalesS[2], splatTextureScalesT[2], 0.0, 1.0);
+    gl_TexCoord[3] = textureSpacePosition * vec4(splatTextureScalesS[3], splatTextureScalesT[3], 0.0, 1.0);
+    
+    // compute the alpha values for each texture
+    float value = materials[0];
+    vec4 valueVector = vec4(value, value, value, value);
+    alphaValues = step(textureValueMinima, valueVector) * step(valueVector, textureValueMaxima) * materialWeights[0];
+    
+    value = materials[1];
+    valueVector = vec4(value, value, value, value);
+    alphaValues += step(textureValueMinima, valueVector) * step(valueVector, textureValueMaxima) * materialWeights[1];
+    
+    value = materials[2];
+    valueVector = vec4(value, value, value, value);
+    alphaValues += step(textureValueMinima, valueVector) * step(valueVector, textureValueMaxima) * materialWeights[2];
+    
+    value = materials[3];
+    valueVector = vec4(value, value, value, value);
+    alphaValues += step(textureValueMinima, valueVector) * step(valueVector, textureValueMaxima) * materialWeights[3];
+}
diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp
index f135579526..d3819bf89b 100644
--- a/interface/src/MetavoxelSystem.cpp
+++ b/interface/src/MetavoxelSystem.cpp
@@ -9,7 +9,11 @@
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
 //
 
+// include this before QOpenGLFramebufferObject, which includes an earlier version of OpenGL
+#include "InterfaceConfig.h"
+
 #include <QMutexLocker>
+#include <QOpenGLFramebufferObject>
 #include <QReadLocker>
 #include <QWriteLocker>
 #include <QtDebug>
@@ -24,6 +28,7 @@
 #include "Application.h"
 #include "MetavoxelSystem.h"
 #include "renderer/Model.h"
+#include "renderer/RenderUtil.h"
 
 REGISTER_META_OBJECT(DefaultMetavoxelRendererImplementation)
 REGISTER_META_OBJECT(SphereRenderer)
@@ -46,6 +51,12 @@ void MetavoxelSystem::init() {
         new BufferDataAttribute("voxelBuffer"));
     _voxelBufferAttribute->setLODThresholdMultiplier(
         AttributeRegistry::getInstance()->getVoxelColorAttribute()->getLODThresholdMultiplier());
+    
+    loadLightProgram("shaders/directional_light.frag", _directionalLight, _directionalLightLocations);
+    loadLightProgram("shaders/directional_light_shadow_map.frag", _directionalLightShadowMap,
+        _directionalLightShadowMapLocations);
+    loadLightProgram("shaders/directional_light_cascaded_shadow_map.frag", _directionalLightCascadedShadowMap,
+        _directionalLightCascadedShadowMapLocations);
 }
 
 MetavoxelLOD MetavoxelSystem::getLOD() {
@@ -116,6 +127,10 @@ int RenderVisitor::visit(MetavoxelInfo& info) {
     return STOP_RECURSION;
 }
 
+const GLenum COLOR_DRAW_BUFFERS[] = { GL_COLOR_ATTACHMENT0 };
+const GLenum NORMAL_DRAW_BUFFERS[] = { GL_COLOR_ATTACHMENT1 };
+const GLenum COLOR_NORMAL_DRAW_BUFFERS[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
+
 void MetavoxelSystem::render() {
     // update the frustum
     ViewFrustum* viewFrustum = Application::getInstance()->getViewFrustum();
@@ -123,8 +138,128 @@ void MetavoxelSystem::render() {
         viewFrustum->getFarBottomRight(), viewFrustum->getNearTopLeft(), viewFrustum->getNearTopRight(),
         viewFrustum->getNearBottomLeft(), viewFrustum->getNearBottomRight());
     
+    _needToLight = false;
+
+    // clear the normal buffer
+    glDrawBuffers(sizeof(NORMAL_DRAW_BUFFERS) / sizeof(NORMAL_DRAW_BUFFERS[0]), NORMAL_DRAW_BUFFERS);
+    glClear(GL_COLOR_BUFFER_BIT);
+
+    glDrawBuffers(sizeof(COLOR_DRAW_BUFFERS) / sizeof(COLOR_DRAW_BUFFERS[0]), COLOR_DRAW_BUFFERS);
+    
     RenderVisitor renderVisitor(getLOD());
     guideToAugmented(renderVisitor, true);
+    
+    // give external parties a chance to join in
+    emit rendering();
+    
+    if (!_needToLight) {
+        return; // skip lighting if not needed
+    }
+    
+    // perform deferred lighting, rendering to free fbo
+    glPushMatrix();
+    glLoadIdentity();
+    
+    glMatrixMode(GL_PROJECTION);
+    glPushMatrix();
+    glLoadIdentity();
+    
+    glDisable(GL_BLEND);
+    glDisable(GL_LIGHTING);
+    glDisable(GL_DEPTH_TEST);
+    glDepthMask(false);
+    
+    QOpenGLFramebufferObject* primaryFBO = Application::getInstance()->getTextureCache()->getPrimaryFramebufferObject();
+    primaryFBO->release();
+    
+    QOpenGLFramebufferObject* freeFBO = Application::getInstance()->getGlowEffect()->getFreeFramebufferObject();
+    freeFBO->bind();
+    glClear(GL_COLOR_BUFFER_BIT);
+    
+    glBindTexture(GL_TEXTURE_2D, primaryFBO->texture());
+    
+    glActiveTexture(GL_TEXTURE1);
+    glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getPrimaryNormalTextureID());
+    
+    if (Menu::getInstance()->getShadowsEnabled()) {
+        glActiveTexture(GL_TEXTURE2);
+        glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getPrimaryDepthTextureID());
+        
+        glActiveTexture(GL_TEXTURE3);
+        glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getShadowDepthTextureID());
+        
+        ProgramObject* program = &_directionalLightShadowMap;
+        const LightLocations* locations = &_directionalLightShadowMapLocations;
+        if (Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows)) {
+            program = &_directionalLightCascadedShadowMap;
+            locations = &_directionalLightCascadedShadowMapLocations;
+            _directionalLightCascadedShadowMap.bind();
+            _directionalLightCascadedShadowMap.setUniform(locations->shadowDistances,
+                Application::getInstance()->getShadowDistances());
+        
+        } else {
+            program->bind();
+        }
+        program->setUniformValue(locations->shadowScale,
+            1.0f / Application::getInstance()->getTextureCache()->getShadowFramebufferObject()->width());
+        
+        float left, right, bottom, top, nearVal, farVal;
+        glm::vec4 nearClipPlane, farClipPlane;
+        Application::getInstance()->computeOffAxisFrustum(
+            left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane);
+        program->setUniformValue(locations->near, nearVal);
+        program->setUniformValue(locations->depthScale, (farVal - nearVal) / farVal);
+        float nearScale = -1.0f / nearVal;
+        program->setUniformValue(locations->depthTexCoordOffset, left * nearScale, bottom * nearScale);
+        program->setUniformValue(locations->depthTexCoordScale, (right - left) * nearScale, (top - bottom) * nearScale);
+        
+        renderFullscreenQuad();
+        
+        program->release();
+        
+        glBindTexture(GL_TEXTURE_2D, 0);
+        
+        glActiveTexture(GL_TEXTURE2);
+        glBindTexture(GL_TEXTURE_2D, 0);
+        
+        glActiveTexture(GL_TEXTURE1);
+    
+    } else {
+        _directionalLight.bind();
+        renderFullscreenQuad();
+        _directionalLight.release();        
+    }
+    
+    glBindTexture(GL_TEXTURE_2D, 0);
+    glActiveTexture(GL_TEXTURE0);
+    
+    freeFBO->release();
+    
+    // now transfer the lit region to the primary fbo
+    glEnable(GL_BLEND);
+    
+    primaryFBO->bind();
+    
+    glBindTexture(GL_TEXTURE_2D, freeFBO->texture());
+    glEnable(GL_TEXTURE_2D);
+    
+    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+    
+    renderFullscreenQuad();
+    
+    glBindTexture(GL_TEXTURE_2D, 0);
+    glDisable(GL_TEXTURE_2D);
+    
+    glEnable(GL_LIGHTING);
+    glEnable(GL_DEPTH_TEST);
+    glDepthMask(true);
+    
+    glDisable(GL_ALPHA_TEST);
+    
+    glPopMatrix();
+    
+    glMatrixMode(GL_MODELVIEW);
+    glPopMatrix();
 }
 
 class RayHeightfieldIntersectionVisitor : public RayIntersectionVisitor {
@@ -484,6 +619,24 @@ void MetavoxelSystem::guideToAugmented(MetavoxelVisitor& visitor, bool render) {
     }
 }
 
+void MetavoxelSystem::loadLightProgram(const char* name, ProgramObject& program, LightLocations& locations) {
+    program.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + name);
+    program.link();
+    
+    program.bind();
+    program.setUniformValue("diffuseMap", 0);
+    program.setUniformValue("normalMap", 1);
+    program.setUniformValue("depthMap", 2);
+    program.setUniformValue("shadowMap", 3);
+    locations.shadowDistances = program.uniformLocation("shadowDistances");
+    locations.shadowScale = program.uniformLocation("shadowScale");
+    locations.near = program.uniformLocation("near");
+    locations.depthScale = program.uniformLocation("depthScale");
+    locations.depthTexCoordOffset = program.uniformLocation("depthTexCoordOffset");
+    locations.depthTexCoordScale = program.uniformLocation("depthTexCoordScale");
+    program.release();
+}
+
 MetavoxelSystemClient::MetavoxelSystemClient(const SharedNodePointer& node, MetavoxelUpdater* updater) :
     MetavoxelClient(node, updater) {
 }
@@ -807,7 +960,6 @@ void HeightfieldBuffer::render(bool cursor) {
         glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0);
     
     } else if (!_materials.isEmpty()) {
-        DefaultMetavoxelRendererImplementation::getBaseHeightfieldProgram().bind();
         DefaultMetavoxelRendererImplementation::getBaseHeightfieldProgram().setUniformValue(
             DefaultMetavoxelRendererImplementation::getBaseHeightScaleLocation(), 1.0f / _heightSize);
         DefaultMetavoxelRendererImplementation::getBaseHeightfieldProgram().setUniformValue(
@@ -817,6 +969,8 @@ void HeightfieldBuffer::render(bool cursor) {
         
         glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0);
         
+        glDrawBuffers(sizeof(COLOR_DRAW_BUFFERS) / sizeof(COLOR_DRAW_BUFFERS[0]), COLOR_DRAW_BUFFERS);
+        
         glDepthFunc(GL_LEQUAL);
         glDepthMask(false);
         glEnable(GL_BLEND);
@@ -825,18 +979,18 @@ void HeightfieldBuffer::render(bool cursor) {
         glPolygonOffset(-1.0f, -1.0f);
         
         DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().bind();
+        const DefaultMetavoxelRendererImplementation::SplatLocations& locations =
+            DefaultMetavoxelRendererImplementation::getSplatHeightfieldLocations();
         DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue(
-            DefaultMetavoxelRendererImplementation::getSplatHeightScaleLocation(), 1.0f / _heightSize);
+            locations.heightScale, 1.0f / _heightSize);
         DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue(
-            DefaultMetavoxelRendererImplementation::getSplatTextureScaleLocation(), (float)_heightSize / innerSize);
+            locations.textureScale, (float)_heightSize / innerSize);
         DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue(
-            DefaultMetavoxelRendererImplementation::getSplatTextureOffsetLocation(),
-            _translation.x / _scale, _translation.z / _scale);
+            locations.splatTextureOffset, _translation.x / _scale, _translation.z / _scale);
             
         glBindTexture(GL_TEXTURE_2D, _materialTextureID);
     
-        const int TEXTURES_PER_SPLAT = 4;
-        for (int i = 0; i < _materials.size(); i += TEXTURES_PER_SPLAT) {
+        for (int i = 0; i < _materials.size(); i += SPLAT_COUNT) {
             QVector4D scalesS, scalesT;
             
             for (int j = 0; j < SPLAT_COUNT; j++) {
@@ -858,23 +1012,20 @@ void HeightfieldBuffer::render(bool cursor) {
             }
             const float QUARTER_STEP = 0.25f * EIGHT_BIT_MAXIMUM_RECIPROCAL;
             DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue(
-                DefaultMetavoxelRendererImplementation::getSplatTextureScalesSLocation(), scalesS);
+                locations.splatTextureScalesS, scalesS);
             DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue(
-                DefaultMetavoxelRendererImplementation::getSplatTextureScalesTLocation(), scalesT);
+                locations.splatTextureScalesT, scalesT);
             DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue(
-                DefaultMetavoxelRendererImplementation::getSplatTextureValueMinimaLocation(),
+                locations.textureValueMinima,
                 (i + 1) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP, (i + 2) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP,
                 (i + 3) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP, (i + 4) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP);
             DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue(
-                DefaultMetavoxelRendererImplementation::getSplatTextureValueMaximaLocation(),
+                locations.textureValueMaxima,
                 (i + 1) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP, (i + 2) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP,
                 (i + 3) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP, (i + 4) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP);
             glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0);
         }
     
-        glEnable(GL_ALPHA_TEST);
-        glBlendFunc(GL_DST_COLOR, GL_ZERO);
-    
         for (int i = 0; i < SPLAT_COUNT; i++) {
             glActiveTexture(GL_TEXTURE0 + SPLAT_TEXTURE_UNITS[i]);
             glBindTexture(GL_TEXTURE_2D, 0);
@@ -882,53 +1033,24 @@ void HeightfieldBuffer::render(bool cursor) {
     
         glActiveTexture(GL_TEXTURE1);
         glBindTexture(GL_TEXTURE_2D, 0);
-    
-        if (Menu::getInstance()->isOptionChecked(MenuOption::SimpleShadows)) {
-            DefaultMetavoxelRendererImplementation::getShadowLightHeightfieldProgram().bind();
-            DefaultMetavoxelRendererImplementation::getShadowLightHeightfieldProgram().setUniformValue(
-                DefaultMetavoxelRendererImplementation::getShadowLightHeightScaleLocation(), 1.0f / _heightSize);
-            glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0);
-            DefaultMetavoxelRendererImplementation::getShadowMapHeightfieldProgram().bind();
-                        
-        } else if (Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows)) {
-            DefaultMetavoxelRendererImplementation::getCascadedShadowLightHeightfieldProgram().bind();
-            DefaultMetavoxelRendererImplementation::getCascadedShadowLightHeightfieldProgram().setUniformValue(
-                DefaultMetavoxelRendererImplementation::getCascadedShadowLightHeightScaleLocation(), 1.0f / _heightSize);
-            glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0);
-            DefaultMetavoxelRendererImplementation::getCascadedShadowMapHeightfieldProgram().bind();
-                    
-        } else {
-            DefaultMetavoxelRendererImplementation::getLightHeightfieldProgram().bind();
-            DefaultMetavoxelRendererImplementation::getLightHeightfieldProgram().setUniformValue(
-                DefaultMetavoxelRendererImplementation::getLightHeightScaleLocation(), 1.0f / _heightSize);
-            glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0);
-            DefaultMetavoxelRendererImplementation::getHeightfieldProgram().bind();    
-        }
-        
-        glDisable(GL_POLYGON_OFFSET_FILL);
-        glDisable(GL_BLEND);
-        glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE);
-        glDepthFunc(GL_LESS);
-        glDepthMask(true);
         
         glActiveTexture(GL_TEXTURE0);
         
+        glDisable(GL_POLYGON_OFFSET_FILL);
+        glEnable(GL_ALPHA_TEST);
+        glDisable(GL_BLEND);
+        glDepthMask(true);
+        glDepthFunc(GL_LESS);
+        
+        glDrawBuffers(sizeof(COLOR_NORMAL_DRAW_BUFFERS) / sizeof(COLOR_NORMAL_DRAW_BUFFERS[0]), COLOR_NORMAL_DRAW_BUFFERS);
+        
+        DefaultMetavoxelRendererImplementation::getBaseHeightfieldProgram().bind();
+        
     } else {
-        int heightScaleLocation = DefaultMetavoxelRendererImplementation::getHeightScaleLocation();
-        int colorScaleLocation = DefaultMetavoxelRendererImplementation::getColorScaleLocation();
-        ProgramObject* program = &DefaultMetavoxelRendererImplementation::getHeightfieldProgram();
-        if (Menu::getInstance()->isOptionChecked(MenuOption::SimpleShadows)) {
-            heightScaleLocation = DefaultMetavoxelRendererImplementation::getShadowMapHeightScaleLocation();
-            colorScaleLocation = DefaultMetavoxelRendererImplementation::getShadowMapColorScaleLocation();
-            program = &DefaultMetavoxelRendererImplementation::getShadowMapHeightfieldProgram();
-            
-        } else if (Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows)) {
-            heightScaleLocation = DefaultMetavoxelRendererImplementation::getCascadedShadowMapHeightScaleLocation();
-            colorScaleLocation = DefaultMetavoxelRendererImplementation::getCascadedShadowMapColorScaleLocation();
-            program = &DefaultMetavoxelRendererImplementation::getCascadedShadowMapHeightfieldProgram();
-        }
-        program->setUniformValue(heightScaleLocation, 1.0f / _heightSize);
-        program->setUniformValue(colorScaleLocation, (float)_heightSize / innerSize);
+        DefaultMetavoxelRendererImplementation::getBaseHeightfieldProgram().setUniformValue(
+            DefaultMetavoxelRendererImplementation::getBaseHeightScaleLocation(), 1.0f / _heightSize);
+        DefaultMetavoxelRendererImplementation::getBaseHeightfieldProgram().setUniformValue(
+            DefaultMetavoxelRendererImplementation::getBaseColorScaleLocation(), (float)_heightSize / innerSize);
         glActiveTexture(GL_TEXTURE1);
         glBindTexture(GL_TEXTURE_2D, _colorTextureID);
         
@@ -944,6 +1066,8 @@ void HeightfieldBuffer::render(bool cursor) {
     
     bufferPair.first.release();
     bufferPair.second.release();
+    
+    Application::getInstance()->getMetavoxels()->noteNeedToLight();
 }
 
 QHash<int, HeightfieldBuffer::BufferPair> HeightfieldBuffer::_bufferPairs;
@@ -959,7 +1083,7 @@ void HeightfieldPreview::render(const glm::vec3& translation, float scale) const
     glEnableClientState(GL_VERTEX_ARRAY);
     glEnableClientState(GL_TEXTURE_COORD_ARRAY);
     
-    DefaultMetavoxelRendererImplementation::getHeightfieldProgram().bind();
+    DefaultMetavoxelRendererImplementation::getBaseHeightfieldProgram().bind();
     
     glPushMatrix();
     glTranslatef(translation.x, translation.y, translation.z);
@@ -971,7 +1095,7 @@ void HeightfieldPreview::render(const glm::vec3& translation, float scale) const
     
     glPopMatrix();
     
-    DefaultMetavoxelRendererImplementation::getHeightfieldProgram().release();
+    DefaultMetavoxelRendererImplementation::getBaseHeightfieldProgram().release();
     
     glDisableClientState(GL_TEXTURE_COORD_ARRAY);
     glDisableClientState(GL_VERTEX_ARRAY);
@@ -1025,8 +1149,89 @@ void VoxelBuffer::render(bool cursor) {
     
     glDrawRangeElements(GL_QUADS, 0, _vertexCount - 1, _indexCount, GL_UNSIGNED_INT, 0);
     
+    if (!_materials.isEmpty()) {
+        glDrawBuffers(sizeof(COLOR_DRAW_BUFFERS) / sizeof(COLOR_DRAW_BUFFERS[0]), COLOR_DRAW_BUFFERS);
+        
+        glDepthFunc(GL_LEQUAL);
+        glDepthMask(false);
+        glEnable(GL_BLEND);
+        glDisable(GL_ALPHA_TEST);
+        glEnable(GL_POLYGON_OFFSET_FILL);
+        glPolygonOffset(-1.0f, -1.0f);
+        
+        DefaultMetavoxelRendererImplementation::getSplatVoxelProgram().bind();
+        const DefaultMetavoxelRendererImplementation::SplatLocations& locations =
+            DefaultMetavoxelRendererImplementation::getSplatVoxelLocations();
+        
+        DefaultMetavoxelRendererImplementation::getSplatVoxelProgram().setAttributeBuffer(locations.materials,
+            GL_UNSIGNED_BYTE, (qint64)&point->materials, SPLAT_COUNT, sizeof(VoxelPoint));
+        DefaultMetavoxelRendererImplementation::getSplatVoxelProgram().enableAttributeArray(locations.materials);
+        
+        DefaultMetavoxelRendererImplementation::getSplatVoxelProgram().setAttributeBuffer(locations.materialWeights,
+            GL_UNSIGNED_BYTE, (qint64)&point->materialWeights, SPLAT_COUNT, sizeof(VoxelPoint));
+        DefaultMetavoxelRendererImplementation::getSplatVoxelProgram().enableAttributeArray(locations.materialWeights);
+        
+        for (int i = 0; i < _materials.size(); i += SPLAT_COUNT) {
+            QVector4D scalesS, scalesT;
+            
+            for (int j = 0; j < SPLAT_COUNT; j++) {
+                glActiveTexture(GL_TEXTURE0 + SPLAT_TEXTURE_UNITS[j]);
+                int index = i + j;
+                if (index < _networkTextures.size()) {
+                    const NetworkTexturePointer& texture = _networkTextures.at(index);
+                    if (texture) {
+                        MaterialObject* material = static_cast<MaterialObject*>(_materials.at(index).data());
+                        scalesS[j] = 1.0f / material->getScaleS();
+                        scalesT[j] = 1.0f / material->getScaleT();
+                        glBindTexture(GL_TEXTURE_2D, texture->getID());    
+                    } else {
+                        glBindTexture(GL_TEXTURE_2D, 0);
+                    }
+                } else {
+                    glBindTexture(GL_TEXTURE_2D, 0);
+                }
+            }
+            const float QUARTER_STEP = 0.25f * EIGHT_BIT_MAXIMUM_RECIPROCAL;
+            DefaultMetavoxelRendererImplementation::getSplatVoxelProgram().setUniformValue(
+                locations.splatTextureScalesS, scalesS);
+            DefaultMetavoxelRendererImplementation::getSplatVoxelProgram().setUniformValue(
+                locations.splatTextureScalesT, scalesT);
+            DefaultMetavoxelRendererImplementation::getSplatVoxelProgram().setUniformValue(
+                locations.textureValueMinima,
+                (i + 1) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP, (i + 2) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP,
+                (i + 3) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP, (i + 4) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP);
+            DefaultMetavoxelRendererImplementation::getSplatVoxelProgram().setUniformValue(
+                locations.textureValueMaxima,
+                (i + 1) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP, (i + 2) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP,
+                (i + 3) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP, (i + 4) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP);
+            glDrawRangeElements(GL_QUADS, 0, _vertexCount - 1, _indexCount, GL_UNSIGNED_INT, 0);
+        }
+    
+        for (int i = 0; i < SPLAT_COUNT; i++) {
+            glActiveTexture(GL_TEXTURE0 + SPLAT_TEXTURE_UNITS[i]);
+            glBindTexture(GL_TEXTURE_2D, 0);
+        }
+    
+        glActiveTexture(GL_TEXTURE0);
+        
+        glDisable(GL_POLYGON_OFFSET_FILL);
+        glEnable(GL_ALPHA_TEST);
+        glDisable(GL_BLEND);
+        glDepthMask(true);
+        glDepthFunc(GL_LESS);
+        
+        glDrawBuffers(sizeof(COLOR_NORMAL_DRAW_BUFFERS) / sizeof(COLOR_NORMAL_DRAW_BUFFERS[0]), COLOR_NORMAL_DRAW_BUFFERS);
+        
+        DefaultMetavoxelRendererImplementation::getSplatVoxelProgram().disableAttributeArray(locations.materials);
+        DefaultMetavoxelRendererImplementation::getSplatVoxelProgram().disableAttributeArray(locations.materialWeights);
+        
+        DefaultMetavoxelRendererImplementation::getBaseVoxelProgram().bind();
+    }
+    
     _vertexBuffer.release();
     _indexBuffer.release();
+    
+    Application::getInstance()->getMetavoxels()->noteNeedToLight();
 }
 
 BufferDataAttribute::BufferDataAttribute(const QString& name) :
@@ -1056,48 +1261,6 @@ void DefaultMetavoxelRendererImplementation::init() {
         _pointScaleLocation = _pointProgram.uniformLocation("pointScale");
         _pointProgram.release();
         
-        _heightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() +
-            "shaders/metavoxel_heightfield.vert");
-        _heightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() +
-            "shaders/metavoxel_heightfield.frag");
-        _heightfieldProgram.link();
-        
-        _heightfieldProgram.bind();
-        _heightfieldProgram.setUniformValue("heightMap", 0);
-        _heightfieldProgram.setUniformValue("diffuseMap", 1);
-        _heightScaleLocation = _heightfieldProgram.uniformLocation("heightScale");
-        _colorScaleLocation = _heightfieldProgram.uniformLocation("colorScale");
-        _heightfieldProgram.release();
-        
-        _shadowMapHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() +
-            "shaders/metavoxel_heightfield.vert");
-        _shadowMapHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() +
-            "shaders/metavoxel_heightfield_shadow_map.frag");
-        _shadowMapHeightfieldProgram.link();
-        
-        _shadowMapHeightfieldProgram.bind();
-        _shadowMapHeightfieldProgram.setUniformValue("heightMap", 0);
-        _shadowMapHeightfieldProgram.setUniformValue("diffuseMap", 1);
-        _shadowMapHeightfieldProgram.setUniformValue("shadowMap", 2);
-        _shadowMapHeightScaleLocation = _shadowMapHeightfieldProgram.uniformLocation("heightScale");
-        _shadowMapColorScaleLocation = _shadowMapHeightfieldProgram.uniformLocation("colorScale");
-        _shadowMapHeightfieldProgram.release();
-        
-        _cascadedShadowMapHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() +
-            "shaders/metavoxel_heightfield.vert");
-        _cascadedShadowMapHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() +
-            "shaders/metavoxel_heightfield_cascaded_shadow_map.frag");
-        _cascadedShadowMapHeightfieldProgram.link();
-        
-        _cascadedShadowMapHeightfieldProgram.bind();
-        _cascadedShadowMapHeightfieldProgram.setUniformValue("heightMap", 0);
-        _cascadedShadowMapHeightfieldProgram.setUniformValue("diffuseMap", 1);
-        _cascadedShadowMapHeightfieldProgram.setUniformValue("shadowMap", 2);
-        _cascadedShadowMapHeightScaleLocation = _cascadedShadowMapHeightfieldProgram.uniformLocation("heightScale");
-        _cascadedShadowMapColorScaleLocation = _cascadedShadowMapHeightfieldProgram.uniformLocation("colorScale");
-        _shadowDistancesLocation = _cascadedShadowMapHeightfieldProgram.uniformLocation("shadowDistances");
-        _cascadedShadowMapHeightfieldProgram.release();
-        
         _baseHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() +
             "shaders/metavoxel_heightfield_base.vert");
         _baseHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() +
@@ -1111,60 +1274,7 @@ void DefaultMetavoxelRendererImplementation::init() {
         _baseColorScaleLocation = _baseHeightfieldProgram.uniformLocation("colorScale");
         _baseHeightfieldProgram.release();
         
-        _splatHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() +
-            "shaders/metavoxel_heightfield_splat.vert");
-        _splatHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() +
-            "shaders/metavoxel_heightfield_splat.frag");
-        _splatHeightfieldProgram.link();
-        
-        _splatHeightfieldProgram.bind();
-        _splatHeightfieldProgram.setUniformValue("heightMap", 0);
-        _splatHeightfieldProgram.setUniformValue("textureMap", 1);
-        _splatHeightfieldProgram.setUniformValueArray("diffuseMaps", SPLAT_TEXTURE_UNITS, SPLAT_COUNT);
-        _splatHeightScaleLocation = _splatHeightfieldProgram.uniformLocation("heightScale");
-        _splatTextureScaleLocation = _splatHeightfieldProgram.uniformLocation("textureScale");
-        _splatTextureOffsetLocation = _splatHeightfieldProgram.uniformLocation("splatTextureOffset");
-        _splatTextureScalesSLocation = _splatHeightfieldProgram.uniformLocation("splatTextureScalesS");
-        _splatTextureScalesTLocation = _splatHeightfieldProgram.uniformLocation("splatTextureScalesT");
-        _splatTextureValueMinimaLocation = _splatHeightfieldProgram.uniformLocation("textureValueMinima");
-        _splatTextureValueMaximaLocation = _splatHeightfieldProgram.uniformLocation("textureValueMaxima");
-        _splatHeightfieldProgram.release();
-        
-        _lightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() +
-            "shaders/metavoxel_heightfield_light.vert");
-        _lightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() +
-            "shaders/metavoxel_heightfield_light.frag");
-        _lightHeightfieldProgram.link();
-        
-        _lightHeightfieldProgram.bind();
-        _lightHeightfieldProgram.setUniformValue("heightMap", 0);
-        _lightHeightScaleLocation = _lightHeightfieldProgram.uniformLocation("heightScale");
-        _lightHeightfieldProgram.release();
-        
-        _shadowLightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() +
-            "shaders/metavoxel_heightfield_light.vert");
-        _shadowLightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() +
-            "shaders/metavoxel_heightfield_light_shadow_map.frag");
-        _shadowLightHeightfieldProgram.link();
-        
-        _shadowLightHeightfieldProgram.bind();
-        _shadowLightHeightfieldProgram.setUniformValue("heightMap", 0);
-        _shadowLightHeightfieldProgram.setUniformValue("shadowMap", 2);
-        _shadowLightHeightScaleLocation = _shadowLightHeightfieldProgram.uniformLocation("heightScale");
-        _shadowLightHeightfieldProgram.release();
-        
-        _cascadedShadowLightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() +
-            "shaders/metavoxel_heightfield_light.vert");
-        _cascadedShadowLightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() +
-            "shaders/metavoxel_heightfield_light_cascaded_shadow_map.frag");
-        _cascadedShadowLightHeightfieldProgram.link();
-        
-        _cascadedShadowLightHeightfieldProgram.bind();
-        _cascadedShadowLightHeightfieldProgram.setUniformValue("heightMap", 0);
-        _cascadedShadowLightHeightfieldProgram.setUniformValue("shadowMap", 2);
-        _cascadedShadowLightHeightScaleLocation = _cascadedShadowLightHeightfieldProgram.uniformLocation("heightScale");
-        _shadowLightDistancesLocation = _cascadedShadowLightHeightfieldProgram.uniformLocation("shadowDistances");
-        _cascadedShadowLightHeightfieldProgram.release();
+        loadSplatProgram("heightfield", _splatHeightfieldProgram, _splatHeightfieldLocations);
         
         _heightfieldCursorProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() +
             "shaders/metavoxel_heightfield_cursor.vert");
@@ -1175,6 +1285,14 @@ void DefaultMetavoxelRendererImplementation::init() {
         _heightfieldCursorProgram.bind();
         _heightfieldCursorProgram.setUniformValue("heightMap", 0);
         _heightfieldCursorProgram.release();
+        
+        _baseVoxelProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() +
+            "shaders/metavoxel_voxel_base.vert");
+        _baseVoxelProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() +
+            "shaders/metavoxel_voxel_base.frag");
+        _baseVoxelProgram.link();
+        
+        loadSplatProgram("voxel", _splatVoxelProgram, _splatVoxelLocations);
     }
 }
 
@@ -1844,7 +1962,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
                     glm::vec3 center;
                     glm::vec3 normal;
                     const int MAX_MATERIALS_PER_VERTEX = 4;
-                    quint8 materials[4];
+                    quint8 materials[] = { 0, 0, 0, 0 };
                     glm::vec4 materialWeights;
                     float totalWeight = 0.0f;
                     int red = 0, green = 0, blue = 0;
@@ -1865,7 +1983,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
                                     totalWeight += 1.0f;
                                     break;
                                     
-                                } else if (materials[j] == 0.0f) {
+                                } else if (materials[j] == 0) {
                                     materials[j] = crossing.material;
                                     materialWeights[j] = 1.0f;
                                     totalWeight += 1.0f;
@@ -2149,38 +2267,22 @@ void DefaultMetavoxelRendererImplementation::render(MetavoxelData& data, Metavox
     
     _pointProgram.release();
     
+    glDrawBuffers(sizeof(COLOR_NORMAL_DRAW_BUFFERS) / sizeof(COLOR_NORMAL_DRAW_BUFFERS[0]), COLOR_NORMAL_DRAW_BUFFERS);
+    
     glEnable(GL_CULL_FACE);
     glEnable(GL_ALPHA_TEST);
     glAlphaFunc(GL_EQUAL, 0.0f);
     
     glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
     
-    ProgramObject* program = &_heightfieldProgram;
-    if (Menu::getInstance()->getShadowsEnabled()) {
-        if (Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows)) {
-            _cascadedShadowLightHeightfieldProgram.bind();
-            _cascadedShadowLightHeightfieldProgram.setUniform(_shadowLightDistancesLocation,
-                Application::getInstance()->getShadowDistances());
-            program = &_cascadedShadowMapHeightfieldProgram;
-            program->bind();
-            program->setUniform(_shadowDistancesLocation, Application::getInstance()->getShadowDistances());
-            
-        } else {
-            program = &_shadowMapHeightfieldProgram;
-        }
-        glActiveTexture(GL_TEXTURE2);
-        glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getShadowDepthTextureID());
-        glActiveTexture(GL_TEXTURE0);
-    }
-    
-    program->bind();
+    _baseHeightfieldProgram.bind();
     
     glEnableClientState(GL_TEXTURE_COORD_ARRAY);
     
     BufferRenderVisitor heightfieldRenderVisitor(Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute());
     data.guide(heightfieldRenderVisitor);
     
-    program->release();
+    _baseHeightfieldProgram.release();
     
     glActiveTexture(GL_TEXTURE2);
     glBindTexture(GL_TEXTURE_2D, 0);
@@ -2189,57 +2291,63 @@ void DefaultMetavoxelRendererImplementation::render(MetavoxelData& data, Metavox
     glDisableClientState(GL_TEXTURE_COORD_ARRAY);
     glDisableClientState(GL_VERTEX_ARRAY);
     
-    glDisable(GL_ALPHA_TEST);
-    glDisable(GL_CULL_FACE);
-    glEnable(GL_BLEND);
-    
     glEnableClientState(GL_VERTEX_ARRAY);
     glEnableClientState(GL_COLOR_ARRAY);
     glEnableClientState(GL_NORMAL_ARRAY);
     
-    glEnable(GL_CULL_FACE);
+    _baseVoxelProgram.bind();
     
     BufferRenderVisitor voxelRenderVisitor(Application::getInstance()->getMetavoxels()->getVoxelBufferAttribute());
     data.guide(voxelRenderVisitor);
     
+    _baseVoxelProgram.release();
+    
+    glDisable(GL_ALPHA_TEST);
     glDisable(GL_CULL_FACE);
+    glEnable(GL_BLEND);
     
     glDisableClientState(GL_VERTEX_ARRAY);
     glDisableClientState(GL_COLOR_ARRAY);
     glDisableClientState(GL_NORMAL_ARRAY);
+    
+    glDrawBuffers(sizeof(COLOR_DRAW_BUFFERS) / sizeof(COLOR_DRAW_BUFFERS[0]), COLOR_DRAW_BUFFERS);
+}
+
+void DefaultMetavoxelRendererImplementation::loadSplatProgram(const char* type,
+        ProgramObject& program, SplatLocations& locations) {
+    program.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() +
+        "shaders/metavoxel_" + type + "_splat.vert");
+    program.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() +
+        "shaders/metavoxel_" + type + "_splat.frag");
+    program.link();
+    
+    program.bind();
+    program.setUniformValue("heightMap", 0);
+    program.setUniformValue("textureMap", 1);
+    program.setUniformValueArray("diffuseMaps", SPLAT_TEXTURE_UNITS, SPLAT_COUNT);
+    locations.heightScale = program.uniformLocation("heightScale");
+    locations.textureScale = program.uniformLocation("textureScale");
+    locations.splatTextureOffset = program.uniformLocation("splatTextureOffset");
+    locations.splatTextureScalesS = program.uniformLocation("splatTextureScalesS");
+    locations.splatTextureScalesT = program.uniformLocation("splatTextureScalesT");
+    locations.textureValueMinima = program.uniformLocation("textureValueMinima");
+    locations.textureValueMaxima = program.uniformLocation("textureValueMaxima");
+    locations.materials = program.attributeLocation("materials");
+    locations.materialWeights = program.attributeLocation("materialWeights");
+    program.release();
 }
 
 ProgramObject DefaultMetavoxelRendererImplementation::_pointProgram;
 int DefaultMetavoxelRendererImplementation::_pointScaleLocation;
-ProgramObject DefaultMetavoxelRendererImplementation::_heightfieldProgram;
-int DefaultMetavoxelRendererImplementation::_heightScaleLocation;
-int DefaultMetavoxelRendererImplementation::_colorScaleLocation;
-ProgramObject DefaultMetavoxelRendererImplementation::_shadowMapHeightfieldProgram;
-int DefaultMetavoxelRendererImplementation::_shadowMapHeightScaleLocation;
-int DefaultMetavoxelRendererImplementation::_shadowMapColorScaleLocation;
-ProgramObject DefaultMetavoxelRendererImplementation::_cascadedShadowMapHeightfieldProgram;
-int DefaultMetavoxelRendererImplementation::_cascadedShadowMapHeightScaleLocation;
-int DefaultMetavoxelRendererImplementation::_cascadedShadowMapColorScaleLocation;
-int DefaultMetavoxelRendererImplementation::_shadowDistancesLocation;
 ProgramObject DefaultMetavoxelRendererImplementation::_baseHeightfieldProgram;
 int DefaultMetavoxelRendererImplementation::_baseHeightScaleLocation;
 int DefaultMetavoxelRendererImplementation::_baseColorScaleLocation;
 ProgramObject DefaultMetavoxelRendererImplementation::_splatHeightfieldProgram;
-int DefaultMetavoxelRendererImplementation::_splatHeightScaleLocation;
-int DefaultMetavoxelRendererImplementation::_splatTextureScaleLocation;
-int DefaultMetavoxelRendererImplementation::_splatTextureOffsetLocation;
-int DefaultMetavoxelRendererImplementation::_splatTextureScalesSLocation;
-int DefaultMetavoxelRendererImplementation::_splatTextureScalesTLocation;
-int DefaultMetavoxelRendererImplementation::_splatTextureValueMinimaLocation;
-int DefaultMetavoxelRendererImplementation::_splatTextureValueMaximaLocation;
-ProgramObject DefaultMetavoxelRendererImplementation::_lightHeightfieldProgram;
-int DefaultMetavoxelRendererImplementation::_lightHeightScaleLocation; 
-ProgramObject DefaultMetavoxelRendererImplementation::_shadowLightHeightfieldProgram;
-int DefaultMetavoxelRendererImplementation::_shadowLightHeightScaleLocation;
-ProgramObject DefaultMetavoxelRendererImplementation::_cascadedShadowLightHeightfieldProgram;
-int DefaultMetavoxelRendererImplementation::_cascadedShadowLightHeightScaleLocation;
-int DefaultMetavoxelRendererImplementation::_shadowLightDistancesLocation; 
+DefaultMetavoxelRendererImplementation::SplatLocations DefaultMetavoxelRendererImplementation::_splatHeightfieldLocations;
 ProgramObject DefaultMetavoxelRendererImplementation::_heightfieldCursorProgram;
+ProgramObject DefaultMetavoxelRendererImplementation::_baseVoxelProgram;
+ProgramObject DefaultMetavoxelRendererImplementation::_splatVoxelProgram;
+DefaultMetavoxelRendererImplementation::SplatLocations DefaultMetavoxelRendererImplementation::_splatVoxelLocations;
 
 static void enableClipPlane(GLenum plane, float x, float y, float z, float w) {
     GLdouble coefficients[] = { x, y, z, w };
diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h
index aa15dcc8db..54bee3d451 100644
--- a/interface/src/MetavoxelSystem.h
+++ b/interface/src/MetavoxelSystem.h
@@ -52,6 +52,12 @@ public:
 
     Q_INVOKABLE void deleteTextures(int heightID, int colorID, int textureID);
 
+    void noteNeedToLight() { _needToLight = true; }
+
+signals:
+
+    void rendering();
+
 protected:
 
     virtual MetavoxelClient* createClient(const SharedNodePointer& node);
@@ -60,6 +66,18 @@ private:
     
     void guideToAugmented(MetavoxelVisitor& visitor, bool render = false);
     
+    class LightLocations {
+    public:
+        int shadowDistances;
+        int shadowScale;
+        int near;
+        int depthScale;
+        int depthTexCoordOffset;
+        int depthTexCoordScale;
+    };
+    
+    static void loadLightProgram(const char* name, ProgramObject& program, LightLocations& locations);
+    
     AttributePointer _pointBufferAttribute;
     AttributePointer _heightfieldBufferAttribute;
     AttributePointer _voxelBufferAttribute;
@@ -67,6 +85,14 @@ private:
     MetavoxelLOD _lod;
     QReadWriteLock _lodLock;
     Frustum _frustum;
+    bool _needToLight;
+    
+    ProgramObject _directionalLight;
+    LightLocations _directionalLightLocations;
+    ProgramObject _directionalLightShadowMap;
+    LightLocations _directionalLightShadowMapLocations;
+    ProgramObject _directionalLightCascadedShadowMap;
+    LightLocations _directionalLightCascadedShadowMapLocations;
 };
 
 /// Describes contents of a point in a point buffer.
@@ -266,42 +292,33 @@ public:
     
     static void init();
 
-    static ProgramObject& getHeightfieldProgram() { return _heightfieldProgram; }
-    static int getHeightScaleLocation() { return _heightScaleLocation; }
-    static int getColorScaleLocation() { return _colorScaleLocation; }
-    
-    static ProgramObject& getShadowMapHeightfieldProgram() { return _shadowMapHeightfieldProgram; }
-    static int getShadowMapHeightScaleLocation() { return _shadowMapHeightScaleLocation; }
-    static int getShadowMapColorScaleLocation() { return _shadowMapColorScaleLocation; }
-    
-    static ProgramObject& getCascadedShadowMapHeightfieldProgram() { return _cascadedShadowMapHeightfieldProgram; }
-    static int getCascadedShadowMapHeightScaleLocation() { return _cascadedShadowMapHeightScaleLocation; }
-    static int getCascadedShadowMapColorScaleLocation() { return _cascadedShadowMapColorScaleLocation; }
-    
     static ProgramObject& getBaseHeightfieldProgram() { return _baseHeightfieldProgram; }
     static int getBaseHeightScaleLocation() { return _baseHeightScaleLocation; }
     static int getBaseColorScaleLocation() { return _baseColorScaleLocation; }
     
+    class SplatLocations {
+    public:
+        int heightScale;
+        int textureScale;
+        int splatTextureOffset;
+        int splatTextureScalesS;
+        int splatTextureScalesT;
+        int textureValueMinima;
+        int textureValueMaxima;
+        int materials;
+        int materialWeights;
+    };
+    
     static ProgramObject& getSplatHeightfieldProgram() { return _splatHeightfieldProgram; }
-    static int getSplatHeightScaleLocation() { return _splatHeightScaleLocation; }
-    static int getSplatTextureScaleLocation() { return _splatTextureScaleLocation; }
-    static int getSplatTextureOffsetLocation() { return _splatTextureOffsetLocation; }
-    static int getSplatTextureScalesSLocation() { return _splatTextureScalesSLocation; }
-    static int getSplatTextureScalesTLocation() { return _splatTextureScalesTLocation; }
-    static int getSplatTextureValueMinimaLocation() { return _splatTextureValueMinimaLocation; }
-    static int getSplatTextureValueMaximaLocation() { return _splatTextureValueMaximaLocation; }
-    
-    static ProgramObject& getLightHeightfieldProgram() { return _lightHeightfieldProgram; }
-    static int getLightHeightScaleLocation() { return _lightHeightScaleLocation; }
-    
-    static ProgramObject& getShadowLightHeightfieldProgram() { return _shadowLightHeightfieldProgram; }
-    static int getShadowLightHeightScaleLocation() { return _shadowLightHeightScaleLocation; }
-    
-    static ProgramObject& getCascadedShadowLightHeightfieldProgram() { return _cascadedShadowLightHeightfieldProgram; }
-    static int getCascadedShadowLightHeightScaleLocation() { return _cascadedShadowLightHeightScaleLocation; }
+    static const SplatLocations& getSplatHeightfieldLocations() { return _splatHeightfieldLocations; }
     
     static ProgramObject& getHeightfieldCursorProgram() { return _heightfieldCursorProgram; }
     
+    static ProgramObject& getBaseVoxelProgram() { return _baseVoxelProgram; }
+    
+    static ProgramObject& getSplatVoxelProgram() { return _splatVoxelProgram; }
+    static const SplatLocations& getSplatVoxelLocations() { return _splatVoxelLocations; }
+    
     Q_INVOKABLE DefaultMetavoxelRendererImplementation();
     
     virtual void augment(MetavoxelData& data, const MetavoxelData& previous, MetavoxelInfo& info, const MetavoxelLOD& lod);
@@ -310,27 +327,18 @@ public:
 
 private:
 
+    static void loadSplatProgram(const char* type, ProgramObject& program, SplatLocations& locations);
+    
     static ProgramObject _pointProgram;
     static int _pointScaleLocation;
     
-    static ProgramObject _heightfieldProgram;
-    static int _heightScaleLocation;
-    static int _colorScaleLocation;
-    
-    static ProgramObject _shadowMapHeightfieldProgram;
-    static int _shadowMapHeightScaleLocation;
-    static int _shadowMapColorScaleLocation;
-    
-    static ProgramObject _cascadedShadowMapHeightfieldProgram;
-    static int _cascadedShadowMapHeightScaleLocation;
-    static int _cascadedShadowMapColorScaleLocation;
-    static int _shadowDistancesLocation;
-    
     static ProgramObject _baseHeightfieldProgram;
     static int _baseHeightScaleLocation;
     static int _baseColorScaleLocation;
     
     static ProgramObject _splatHeightfieldProgram;
+    static SplatLocations _splatHeightfieldLocations;
+    
     static int _splatHeightScaleLocation;
     static int _splatTextureScaleLocation;
     static int _splatTextureOffsetLocation;
@@ -339,17 +347,11 @@ private:
     static int _splatTextureValueMinimaLocation;
     static int _splatTextureValueMaximaLocation;
     
-    static ProgramObject _lightHeightfieldProgram;
-    static int _lightHeightScaleLocation;
-    
-    static ProgramObject _shadowLightHeightfieldProgram;
-    static int _shadowLightHeightScaleLocation;
-    
-    static ProgramObject _cascadedShadowLightHeightfieldProgram;
-    static int _cascadedShadowLightHeightScaleLocation;
-    static int _shadowLightDistancesLocation;
-    
     static ProgramObject _heightfieldCursorProgram;
+    
+    static ProgramObject _baseVoxelProgram;
+    static ProgramObject _splatVoxelProgram;
+    static SplatLocations _splatVoxelLocations;
 };
 
 /// Base class for spanner renderers; provides clipping.
diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp
index d960525817..bb07e83980 100644
--- a/interface/src/renderer/TextureCache.cpp
+++ b/interface/src/renderer/TextureCache.cpp
@@ -29,6 +29,7 @@ TextureCache::TextureCache() :
     _whiteTextureID(0),
     _blueTextureID(0),
     _primaryDepthTextureID(0),
+    _primaryNormalTextureID(0),
     _primaryFramebufferObject(NULL),
     _secondaryFramebufferObject(NULL),
     _tertiaryFramebufferObject(NULL),
@@ -46,6 +47,7 @@ TextureCache::~TextureCache() {
     }
     if (_primaryFramebufferObject) {
         glDeleteTextures(1, &_primaryDepthTextureID);
+        glDeleteTextures(1, &_primaryNormalTextureID);
     }
     
     if (_primaryFramebufferObject) {
@@ -71,6 +73,8 @@ void TextureCache::setFrameBufferSize(QSize frameBufferSize) {
             _primaryFramebufferObject = NULL;
             glDeleteTextures(1, &_primaryDepthTextureID);
             _primaryDepthTextureID = 0;
+            glDeleteTextures(1, &_primaryNormalTextureID);
+            _primaryNormalTextureID = 0;
         }
 
         if (_secondaryFramebufferObject) {
@@ -205,15 +209,22 @@ QOpenGLFramebufferObject* TextureCache::getPrimaryFramebufferObject() {
        
         glGenTextures(1, &_primaryDepthTextureID);
         glBindTexture(GL_TEXTURE_2D, _primaryDepthTextureID);
-
         glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, _frameBufferSize.width(), _frameBufferSize.height(),
             0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        
+        glGenTextures(1, &_primaryNormalTextureID);
+        glBindTexture(GL_TEXTURE_2D, _primaryNormalTextureID);
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _frameBufferSize.width(), _frameBufferSize.height(),
+            0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
         glBindTexture(GL_TEXTURE_2D, 0);
         
         _primaryFramebufferObject->bind();
         glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, _primaryDepthTextureID, 0);
+        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, _primaryNormalTextureID, 0);
         _primaryFramebufferObject->release();
     }
     return _primaryFramebufferObject;
@@ -225,6 +236,12 @@ GLuint TextureCache::getPrimaryDepthTextureID() {
     return _primaryDepthTextureID;
 }
 
+GLuint TextureCache::getPrimaryNormalTextureID() {
+    // ensure that the primary framebuffer object is initialized before returning the normal texture id
+    getPrimaryFramebufferObject();
+    return _primaryNormalTextureID;
+}
+
 QOpenGLFramebufferObject* TextureCache::getSecondaryFramebufferObject() {
     if (!_secondaryFramebufferObject) {
         _secondaryFramebufferObject = createFramebufferObject();
@@ -278,6 +295,7 @@ bool TextureCache::eventFilter(QObject* watched, QEvent* event) {
             delete _primaryFramebufferObject;
             _primaryFramebufferObject = NULL;
             glDeleteTextures(1, &_primaryDepthTextureID);
+            glDeleteTextures(1, &_primaryNormalTextureID);
         }
         if (_secondaryFramebufferObject && _secondaryFramebufferObject->size() != size) {
             delete _secondaryFramebufferObject;
diff --git a/interface/src/renderer/TextureCache.h b/interface/src/renderer/TextureCache.h
index e1d69677f6..ce0e7661af 100644
--- a/interface/src/renderer/TextureCache.h
+++ b/interface/src/renderer/TextureCache.h
@@ -61,6 +61,9 @@ public:
     /// Returns the ID of the primary framebuffer object's depth texture.  This contains the Z buffer used in rendering.
     GLuint getPrimaryDepthTextureID();
     
+    /// Returns the ID of the primary framebuffer object's normal texture.
+    GLuint getPrimaryNormalTextureID();
+    
     /// Returns a pointer to the secondary framebuffer object, used as an additional render target when performing full
     /// screen effects.
     QOpenGLFramebufferObject* getSecondaryFramebufferObject();
@@ -95,6 +98,7 @@ private:
     QHash<QUrl, QWeakPointer<NetworkTexture> > _dilatableNetworkTextures;
     
     GLuint _primaryDepthTextureID;
+    GLuint _primaryNormalTextureID;
     QOpenGLFramebufferObject* _primaryFramebufferObject;
     QOpenGLFramebufferObject* _secondaryFramebufferObject;
     QOpenGLFramebufferObject* _tertiaryFramebufferObject;
diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp
index b9e262f3fc..dc669365e8 100644
--- a/interface/src/ui/MetavoxelEditor.cpp
+++ b/interface/src/ui/MetavoxelEditor.cpp
@@ -971,11 +971,9 @@ ImportHeightfieldTool::ImportHeightfieldTool(MetavoxelEditor* editor) :
     connect(_height, &QAbstractButton::clicked, this, &ImportHeightfieldTool::selectHeightFile);
     _form->addRow("Color:", _color = new QPushButton());
     connect(_color, &QAbstractButton::clicked, this, &ImportHeightfieldTool::selectColorFile);
-}
-
-void ImportHeightfieldTool::render() {
-    HeightfieldTool::render();
-    _preview.render(_translation->getValue(), _translation->getSingleStep());
+    
+    connect(Application::getInstance()->getMetavoxels(), &MetavoxelSystem::rendering,
+        this, &ImportHeightfieldTool::renderPreview);
 }
 
 void ImportHeightfieldTool::apply() {
@@ -1084,6 +1082,12 @@ void ImportHeightfieldTool::updatePreview() {
     _preview.setBuffers(buffers);
 }
 
+void ImportHeightfieldTool::renderPreview() {
+    if (isVisible()) {
+        _preview.render(_translation->getValue(), _translation->getSingleStep());
+    }
+}
+
 EraseHeightfieldTool::EraseHeightfieldTool(MetavoxelEditor* editor) :
     HeightfieldTool(editor, "Erase Heightfield") {
     
diff --git a/interface/src/ui/MetavoxelEditor.h b/interface/src/ui/MetavoxelEditor.h
index 96f4a2abe9..447d2197cd 100644
--- a/interface/src/ui/MetavoxelEditor.h
+++ b/interface/src/ui/MetavoxelEditor.h
@@ -283,8 +283,6 @@ public:
     
     ImportHeightfieldTool(MetavoxelEditor* editor);
     
-    virtual void render();
-
 protected:
 
     virtual void apply();
@@ -294,6 +292,7 @@ private slots:
     void selectHeightFile();
     void selectColorFile();
     void updatePreview();
+    void renderPreview();
     
 private: