Fun with Shaders and the Depth Buffer

Recently, I read this presentation by Kuba Cupisz and Ole Ciliox and I wanted to try my hand at implementing some of the techniques mentioned in the slides. So far, I’ve written two successful shaders: Rendering the depth buffer and highlighting intersections (the scan field). Since there is no source code to accompany the presentation, I had to do a bit of digging to get everything to work.

The End Result
Here is a quick video of what these two shaders accomplish. Both cubes use the Depth Shader, while the sphere and the plane use the Intersection Highlighting Shader

First Things First

In order to read the depth buffer, we have to tell the camera to write to it. Luckily, that is as easy as flipping a switch:

using UnityEngine;
using System.Collections;

public class TurnOnDepthBuffer : MonoBehaviour {

	// Use this for initialization
	void Start ()
	{
		camera.depthTextureMode = DepthTextureMode.Depth;
	}
}

Simply put this script on the camera and everything should be good to go. Contrary to the presentation, I tried running rendering in deferred without this script and was unable to get the depth texture.

The Depth Shader
The main reason I wrote this shader is to verify that I was accessing the depth buffer correctly. I doubt it will ever see use in a game, but it serves as a great simplified example.

//Shows the grayscale of the depth from the camera.

Shader "Custom/DepthShader"
{
	SubShader
	{
		Tags { "RenderType"="Opaque" }

		Pass
		{

			CGPROGRAM
			#pragma target 3.0
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"

			uniform sampler2D _CameraDepthTexture; //the depth texture

			struct v2f
			{
				float4 pos : SV_POSITION;
				float4 projPos : TEXCOORD1; //Screen position of pos
			};

			v2f vert(appdata_base v)
			{
				v2f o;
				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
				o.projPos = ComputeScreenPos(o.pos);

				return o;
			}

			half4 frag(v2f i) : COLOR
			{
				//Grab the depth value from the depth texture
				//Linear01Depth restricts this value to [0, 1]
				float depth = Linear01Depth (tex2Dproj(_CameraDepthTexture,
															 UNITY_PROJ_COORD(i.projPos)).r);

				half4 c;
				c.r = depth;
				c.g = depth;
				c.b = depth;
				c.a = 1;

				return c;
			}

			ENDCG
		}
	}
	FallBack "VertexLit"
}

The Intersection Highlights Shader
The math behind this shader is covered very well by the presentation, but I’ll write a quick run through. First off, this shader does not write to the depth buffer. This is to prevent it from recording intersections with itself. Instead, it reads the distance from the depth texture to the camera and computes its own. When the two values are reasonably close, that means there is another object at the same distance away from the camera (an intersection). The end result is a neat effect useful for room scans or sci-fi doors.

//Highlights intersections with other objects

Shader "Custom/IntersectionHighlights"
{
	Properties
	{
		_RegularColor("Main Color", Color) = (1, 1, 1, .5) //Color when not intersecting
		_HighlightColor("Highlight Color", Color) = (1, 1, 1, .5) //Color when intersecting
		_HighlightThresholdMax("Highlight Threshold Max", Float) = 1 //Max difference for intersections
	}
	SubShader
	{
		Tags { "Queue" = "Transparent" "RenderType"="Transparent"  }

		Pass
		{
		 	Blend SrcAlpha OneMinusSrcAlpha
		 	ZWrite Off
		 	Cull Off

			CGPROGRAM
			#pragma target 3.0
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"

			uniform sampler2D _CameraDepthTexture; //Depth Texture
			uniform float4 _RegularColor;
			uniform float4 _HighlightColor;
			uniform float _HighlightThresholdMax;

			struct v2f
			{
				float4 pos : SV_POSITION;
				float4 projPos : TEXCOORD1; //Screen position of pos
			};

			v2f vert(appdata_base v)
			{
				v2f o;
				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
				o.projPos = ComputeScreenPos(o.pos);

				return o;
			}

			half4 frag(v2f i) : COLOR
			{
			    float4 finalColor = _RegularColor;

				//Get the distance to the camera from the depth buffer for this point
				float sceneZ = LinearEyeDepth (tex2Dproj(_CameraDepthTexture,
														 UNITY_PROJ_COORD(i.projPos)).r);

				//Actual distance to the camera
                float partZ = i.projPos.z;

                //If the two are similar, then there is an object intersecting with our object
                float diff = (abs(sceneZ - partZ)) /
                	_HighlightThresholdMax;

                if(diff <= 1)
                {
                	finalColor = lerp(_HighlightColor,
                					  _RegularColor,
                					  float4(diff, diff, diff, diff));
                }

				half4 c;
				c.r = finalColor.r;
				c.g = finalColor.g;
				c.b = finalColor.b;
				c.a = finalColor.a;

				return c;
			}

			ENDCG
		}
	}
	FallBack "VertexLit"
}

Final Thoughts
There were definitely some challenges with getting the shaders to correctly read from the depth texture, but I am really happy with the end results. The intersection highlights is a really good special effect to keep in my back pocket.

Advertisements
This entry was posted in Uncategorized. Bookmark the permalink.

4 Responses to Fun with Shaders and the Depth Buffer

  1. Julien says:

    Hi, i like this effect, was looking for something like that for quite a while and i have a question: is this ‘pro only’ (is depth Texture a renderTexture?). If yes, could it also be achieved without pro (some other shader trick perhaps) ?

  2. Axis44 says:

    Thank you! I really needed good example how to use depth textures in unity, and this one is excellent!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s