uniform sampler2D bgl_RenderedTexture;
uniform sampler2D bgl_DepthTexture;
uniform sampler2D randTexture; // Random texture
uniform sampler2D light;
float bgl_RenderedTextureWidth = 1024.0;
float bgl_RenderedTextureHeight = 768.0;

#define PI  3.14159265

float width = bgl_RenderedTextureWidth; //texture width
float height = bgl_RenderedTextureHeight; //texture height

vec2 texel = vec2(1.0/width,1.0/height);

float focalDepth = 50.4;  //focal distance value in meters, but you may use autofocus option below
float focalLength = 15.0; //focal length in mm
float aperture = 1.7; //pupil diameter in mm
bool showFocus = false; //show debug focus point and focal range (red = focal point, green = focal range)

/* 
make sure that these two values are the same for your camera, otherwise distances will be wrong.
*/

float znear = 1.0; //camera clipping start
float zfar = 100.0; //camera clipping end

//------------------------------------------
//user variables

int samples = 3; //samples on the first ring
int rings = 4; //ring count

bool autofocus = false; //use autofocus in shader? disable if you use external focalDepth value
vec2 focus = vec2(0.5,0.5); // autofocus point on screen (0.0,0.0 - left lower corner, 1.0,1.0 - upper right)
float maxblur = 1.0; //clamp value of max blur (0.0 = no blur,1.0 default)

float threshold = 0.5; //highlight threshold;
float gain = 2.0; //highlight gain;

float bias = 0.5; //bokeh edge bias
float fringe = 0.7; //bokeh chromatic aberration/fringing

bool noise = true; //use noise instead of pattern for sample dithering
float namount = 0.0001; //dither amount

bool depthblur = false; //blur the depth buffer?
float dbsize = 1.25; //depthblursize

/*
next part is experimental
not looking good with small sample and ring count
looks okay starting from samples = 4, rings = 4
*/

bool pentagon = false; //use pentagon as bokeh shape?
float feather = 0.4; //pentagon shape feather

//------------------------------------------

vec4 SSAOColour;
vec4 DOFColour;

// Util vars
uniform int screenWidth;
uniform int screenHeight;

vec2 camerarange = vec2(1.0, 1024.0);
uniform bool renderColor;

float pw = 1.0/float(screenWidth);
float ph = 1.0/float(screenHeight);

uniform float lightDecrease;

float maxLightDecrease = 0.5;
float actualLightDecrease = lightDecrease * maxLightDecrease;

float readDepth(in vec2 coord)  
{  
    if (coord.x < 0.0|| coord.y < 0.0)
		return 1.0;

    float nearZ = 2.0;
    float farZ = 1000.0;
    float posZ = texture2D(bgl_DepthTexture, coord).x;

    return (2.0 * nearZ) / (nearZ + farZ - posZ * (farZ - nearZ));  
} 

vec3 readColor(in vec2 coord)  
{  
    return DOFColour.xyz; 
} 

float compareDepths(in float depth1, in float depth2,inout int far)  
{  

    float diff = (depth1 - depth2)*100.0; //depth difference (0-100)
    float gdisplace = 0.2; //gauss bell center
    float garea = 2.0; //gauss bell width 2

    //reduce left bell width to avoid self-shadowing
    if (diff<gdisplace){ 
    garea = 0.1;
    }else{
    far = 1;
    }
    float gauss = pow(2.7182,-2.0*(diff-gdisplace)*(diff-gdisplace)/(garea*garea));

    return gauss;
}  

float calAO(float depth,float dw, float dh)  
{  
    float temp = 0.0;
    float temp2 = 0.0;
    float coordw = gl_TexCoord[0].x + dw/depth;
    float coordh = gl_TexCoord[0].y + dh/depth;
    float coordw2 = gl_TexCoord[0].x - dw/depth;
    float coordh2 = gl_TexCoord[0].y - dh/depth;

    if (coordw  < 1.0 && coordw  > 0.0 && coordh < 1.0 && coordh  > 0.0){
    vec2 coord = vec2(coordw , coordh);
    vec2 coord2 = vec2(coordw2, coordh2);
    int far = 0;
    temp = compareDepths(depth, readDepth(coord),far);

    //DEPTH EXTRAPOLATION:
    if (far > 0){
        temp2 = compareDepths(readDepth(coord2),depth,far);
        temp += (1.0-temp)*temp2; 
    }
    }
 
    return temp;  
}   

