Real-time Unity post processing shader woes.

Posted by jimi on November 22nd, 2009 filed in Game Programming, Shaders

I haven’t spoken much about the work I’ve been doing in my job at Prophecy Games, largely due to the fact I’ve been very busy with it.  Like all of us though, we do need some help from time to time.

I’ve been doing the shaders for our project, which can be quite complicated.  I’ve done pretty well I think, although I’m at a point where I need some help.  Basically this shader takes the normal and depth information from the scene, and uses that information to determine where a black outline should be drawn.  This creates a very cartoony look for the scene.

All was well with this shader, until I turned on anti-aliasing.  This actually caused the outline to flip upside down, however the rest of the scene is still rendered normally.  I’m posting it here so that I can get some help from the guys at Unity.  I will be updating this post once I have the solution to the problem, just in case anyone else runs into it as well.

Here’s the shader code.

Shader “Hidden/Edge Detect Normals XNA” {
Properties {
_MainTex (”", RECT) = “” {}
_DepthNormalsTexture (”DepthNormalsTexture”, RECT) = “” {}
_NormalThreshold (”NormalThreshold”, float) = 1.0
_DepthThreshold (”DepthThreshold”, float) = 1.0
_NormalSensitivity (”NormalSensitivity”, float) = 1.0
_DepthSensitivity (”DepthSensitivity”, float) = 1.0
_EdgeIntensity (”EdgeIntensity”, float) = 1.0
_EdgeWidth (”EdgeWidth”, float) = 1.0
_ScreenHeight (”", float) = 1.0
_ScreenWidth (”", float) = 1.0

SubShader {
Pass {
ZTest Always Cull Off ZWrite Off
Fog { Mode off }

#pragma target 3.0
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#include “UnityCG.cginc”

uniform samplerRECT _MainTex;
uniform samplerRECT _DepthNormalsTexture;

uniform float4 _MainTex_TexelSize;
uniform float4 _DepthNormalsTexture_TexelSize;
uniform float _NormalThreshold;
uniform float _DepthThreshold;
uniform float _NormalSensitivity;
uniform float _DepthSensitivity;
uniform float _EdgeWidth;
uniform float _EdgeIntensity;
uniform float _ScreenHeight;
uniform float _ScreenWidth;

struct v2f {
float4 pos : POSITION;
float2 uv : TEXCOORD0;

v2f vert( appdata_img v )
v2f o;
o.pos = mul (glstate.matrix.mvp, v.vertex);
o.uv = MultiplyUV( glstate.matrix.texture[0], v.texcoord );
return o;

float4 frag (v2f i) : COLOR
float4 original = texRECT(_MainTex, i.uv);
float2 edgeOffset = _EdgeWidth / float2(_ScreenWidth, _ScreenHeight);
float2 offset = float2(1,1) * _DepthNormalsTexture_TexelSize.xy;
float2 invOffset = float2(-1,1) * _DepthNormalsTexture_TexelSize.xy;
// 4 samples from normals+depth buffer
float4 normalD1 = texRECT(_DepthNormalsTexture, i.uv - offset);
float4 normalD2 = texRECT(_DepthNormalsTexture, i.uv + offset);
float4 normalD3 = texRECT(_DepthNormalsTexture, i.uv + invOffset);
float4 normalD4 = texRECT(_DepthNormalsTexture, i.uv - invOffset);

float3 normal1;
float depth1;
float3 normal2;
float depth2;
float3 normal3;
float depth3;
float3 normal4;
float depth4;

// Decode normal/depth data
DecodeDepthNormal(normalD1, depth1, normal1);
DecodeDepthNormal(normalD2, depth2, normal2);
DecodeDepthNormal(normalD3, depth3, normal3);
DecodeDepthNormal(normalD4, depth4, normal4);

//return float4(depth1, depth1, depth1, 1);

// Work out how much the normal and depth values are changing
float4 diagonalDelta = abs(float4(normal1, depth1) - float4(normal2, depth2)) + abs(float4(normal3, depth3) - float4(normal4, depth4));

float4 normalDelta = dot(, 1);
float depthDelta = diagonalDelta.w;

// Filter out very small changes, in order to produce nice clean results
normalDelta = saturate((normalDelta - _NormalThreshold) * _NormalSensitivity);
depthDelta = saturate((depthDelta - _DepthThreshold) * _DepthSensitivity);

// Does this pixel lie on an edge?
float edgeAmount = saturate(normalDelta + depthDelta) * _EdgeIntensity;

original *= (1 - edgeAmount);
return original;

Fallback off


If you enjoyed this post, make sure you subscribe to my RSS feed!

Leave a Comment