1 /*
2 CAMERA MOTION BLUR IMAGE EFFECTS
3
4 Reconstruction Filter:
5 Based on "Plausible Motion Blur"
6 http://graphics.cs.williams.edu/papers/MotionBlurI3D12/
7
8 CameraMotion:
9 Based on Alex Vlacho's technique in
10 http://www.valvesoftware.com/publications/2008/GDC2008_PostProcessingInTheOrangeBox.pdf
11
12 SimpleBlur:
13 Straightforward sampling along velocities
14
15 ScatterFromGather:
16 Combines Reconstruction with depth of field type defocus
17 */
18
19 Shader "Hidden/CameraMotionBlur" {
20 Properties {
21 _MainTex ("-", 2D) = "" {}
22 _NoiseTex ("-", 2D) = "grey" {}
23 _VelTex ("-", 2D) = "black" {}
24 _NeighbourMaxTex ("-", 2D) = "black" {}
25 }
26
27 CGINCLUDE
28
29 #include "UnityCG.cginc"
30
31 // 's' in paper (# of samples for reconstruction)
32 #define NUM_SAMPLES (11)
33 // # samples for valve style blur
34 #define MOTION_SAMPLES (16)
35 // 'k' in paper
36 float _MaxRadiusOrKInPaper;
37
38 static const int SmallDiscKernelSamples = 12;
39 static const float2 SmallDiscKernel[SmallDiscKernelSamples] =
40 {
41 float2(-0.326212,-0.40581),
42 float2(-0.840144,-0.07358),
43 float2(-0.695914,0.457137),
44 float2(-0.203345,0.620716),
45 float2(0.96234,-0.194983),
46 float2(0.473434,-0.480026),
47 float2(0.519456,0.767022),
48 float2(0.185461,-0.893124),
49 float2(0.507431,0.064425),
50 float2(0.89642,0.412458),
51 float2(-0.32194,-0.932615),
52 float2(-0.791559,-0.59771)
53 };
54
55 struct v2f
56 {
57 float4 pos : SV_POSITION;
58 float2 uv : TEXCOORD0;
59 };
60
61 sampler2D _MainTex;
62 sampler2D_float _CameraDepthTexture;
63 sampler2D _VelTex;
64 sampler2D _NeighbourMaxTex;
65 sampler2D _NoiseTex;
66 sampler2D _TileTexDebug;
67
68 float4 _MainTex_TexelSize;
69 float4 _CameraDepthTexture_TexelSize;
70 float4 _VelTex_TexelSize;
71
72 float4x4 _InvViewProj; // inverse view-projection matrix
73 float4x4 _PrevViewProj; // previous view-projection matrix
74 float4x4 _ToPrevViewProjCombined; // combined
75
76 float _Jitter;
77
78 float _VelocityScale;
79 float _DisplayVelocityScale;
80
81 float _MaxVelocity;
82 float _MinVelocity;
83
84 float4 _BlurDirectionPacked;
85
86 float _SoftZDistance;
87
88 v2f vert(appdata_img v)
89 {
90 v2f o;
91 o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
92 o.uv = v.texcoord.xy;
93 return o;
94 }
95
96 float4 CameraVelocity(v2f i) : SV_Target
97 {
98 float2 depth_uv = i.uv;
99
100 #if UNITY_UV_STARTS_AT_TOP
101 if (_MainTex_TexelSize.y < 0)
102 depth_uv.y = 1 - depth_uv.y;
103 #endif
104
105 // read depth
106 float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, depth_uv);
107
108 // calculate position from pixel from depth
109 float3 clipPos = float3(i.uv.x*2.0-1.0, (i.uv.y)*2.0-1.0, d);
110
111 // only 1 matrix mul:
112 float4 prevClipPos = mul(_ToPrevViewProjCombined, float4(clipPos, 1.0));
113 prevClipPos.xyz /= prevClipPos.w;
114
115 /*
116 float4 ws = mul(_InvViewProj, float4(clipPos, 1.0));
117 ws /= ws.w;
118 prevClipPos = mul(_PrevViewProj,ws);
119 prevClipPos.xyz /= prevClipPos.w;
120 */
121
122 /*
123 float2 vel = _VelocityScale *(clipPos.xy - prevClipPos.xy) / 2.f;
124 // clamp to maximum velocity (in pixels)
125 float maxVel = length(_MainTex_TexelSize.xy*_MaxVelocity);
126 if (length(vel) > maxVel) {
127 vel = normalize(vel) * maxVel;
128 }
129 return float4(vel, 0.0, 0.0);
130 */
131
132 float2 vel = _MainTex_TexelSize.zw * _VelocityScale * (clipPos.xy - prevClipPos.xy) / 2.f;
133 float vellen = length(vel);
134 float maxVel = _MaxVelocity;
135 float2 velOut = vel * max(0.5, min(vellen, maxVel)) / (vellen + 1e-2f);
136 velOut *= _MainTex_TexelSize.xy;
137 return float4(velOut, 0.0, 0.0);
138
139 }
140
141 // vector with largest magnitude
142 float2 vmax(float2 a, float2 b)
143 {
144 float ma = dot(a, a);
145 float mb = dot(b, b);
146 return (ma > mb) ? a : b;
147 }
148
149 // find dominant velocity for each tile
150 float4 TileMax(v2f i) : SV_Target
151 {
152 float2 uvCorner = i.uv - _MainTex_TexelSize.xy * (_MaxRadiusOrKInPaper * 0.5);
153 float2 maxvel = float2(0,0);
154 float4 baseUv = float4(uvCorner,0,0);
155 float4 uvScale = float4(_MainTex_TexelSize.xy, 0, 0);
156
157 for(int l=0; l<(int)_MaxRadiusOrKInPaper; l++)
158 {
159 for(int k=0; k<(int)_MaxRadiusOrKInPaper; k++)
160 {
161 maxvel = vmax(maxvel, tex2Dlod(_MainTex, baseUv + float4(l,k,0,0) * uvScale).xy);
162 }
163 }
164 return float4(maxvel, 0, 1);
165 }
166
167 // find maximum velocity in any adjacent tile
168 float4 NeighbourMax(v2f i) : SV_Target
169 {
170 float2 x_ = i.uv;
171
172 // to fetch all neighbours, we need 3x3 point filtered samples
173
174 float2 nx = tex2D(_MainTex, x_+float2(1.0, 1.0)*_MainTex_TexelSize.xy).xy;
175 nx = vmax(nx, tex2D(_MainTex, x_+float2(1.0, 0.0)*_MainTex_TexelSize.xy).xy);
176 nx = vmax(nx, tex2D(_MainTex, x_+float2(1.0,-1.0)*_MainTex_TexelSize.xy).xy);
177 nx = vmax(nx, tex2D(_MainTex, x_+float2(0.0, 1.0)*_MainTex_TexelSize.xy).xy);
178 nx = vmax(nx, tex2D(_MainTex, x_+float2(0.0, 0.0)*_MainTex_TexelSize.xy).xy);
179 nx = vmax(nx, tex2D(_MainTex, x_+float2(0.0,-1.0)*_MainTex_TexelSize.xy).xy);
180 nx = vmax(nx, tex2D(_MainTex, x_+float2(-1.0, 1.0)*_MainTex_TexelSize.xy).xy);
181 nx = vmax(nx, tex2D(_MainTex, x_+float2(-1.0, 0.0)*_MainTex_TexelSize.xy).xy);
182 nx = vmax(nx, tex2D(_MainTex, x_+float2(-1.0,-1.0)*_MainTex_TexelSize.xy).xy);
183
184 return float4(nx, 0, 0);
185 }
186
187 float4 Debug(v2f i) : SV_Target
188 {
189 return saturate( float4(tex2D(_MainTex, i.uv).x,abs(tex2D(_MainTex, i.uv).y),-tex2D(_MainTex, i.uv).xy) * _DisplayVelocityScale);
190 }
191
192 // classification filters
193 float cone(float2 px, float2 py, float2 v)
194 {
195 return clamp(1.0 - (length(px - py) / length(v)), 0.0, 1.0);
196 }
197
198 float cylinder(float2 x, float2 y, float2 v)
199 {
200 float lv = length(v);
201 return 1.0 - smoothstep(0.95*lv, 1.05*lv, length(x - y));
202 }
203
204 // is zb closer than za?
205 float softDepthCompare(float za, float zb)
206 {
207 return clamp(1.0 - (za - zb) / _SoftZDistance, 0.0, 1.0);
208 }
209
210 float4 SimpleBlur (v2f i) : SV_Target
211 {
212 float2 x = i.uv;
213 float2 xf = x;
214
215 #if UNITY_UV_STARTS_AT_TOP
216 if (_MainTex_TexelSize.y < 0)
217 xf.y = 1 - xf.y;
218 #endif
219
220 float2 vx = tex2D(_VelTex, xf).xy; // vel at x
221
222 float4 sum = float4(0, 0, 0, 0);
223 for(int l=0; l<NUM_SAMPLES; l++) {
224 float t = l / (float) (NUM_SAMPLES - 1);
225 t = t-0.5;
226 float2 y = x - vx*t;
227 float4 cy = tex2D(_MainTex, y);
228 sum += cy;
229 }
230 sum /= NUM_SAMPLES;
231 return sum;
232 }
233
234 float4 ReconstructFilterBlur(v2f i) : SV_Target
235 {
236 // uv's
237
238 float2 x = i.uv;
239 float2 xf = x;
240
241 #if UNITY_UV_STARTS_AT_TOP
242 if (_MainTex_TexelSize.y < 0)
243 xf.y = 1-xf.y;
244 #endif
245
246 float2 x2 = xf;
247
248 float2 vn = tex2Dlod(_NeighbourMaxTex, float4(x2,0,0)).xy; // largest velocity in neighbourhood
249 float4 cx = tex2Dlod(_MainTex, float4(x,0,0)); // color at x
250 float2 vx = tex2Dlod(_VelTex, float4(xf,0,0)).xy; // vel at x
251
252 float zx = SAMPLE_DEPTH_TEXTURE_LOD(_CameraDepthTexture, float4(x,0,0));
253 zx = -Linear01Depth(zx);
254
255 // random offset [-0.5, 0.5]
256 float j = (tex2Dlod(_NoiseTex, float4(i.uv,0,0) * 11.0f).r*2-1) * _Jitter;
257
258 // sample current pixel
259 float weight = 0.75; // <= good start weight choice??
260 float4 sum = cx * weight;
261
262 int centerSample = (int)(NUM_SAMPLES-1)/2;
263
264 for(int l=0; l<NUM_SAMPLES; l++)
265 {
266 float contrib = 1.0f;
267 #if SHADER_API_D3D11
268 if (l==centerSample) continue; // skip center sample
269 #else
270 if (l==centerSample) contrib = 0.0f; // skip center sample
271 #endif
272
273 float t = lerp(-1.0, 1.0, (l + j) / (-1 + _Jitter + (float)NUM_SAMPLES));
274 //float t = lerp(-1.0, 1.0, l / (float)(NUM_SAMPLES - 1));
275
276 float2 y = x + vn * t;
277
278 float2 yf = y;
279 #if UNITY_UV_STARTS_AT_TOP
280 if (_MainTex_TexelSize.y < 0)
281 yf.y = 1-yf.y;
282 #endif
283
284 // velocity at y
285 float2 vy = tex2Dlod(_VelTex, float4(yf,0,0)).xy;
286
287 float zy = SAMPLE_DEPTH_TEXTURE_LOD(_CameraDepthTexture, float4(y,0,0));
288 zy = -Linear01Depth(zy);
289 float f = softDepthCompare(zx, zy);
290 float b = softDepthCompare(zy, zx);
291 float alphay = b * cone(x, y, vx) + f * cone(y, x, vy) + cylinder(y, x, vy) * cylinder(x, y, vx) * 2.0;
292
293 float4 cy = tex2Dlod(_MainTex, float4(y,0,0));
294 sum += cy * alphay * contrib;
295 weight += alphay * contrib;
296 }
297 sum /= weight;
298 return sum;
299 }
300
301 float4 ReconstructionDiscBlur (v2f i) : SV_Target
302 {
303 float2 xf = i.uv;
304 float2 x = i.uv;
305
306 #if UNITY_UV_STARTS_AT_TOP
307 if (_MainTex_TexelSize.y < 0)
308 xf.y = 1 - xf.y;
309 #endif
310
311 float2 x2 = xf;
312
313 float2 vn = tex2Dlod(_NeighbourMaxTex, float4(x2,0,0)).xy; // largest velocity in neighbourhood
314 float4 cx = tex2Dlod(_MainTex, float4(x,0,0)); // color at x
315 float2 vx = tex2Dlod(_VelTex, float4(xf,0,0)).xy; // vel at x
316
317 float4 noise = tex2Dlod(_NoiseTex, float4(i.uv,0,0)*11.0f)*2-1;
318 float zx = SAMPLE_DEPTH_TEXTURE_LOD(_CameraDepthTexture, float4(x,0,0));
319
320 zx = -Linear01Depth(zx);
321
322 noise *= _MainTex_TexelSize.xyxy * _Jitter;
323
324 //return abs(blurDir.xyxy)*10 + centerTap;
325
326 float weight = 1.0; // <- maybe tweak this: bluriness amount ...
327 float4 sum = cx * weight;
328
329 float4 jitteredDir = vn.xyxy + noise.xyyz;
330 #ifdef SHADER_API_D3D11
331 jitteredDir = max(abs(jitteredDir.xyxy), _MainTex_TexelSize.xyxy * _MaxVelocity * 0.5) * sign(jitteredDir.xyxy) * float4(1,1,-1,-1);
332 #else
333 jitteredDir = max(abs(jitteredDir.xyxy), _MainTex_TexelSize.xyxy * _MaxVelocity * 0.15) * sign(jitteredDir.xyxy) * float4(1,1,-1,-1);
334 #endif
335
336 for(int l=0; l<SmallDiscKernelSamples; l++)
337 {
338 float4 y = i.uv.xyxy + jitteredDir.xyxy * SmallDiscKernel[l].xyxy * float4(1,1,-1,-1);
339
340 float4 yf = y;
341 #if UNITY_UV_STARTS_AT_TOP
342 if (_MainTex_TexelSize.y < 0)
343 yf.yw = 1-yf.yw;
344 #endif
345
346 // velocity at y
347 float2 vy = tex2Dlod(_VelTex, float4(yf.xy,0,0)).xy;
348
349 float zy = SAMPLE_DEPTH_TEXTURE_LOD(_CameraDepthTexture, float4(y.xy,0,0) );
350 zy = -Linear01Depth(zy);
351
352 float f = softDepthCompare(zx, zy);
353 float b = softDepthCompare(zy, zx);
354 float alphay = b * cone(x, y.xy, vx) + f * cone(y.xy, x, vy) + cylinder(y.xy, x, vy) * cylinder(x, y.xy, vx) * 2.0;
355
356 float4 cy = tex2Dlod(_MainTex, float4(y.xy,0,0));
357 sum += cy * alphay;
358 weight += alphay;
359
360 #ifdef SHADER_API_D3D11
361
362 vy = tex2Dlod(_VelTex, float4(yf.zw,0,0)).xy;
363
364 zy = SAMPLE_DEPTH_TEXTURE_LOD(_CameraDepthTexture, float4(y.zw,0,0) );
365 zy = -Linear01Depth(zy);
366
367 f = softDepthCompare(zx, zy);
368 b = softDepthCompare(zy, zx);
369 alphay = b * cone(x, y.zw, vx) + f * cone(y.zw, x, vy) + cylinder(y.zw, x, vy) * cylinder(x, y.zw, vx) * 2.0;
370
371 cy = tex2Dlod(_MainTex, float4(y.zw,0,0));
372 sum += cy * alphay;
373 weight += alphay;
374
375 #endif
376 }
377
378 return sum / weight;
379 }
380
381 float4 MotionVectorBlur (v2f i) : SV_Target
382 {
383 float2 x = i.uv;
384
385 float2 insideVector = (x*2-1) * float2(1,_MainTex_TexelSize.w/_MainTex_TexelSize.z);
386 float2 rollVector = float2(insideVector.y, -insideVector.x);
387
388 float2 blurDir = _BlurDirectionPacked.x * float2(0,1);
389 blurDir += _BlurDirectionPacked.y * float2(1,0);
390 blurDir += _BlurDirectionPacked.z * rollVector;
391 blurDir += _BlurDirectionPacked.w * insideVector;
392 blurDir *= _VelocityScale;
393
394 // clamp to maximum velocity (in pixels)
395 float velMag = length(blurDir);
396 if (velMag > _MaxVelocity) {
397 blurDir *= (_MaxVelocity / velMag);
398 velMag = _MaxVelocity;
399 }
400
401 float4 centerTap = tex2D(_MainTex, x);
402 float4 sum = centerTap;
403
404 blurDir *= smoothstep(_MinVelocity * 0.25f, _MinVelocity * 2.5, velMag);
405
406 blurDir *= _MainTex_TexelSize.xy;
407 blurDir /= MOTION_SAMPLES;
408
409 for(int i=0; i<MOTION_SAMPLES; i++) {
410 float4 tap = tex2D(_MainTex, x+i*blurDir);
411 sum += tap;
412 }
413
414 return sum/(1+MOTION_SAMPLES);
415 }
416
417 ENDCG
418
419 Subshader {
420
421 // pass 0
422 Pass {
423 ZTest Always Cull Off ZWrite On Blend Off
424
425 CGPROGRAM
426 #pragma target 3.0
427 #pragma vertex vert
428 #pragma fragment CameraVelocity
429
430 ENDCG
431 }
432
433 // pass 1
434 Pass {
435 ZTest Always Cull Off ZWrite Off Blend Off
436
437 CGPROGRAM
438 #pragma target 3.0
439 #pragma vertex vert
440 #pragma fragment Debug
441
442 ENDCG
443 }
444
445 // pass 2
446 Pass {
447 ZTest Always Cull Off ZWrite Off Blend Off
448
449 CGPROGRAM
450 #pragma target 3.0
451 #pragma vertex vert
452 #pragma fragment TileMax
453
454 ENDCG
455 }
456
457 // pass 3
458 Pass {
459 ZTest Always Cull Off ZWrite Off Blend Off
460
461 CGPROGRAM
462 #pragma target 3.0
463 #pragma vertex vert
464 #pragma fragment NeighbourMax
465
466 ENDCG
467 }
468
469 // pass 4
470 Pass {
471 ZTest Always Cull Off ZWrite Off Blend Off
472
473 CGPROGRAM
474 #pragma target 3.0
475 #pragma vertex vert
476 #pragma fragment ReconstructFilterBlur
477
478 ENDCG
479 }
480
481 // pass 5
482 Pass {
483 ZTest Always Cull Off ZWrite Off Blend Off
484
485 CGPROGRAM
486 #pragma target 3.0
487 #pragma vertex vert
488 #pragma fragment SimpleBlur
489 ENDCG
490 }
491
492 // pass 6
493 Pass {
494 ZTest Always Cull Off ZWrite Off Blend Off
495
496 CGPROGRAM
497 #pragma target 3.0
498 #pragma vertex vert
499 #pragma fragment MotionVectorBlur
500 ENDCG
501 }
502
503 // pass 7
504 Pass {
505 ZTest Always Cull Off ZWrite Off Blend Off
506
507 CGPROGRAM
508 #pragma target 3.0
509 #pragma vertex vert
510 #pragma fragment ReconstructionDiscBlur
511 ENDCG
512 }
513 }
514
515 Fallback off
516 }