float penta(vec2 coords) //pentagonal shape
{
	float scale = float(rings) - 1.3;
	vec4  HS0 = vec4( 1.0,         0.0,         0.0,  1.0);
	vec4  HS1 = vec4( 0.309016994, 0.951056516, 0.0,  1.0);
	vec4  HS2 = vec4(-0.809016994, 0.587785252, 0.0,  1.0);
	vec4  HS3 = vec4(-0.809016994,-0.587785252, 0.0,  1.0);
	vec4  HS4 = vec4( 0.309016994,-0.951056516, 0.0,  1.0);
	vec4  HS5 = vec4( 0.0        ,0.0         , 1.0,  1.0);
	
	vec4  one = vec4( 1.0 );
	
	vec4 P = vec4((coords),vec2(scale, scale)); 
	
	vec4 dist = vec4(0.0);
	float inorout = -4.0;
	
	dist.x = dot( P, HS0 );
	dist.y = dot( P, HS1 );
	dist.z = dot( P, HS2 );
	dist.w = dot( P, HS3 );
	
	dist = smoothstep( -feather, feather, dist );
	
	inorout += dot( dist, one );
	
	dist.x = dot( P, HS4 );
	dist.y = HS5.w - abs( P.z );
	
	dist = smoothstep( -feather, feather, dist );
	inorout += dist.x;
	
	return clamp( inorout, 0.0, 1.0 );
}

float bdepth(vec2 coords) //blurring depth
{
	float d = 0.0;
	float kernel[9];
	vec2 offset[9];
	
	vec2 wh = vec2(texel.x, texel.y) * dbsize;
	
	offset[0] = vec2(-wh.x,-wh.y);
	offset[1] = vec2( 0.0, -wh.y);
	offset[2] = vec2( wh.x -wh.y);
	
	offset[3] = vec2(-wh.x,  0.0);
	offset[4] = vec2( 0.0,   0.0);
	offset[5] = vec2( wh.x,  0.0);
	
	offset[6] = vec2(-wh.x, wh.y);
	offset[7] = vec2( 0.0,  wh.y);
	offset[8] = vec2( wh.x, wh.y);
	
	kernel[0] = 1.0/16.0;   kernel[1] = 2.0/16.0;   kernel[2] = 1.0/16.0;
	kernel[3] = 2.0/16.0;   kernel[4] = 4.0/16.0;   kernel[5] = 2.0/16.0;
	kernel[6] = 1.0/16.0;   kernel[7] = 2.0/16.0;   kernel[8] = 1.0/16.0;
	
	
	for( int i=0; i<9; i++ )
	{
		float tmp = texture2D(bgl_DepthTexture, coords + offset[i]).r;
		d += tmp * kernel[i];
	}
	
	return d;
}


vec3 color(vec2 coords,float blur) //processing the sample
{
	vec3 col = vec3(0.0);
	
	col.r = texture2D(bgl_RenderedTexture,coords + vec2(0.0,1.0)*texel*fringe*blur).r - actualLightDecrease + texture2D(light, coords + vec2(0.0,1.0)*texel*fringe*blur).r;
	col.g = texture2D(bgl_RenderedTexture,coords + vec2(-0.866,-0.5)*texel*fringe*blur).g - actualLightDecrease + texture2D(light, coords + vec2(-0.866,-0.5)*texel*fringe*blur).g;
	col.b = texture2D(bgl_RenderedTexture,coords + vec2(0.866,-0.5)*texel*fringe*blur).b - actualLightDecrease + texture2D(light, coords + vec2(0.866,-0.5)*texel*fringe*blur).b;
	
	vec3 lumcoeff = vec3(0.299,0.587,0.114);
	float lum = dot(col.rgb, lumcoeff);
	float thresh = max((lum-threshold)*gain, 0.0);
	return col+mix(vec3(0.0),col,thresh*blur);
}

vec2 rand(vec2 coord) //generating noise/pattern texture for dithering
{
	float noiseX = ((fract(1.0-coord.s*(width/2.0))*0.25)+(fract(coord.t*(height/2.0))*0.75))*2.0-1.0;
	float noiseY = ((fract(1.0-coord.s*(width/2.0))*0.75)+(fract(coord.t*(height/2.0))*0.25))*2.0-1.0;
	
	if (noise)
	{
		noiseX = clamp(fract(sin(dot(coord ,vec2(12.9898,78.233))) * 43758.5453),0.0,1.0)*2.0-1.0;
		noiseY = clamp(fract(sin(dot(coord ,vec2(12.9898,78.233)*2.0)) * 43758.5453),0.0,1.0)*2.0-1.0;
	}
	return vec2(noiseX,noiseY);
}

