diff --git a/applications/gui/src/camera.cpp b/applications/gui/src/camera.cpp
index fe99677391afe445b9a684d8b952c4994c479289..d4c85b5d2e5e72b6c6d6c296ebf2921a7d13c636 100644
--- a/applications/gui/src/camera.cpp
+++ b/applications/gui/src/camera.cpp
@@ -235,6 +235,7 @@ void ftl::gui::Camera::setChannel(Channel c) {
 	channel_ = c;
 	switch (c) {
 	case Channel::Energy:
+	case Channel::Density:
 	case Channel::Flow:
 	case Channel::Confidence:
 	case Channel::Normals:
@@ -381,6 +382,7 @@ const GLTexture &ftl::gui::Camera::captureFrame() {
 				visualizeEnergy(depth_, tmp, 1.0);
 				texture_.update(tmp);
 				break;
+			case Channel::Density:
 			case Channel::Energy:
 				if (depth_.rows == 0) { break; }
 				visualizeEnergy(depth_, tmp, 10.0);
diff --git a/applications/gui/src/media_panel.cpp b/applications/gui/src/media_panel.cpp
index cb44400bb1c850669332376e0134f138b9c460f3..9aa81932da0eab2aa814846fccc9f107e176a7ff 100644
--- a/applications/gui/src/media_panel.cpp
+++ b/applications/gui/src/media_panel.cpp
@@ -191,6 +191,15 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""),
         }
     });
 
