diff --git a/applications/gui/src/camera.cpp b/applications/gui/src/camera.cpp
index abb7fdb80100d85491dbf929909a8f1524365b2b..32d78a4a0ff8bbccfed60b13293b1277ab599792 100644
--- a/applications/gui/src/camera.cpp
+++ b/applications/gui/src/camera.cpp
@@ -376,7 +376,6 @@ const GLTexture &ftl::gui::Camera::captureFrame() {
 		cv::Mat tmp;
 
 		switch(channel_) {
-			case Channel::Normals:
 			case Channel::Energy:
 				if (depth_.rows == 0) { break; }
 				visualizeEnergy(depth_, tmp, 10.0);
@@ -400,6 +399,7 @@ const GLTexture &ftl::gui::Camera::captureFrame() {
 
 		case Channel::Flow:
 		case Channel::Confidence:
+		case Channel::Normals:
 		case Channel::Right:
 				if (depth_.rows == 0 || depth_.type() != CV_8UC3) { break; }
 				texture_.update(depth_);
diff --git a/components/renderers/cpp/include/ftl/cuda/normals.hpp b/components/renderers/cpp/include/ftl/cuda/normals.hpp
index 5481b4662a937bb6915b53b2150c3782b937af2e..b4d2ced197c383a6d2dc185bf1c8883d6dde356a 100644
--- a/components/renderers/cpp/include/ftl/cuda/normals.hpp
+++ b/components/renderers/cpp/include/ftl/cuda/normals.hpp
@@ -13,8 +13,8 @@ void normals(ftl::cuda::TextureObject<float4> &output,
         ftl::cuda::TextureObject<float4> &input, cudaStream_t stream);
 
 void normal_visualise(ftl::cuda::TextureObject<float4> &norm,
-        ftl::cuda::TextureObject<float> &output,
-        const ftl::rgbd::Camera &camera, const float4x4 &pose,
+        ftl::cuda::TextureObject<uchar4> &output,
+        const float3 &light, const uchar4 &diffuse, const uchar4 &ambient,
         cudaStream_t stream);
 
 void normal_filter(ftl::cuda::TextureObject<float4> &norm,
diff --git a/components/renderers/cpp/include/ftl/render/splat_render.hpp b/components/renderers/cpp/include/ftl/render/splat_render.hpp
index 5f8a8ba4940e910efd1e9b6f5a0f0e900fde1ff7..af6affb1020ee66b52796bb2cd0e26effa2d11ea 100644
--- a/components/renderers/cpp/include/ftl/render/splat_render.hpp
+++ b/components/renderers/cpp/include/ftl/render/splat_render.hpp
@@ -47,6 +47,9 @@ class Splatter : public ftl::render::Renderer {
 	bool backcull_;
 	cv::Scalar background_;
 	bool splat_;
+	float3 light_dir_;
+	uchar4 light_diffuse_;
+	uchar4 light_ambient_;
 };
 
 }
diff --git a/components/renderers/cpp/src/normals.cu b/components/renderers/cpp/src/normals.cu
index eec540ccd1ef022b91d6fb003b3cfa1d62a0a0a6..cbbd0e96797ee11fffd43de0df399a03efcf8db8 100644
--- a/components/renderers/cpp/src/normals.cu
+++ b/components/renderers/cpp/src/normals.cu
@@ -89,38 +89,37 @@ void ftl::cuda::normals(ftl::cuda::TextureObject<float4> &output,
 //==============================================================================
 
 __global__ void vis_normals_kernel(ftl::cuda::TextureObject<float4> norm,
-        ftl::cuda::TextureObject<float> output,
-        ftl::rgbd::Camera camera, float4x4 pose) {
+        ftl::cuda::TextureObject<uchar4> output,
+        float3 direction, uchar4 diffuse, uchar4 ambient) {
     const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
     const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
 
     if(x >= norm.width() || y >= norm.height()) return;
 
-    output(x,y) = 0.0f;
-    float3 ray = pose.getFloat3x3() * camera.screenToCam(x,y,1.0f);
+    output(x,y) = make_uchar4(0,0,0,0);
+    float3 ray = direction;
     ray = ray / length(ray);
     float3 n = make_float3(norm.tex2D((int)x,(int)y));
     float l = length(n);
     if (l == 0) return;
     n /= l;
 
-    const float d = dot(ray, n);
-    output(x,y) = (1.0f + d)*3.5f;  // FIXME: Do not hard code these value scalings
-    
-    //if (d > 0.2f) {
-    //    output(x,y) = d * 7.0f;
-    //}
+    const float d = max(dot(ray, n), 0.0f);
+    output(x,y) = make_uchar4(
+		min(255.0f, diffuse.x*d + ambient.x),
+		min(255.0f, diffuse.y*d + ambient.y),
+		min(255.0f, diffuse.z*d + ambient.z), 255);
 }
 
 void ftl::cuda::normal_visualise(ftl::cuda::TextureObject<float4> &norm,
-        ftl::cuda::TextureObject<float> &output,
-        const ftl::rgbd::Camera &camera, const float4x4 &pose,
+        ftl::cuda::TextureObject<uchar4> &output,
+        const float3 &light, const uchar4 &diffuse, const uchar4 &ambient,
         cudaStream_t stream) {
 
     const dim3 gridSize((norm.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (norm.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
     const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
 
-    vis_normals_kernel<<<gridSize, blockSize, 0, stream>>>(norm, output, camera, pose);
+    vis_normals_kernel<<<gridSize, blockSize, 0, stream>>>(norm, output, light, diffuse, ambient);
 
     cudaSafeCall( cudaGetLastError() );
 #ifdef _DEBUG
diff --git a/components/renderers/cpp/src/splat_render.cpp b/components/renderers/cpp/src/splat_render.cpp
index 26f8e893ee3846a06adadfa6d0fd42393d1e4c83..d993e145856840e7af545de7365e1e77af7f0866 100644
--- a/components/renderers/cpp/src/splat_render.cpp
+++ b/components/renderers/cpp/src/splat_render.cpp
@@ -25,7 +25,10 @@ static Eigen::Affine3d create_rotation_matrix(float ax, float ay, float az) {
   return rz * rx * ry;
 }
 
-static cv::Scalar parseColour(const std::string &colour) {
+/*
+ * Parse a CSS style colour string into a scalar.
+ */
+static cv::Scalar parseCVColour(const std::string &colour) {
 	std::string c = colour;
 	if (c[0] == '#') {
 		c.erase(0, 1);
@@ -41,6 +44,25 @@ static cv::Scalar parseColour(const std::string &colour) {
 	return cv::Scalar(0,0,0,0);
 }
 
+/*
+ * Parse a CSS style colour string into a scalar.
+ */
+static uchar4 parseCUDAColour(const std::string &colour) {
+	std::string c = colour;
+	if (c[0] == '#') {
+		c.erase(0, 1);
+		unsigned long value = stoul(c.c_str(), nullptr, 16);
+		return make_uchar4(
+			(value >> 0) & 0xff,
+			(value >> 8) & 0xff,
+			(value >> 16) & 0xff,
+			(value >> 24) & 0xff
+		);
+	}
+
+	return make_uchar4(0,0,0,0);
+}
+
 Splatter::Splatter(nlohmann::json &config, ftl::rgbd::FrameSet *fs) : ftl::render::Renderer(config), scene_(fs) {
 	if (config["clipping"].is_object()) {
 		auto &c = config["clipping"];
@@ -84,9 +106,19 @@ Splatter::Splatter(nlohmann::json &config, ftl::rgbd::FrameSet *fs) : ftl::rende
 		splat_ = value("splatting", true);
 	});
 
-	background_ = parseColour(value("background", std::string("#e0e0e0")));
+	background_ = parseCVColour(value("background", std::string("#4c4c4c")));
 	on("background", [this](const ftl::config::Event &e) {
-		background_ = parseColour(value("background", std::string("#e0e0e0")));
+		background_ = parseCVColour(value("background", std::string("#4c4c4c")));
+	});
+
+	light_diffuse_ = parseCUDAColour(value("diffuse", std::string("#e0e0e0")));
+	on("diffuse", [this](const ftl::config::Event &e) {
+		light_diffuse_ = parseCUDAColour(value("diffuse", std::string("#e0e0e0")));
+	});
+
+	light_ambient_ = parseCUDAColour(value("ambient", std::string("#0e0e0e")));
+	on("ambient", [this](const ftl::config::Event &e) {
+		light_ambient_ = parseCUDAColour(value("ambient", std::string("#0e0e0e")));
 	});
 }
 
@@ -322,11 +354,14 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cuda
 		renderChannel(params, out, Channel::Normals, stream);
 
 		// Convert normal to single float value
-		temp_.create<GpuMat>(Channel::Contribution, Format<float>(camera.width, camera.height));
-		ftl::cuda::normal_visualise(out.getTexture<float4>(Channel::Normals), temp_.createTexture<float>(Channel::Contribution), camera, params.m_viewMatrixInverse, stream);
+		temp_.create<GpuMat>(Channel::Colour, Format<uchar4>(camera.width, camera.height));
+		ftl::cuda::normal_visualise(out.getTexture<float4>(Channel::Normals), temp_.createTexture<uchar4>(Channel::Colour),
+				make_float3(-0.3f, 0.2f, 1.0f),
+				light_diffuse_,
+				light_ambient_, stream);
 
 		// Put in output as single float
-		cv::cuda::swap(temp_.get<GpuMat>(Channel::Contribution), out.create<GpuMat>(Channel::Normals));
+		cv::cuda::swap(temp_.get<GpuMat>(Channel::Colour), out.create<GpuMat>(Channel::Normals));
 		out.resetTexture(Channel::Normals);
 	}
 	else if (chan == Channel::Contribution)
diff --git a/components/rgbd-sources/include/ftl/rgbd/channels.hpp b/components/rgbd-sources/include/ftl/rgbd/channels.hpp
index d1d8dcdbf2f66841f5e12da01d04a4438e145d23..e89a42ce3c808e254e8ae6085b4517a1f74bd79d 100644
--- a/components/rgbd-sources/include/ftl/rgbd/channels.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/channels.hpp
@@ -103,7 +103,7 @@ static const Channels kAllChannels(0xFFFFFFFFu);
 inline bool isFloatChannel(ftl::rgbd::Channel chan) {
 	switch (chan) {
 	case Channel::Depth		:
-    case Channel::Normals   :
+    //case Channel::Normals   :
 	case Channel::Energy	: return true;
 	default					: return false;
 	}