「グラブ」→「平均化」→「グラブ」→「平均化」→...→「最終処理」方式だとこんな感じでしょうかね?
コードの再利用のため「RepeatAve.shader」ファイルと「RepeatAve.cginc」ファイルに分かれていますが、シェーダーとしては単一ですので、補助スクリプトなしでマテリアルをアタッチするだけとなります。ちょっと異様な形になってしまいましたが...
RepeatAve.cginc
ShaderLab
1sampler2D _GrabTexture;
2half _Blur;
3int _Size;
4struct v2fAve
5{
6 float4 pos: SV_POSITION;
7 float4 screenPos: TEXCOORD1;
8};
9v2fAve vertAve(appdata_full v)
10{
11 v2fAve o;
12 o.pos = UnityObjectToClipPos(v.vertex);
13 o.screenPos = ComputeScreenPos(o.pos);
14 return o;
15}
16half4 fragAve(v2fAve i): SV_Target
17{
18 float2 uv = i.screenPos.xy / i.screenPos.w;
19 half4 frost = 0.0;
20 #ifdef _AVE
21 for (int m = - (_Size - 1) / 2; m <= (_Size - 1) / 2; m ++)
22 {
23 for (int n = - (_Size - 1) / 2; n <= (_Size - 1) / 2; n ++)
24 {
25 frost += tex2D(_GrabTexture, uv + float2(_Blur * m, _Blur * n));
26 }
27 }
28 frost /= _Size * _Size;
29 #else
30 frost = tex2D(_GrabTexture, uv);
31 #endif
32 return half4(frost.xyz, 1);
33}
RepeatAve.shader
ShaderLab
1Shader "Custom/RepeatAve"
2{
3 Properties
4 {
5 _Color ("Color", Color) = (1, 1, 1, 1)
6 _MainTex ("Diffuse", 2D) = "white" {}
7 _Noise ("Noise", 2D) = "gray" {}
8 _Range ("Range", Float) = 0.025
9 _Blur ("Blur", Float) = 0.005
10 [KeywordEnum(R0,R1,R2,R3,R4,R5,R6,R7,R8)] _Repeat ("Repeat", Float) = 0
11 _Size ("Size", int) = 3
12 }
13
14 SubShader
15 {
16 Tags { "Queue" = "Transparent" }
17 Cull Off
18
19 CGINCLUDE
20 #pragma target 3.0
21 #include "UnityCG.cginc"
22 ENDCG
23
24 GrabPass{}
25 pass
26 {
27 CGPROGRAM
28 #pragma multi_compile _ _REPEAT_R1 _REPEAT_R2 _REPEAT_R3 _REPEAT_R4 _REPEAT_R5 _REPEAT_R6 _REPEAT_R7 _REPEAT_R8
29 #pragma vertex vertAve
30 #pragma fragment fragAve
31 #if _REPEAT_R1 | _REPEAT_R2 | _REPEAT_R3 | _REPEAT_R4 | _REPEAT_R5 | _REPEAT_R6 | _REPEAT_R7 | _REPEAT_R8
32 #define _AVE
33 #endif
34 #include "RepeatAve.cginc"
35 ENDCG
36 }
37
38 GrabPass{}
39 pass
40 {
41 CGPROGRAM
42 #pragma multi_compile _ _REPEAT_R2 _REPEAT_R3 _REPEAT_R4 _REPEAT_R5 _REPEAT_R6 _REPEAT_R7 _REPEAT_R8
43 #pragma vertex vertAve
44 #pragma fragment fragAve
45 #if _REPEAT_R2 | _REPEAT_R3 | _REPEAT_R4 | _REPEAT_R5 | _REPEAT_R6 | _REPEAT_R7 | _REPEAT_R8
46 #define _AVE
47 #endif
48 #include "RepeatAve.cginc"
49 ENDCG
50 }
51
52 GrabPass{}
53 pass
54 {
55 CGPROGRAM
56 #pragma multi_compile _ _REPEAT_R3 _REPEAT_R4 _REPEAT_R5 _REPEAT_R6 _REPEAT_R7 _REPEAT_R8
57 #pragma vertex vertAve
58 #pragma fragment fragAve
59 #if _REPEAT_R3 | _REPEAT_R4 | _REPEAT_R5 | _REPEAT_R6 | _REPEAT_R7 | _REPEAT_R8
60 #define _AVE
61 #endif
62 #include "RepeatAve.cginc"
63 ENDCG
64 }
65
66 GrabPass{}
67 pass
68 {
69 CGPROGRAM
70 #pragma multi_compile _ _REPEAT_R4 _REPEAT_R5 _REPEAT_R6 _REPEAT_R7 _REPEAT_R8
71 #pragma vertex vertAve
72 #pragma fragment fragAve
73 #if _REPEAT_R4 | _REPEAT_R5 | _REPEAT_R6 | _REPEAT_R7 | _REPEAT_R8
74 #define _AVE
75 #endif
76 #include "RepeatAve.cginc"
77 ENDCG
78 }
79
80 GrabPass{}
81 pass
82 {
83 CGPROGRAM
84 #pragma multi_compile _ _REPEAT_R5 _REPEAT_R6 _REPEAT_R7 _REPEAT_R8
85 #pragma vertex vertAve
86 #pragma fragment fragAve
87 #if _REPEAT_R5 | _REPEAT_R6 | _REPEAT_R7 | _REPEAT_R8
88 #define _AVE
89 #endif
90 #include "RepeatAve.cginc"
91 ENDCG
92 }
93
94 GrabPass{}
95 pass
96 {
97 CGPROGRAM
98 #pragma multi_compile _ _REPEAT_R6 _REPEAT_R7 _REPEAT_R8
99 #pragma vertex vertAve
100 #pragma fragment fragAve
101 #if _REPEAT_R6 | _REPEAT_R7 | _REPEAT_R8
102 #define _AVE
103 #endif
104 #include "RepeatAve.cginc"
105 ENDCG
106 }
107
108 GrabPass{}
109 pass
110 {
111 CGPROGRAM
112 #pragma multi_compile _ _REPEAT_R7 _REPEAT_R8
113 #pragma vertex vertAve
114 #pragma fragment fragAve
115 #if _REPEAT_R7 | _REPEAT_R8
116 #define _AVE
117 #endif
118 #include "RepeatAve.cginc"
119 ENDCG
120 }
121
122 GrabPass{}
123 pass
124 {
125 CGPROGRAM
126 #pragma multi_compile _ _REPEAT_R8
127 #pragma vertex vertAve
128 #pragma fragment fragAve
129 #if _REPEAT_R8
130 #define _AVE
131 #endif
132 #include "RepeatAve.cginc"
133 ENDCG
134 }
135
136 GrabPass{}
137 Pass
138 {
139 CGPROGRAM
140 #pragma vertex vert
141 #pragma fragment frag
142 half4 _Color;
143 sampler2D _MainTex;
144 float4 _MainTex_ST;
145 sampler2D _Noise;
146 float4 _Noise_ST;
147 sampler2D _GrabTexture;
148 half _Range;
149 struct v2f
150 {
151 float4 pos: SV_POSITION;
152 float3 uv: TEXCOORD;
153 float4 screenPos: TEXCOORD1;
154 float3 ray: TEXCOORD2;
155 };
156 v2f vert(appdata_full v)
157 {
158 v2f o;
159 o.pos = UnityObjectToClipPos(v.vertex);
160 o.uv = v.texcoord;
161 o.screenPos = ComputeScreenPos(o.pos);
162 o.ray = UnityObjectToViewPos(v.vertex).xyz * float3(-1, -1, 1);
163 return o;
164 }
165 half4 frag(v2f i): SV_Target
166 {
167 i.ray = i.ray * (_ProjectionParams.z / i.ray.z);
168 float2 uv = i.screenPos.xy / i.screenPos.w;
169 float2 frostUV = tex2D(_Noise, i.uv * _Noise_ST.xy + _Noise_ST.zw).xy;
170 frostUV -= 0.5;
171 frostUV *= _Range;
172 frostUV += uv;
173 half4 frost = tex2D(_GrabTexture, frostUV);
174 half4 diffuse = tex2D(_MainTex, i.uv * _MainTex_ST.xy + _MainTex_ST.zw);
175 half alpha = _Color.a * diffuse.a;
176 return half4(frost.xyz + (diffuse.rgb * _Color.rgb * alpha), 1);
177 }
178 ENDCG
179 }
180 }
181 Fallback Off
182}
_Size
は規定値の3、_Blur
も規定値の0.005で、RepeatをR0~R8に切り替えると下図のようになりました。

