Chain multiple custom camera image effects?

UPDATE: I’ve added the shader code to the post. Sorry that it makes the post so long!

I wrote my own custom camera image effects. Individually, they work great. Unfortunately, if I add more than two to the camera, only the last effect is applied. I pored over the documentation for OnRenderImage (1), which says this:

When there are multiple image filters
attached to the camera, they process
image sequentially, by passing first
filter’s destination as the source to
the next filter.

But for some reason, this isn’t working for me. Can you think of any reasons why I can’t chain my custom effects together? I’ve posted the source for the C# scripts below. (NOTE: I also tried inheriting my scripts from the ImageEffectBase class in the UnityStandardAssets.ImageEffects namespace, but it didn’t fix anything.) Thanks!

JoshImageEffect.cs (base class)

using UnityEngine;
using System.Collections;

public class JoshImageEffect : MonoBehaviour {
	public Shader shader;

	protected Material _material;
	protected Material material{
		get {
			if (!_material) _material = new Material(shader);
			return _material;
		}
	}

	void OnDisable(){
		Destroy(material);
	}

	public virtual void OnRenderImage(RenderTexture src, RenderTexture dst){
		Graphics.Blit(src, dst, material);
	}
}

TintImageEffect.cs (inherits from JoshImageEffect)

using UnityEngine;
using System.Collections;

public class TintImageEffect : JoshImageEffect {
	public Color tint;
	public float intensity;

	void Awake(){
		material.SetColor("_Tint", tint);
		material.SetFloat("_Intensity", intensity);
	}
}

PixellatorImageEffect.cs (inherits from JoshImageEffect)

using UnityEngine;
using System.Collections;

public class PixellatorImageEffect : JoshImageEffect {
	public Vector2 grid;

	void Awake(){
		material.SetVector("_Grid", new Vector4(grid.x, grid.y, 0, 0));
	}
}

GhostImageEffect.cs (inherits from JoshImageEffect)

using UnityEngine;
using System.Collections;

public class GhostImageEffect : JoshImageEffect {
	public int iterations;
	public float radius;
	public float timeScale;
	public float shadowBrightness;
	public float overallBrightness;

	void Awake(){
		material.SetFloat("_Iterations", iterations);
		material.SetFloat("_Radius", radius);
		material.SetFloat("_TimeScale", timeScale);
		material.SetFloat("_ShadowBrightness", shadowBrightness);
		material.SetFloat("_Brightness", overallBrightness);
	}
}

TintShader.shader

Shader "Custom/TintShader" {
	Properties {
		_MainTex ("Texture", 2D) = "white" {}
		_Tint ("Tint", Color) = (1, 1, 1, 1)
		_Intensity ("Intensity", Float) = 0.1
	}
	
	SubShader {
		Pass {
			CGPROGRAM
			
			#pragma vertex vert_img
			#pragma fragment frag
			#include "UnityCG.cginc"
			
			uniform sampler2D _MainTex;
			uniform float4 _Tint;
			uniform float _Intensity;
			
			float4 frag(v2f_img i) : COLOR {
				float4 col = tex2D(_MainTex, i.uv);
				return lerp(col, _Tint, _Intensity);
			}
			
			ENDCG
		}
	}
	
	FallBack Off
}

PixellatorShader.shader

Shader "Custom/PixellatorShader" {
	Properties {
		_MainTex ("Texture", 2D) = "white" {}
		_Grid ("Grid", Vector) = (0.02, 0.02, 0, 0)
	}
	
	SubShader {
		Pass {
			CGPROGRAM
			
			#pragma vertex vert_img
			#pragma fragment frag
			#include "UnityCG.cginc"
			
			uniform sampler2D _MainTex;
			uniform float4 _Grid;
			
			float4 frag(v2f_img i) : COLOR{
				float u = ((i.uv.x / _Grid.x) - fmod(i.uv.x, _Grid.x)/(_Grid.x)) * _Grid.x;
				float v = ((i.uv.y / _Grid.y) - fmod(i.uv.y, _Grid.y)/(_Grid.y)) * _Grid.y;
				float4 col = tex2D(_MainTex, float2(u, v));
				return col;
			}
			
			ENDCG
		}
	}
}

GhostShader.shader

Shader "Custom/GhostShader" {
	Properties {
		_MainTex ("Main Texture", 2D) = "white" {}
		_Iterations ("Iterations", Float) = 10
		_Radius ("Radius", Float) = 0.01
		_TimeScale ("Time Scale", Float) = 0.1
		_ShadowBrightness ("Shadow Brightness", Float) = 500
		_Brightness ("Overall Brightness", Float) = 0.5
	}
	
	SubShader {
		Pass {
			CGPROGRAM
			
			#pragma vertex vert_img
			#pragma fragment frag
			#include "UnityCG.cginc"
			
			uniform sampler2D _MainTex;
			uniform float _Iterations;
			uniform float _Radius;
			uniform float _TimeScale;
			uniform float _ShadowBrightness;
			uniform float _Brightness;
			
			float4 frag(v2f_img i) : COLOR {
				float4 col = tex2D(_MainTex, i.uv);
				
				for (int c=0; c<_Iterations; c++){
					float x = _Time.y * _TimeScale + c;
					float xOffset = sin(x*x*x + x*x + x) * _Radius;
					float yOffset = sin(x*x*x - x*x - x) * _Radius;
					float2 off = float2(xOffset, yOffset);
					col += mul(tex2D(_MainTex, i.uv + off), _ShadowBrightness * pow(length(off), 2));
				}
				
				return mul(col, _Brightness);
			}
			
			ENDCG
		}
	}
	
	FallBack Off
}

As said in my comment there has to be something wrong with your shaders. I just created two simple shaders, one that inverts the colors (the unaltered image-effect- template shader) and one that simply adds a bit red to the whole color. I also created a script which basically is identical to your “JoshImageEffect” script.

I attached this script two times to my camera and set my two shaders in the public variables. The resulting image has inverted colors and a red touch.

If i swap the two instances (I moved the second one up in the context menu) I get inverted colors and a blue-green touch instead since the red touch is added first and then the colors are inverted. So on my side it works as it should.

edit

So after a long try and error session it turns out that the shader were missing those important settings in the subshader:

// the culling isn't that important.
Cull Off
ZWrite Off
ZTest Always

More details in the comments below ^^.