+	button = new Button(popup, "Density");
+    button->setFlags(Button::RadioButton);
+    button->setCallback([this]() {
+        ftl::gui::Camera *cam = screen_->activeCamera();
+        if (cam) {
+            cam->setChannel(Channel::Density);
+        }
+    });
+
 }
 
 MediaPanel::~MediaPanel() {
diff --git a/applications/reconstruct/src/ilw/ilw.cu b/applications/reconstruct/src/ilw/ilw.cu
index 831ff641b50dd7396a7282b99119a72164f6a34e..97ea74eebdc8f3b3d8aeec75f5153ce468808a33 100644
--- a/applications/reconstruct/src/ilw/ilw.cu
+++ b/applications/reconstruct/src/ilw/ilw.cu
@@ -195,14 +195,14 @@ __global__ void correspondence_energy_vector_kernel(
 	const float avgcolour = totalcolour/(float)count;
     const float confidence = bestcolour / totalcolour; //bestcolour - avgcolour;
 
-    if (bestweight > 0.0f) {
+    //if (bestweight > 0.0f) {
         float old = conf.tex2D(x,y);
 
-        if (bestweight * confidence > old) {
+        if (bestweight * confidence >= old) {
 			dout(x,y) = bestdepth;
 			conf(x,y) = bestweight * confidence;
 		}
-    }
+    //}
 }
 
 void ftl::cuda::correspondence(
diff --git a/components/common/cpp/include/ftl/exception.hpp b/components/common/cpp/include/ftl/exception.hpp
index 6719158bba5f5f53abfcdeb0fb39a5b5dda0d5f2..921e53493eda023a35af6f4c53b43ca2e26125d9 100644
--- a/components/common/cpp/include/ftl/exception.hpp
+++ b/components/common/cpp/include/ftl/exception.hpp
@@ -1,18 +1,49 @@
 #ifndef _FTL_EXCEPTION_HPP_
 #define _FTL_EXCEPTION_HPP_
 
+#include <sstream>
+
 namespace ftl {
+class Formatter {
+	public:
+	Formatter() {}
+    ~Formatter() {}
+
+    template <typename Type>
+    inline Formatter & operator << (const Type & value)
+    {
+        stream_ << value;
+        return *this;
+    }
+
+    inline std::string str() const         { return stream_.str(); }
+    inline operator std::string () const   { return stream_.str(); }
+
+    enum ConvertToString 
+    {
+        to_str
+    };
+    inline std::string operator >> (ConvertToString) { return stream_.str(); }
+
+private:
+    std::stringstream stream_;
+
+    Formatter(const Formatter &);
+    Formatter & operator = (Formatter &);
+};
+
 class exception : public std::exception
 {
 	public:
-	explicit exception(const char *msg) : msg_(msg) {};
+	explicit exception(const char *msg) : msg_(msg) {}
+	explicit exception(const Formatter &msg) : msg_(msg.str()) {}
 
 	const char * what () const throw () {
-    	return msg_;
+    	return msg_.c_str();
     }
 
 	private:
-	const char *msg_;
+	std::string msg_;
 };
 }
 
diff --git a/components/renderers/cpp/include/ftl/cuda/intersections.hpp b/components/renderers/cpp/include/ftl/cuda/intersections.hpp
index 9cfdbc2544d9c1bd32c9f5e12a0a161f45c50d54..9008d68c66ac258d1dca651f7cc0e4a3230bdbda 100644
--- a/components/renderers/cpp/include/ftl/cuda/intersections.hpp
+++ b/components/renderers/cpp/include/ftl/cuda/intersections.hpp
@@ -49,8 +49,7 @@ __device__ inline bool intersectDisk(const float3 &n, const float3 &p0, float ra
  * @param l Normalised ray direction in camera space
  * @return Radius from centre of disk where intersection occurred.
  */
-__device__ inline float intersectDistance(const float3 &n, const float3 &p0, const float3 &l0, const float3 &l) { 
-    float t = 0; 
+__device__ inline float intersectDistance(const float3 &n, const float3 &p0, const float3 &l0, const float3 &l, float &t) { 
     if (intersectPlane(n, p0, l0, l, t)) { 
         const float3 p = l0 + l * t; 
         const float3 v = p - p0; 
diff --git a/components/renderers/cpp/include/ftl/cuda/normals.hpp b/components/renderers/cpp/include/ftl/cuda/normals.hpp
index b9801743a5e505cf38a7b632127755f248e618c9..0e1b8a46fc59d9b177669f63d446d00bd3452149 100644
--- a/components/renderers/cpp/include/ftl/cuda/normals.hpp
+++ b/components/renderers/cpp/include/ftl/cuda/normals.hpp
@@ -11,9 +11,19 @@ namespace cuda {
 void normals(ftl::cuda::TextureObject<float4> &output,
         ftl::cuda::TextureObject<float4> &temp,
         ftl::cuda::TextureObject<float4> &input,
+		int radius,
+		float smoothing,
         const ftl::rgbd::Camera &camera,
         const float3x3 &pose, cudaStream_t stream);
 
+void normals(ftl::cuda::TextureObject<float4> &output,
+        ftl::cuda::TextureObject<float4> &temp,
+        ftl::cuda::TextureObject<int> &input,  // Integer depth values
+		int radius,
+		float smoothing,
+        const ftl::rgbd::Camera &camera,
+        const float3x3 &pose_inv, const float3x3 &pose, cudaStream_t stream);
+
 void normal_visualise(ftl::cuda::TextureObject<float4> &norm,
         ftl::cuda::TextureObject<uchar4> &output,
         const float3 &light, const uchar4 &diffuse, const uchar4 &ambient,
diff --git a/components/renderers/cpp/include/ftl/render/splat_render.hpp b/components/renderers/cpp/include/ftl/render/splat_render.hpp
index af6affb1020ee66b52796bb2cd0e26effa2d11ea..8fc0e10311668c3938b77c835ef31f8100905f64 100644
--- a/components/renderers/cpp/include/ftl/render/splat_render.hpp
+++ b/components/renderers/cpp/include/ftl/render/splat_render.hpp
@@ -26,7 +26,7 @@ class Splatter : public ftl::render::Renderer {
 	//void setOutputDevice(int);
 
 	protected:
-	void renderChannel(ftl::render::SplatParams &params, ftl::rgbd::Frame &out, const ftl::rgbd::Channel &channel, cudaStream_t stream);
+	void _renderChannel(ftl::rgbd::Frame &out, ftl::rgbd::Channel channel_in, ftl::rgbd::Channel channel_out, cudaStream_t stream);
 
 	private:
 	int device_;
@@ -40,6 +40,7 @@ class Splatter : public ftl::render::Renderer {
 	//SplatParams params_;
 
 	ftl::rgbd::Frame temp_;
+	ftl::rgbd::Frame accum_;
 	ftl::rgbd::FrameSet *scene_;
 	ftl::cuda::ClipSpace clip_;
 	bool clipping_;
@@ -50,6 +51,12 @@ class Splatter : public ftl::render::Renderer {
 	float3 light_dir_;
 	uchar4 light_diffuse_;
 	uchar4 light_ambient_;
+	ftl::render::SplatParams params_;
+
+	template <typename T>
+	void __blendChannel(ftl::rgbd::Frame &, ftl::rgbd::Channel in, ftl::rgbd::Channel out, cudaStream_t);
+	void _blendChannel(ftl::rgbd::Frame &, ftl::rgbd::Channel in, ftl::rgbd::Channel out, cudaStream_t);
+	void _dibr(cudaStream_t);
 };
 
 }
diff --git a/components/renderers/cpp/src/normals.cu b/components/renderers/cpp/src/normals.cu
index 4d66869316bb3cc4f7f9bd56f889bf92ecc4635c..13b82e3a09170b5223185a92fdc731ddc209ccdf 100644
--- a/components/renderers/cpp/src/normals.cu
+++ b/components/renderers/cpp/src/normals.cu
@@ -31,6 +31,38 @@ __global__ void computeNormals_kernel(ftl::cuda::TextureObject<float4> output,
 	}
 }
 
+__device__ inline bool isValid(const ftl::rgbd::Camera &camera, const float3 &d) {
+	return d.z >= camera.minDepth && d.z <= camera.maxDepth;
+}
+
+__global__ void computeNormals_kernel(ftl::cuda::TextureObject<float4> output,
+		ftl::cuda::TextureObject<int> input, ftl::rgbd::Camera camera, float3x3 pose) {
+	const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
+	const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
+
+	if(x >= input.width() || y >= input.height()) return;
+
+	output(x,y) = make_float4(0, 0, 0, 0);
+
+	if(x > 0 && x < input.width()-1 && y > 0 && y < input.height()-1) {
+		const float3 CC = camera.screenToCam(x+0, y+0, (float)input.tex2D((int)x+0, (int)y+0) / 1000.0f);
+		const float3 PC = camera.screenToCam(x+0, y+1, (float)input.tex2D((int)x+0, (int)y+1) / 1000.0f);
+		const float3 CP = camera.screenToCam(x+1, y+0, (float)input.tex2D((int)x+1, (int)y+0) / 1000.0f);
+		const float3 MC = camera.screenToCam(x+0, y-1, (float)input.tex2D((int)x+0, (int)y-1) / 1000.0f);
+		const float3 CM = camera.screenToCam(x-1, y+0, (float)input.tex2D((int)x-1, (int)y+0) / 1000.0f);
+
+		//if(CC.z <  && PC.x != MINF && CP.x != MINF && MC.x != MINF && CM.x != MINF) {
+		if (isValid(camera,CC) && isValid(camera,PC) && isValid(camera,CP) && isValid(camera,MC) && isValid(camera,CM)) {
+			const float3 n = cross(PC-MC, CP-CM);
+			const float  l = length(n);
+
+			if(l > 0.0f) {
+				output(x,y) = make_float4((n/-l), 1.0f);
+			}
+		}
+	}
+}
+
 template <int RADIUS>
 __global__ void smooth_normals_kernel(ftl::cuda::TextureObject<float4> norms,
         ftl::cuda::TextureObject<float4> output,
@@ -74,9 +106,54 @@ __global__ void smooth_normals_kernel(ftl::cuda::TextureObject<float4> norms,
     output(x,y) = (contrib > 0.0f) ? make_float4(nsum, dot(nsum, ray)) : make_float4(0.0f);
 }
 
+template <int RADIUS>
+__global__ void smooth_normals_kernel(ftl::cuda::TextureObject<float4> norms,
+        ftl::cuda::TextureObject<float4> output,
+        ftl::cuda::TextureObject<int> depth,
+        ftl::rgbd::Camera camera, float3x3 pose, float smoothing) {
+    const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
+    const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
+
+    if(x >= depth.width() || y >= depth.height()) return;
+
+    const float3 p0 = camera.screenToCam(x,y, (float)depth.tex2D((int)x,(int)y) / 1000.0f);
+    float3 nsum = make_float3(0.0f);
+    float contrib = 0.0f;
+
+    if (p0.z < camera.minDepth || p0.z > camera.maxDepth) return;
+
+    for (int v=-RADIUS; v<=RADIUS; ++v) {
+        for (int u=-RADIUS; u<=RADIUS; ++u) {
+            const float3 p = camera.screenToCam(x+u,y+v, (float)depth.tex2D((int)x+u,(int)y+v) / 1000.0f);
+            if (p.z < camera.minDepth || p.z > camera.maxDepth) continue;
+            const float s = ftl::cuda::spatialWeighting(p0, p, smoothing);
+            //const float s = 1.0f;
+
+            //if (s > 0.0f) {
+                const float4 n = norms.tex2D((int)x+u,(int)y+v);
+                if (n.w > 0.0f) {
+                    nsum += make_float3(n) * s;
+                    contrib += s;
+                }
+            //}
+        }
+    }
+
+    // Compute dot product of normal with camera to obtain measure of how
+    // well this point faces the source camera, a measure of confidence
+    float3 ray = camera.screenToCam(x, y, 1.0f);
+    ray = ray / length(ray);
+    nsum /= contrib;
+    nsum /= length(nsum);
+
+    output(x,y) = (contrib > 0.0f) ? make_float4(pose*nsum, 1.0f) : make_float4(0.0f);
+}
+
 void ftl::cuda::normals(ftl::cuda::TextureObject<float4> &output,
         ftl::cuda::TextureObject<float4> &temp,
-        ftl::cuda::TextureObject<float4> &input,
+		ftl::cuda::TextureObject<float4> &input,
+		int radius,
+		float smoothing,
         const ftl::rgbd::Camera &camera,
         const float3x3 &pose,cudaStream_t stream) {
 	const dim3 gridSize((input.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (input.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
@@ -85,7 +162,12 @@ void ftl::cuda::normals(ftl::cuda::TextureObject<float4> &output,
 	computeNormals_kernel<<<gridSize, blockSize, 0, stream>>>(temp, input);
     cudaSafeCall( cudaGetLastError() );
 
-    smooth_normals_kernel<3><<<gridSize, blockSize, 0, stream>>>(temp, output, input, camera, pose, 0.04f);
+	switch (radius) {
+	case 9: smooth_normals_kernel<9><<<gridSize, blockSize, 0, stream>>>(temp, output, input, camera, pose, smoothing); break;
+	case 7: smooth_normals_kernel<7><<<gridSize, blockSize, 0, stream>>>(temp, output, input, camera, pose, smoothing); break;
+	case 5: smooth_normals_kernel<5><<<gridSize, blockSize, 0, stream>>>(temp, output, input, camera, pose, smoothing); break;
+	case 3: smooth_normals_kernel<3><<<gridSize, blockSize, 0, stream>>>(temp, output, input, camera, pose, smoothing); break;
+	}
     cudaSafeCall( cudaGetLastError() );
 
 #ifdef _DEBUG
@@ -94,6 +176,32 @@ void ftl::cuda::normals(ftl::cuda::TextureObject<float4> &output,
 #endif
 }
 
+void ftl::cuda::normals(ftl::cuda::TextureObject<float4> &output,
+		ftl::cuda::TextureObject<float4> &temp,
+		ftl::cuda::TextureObject<int> &input,
+		int radius,
+		float smoothing,
+		const ftl::rgbd::Camera &camera,
+		const float3x3 &pose_inv, const float3x3 &pose,cudaStream_t stream) {
+	const dim3 gridSize((input.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (input.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
+	const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
+
+	computeNormals_kernel<<<gridSize, blockSize, 0, stream>>>(temp, input, camera, pose);
+	cudaSafeCall( cudaGetLastError() );
+
+	switch (radius) {
+	case 7: smooth_normals_kernel<7><<<gridSize, blockSize, 0, stream>>>(temp, output, input, camera, pose, smoothing);
+	case 5: smooth_normals_kernel<5><<<gridSize, blockSize, 0, stream>>>(temp, output, input, camera, pose, smoothing);
+	case 3: smooth_normals_kernel<3><<<gridSize, blockSize, 0, stream>>>(temp, output, input, camera, pose, smoothing);
+	}
+	cudaSafeCall( cudaGetLastError() );
+
+	#ifdef _DEBUG
+	cudaSafeCall(cudaDeviceSynchronize());
+	//cutilCheckMsg(__FUNCTION__);
+	#endif
+}
+
 //==============================================================================
 
 __global__ void vis_normals_kernel(ftl::cuda::TextureObject<float4> norm,
diff --git a/components/renderers/cpp/src/splat_render.cpp b/components/renderers/cpp/src/splat_render.cpp
index 1ccaf156cc31d7ba2097364e627d6d93500d293a..813cbda8392ce36d8b9f70d1239087e55cbfd8f7 100644
--- a/components/renderers/cpp/src/splat_render.cpp
+++ b/components/renderers/cpp/src/splat_render.cpp
@@ -126,23 +126,73 @@ Splatter::~Splatter() {
 
 }
 
-void Splatter::renderChannel(
-					ftl::render::SplatParams &params, ftl::rgbd::Frame &out,
-					const Channel &channel, cudaStream_t stream)
-{
-	if (channel == Channel::None) return;
+template <typename T>
+struct AccumSelector {
+	typedef float4 type;
+	static constexpr Channel channel = Channel::Colour;
+	//static constexpr cv::Scalar value = cv::Scalar(0.0f,0.0f,0.0f,0.0f);
+};
+
+template <>
+struct AccumSelector<float> {
+	typedef float type;
+	static constexpr Channel channel = Channel::Colour2;
+	//static constexpr cv::Scalar value = cv::Scalar(0.0f);
+};
+
+template <typename T>
+void Splatter::__blendChannel(ftl::rgbd::Frame &output, ftl::rgbd::Channel in, ftl::rgbd::Channel out, cudaStream_t stream) {
 	cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream);
-	temp_.get<GpuMat>(Channel::Depth).setTo(cv::Scalar(0x7FFFFFFF), cvstream);
-	temp_.get<GpuMat>(Channel::Depth2).setTo(cv::Scalar(0x7FFFFFFF), cvstream);
-	//temp_.get<GpuMat>(Channel::Colour).setTo(cv::Scalar(0.0f,0.0f,0.0f,0.0f), cvstream);
-	//temp_.get<GpuMat>(Channel::Contribution).setTo(cv::Scalar(0.0f), cvstream);
+	temp_.create<GpuMat>(
+		AccumSelector<T>::channel,
+		Format<typename AccumSelector<T>::type>(params_.camera.width, params_.camera.height)
+	).setTo(cv::Scalar(0.0f), cvstream);
+	temp_.get<GpuMat>(Channel::Contribution).setTo(cv::Scalar(0.0f), cvstream);
 
-	if (scene_->frames.size() < 1) return;
-	bool is_float = out.get<GpuMat>(channel).type() == CV_32F; //ftl::rgbd::isFloatChannel(channel);
-	bool is_4chan = out.get<GpuMat>(channel).type() == CV_32FC4;
+	temp_.createTexture<float>(Channel::Contribution);
+
+	for (auto &f : scene_->frames) {
+		if (f.get<GpuMat>(in).type() == CV_8UC3) {
+			// Convert to 4 channel colour
+			auto &col = f.get<GpuMat>(in);
+			GpuMat tmp(col.size(), CV_8UC4);
+			cv::cuda::swap(col, tmp);
+			cv::cuda::cvtColor(tmp,col, cv::COLOR_BGR2BGRA);
+		}
+
+		ftl::cuda::dibr_attribute(
+			f.createTexture<T>(in),
+			f.createTexture<float4>(Channel::Points),
+			temp_.getTexture<int>(Channel::Depth2),
+			temp_.createTexture<typename AccumSelector<T>::type>(AccumSelector<T>::channel),
+			temp_.getTexture<float>(Channel::Contribution),
+			params_, stream
+		);
+	}
+
+	ftl::cuda::dibr_normalise(
+		temp_.getTexture<typename AccumSelector<T>::type>(AccumSelector<T>::channel),
+		output.createTexture<T>(out),
+		temp_.getTexture<float>(Channel::Contribution),
+		stream
+	);
+}
+
+void Splatter::_blendChannel(ftl::rgbd::Frame &output, ftl::rgbd::Channel in, ftl::rgbd::Channel out, cudaStream_t stream) {
+	int type = output.get<GpuMat>(out).type(); // == CV_32F; //ftl::rgbd::isFloatChannel(channel);
 	
-	// Render each camera into virtual view
-	// TODO: Move out of renderChannel, this is a common step to all channels
+	switch (type) {
+	case CV_32F		: __blendChannel<float>(output, in, out, stream); break;
+	case CV_32FC4	: __blendChannel<float4>(output, in, out, stream); break;
+	case CV_8UC4	: __blendChannel<uchar4>(output, in, out, stream); break;
+	default			: LOG(ERROR) << "Invalid output channel format";
+	}
+}
+
+void Splatter::_dibr(cudaStream_t stream) {
+	cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream);
+	temp_.get<GpuMat>(Channel::Depth2).setTo(cv::Scalar(0x7FFFFFFF), cvstream);
+
 	for (size_t i=0; i < scene_->frames.size(); ++i) {
 		auto &f = scene_->frames[i];
 		auto *s = scene_->sources[i];
@@ -156,111 +206,89 @@ void Splatter::renderChannel(
 			f.createTexture<float4>(Channel::Points),
 			f.createTexture<float4>(Channel::Normals),
 			temp_.createTexture<int>(Channel::Depth2),
-			params, backcull_, stream
+			params_, backcull_, stream
 		);
 
 		//LOG(INFO) << "DIBR DONE";
 	}
+}
 
-	//temp_.createTexture<float4>(Channel::Colour);
-	//temp_.createTexture<float>(Channel::Contribution);
-	out.create<GpuMat>(Channel::Normals, Format<float4>(params.camera.width, params.camera.height));
+void Splatter::_renderChannel(
+		ftl::rgbd::Frame &out,
+		Channel channel_in, Channel channel_out, cudaStream_t stream)
+{
+	if (channel_out == Channel::None || channel_in == Channel::None) return;
+	cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream);
 
-	// Create normals first
-	for (auto &f : scene_->frames) {
-		ftl::cuda::dibr_attribute(
-			f.createTexture<float4>(Channel::Normals),
-			f.createTexture<float4>(Channel::Points),
-			temp_.getTexture<int>(Channel::Depth2),
-			out.createTexture<float4>(Channel::Normals),
-			params, stream
-		);
-	}
+	if (scene_->frames.size() < 1) return;
+	bool is_float = out.get<GpuMat>(channel_out).type() == CV_32F; //ftl::rgbd::isFloatChannel(channel);
+	bool is_4chan = out.get<GpuMat>(channel_out).type() == CV_32FC4;
+
+
+	temp_.createTexture<float4>(Channel::Colour);
+	temp_.createTexture<float>(Channel::Contribution);
+
+	// Generate initial normals for the splats
+	accum_.create<GpuMat>(Channel::Normals, Format<float4>(params_.camera.width, params_.camera.height));
+	_blendChannel(accum_, Channel::Normals, Channel::Normals, stream);
 
-	//temp_.get<GpuMat>(Channel::Colour).setTo(cv::Scalar(0.0f,0.0f,0.0f,0.0f), cvstream);
-	//temp_.get<GpuMat>(Channel::Contribution).setTo(cv::Scalar(0.0f), cvstream);
+	// Estimate point density
+	accum_.create<GpuMat>(Channel::Density, Format<float>(params_.camera.width, params_.camera.height));
+	_blendChannel(accum_, Channel::Depth, Channel::Density, stream);
 
+	// FIXME: Using colour 2 in this way seems broken since it is already used
 	if (is_4chan) {
-		temp_.create<GpuMat>(Channel::Colour2, Format<float4>(params.camera.width, params.camera.height));
-		temp_.get<GpuMat>(Channel::Colour2).setTo(cv::Scalar(0.0f,0.0f,0.0f,0.0f), cvstream);
+		accum_.create<GpuMat>(channel_out, Format<float4>(params_.camera.width, params_.camera.height));
+		accum_.get<GpuMat>(channel_out).setTo(cv::Scalar(0.0f,0.0f,0.0f,0.0f), cvstream);
 	} else if (is_float) {
-		temp_.create<GpuMat>(Channel::Colour2, Format<float>(params.camera.width, params.camera.height));
-		temp_.get<GpuMat>(Channel::Colour2).setTo(cv::Scalar(0.0f), cvstream);
+		accum_.create<GpuMat>(channel_out, Format<float>(params_.camera.width, params_.camera.height));
+		accum_.get<GpuMat>(channel_out).setTo(cv::Scalar(0.0f), cvstream);
 	} else {
-		temp_.create<GpuMat>(Channel::Colour2, Format<uchar4>(params.camera.width, params.camera.height));
-		temp_.get<GpuMat>(Channel::Colour2).setTo(cv::Scalar(0,0,0,0), cvstream);
+		accum_.create<GpuMat>(channel_out, Format<uchar4>(params_.camera.width, params_.camera.height));
+		accum_.get<GpuMat>(channel_out).setTo(cv::Scalar(0,0,0,0), cvstream);
 	}
 
-	// Create attribute first
-	for (auto &f : scene_->frames) {
-		// Convert colour from BGR to BGRA if needed
-		if (f.get<GpuMat>(channel).type() == CV_8UC3) {
-			// Convert to 4 channel colour
-			auto &col = f.get<GpuMat>(Channel::Colour);
-			GpuMat tmp(col.size(), CV_8UC4);
-			cv::cuda::swap(col, tmp);
-			cv::cuda::cvtColor(tmp,col, cv::COLOR_BGR2BGRA);
-		}
-		
-		if (is_4chan) {
-			ftl::cuda::dibr_attribute(
-				f.createTexture<float4>(channel),
-				f.createTexture<float4>(Channel::Points),
-				temp_.getTexture<int>(Channel::Depth2),
-				(splat_) ? temp_.createTexture<float4>(Channel::Colour2) : out.createTexture<float4>(channel),
-				params, stream
-			);
-		} else if (is_float) {
-			ftl::cuda::dibr_attribute(
-				f.createTexture<float>(channel),
-				f.createTexture<float4>(Channel::Points),
-				temp_.getTexture<int>(Channel::Depth2),
-				(splat_) ? temp_.createTexture<float>(Channel::Colour2) : out.createTexture<float>(channel),
-				params, stream
-			);
-		} else {
-			ftl::cuda::dibr_attribute(
-				f.createTexture<uchar4>(channel),
-				f.createTexture<float4>(Channel::Points),
-				temp_.getTexture<int>(Channel::Depth2),
-				(splat_) ? temp_.createTexture<uchar4>(Channel::Colour2) : out.createTexture<uchar4>(channel),
-				params, stream
-			);
-		}
-	}
-
-	//out.get<GpuMat>(Channel::Left).setTo(cv::Scalar(0,0,0,0), cvstream);
+	//if (splat_) {
+		_blendChannel(accum_, channel_in, channel_out, stream);
+	//} else {
+	//	_blendChannel(out, channel, channel, stream);
+	//}
 
 	// Now splat the points
 	if (splat_) {
 		if (is_4chan) {
 			ftl::cuda::splat(
-				out.getTexture<float4>(Channel::Normals),
-				temp_.getTexture<float4>(Channel::Colour2),
+				accum_.getTexture<float4>(Channel::Normals),
+				accum_.getTexture<float>(Channel::Density),
+				accum_.getTexture<float4>(channel_out),
 				temp_.getTexture<int>(Channel::Depth2),
 				out.createTexture<float>(Channel::Depth),
-				out.createTexture<float4>(channel),
-				params, stream
+				out.createTexture<float4>(channel_out),
+				params_, stream
 			);
 		} else if (is_float) {
 			ftl::cuda::splat(
-				out.getTexture<float4>(Channel::Normals),
-				temp_.getTexture<float>(Channel::Colour2),
+				accum_.getTexture<float4>(Channel::Normals),
+				accum_.getTexture<float>(Channel::Density),
+				accum_.getTexture<float>(channel_out),
 				temp_.getTexture<int>(Channel::Depth2),
 				out.createTexture<float>(Channel::Depth),
-				out.createTexture<float>(channel),
-				params, stream
+				out.createTexture<float>(channel_out),
+				params_, stream
 			);
 		} else {
 			ftl::cuda::splat(
-				out.getTexture<float4>(Channel::Normals),
-				temp_.getTexture<uchar4>(Channel::Colour2),
+				accum_.getTexture<float4>(Channel::Normals),
+				accum_.getTexture<float>(Channel::Density),
+				accum_.getTexture<uchar4>(channel_out),
 				temp_.getTexture<int>(Channel::Depth2),
 				out.createTexture<float>(Channel::Depth),
-				out.createTexture<uchar4>(channel),
-				params, stream
+				out.createTexture<uchar4>(channel_out),
+				params_, stream
 			);
 		}
+	} else {
+		// Swap accum frames directly to output.
 	}
 }
 
@@ -277,8 +305,8 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cuda
 	out.create<GpuMat>(Channel::Colour, Format<uchar4>(camera.width, camera.height));
 
 	// FIXME: Use source resolutions, not virtual resolution
-	//temp_.create<GpuMat>(Channel::Colour, Format<float4>(camera.width, camera.height));
-	//temp_.create<GpuMat>(Channel::Contribution, Format<float>(camera.width, camera.height));
+	temp_.create<GpuMat>(Channel::Colour, Format<float4>(camera.width, camera.height));
+	temp_.create<GpuMat>(Channel::Contribution, Format<float>(camera.width, camera.height));
 	temp_.create<GpuMat>(Channel::Depth, Format<int>(camera.width, camera.height));
 	temp_.create<GpuMat>(Channel::Depth2, Format<int>(camera.width, camera.height));
 	temp_.create<GpuMat>(Channel::Normals, Format<float4>(camera.width, camera.height));
@@ -286,7 +314,7 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cuda
 	cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream);
 
 	// Parameters object to pass to CUDA describing the camera
-	SplatParams params;
+	SplatParams &params = params_;
 	params.m_flags = 0;
 	if (value("show_discontinuity_mask", false)) params.m_flags |= ftl::render::kShowDisconMask;
 	if (value("normal_weight_colours", true)) params.m_flags |= ftl::render::kNormalWeightColours;
@@ -302,6 +330,7 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cuda
 	//LOG(INFO) << "Render ready: " << camera.width << "," << camera.height;
 
 	temp_.createTexture<int>(Channel::Depth);
+	//temp_.get<GpuMat>(Channel::Normals).setTo(cv::Scalar(0.0f,0.0f,0.0f,0.0f), cvstream);
 
 	// First make sure each input has normals
 	temp_.createTexture<float4>(Channel::Normals);
@@ -332,7 +361,9 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cuda
 			auto &g = f.get<GpuMat>(Channel::Colour);
 			ftl::cuda::normals(f.createTexture<float4>(Channel::Normals, Format<float4>(g.cols, g.rows)),
 				temp_.getTexture<float4>(Channel::Normals),  // FIXME: Uses assumption of vcam res same as input res
-				f.getTexture<float4>(Channel::Points), s->parameters(), pose.getFloat3x3(), stream);
+				f.getTexture<float4>(Channel::Points),
+				3, 0.04f,
+				s->parameters(), pose.getFloat3x3(), stream);
 
 			if (norm_filter_ > -0.1f) {
 				ftl::cuda::normal_filter(f.getTexture<float4>(Channel::Normals), f.getTexture<float4>(Channel::Points), s->parameters(), pose, norm_filter_, stream);
@@ -340,7 +371,8 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cuda
 		}
 	}
 
-	renderChannel(params, out, Channel::Colour, stream);
+	_dibr(stream);
+	_renderChannel(out, Channel::Colour, Channel::Colour, stream);
 	
 	Channel chan = src->getChannel();
 	if (chan == Channel::Depth)
@@ -350,12 +382,12 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cuda
 		out.create<GpuMat>(Channel::Normals, Format<float4>(camera.width, camera.height));
 
 		// Render normal attribute
-		renderChannel(params, out, Channel::Normals, stream);
+		_renderChannel(out, Channel::Normals, Channel::Normals, stream);
 
 		// Convert normal to single float value
 		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),
+				make_float3(0.3f, 0.2f, 1.0f),
 				light_diffuse_,
 				light_ambient_, stream);
 
@@ -367,6 +399,11 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cuda
 	//{
 	//	cv::cuda::swap(temp_.get<GpuMat>(Channel::Contribution), out.create<GpuMat>(Channel::Contribution));
 	//}
+	else if (chan == Channel::Density) {
+		out.create<GpuMat>(chan, Format<float>(camera.width, camera.height));
+		out.get<GpuMat>(chan).setTo(cv::Scalar(0.0f), cvstream);
+		_renderChannel(out, Channel::Depth, Channel::Density, stream);
+	}
 	else if (chan == Channel::Right)
 	{
 		Eigen::Affine3f transform(Eigen::Translation3f(camera.baseline,0.0f,0.0f));
@@ -376,7 +413,9 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cuda
 		
 		out.create<GpuMat>(Channel::Right, Format<uchar4>(camera.width, camera.height));
 		out.get<GpuMat>(Channel::Right).setTo(background_, cvstream);
-		renderChannel(params, out, Channel::Right, stream);
+
+		_dibr(stream); // Need to re-dibr due to pose change
+		_renderChannel(out, Channel::Right, Channel::Right, stream);
 	} else if (chan != Channel::None) {
 		if (ftl::rgbd::isFloatChannel(chan)) {
 			out.create<GpuMat>(chan, Format<float>(camera.width, camera.height));
@@ -385,7 +424,7 @@ bool Splatter::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, cuda
 			out.create<GpuMat>(chan, Format<uchar4>(camera.width, camera.height));
 			out.get<GpuMat>(chan).setTo(background_, cvstream);
 		}
-		renderChannel(params, out, chan, stream);
+		_renderChannel(out, chan, chan, stream);
 	}
 
 	return true;
diff --git a/components/renderers/cpp/src/splatter.cu b/components/renderers/cpp/src/splatter.cu
index 3567d5c07dcdc819b93989992069b3244149855f..253072ca9e3e3aa9e467118563e9f004a636ad21 100644
--- a/components/renderers/cpp/src/splatter.cu
+++ b/components/renderers/cpp/src/splatter.cu
@@ -127,6 +127,22 @@ __device__ inline float make(const float4 &v) {
 	return v.x;
 }
 
+template <typename T>
+__device__ inline T make(const uchar4 &v);
+
+template <>
+__device__ inline float4 make(const uchar4 &v) {
+	return make_float4((float)v.x, (float)v.y, (float)v.z, (float)v.w);
+}
+
+template <typename T>
+__device__ inline T make(float v);
+
+template <>
+__device__ inline float make(float v) {
+	return v;
+}
+
 /*
  * Pass 1b: Expand splats to full size and merge
  */
@@ -134,6 +150,7 @@ __device__ inline float make(const float4 &v) {
  __global__ void splat_kernel(
         //TextureObject<float4> points,       // Original 3D points
         TextureObject<float4> normals,
+        TextureObject<float> density,
         TextureObject<T> in,
         TextureObject<int> depth_in,        // Virtual depth map
         TextureObject<float> depth_out,   // Accumulated output
@@ -160,6 +177,7 @@ __device__ inline float make(const float4 &v) {
     //float depth = 0.0f;
     //float contrib = 0.0f;
     float depth = 1000.0f;
+    float pdepth = 1000.0f;
 
     struct Result {
         float weight;
@@ -182,21 +200,24 @@ __device__ inline float make(const float4 &v) {
 
         if (d < params.camera.minDepth || d > params.camera.maxDepth) continue;
 
-        const float3 camPos = params.camera.screenToCam((int)(x+u),(int)(y+v),d);
+        const float3 camPos = params.camera.screenToCam((int)(x)+u,(int)(y)+v,d);
         const float3 camPos2 = params.camera.screenToCam((int)(x),(int)(y),d);
         const float3 worldPos = params.m_viewMatrixInverse * camPos;
 
 
         // Assumed to be normalised
-        float4 n = normals.tex2D((int)(x+u), (int)(y+v));
+        float4 n = normals.tex2D((int)(x)+u, (int)(y)+v);
+        n /= length(n);
+		//if (length(make_float3(n)) == 0.0f) printf("BAD NORMAL\n");
 
         // Does the ray intersect plane of splat?
         float t = 1000.0f;
-        if (ftl::cuda::intersectPlane(make_float3(n), worldPos, origin, ray, t)) { //} && fabs(t-camPos.z) < 0.01f) {
+        const float r = ftl::cuda::intersectDistance(make_float3(n), worldPos, origin, ray, t);
+        if (r != PINF) { //} && fabs(t-camPos.z) < 0.01f) {
             // Adjust from normalised ray back to original meters units
             t *= scale;
-            const float3 camPos3 = params.camera.screenToCam((int)(x),(int)(y),t);
-            float weight = ftl::cuda::spatialWeighting(camPos, camPos3, 2.0f*(camPos3.z/params.camera.fx));
+            const float dens = density.tex2D((int)(x)+u, (int)(y)+v);
+            float weight = ftl::cuda::weighting(r, dens/params.camera.fx); // (1.0f/params.camera.fx) / (t/params.camera.fx)
 
             /* Buehler C. et al. 2001. Unstructured Lumigraph Rendering. */
             /* Orts-Escolano S. et al. 2016. Holoportation: Virtual 3D teleportation in real-time. */
@@ -209,12 +230,17 @@ __device__ inline float make(const float4 &v) {
 
             if (weight <= 0.0f) continue;
 
-            depth = min(depth, t);
+            //depth = min(depth, t);
+            if (t < depth) {
+                pdepth = depth;
+                depth = t;
+            }
             results[i/WARP_SIZE] = {weight, t, in.tex2D((int)x+u, (int)y+v)};
         }
     }
 
     depth = warpMin(depth);
+    pdepth = warpMin(pdepth);
 
     float adepth = 0.0f;
     float contrib = 0.0f;
@@ -245,6 +271,7 @@ __device__ inline float make(const float4 &v) {
 template <typename T>
 void ftl::cuda::splat(
         TextureObject<float4> &normals,
+        TextureObject<float> &density,
         TextureObject<T> &colour_in,
         TextureObject<int> &depth_in,        // Virtual depth map
         TextureObject<float> &depth_out,
@@ -255,6 +282,7 @@ void ftl::cuda::splat(
 
     splat_kernel<8,T><<<gridSize, blockSize, 0, stream>>>(
         normals,
+        density,
         colour_in,
         depth_in,
         depth_out,
@@ -266,6 +294,7 @@ void ftl::cuda::splat(
 
 template void ftl::cuda::splat<uchar4>(
         TextureObject<float4> &normals,
+        TextureObject<float> &density,
         TextureObject<uchar4> &colour_in,
         TextureObject<int> &depth_in,        // Virtual depth map
         TextureObject<float> &depth_out,
@@ -273,7 +302,8 @@ template void ftl::cuda::splat<uchar4>(
 		const SplatParams &params, cudaStream_t stream);
 		
 template void ftl::cuda::splat<float4>(
-	TextureObject<float4> &normals,
+    TextureObject<float4> &normals,
+    TextureObject<float> &density,
 	TextureObject<float4> &colour_in,
 	TextureObject<int> &depth_in,        // Virtual depth map
 	TextureObject<float> &depth_out,
@@ -281,7 +311,8 @@ template void ftl::cuda::splat<float4>(
 	const SplatParams &params, cudaStream_t stream);
 
 template void ftl::cuda::splat<float>(
-	TextureObject<float4> &normals,
+    TextureObject<float4> &normals,
+    TextureObject<float> &density,
 	TextureObject<float> &colour_in,
 	TextureObject<int> &depth_in,        // Virtual depth map
 	TextureObject<float> &depth_out,
@@ -302,15 +333,45 @@ __device__ inline uchar4 generateInput(const uchar4 &in, const SplatParams &para
 		in;
 }
 
+template <typename A, typename B>
+__device__ inline B weightInput(const A &in, float weight) {
+	return in * weight;
+}
+
+template <>
+__device__ inline float4 weightInput(const uchar4 &in, float weight) {
+	return make_float4(
+		(float)in.x * weight,
+		(float)in.y * weight,
+		(float)in.z * weight,
+		(float)in.w * weight);
+}
+
+template <typename T>
+__device__ inline void accumulateOutput(TextureObject<T> &out, TextureObject<float> &contrib, const uint2 &pos, const T &in, float w) {
+	atomicAdd(&out(pos.x, pos.y), in);
+	atomicAdd(&contrib(pos.x, pos.y), w);
+} 
+
+template <>
+__device__ inline void accumulateOutput(TextureObject<float4> &out, TextureObject<float> &contrib, const uint2 &pos, const float4 &in, float w) {
+	atomicAdd((float*)&out(pos.x, pos.y), in.x);
+	atomicAdd(((float*)&out(pos.x, pos.y))+1, in.y);
+	atomicAdd(((float*)&out(pos.x, pos.y))+2, in.z);
+	atomicAdd(((float*)&out(pos.x, pos.y))+3, in.w);
+	atomicAdd(&contrib(pos.x, pos.y), w);
+} 
+
 /*
  * Pass 2: Accumulate attribute contributions if the points pass a visibility test.
  */
- template <typename T>
+ template <typename A, typename B>
 __global__ void dibr_attribute_contrib_kernel(
-        TextureObject<T> in,				// Attribute input
+        TextureObject<A> in,				// Attribute input
         TextureObject<float4> points,       // Original 3D points
         TextureObject<int> depth_in,        // Virtual depth map
-        TextureObject<T> out,			// Accumulated output
+		TextureObject<B> out,			// Accumulated output
+		TextureObject<float> contrib,
         SplatParams params) {
         
 	const int x = (blockIdx.x*blockDim.x + threadIdx.x);
@@ -328,25 +389,26 @@ __global__ void dibr_attribute_contrib_kernel(
 	if (screenPos.x >= depth_in.width() || screenPos.y >= depth_in.height()) return;
             
     // Is this point near the actual surface and therefore a contributor?
-    const int d = depth_in.tex2D((int)screenPos.x, (int)screenPos.y);
-
-	const T input = generateInput(in.tex2D(x, y), params, worldPos);
+    const float d = (float)depth_in.tex2D((int)screenPos.x, (int)screenPos.y) / 1000.0f;
 
-	//const float3 nearest = params.camera.screenToCam((int)(screenPos.x),(int)(screenPos.y),d);
+	const A input = generateInput(in.tex2D(x, y), params, worldPos);
+	const float weight = ftl::cuda::weighting(fabs(camPos.z - d), 0.02f);
+	const B weighted = make<B>(input) * weight; //weightInput(input, weight);
 
-	//const float l = length(nearest - camPos);
-	if (d == (int)(camPos.z*1000.0f)) {
-		out(screenPos.x, screenPos.y) = input;
+	if (weight > 0.0f) {
+		accumulateOutput(out, contrib, screenPos, weighted, weight);
+		//out(screenPos.x, screenPos.y) = input;
 	}
 }
 
 
-template <typename T>
+template <typename A, typename B>
 void ftl::cuda::dibr_attribute(
-        TextureObject<T> &in,
+        TextureObject<A> &in,
         TextureObject<float4> &points,       // Original 3D points
         TextureObject<int> &depth_in,        // Virtual depth map
-        TextureObject<T> &out,   // Accumulated output
+		TextureObject<B> &out,   // Accumulated output
+		TextureObject<float> &contrib,
         SplatParams &params, cudaStream_t stream) {
 	const dim3 gridSize((in.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (in.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
 	const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
@@ -355,115 +417,68 @@ void ftl::cuda::dibr_attribute(
         in,
         points,
         depth_in,
-        out,
+		out,
+		contrib,
         params
     );
     cudaSafeCall( cudaGetLastError() );
 }
 
-template void ftl::cuda::dibr_attribute<uchar4>(
+template void ftl::cuda::dibr_attribute(
 	ftl::cuda::TextureObject<uchar4> &in,	// Original colour image
 	ftl::cuda::TextureObject<float4> &points,		// Original 3D points
 	ftl::cuda::TextureObject<int> &depth_in,		// Virtual depth map
-	ftl::cuda::TextureObject<uchar4> &out,	// Accumulated output
+	ftl::cuda::TextureObject<float4> &out,	// Accumulated output
+	ftl::cuda::TextureObject<float> &contrib,
 	ftl::render::SplatParams &params, cudaStream_t stream);
 
-template void ftl::cuda::dibr_attribute<float>(
+template void ftl::cuda::dibr_attribute(
 	ftl::cuda::TextureObject<float> &in,	// Original colour image
 	ftl::cuda::TextureObject<float4> &points,		// Original 3D points
 	ftl::cuda::TextureObject<int> &depth_in,		// Virtual depth map
 	ftl::cuda::TextureObject<float> &out,	// Accumulated output
+	ftl::cuda::TextureObject<float> &contrib,
 	ftl::render::SplatParams &params, cudaStream_t stream);
 
-template void ftl::cuda::dibr_attribute<float4>(
+template void ftl::cuda::dibr_attribute(
 	ftl::cuda::TextureObject<float4> &in,	// Original colour image
 	ftl::cuda::TextureObject<float4> &points,		// Original 3D points
 	ftl::cuda::TextureObject<int> &depth_in,		// Virtual depth map
 	ftl::cuda::TextureObject<float4> &out,	// Accumulated output
+	ftl::cuda::TextureObject<float> &contrib,
 	ftl::render::SplatParams &params, cudaStream_t stream);
 
 //==============================================================================
 
-/*__global__ void dibr_normalise_kernel(
-        TextureObject<float4> colour_in,
-        TextureObject<uchar4> colour_out,
-        //TextureObject<float4> normals,
-        TextureObject<float> contribs) {
-    const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
-    const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
-
-    if (x < colour_in.width() && y < colour_in.height()) {
-        const float4 colour = colour_in.tex2D((int)x,(int)y);
-        //const float4 normal = normals.tex2D((int)x,(int)y);
-        const float contrib = contribs.tex2D((int)x,(int)y);
-
-        if (contrib > 0.0f) {
-            colour_out(x,y) = make_uchar4(colour.x / contrib, colour.y / contrib, colour.z / contrib, 0);
-            //normals(x,y) = normal / contrib;
-        }
-    }
-}
-
+template <typename A, typename B>
 __global__ void dibr_normalise_kernel(
-        TextureObject<float4> colour_in,
-        TextureObject<float> colour_out,
-        //TextureObject<float4> normals,
+        TextureObject<A> in,
+        TextureObject<B> out,
         TextureObject<float> contribs) {
     const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
     const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
 
-    if (x < colour_in.width() && y < colour_in.height()) {
-        const float4 colour = colour_in.tex2D((int)x,(int)y);
+    if (x < in.width() && y < in.height()) {
+        const A a = in.tex2D((int)x,(int)y);
         //const float4 normal = normals.tex2D((int)x,(int)y);
         const float contrib = contribs.tex2D((int)x,(int)y);
 
         if (contrib > 0.0f) {
-            colour_out(x,y) = colour.x / contrib;
+            out(x,y) = make<B>(a / contrib);
             //normals(x,y) = normal / contrib;
         }
     }
 }
 
-__global__ void dibr_normalise_kernel(
-        TextureObject<float4> colour_in,
-        TextureObject<float4> colour_out,
-        //TextureObject<float4> normals,
-        TextureObject<float> contribs) {
-    const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
-    const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
-
-    if (x < colour_in.width() && y < colour_in.height()) {
-        const float4 colour = colour_in.tex2D((int)x,(int)y);
-        //const float4 normal = normals.tex2D((int)x,(int)y);
-        const float contrib = contribs.tex2D((int)x,(int)y);
-
-        if (contrib > 0.0f) {
-            colour_out(x,y) = make_float4(colour.x / contrib, colour.y / contrib, colour.z / contrib, 0);
-            //normals(x,y) = normal / contrib;
-        }
-    }
-}
-
-void ftl::cuda::dibr_normalise(TextureObject<float4> &colour_in, TextureObject<uchar4> &colour_out, TextureObject<float> &contribs, cudaStream_t stream) {
-    const dim3 gridSize((colour_in.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (colour_in.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
-    const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
-
-    dibr_normalise_kernel<<<gridSize, blockSize, 0, stream>>>(colour_in, colour_out, contribs);
-    cudaSafeCall( cudaGetLastError() );
-}
-
-void ftl::cuda::dibr_normalise(TextureObject<float4> &colour_in, TextureObject<float> &colour_out, TextureObject<float> &contribs, cudaStream_t stream) {
-    const dim3 gridSize((colour_in.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (colour_in.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
+template <typename A, typename B>
+void ftl::cuda::dibr_normalise(TextureObject<A> &in, TextureObject<B> &out, TextureObject<float> &contribs, cudaStream_t stream) {
+    const dim3 gridSize((in.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (in.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
     const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
 
-    dibr_normalise_kernel<<<gridSize, blockSize, 0, stream>>>(colour_in, colour_out, contribs);
+    dibr_normalise_kernel<<<gridSize, blockSize, 0, stream>>>(in, out, contribs);
     cudaSafeCall( cudaGetLastError() );
 }
 
-void ftl::cuda::dibr_normalise(TextureObject<float4> &colour_in, TextureObject<float4> &colour_out, TextureObject<float> &contribs, cudaStream_t stream) {
-    const dim3 gridSize((colour_in.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (colour_in.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
-    const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
-
-    dibr_normalise_kernel<<<gridSize, blockSize, 0, stream>>>(colour_in, colour_out, contribs);
-    cudaSafeCall( cudaGetLastError() );
-}*/
+template void ftl::cuda::dibr_normalise<float4,uchar4>(TextureObject<float4> &in, TextureObject<uchar4> &out, TextureObject<float> &contribs, cudaStream_t stream);
+template void ftl::cuda::dibr_normalise<float,float>(TextureObject<float> &in, TextureObject<float> &out, TextureObject<float> &contribs, cudaStream_t stream);
+template void ftl::cuda::dibr_normalise<float4,float4>(TextureObject<float4> &in, TextureObject<float4> &out, TextureObject<float> &contribs, cudaStream_t stream);
diff --git a/components/renderers/cpp/src/splatter_cuda.hpp b/components/renderers/cpp/src/splatter_cuda.hpp
index 1888a586720d388567a6c4d48f4a29b6b50848ac..684b0de5597f79ab8df78188d6552c08b5734c9a 100644
--- a/components/renderers/cpp/src/splatter_cuda.hpp
+++ b/components/renderers/cpp/src/splatter_cuda.hpp
@@ -17,19 +17,28 @@ namespace cuda {
 	template <typename T>
 	void splat(
         ftl::cuda::TextureObject<float4> &normals,
+		ftl::cuda::TextureObject<float> &density,
 		ftl::cuda::TextureObject<T> &colour_in,
         ftl::cuda::TextureObject<int> &depth_in,        // Virtual depth map
         ftl::cuda::TextureObject<float> &depth_out,
 		ftl::cuda::TextureObject<T> &colour_out,
         const ftl::render::SplatParams &params, cudaStream_t stream);
 
-	template <typename T>
+	template <typename A, typename B>
 	void dibr_attribute(
-		ftl::cuda::TextureObject<T> &in,	// Original colour image
+		ftl::cuda::TextureObject<A> &in,	// Original colour image
 		ftl::cuda::TextureObject<float4> &points,		// Original 3D points
 		ftl::cuda::TextureObject<int> &depth_in,		// Virtual depth map
-		ftl::cuda::TextureObject<T> &out,	// Accumulated output
+		ftl::cuda::TextureObject<B> &out,	// Accumulated output
+		ftl::cuda::TextureObject<float> &contrib,
 		ftl::render::SplatParams &params, cudaStream_t stream);
+
+	template <typename A, typename B>
+	void dibr_normalise(
+		ftl::cuda::TextureObject<A> &in,
+		ftl::cuda::TextureObject<B> &out,
+		ftl::cuda::TextureObject<float> &contribs,
+		cudaStream_t stream);
 }
 }
 
diff --git a/components/rgbd-sources/include/ftl/rgbd/channels.hpp b/components/rgbd-sources/include/ftl/rgbd/channels.hpp
index 467df0b1eead1518acab99306a0729aa5fcc5849..e00647271574584a1305fc86b41491a93e88a88a 100644
--- a/components/rgbd-sources/include/ftl/rgbd/channels.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/channels.hpp
@@ -25,6 +25,7 @@ enum struct Channel : int {
     Flow,               // 32F
     Energy,             // 32F
 	Mask,				// 32U
+	Density,			// 32F
     LeftGray,
     RightGray,
     Overlay1
@@ -106,6 +107,7 @@ inline bool isFloatChannel(ftl::rgbd::Channel chan) {
 	case Channel::Depth		:
     //case Channel::Normals   :
 	case Channel::Confidence:
+	case Channel::Density:
 	case Channel::Energy	: return true;
 	default					: return false;
 	}
diff --git a/components/rgbd-sources/include/ftl/rgbd/frame.hpp b/components/rgbd-sources/include/ftl/rgbd/frame.hpp
index 338674745392a1c180b65af9c8a1d35d37d1c916..45bf3f4a9b8f862e3d70c22abfcddb096855d7c2 100644
--- a/components/rgbd-sources/include/ftl/rgbd/frame.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/frame.hpp
@@ -181,12 +181,13 @@ template <> cv::cuda::GpuMat &Frame::create(ftl::rgbd::Channel c);
 
 template <typename T>
 ftl::cuda::TextureObject<T> &Frame::getTexture(ftl::rgbd::Channel c) {
-	if (!channels_.has(c)) throw ftl::exception("Texture channel does not exist");
+	if (!channels_.has(c)) throw ftl::exception(ftl::Formatter() << "Texture channel does not exist: " << (int)c);
 	if (!gpu_.has(c)) throw ftl::exception("Texture channel is not on GPU");
 
 	auto &m = _get(c);
 
 	if (m.tex.cvType() != ftl::traits::OpenCVType<T>::value || m.tex.width() != m.gpu.cols || m.tex.height() != m.gpu.rows || m.gpu.type() != m.tex.cvType()) {
+		LOG(ERROR) << "Texture has not been created for channel = " << (int)c;
 		throw ftl::exception("Texture has not been created properly for this channel");
 	}