vec3 debugFocus(vec3 col, float blur)
{
	float m = smoothstep(0.0,0.01,blur);
	float e = smoothstep(0.99,1.0,blur);
	float ee = smoothstep(0.97,1.0,blur)-e;
	
	col = mix(vec3(1.0,0.0,0.0),col,m);
	col = mix(vec3(0.0,1.0,0.0),col,(1.0-ee)-(1.0-e)*0.1);
	return col;
}

float linearize(float depth)
{
	return -zfar * znear / (depth * (zfar - znear) - zfar);
}

void main() 
{
	float blur = 0.0;
	
	float depth = linearize(texture2D(bgl_DepthTexture,gl_TexCoord[0].xy).x);
	
	if (depthblur)
	{
		depth = linearize(bdepth(gl_TexCoord[0].xy));
	}
 
    blur = abs(aperture * (focalLength * (depth - focalDepth)) /(depth * (focalDepth - focalLength)));
    
	if (autofocus)
	{
		float fDepth = linearize(texture2D(bgl_DepthTexture,focus).x);
		blur = abs(aperture * (focalLength * (depth - fDepth)) /
        (depth * (fDepth - focalLength)));
	}

	blur = clamp(blur,0.0,1.0);

	vec2 noise = rand(gl_TexCoord[0].xy)*namount*blur;
	
	float w = (1.0/width)*blur*maxblur+noise.x;
	float h = (1.0/height)*blur*maxblur+noise.y;
	
	vec3 col = (texture2D(bgl_RenderedTexture, gl_TexCoord[0].xy).xyz-actualLightDecrease) + texture2D(light, gl_TexCoord[0].xy).xyz;
	float s = 1.0;
	
	int ringsamples;
	
	for (int i = 1; i <= rings; i += 1)
	{   
		ringsamples = i * samples;
		 
		for (int j = 0 ; j < ringsamples ; j += 1)   
		{
			float step = PI*2.0 / float(ringsamples);
			float pw = (cos(float(j)*step)*float(i));
			float ph = (sin(float(j)*step)*float(i));
			float p = 1.0;
			if (pentagon)
			{ 
			p = penta(vec2(pw,ph));
			}
			col += color(gl_TexCoord[0].xy + vec2(pw*w,ph*h),blur)*mix(1.0,(float(i))/(float(rings)),bias)*p;  
			s += 1.0*mix(1.0,(float(i))/(float(rings)),bias)*p;   
		}
	}
	
	
	col /= s;   
	
	if (showFocus)
	{
	    col = debugFocus(col, blur);
	}
	
	DOFColour = vec4(col, 1.0);

	// -------------------------------------------------------------------------------

	//randomization texture:
    vec2 fres = vec2(20,20);
    vec4 random = texture2D(randTexture, gl_TexCoord[0].st*fres.xy);
    random = random*3.5-vec4(1.0);

    //initialize stuff:
	vec2 texturecoord = gl_TexCoord[0].xy;
    float depth1 = readDepth(texturecoord);  
    float ao = 0.0;

    for(int i=0; i<4; ++i) 
    {  
    //calculate color bleeding and ao:
    ao+=calAO(depth1,  pw, ph);  
    ao+=calAO(depth1,  pw, -ph);  
    ao+=calAO(depth1,  -pw, ph);  
    ao+=calAO(depth1,  -pw, -ph);

    ao+=calAO(depth1,  pw*1.2, 0.0);  
    ao+=calAO(depth1,  -pw*1.2, 0.0);  
    ao+=calAO(depth1,  0.0, ph*1.2);  
    ao+=calAO(depth1,  0.0, -ph*1.2);
     
    //sample jittering:
    pw += random.x*0.0007;
    ph += random.y*0.0007;

    //increase sampling area:
    pw *= 1.7;  
    ph *= 1.7;    
    }         

    //final values, some adjusting:
    vec3 finalAO = vec3(1.0-(ao/32.0));

	SSAOColour = vec4((readColor(gl_TexCoord[0].xy)+0.1)*finalAO*0.7,1.0);

	// -------------------------------------------------------------------------------

	gl_FragColor = SSAOColour;
}