1/*
2DoF with bokeh GLSL shader v2.4
3by Martins Upitis (martinsh) (devlog-martinsh.blogspot.com)
4
5----------------------
6The shader is Blender Game Engine ready, but it should be quite simple to adapt for your engine.
7
8This work is licensed under a Creative Commons Attribution 3.0 Unported License.
9So you are free to share, modify and adapt it for your needs, and even use it for commercial use.
10I would also love to hear about a project you are using it.
11
12Have fun,
13Martins
14----------------------
15
16changelog:
17
182.4:
19- physically accurate DoF simulation calculated from "focalDepth" ,"focalLength", "f-stop" and "CoC" parameters.
20- option for artist controlled DoF simulation calculated only from "focalDepth" and individual controls for near and far blur
21- added "circe of confusion" (CoC) parameter in mm to accurately simulate DoF with different camera sensor or film sizes
22- cleaned up the code
23- some optimization
24
252.3:
26- new and physically little more accurate DoF
27- two extra input variables - focal length and aperture iris diameter
28- added a debug visualization of focus point and focal range
29
302.1:
31- added an option for pentagonal bokeh shape
32- minor fixes
33
342.0:
35- variable sample count to increase quality/performance
36- option to blur depth buffer to reduce hard edges
37- option to dither the samples with noise or pattern
38- bokeh chromatic aberration/fringing
39- bokeh bias to bring out bokeh edges
40- image thresholding to bring out highlights when image is out of focus
41
42*/
43
44uniform sampler2D bgl_RenderedTexture;
45uniform sampler2D bgl_DepthTexture;
46uniform float bgl_RenderedTextureWidth;
47uniform float bgl_RenderedTextureHeight;
48
49#define PI  3.14159265
50
51float width = bgl_RenderedTextureWidth; //texture width
52float height = bgl_RenderedTextureHeight; //texture height
53
54vec2 texel = vec2(1.0/width,1.0/height);
55
56//uniform variables from external script
57
58uniform float focalDepth;  //focal distance value in meters, but you may use autofocus option below
59uniform float focalLength; //focal length in mm
60uniform float fstop; //f-stop value
61uniform bool showFocus; //show debug focus point and focal range (red = focal point, green = focal range)
62
63/*
64make sure that these two values are the same for your camera, otherwise distances will be wrong.
65*/
66
67float znear = 0.1; //camera clipping start
68float zfar = 100.0; //camera clipping end
69
70//------------------------------------------
71//user variables
72
73int samples = 3; //samples on the first ring
74int rings = 3; //ring count
75
76bool manualdof = false; //manual dof calculation
77float ndofstart = 1.0; //near dof blur start
78float ndofdist = 2.0; //near dof blur falloff distance
79float fdofstart = 1.0; //far dof blur start
80float fdofdist = 3.0; //far dof blur falloff distance
81
82float CoC = 0.03;//circle of confusion size in mm (35mm film = 0.03mm)
83
84bool vignetting = true; //use optical lens vignetting?
85float vignout = 1.3; //vignetting outer border
86float vignin = 0.0; //vignetting inner border
87float vignfade = 22.0; //f-stops till vignete fades
88
89bool autofocus = false; //use autofocus in shader? disable if you use external focalDepth value
90vec2 focus = vec2(0.5,0.5); // autofocus point on screen (0.0,0.0 - left lower corner, 1.0,1.0 - upper right)
91float maxblur = 1.0; //clamp value of max blur (0.0 = no blur,1.0 default)
92
93float threshold = 0.5; //highlight threshold;
94float gain = 2.0; //highlight gain;
95
96float bias = 0.5; //bokeh edge bias
97float fringe = 0.7; //bokeh chromatic aberration/fringing
98
99bool noise = true; //use noise instead of pattern for sample dithering
100float namount = 0.0001; //dither amount
101
102bool depthblur = false; //blur the depth buffer?
103float dbsize = 1.25; //depthblursize
104
105/*
106next part is experimental
107not looking good with small sample and ring count
108looks okay starting from samples = 4, rings = 4
109*/
110
111bool pentagon = false; //use pentagon as bokeh shape?
112float feather = 0.4; //pentagon shape feather
113
114//------------------------------------------
115
116
117float penta(vec2 coords) //pentagonal shape
118{
119	float scale = float(rings) - 1.3;
120	vec4  HS0 = vec4( 1.0,         0.0,         0.0,  1.0);
121	vec4  HS1 = vec4( 0.309016994, 0.951056516, 0.0,  1.0);
122	vec4  HS2 = vec4(-0.809016994, 0.587785252, 0.0,  1.0);
123	vec4  HS3 = vec4(-0.809016994,-0.587785252, 0.0,  1.0);
124	vec4  HS4 = vec4( 0.309016994,-0.951056516, 0.0,  1.0);
125	vec4  HS5 = vec4( 0.0        ,0.0         , 1.0,  1.0);
126
127	vec4  one = vec4( 1.0 );
128
129	vec4 P = vec4((coords),vec2(scale, scale));
130
131	vec4 dist = vec4(0.0);
132	float inorout = -4.0;
133
134	dist.x = dot( P, HS0 );
135	dist.y = dot( P, HS1 );
136	dist.z = dot( P, HS2 );
137	dist.w = dot( P, HS3 );
138
139	dist = smoothstep( -feather, feather, dist );
140
141	inorout += dot( dist, one );
142
143	dist.x = dot( P, HS4 );
144	dist.y = HS5.w - abs( P.z );
145
146	dist = smoothstep( -feather, feather, dist );
147	inorout += dist.x;
148
149	return clamp( inorout, 0.0, 1.0 );
150}
151
152float bdepth(vec2 coords) //blurring depth
153{
154	float d = 0.0;
155	float kernel[9];
156	vec2 offset[9];
157
158	vec2 wh = vec2(texel.x, texel.y) * dbsize;
159
160	offset[0] = vec2(-wh.x,-wh.y);
161	offset[1] = vec2( 0.0, -wh.y);
162	offset[2] = vec2( wh.x -wh.y);
163
164	offset[3] = vec2(-wh.x,  0.0);
165	offset[4] = vec2( 0.0,   0.0);
166	offset[5] = vec2( wh.x,  0.0);
167
168	offset[6] = vec2(-wh.x, wh.y);
169	offset[7] = vec2( 0.0,  wh.y);
170	offset[8] = vec2( wh.x, wh.y);
171
172	kernel[0] = 1.0/16.0;   kernel[1] = 2.0/16.0;   kernel[2] = 1.0/16.0;
173	kernel[3] = 2.0/16.0;   kernel[4] = 4.0/16.0;   kernel[5] = 2.0/16.0;
174	kernel[6] = 1.0/16.0;   kernel[7] = 2.0/16.0;   kernel[8] = 1.0/16.0;
175
176
177	for( int i=0; i<9; i++ )
178	{
179		float tmp = texture2D(bgl_DepthTexture, coords + offset[i]).r;
180		d += tmp * kernel[i];
181	}
182
183	return d;
184}
185
186
187vec3 color(vec2 coords,float blur) //processing the sample
188{
189	vec3 col = vec3(0.0);
190
191	col.r = texture2D(bgl_RenderedTexture,coords + vec2(0.0,1.0)*texel*fringe*blur).r;
192	col.g = texture2D(bgl_RenderedTexture,coords + vec2(-0.866,-0.5)*texel*fringe*blur).g;
193	col.b = texture2D(bgl_RenderedTexture,coords + vec2(0.866,-0.5)*texel*fringe*blur).b;
194
195	vec3 lumcoeff = vec3(0.299,0.587,0.114);
196	float lum = dot(col.rgb, lumcoeff);
197	float thresh = max((lum-threshold)*gain, 0.0);
198	return col+mix(vec3(0.0),col,thresh*blur);
199}
200
201vec2 rand(vec2 coord) //generating noise/pattern texture for dithering
202{
203	float noiseX = ((fract(1.0-coord.s*(width/2.0))*0.25)+(fract(coord.t*(height/2.0))*0.75))*2.0-1.0;
204	float noiseY = ((fract(1.0-coord.s*(width/2.0))*0.75)+(fract(coord.t*(height/2.0))*0.25))*2.0-1.0;
205
206	if (noise)
207	{
208		noiseX = clamp(fract(sin(dot(coord ,vec2(12.9898,78.233))) * 43758.5453),0.0,1.0)*2.0-1.0;
209		noiseY = clamp(fract(sin(dot(coord ,vec2(12.9898,78.233)*2.0)) * 43758.5453),0.0,1.0)*2.0-1.0;
210	}
211	return vec2(noiseX,noiseY);
212}
213
214vec3 debugFocus(vec3 col, float blur, float depth)
215{
216	float edge = 0.002*depth; //distance based edge smoothing
217	float m = clamp(smoothstep(0.0,edge,blur),0.0,1.0);
218	float e = clamp(smoothstep(1.0-edge,1.0,blur),0.0,1.0);
219
220	col = mix(col,vec3(1.0,0.5,0.0),(1.0-m)*0.6);
221	col = mix(col,vec3(0.0,0.5,1.0),((1.0-e)-(1.0-m))*0.2);
222
223	return col;
224}
225
226float linearize(float depth)
227{
228	return -zfar * znear / (depth * (zfar - znear) - zfar);
229}
230
231float vignette()
232{
233	float dist = distance(gl_TexCoord[3].xy, vec2(0.5,0.5));
234	dist = smoothstep(vignout+(fstop/vignfade), vignin+(fstop/vignfade), dist);
235	return clamp(dist,0.0,1.0);
236}
237
238void main()
239{
240	//scene depth calculation
241
242	float depth = linearize(texture2D(bgl_DepthTexture,gl_TexCoord[0].xy).x);
243
244	if (depthblur)
245	{
246		depth = linearize(bdepth(gl_TexCoord[0].xy));
247	}
248
249	//focal plane calculation
250
251	float fDepth = focalDepth;
252
253	if (autofocus)
254	{
255		fDepth = linearize(texture2D(bgl_DepthTexture,focus).x);
256	}
257
258	//dof blur factor calculation
259
260	float blur = 0.0;
261
262	if (manualdof)
263	{
264		float a = depth-fDepth; //focal plane
265		float b = (a-fdofstart)/fdofdist; //far DoF
266		float c = (-a-ndofstart)/ndofdist; //near Dof
267		blur = (a>0.0)?b:c;
268	}
269
270	else
271	{
272		float f = focalLength; //focal length in mm
273		float d = fDepth*1000.0; //focal plane in mm
274		float o = depth*1000.0; //depth in mm
275
276		float a = (o*f)/(o-f);
277		float b = (d*f)/(d-f);
278		float c = (d-f)/(d*fstop*CoC);
279
280		blur = abs(a-b)*c;
281	}
282
283	blur = clamp(blur,0.0,1.0);
284
285	// calculation of pattern for ditering
286
287	vec2 noise = rand(gl_TexCoord[0].xy)*namount*blur;
288
289	// getting blur x and y step factor
290
291	float w = (1.0/width)*blur*maxblur+noise.x;
292	float h = (1.0/height)*blur*maxblur+noise.y;
293
294	// calculation of final color
295
296	vec3 col = vec3(0.0);
297
298	if(blur < 0.05) //some optimization thingy
299	{
300		col = texture2D(bgl_RenderedTexture, gl_TexCoord[0].xy).rgb;
301	}
302
303	else
304	{
305		col = texture2D(bgl_RenderedTexture, gl_TexCoord[0].xy).rgb;
306		float s = 1.0;
307		int ringsamples;
308
309		for (int i = 1; i <= rings; i += 1)
310		{
311			ringsamples = i * samples;
312
313			for (int j = 0 ; j < ringsamples ; j += 1)
314			{
315				float step = PI*2.0 / float(ringsamples);
316				float pw = (cos(float(j)*step)*float(i));
317				float ph = (sin(float(j)*step)*float(i));
318				float p = 1.0;
319				if (pentagon)
320				{
321					p = penta(vec2(pw,ph));
322				}
323				col += color(gl_TexCoord[0].xy + vec2(pw*w,ph*h),blur)*mix(1.0,(float(i))/(float(rings)),bias)*p;
324				s += 1.0*mix(1.0,(float(i))/(float(rings)),bias)*p;
325			}
326		}
327		col /= s; //divide by sample count
328	}
329
330	if (showFocus)
331	{
332		col = debugFocus(col, blur, depth);
333	}
334
335	if (vignetting)
336	{
337		col *= vignette();
338	}
339
340	gl_FragColor.rgb = col;
341	gl_FragColor.a = 1.0;
342}