今回は単一のシェーダーとしましたので、反復回数を切り替えても常に「グラブ」→「描画」のプロセスが9回行われます(とはいえ設定した反復回数を超えた部分では「グラブ」→「そのまま描画」となりサンプリング回数が1回となるため、反復回数を小さくすれば負荷は小さくなるでしょう)。
ご質問者さんのおっしゃる「反復回数の異なる複数のシェーダーを作る」という方式なら、小反復回数のときの無駄な「グラブ」→「描画」部分を丸ごと削除することができるため、より高速化できると思います。
やはり、補助的なC#スクリプト抜きだとちょっと非効率的になってしまいますね。特に多数の曇りガラスオブジェクトがある場合は、オブジェクトの数だけグラブパスが増えますので負荷が気になりそうです。
ただ、この形ですと「曇りガラスオブジェクトの手前に別の曇りガラスオブジェクトがある場合、手前のオブジェクトを透かして見た背後の曇りガラスオブジェクトはさらにぼやけて見える」という視覚的もっともらしさがあると思います。
追記
1回目の平均化処理を行う部分までを下記のように変更してみました。
RepeatAve.shader
ShaderLab
1Shader "Custom/RepeatAve"
2{
3 Properties
4 {
5 _Color ("Color", Color) = (1, 1, 1, 1)
6 _MainTex ("Diffuse", 2D) = "white" {}
7 _Noise ("Noise", 2D) = "gray" {}
8 _Range ("Range", Float) = 0.025
9 _Blur ("Blur", Float) = 0.005
10 [KeywordEnum(R0,R1,R2,R3,R4,R5,R6,R7,R8)] _Repeat ("Repeat", Float) = 0
11 _Size ("Size", int) = 3
12 }
13
14 SubShader
15 {
16 Tags { "Queue" = "Transparent" }
17 Cull Off
18 ZWrite Off
19 Stencil
20 {
21 Ref 0
22 Comp Equal
23 }
24
25 CGINCLUDE
26 #pragma target 3.0
27 #include "UnityCG.cginc"
28 ENDCG
29
30 GrabPass{"_FirstGrabTexture"}
31 pass
32 {
33 CGPROGRAM
34 #pragma multi_compile _ _REPEAT_R1 _REPEAT_R2 _REPEAT_R3 _REPEAT_R4 _REPEAT_R5 _REPEAT_R6 _REPEAT_R7 _REPEAT_R8
35 #pragma vertex vertAve
36 #pragma fragment fragAveFirst
37 #if _REPEAT_R1 | _REPEAT_R2 | _REPEAT_R3 | _REPEAT_R4 | _REPEAT_R5 | _REPEAT_R6 | _REPEAT_R7 | _REPEAT_R8
38 #define _AVE
39 #endif
40 #include "RepeatAve.cginc"
41 sampler2D _FirstGrabTexture;
42 half4 fragAveFirst(v2fAve i): SV_Target
43 {
44 float2 uv = i.screenPos.xy / i.screenPos.w;
45 half4 frost = 0.0;
46 #ifdef _AVE
47 for (int m = - (_Size - 1) / 2; m <= (_Size - 1) / 2; m ++)
48 {
49 for (int n = - (_Size - 1) / 2; n <= (_Size - 1) / 2; n ++)
50 {
51 frost += tex2D(_FirstGrabTexture, uv + float2(_Blur * m, _Blur * n));
52 }
53 }
54 frost /= _Size * _Size;
55 #else
56 frost = tex2D(_FirstGrabTexture, uv);
57 #endif
58 return half4(frost.xyz, 1);
59 }
60 ENDCG
61 }
62
63 // 省略...残り7回のフィルタ処理および最終合成処理には変更なし
64 }
65 Fallback Off
66}
RepeatAve.cgincに変更はありません。
1回目のGrabPassのみグラブ先を_FirstGrabTexture
として平均化もそのテクスチャを参照するようにし、2回目以降は先と同様にテクスチャ名指定なしでグラブ・平均化しています。
テクスチャ名指定ありでグラブすると最初の1回だけグラブが行われるので、フィルタ処理の起点は毎回最初のグラブ結果となります。このため、後で描画されるマテリアルにそれまでのフィルタ処理の影響が及ばなくなり、「曇りガラスを重ねるほどぼけ幅が大きくなる」という現象を回避できるかと思います。
また、ついでにコメントで申し上げたZWrite Off
を加え、さらにステンシルテストを有効にしてステンシルバッファの内容が0の場合だけ描画を行うようにしました。
これと併せて、ステンシル値を加算してステンシルバッファの内容を0でなくするマテリアルをマスク用オブジェクトに使用すれば、キューの大小に基づいたマスキングが可能かと思います。
StencilMask.shader
ShaderLab
1Shader "Custom/StencilMask"
2{
3 Properties
4 {
5 }
6
7 SubShader
8 {
9 Tags { "Queue" = "Transparent" }
10 Cull Off
11 ZWrite Off
12 Stencil
13 {
14 Pass IncrSat
15 }
16 ColorMask 0
17
18 Pass
19 {
20 CGPROGRAM
21 #pragma target 3.0
22 #pragma vertex vert
23 #pragma fragment frag
24 #include "UnityCG.cginc"
25 struct v2f
26 {
27 float4 pos: SV_POSITION;
28 };
29 v2f vert(appdata_full v)
30 {
31 v2f o;
32 o.pos = UnityObjectToClipPos(v.vertex);
33 return o;
34 }
35 half4 frag(v2f i): SV_Target
36 {
37 return 0.0;
38 }
39 ENDCG
40 }
41 }
42 Fallback Off
43}
テストとして、下記3種のマテリアルを作成し...
- マテリアルA...赤ガラス、ぼけ幅中、ノイズ効果小、キュー2501
- マテリアルB...緑ガラス、ぼけ幅大、ノイズ効果大、キュー2511
- マテリアルC...青ガラス、ぼけ幅小、ノイズ効果なし、キュー2521
これらを、_FirstGrabTexture
を使わない従来通りの方法で描画すると、下図のように先に描画した曇りガラスの影響が後段にも引き継がれますが...

_FirstGrabTexture
を使う方式にすると、下図のような結果となります。

また、キュー2510のマスクオブジェクト(Aの次に描画され、描画領域へのB・Cの描画を防止する)を左右に横切るよう配置、キュー2520のマスクオブジェクト(Bの次に描画され、描画領域へのCの描画を防止する)を上下に横切るよう配置して、描画過程を見てみると下図のようになります。
