#version 120
#define VIGNETTE_STRENGTH 1. 
#define VIGNETTE_START 0.3	//distance from the center of the screen where the vignette effect start (0-1)
#define VIGNETTE_END 0.95		//distance from the center of the screen where the vignette effect end (0-1), bigger than VIGNETTE_START
uniform sampler2D colortex0;
uniform sampler2D colortex1;
uniform sampler2D colortex4;
uniform sampler2D colortex5;
uniform sampler2D depthtex0;


varying vec2 texcoord;
uniform vec3 cameraPosition;
uniform vec3 previousCameraPosition;
uniform vec3 sunPosition;
uniform vec3 moonPosition;
uniform mat4 gbufferProjection;
uniform mat4 gbufferProjectionInverse;
uniform mat4 gbufferPreviousProjection;
uniform mat4 gbufferModelViewInverse;
uniform mat4 gbufferPreviousModelView;
uniform ivec2 eyeBrightness;
uniform int isEyeInWater;
uniform int worldTime;
uniform float aspectRatio;
uniform float near;
uniform float far;
uniform float viewWidth;
uniform float viewHeight;
uniform float rainStrength;
uniform float wetness;
uniform float frameTimeCounter;

float pw = 1.0/viewWidth;
float ph = 1.0/viewHeight;


vec3 ACESFilm( vec3 x )
{
    float a = 2.51f;
    float b = 0.03f;
    float c = 2.43f;
    float d = 0.59f;
    float e = 0.14f;
	vec3 r = (x*(a*x+b))/(x*(c*x+d)+e);

    return pow(r,vec3(1.0/2.2));
}

vec3 invACESFilm( vec3 r )
{
    float a = 2.51f;
    float b = 0.03f;
    float c = 2.43f;
    float d = 0.59f;
    float e = 0.14f;
	r = pow(r,vec3(2.2));
	return (0.00617284 - 0.121399*r - 0.00205761 *sqrt(9 + 13702*r - 10127*r*r))/(-1.03292 + r);
	//return (0.5 * (-b + d * r - sqrt(-4 * e * r * (-a + c * r) + pow(-b + d * r,vec3(2.0)))))/(a - c*r);

}

vec4 smoothfilter(in sampler2D tex, in vec2 uv)
{
	vec2 textureResolution = vec2(viewWidth,viewHeight);
	uv = uv*textureResolution + 0.5;
	vec2 iuv = floor( uv );
	vec2 fuv = fract( uv );
	uv = iuv + fuv*fuv*fuv*(fuv*(fuv*6.0-15.0)+10.0);
	uv = (uv - 0.5)/textureResolution;
	return texture2D( tex, uv);
}


vec3 clip_aabb(vec3 aabb_min, vec3 aabb_max, vec3 p, vec3 q)
	{
		vec3 p_clip = 0.5 * (aabb_max + aabb_min);
		vec3 e_clip = 0.5 * (aabb_max - aabb_min) + 0.00000001;

		vec3 v_clip = q - vec3(p_clip);
		vec3 v_unit = v_clip.xyz / e_clip;
		vec3 a_unit = abs(v_unit);
		float ma_unit = max(a_unit.x, max(a_unit.y, a_unit.z));

		if (ma_unit > 1.0)
			return vec3(p_clip) + v_clip / ma_unit;
		else
			return q;
	}
	
float distratio(vec2 pos, vec2 pos2) {
	
		return distance(pos*vec2(aspectRatio,1.0),pos2*vec2(aspectRatio,1.0));
	}


float smStep (float edge0,float edge1,float x) {
float t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
return t * t * (3.0 - 2.0 * t); }

float nrand( vec2 n )
{
	return fract(sin(dot(n.xy, vec2(12.9898, 78.233)))* 43758.5453);
}
float triangWhiteNoise( vec2 n )
{
	//uses white noise for color dithering : gives a somewhat more "filmic" look when noise is visible
	float t = fract( frameTimeCounter );
	float rnd = nrand( n + 0.07*t );

    float center = rnd*2.0-1.0;
    rnd = center*inversesqrt(abs(center));
    rnd = max(-1.0,rnd); 
    return rnd-sign(center);
}



#define diagonal3(m) vec3((m)[0].x, (m)[1].y, m[2].z)

#define  projMAD(m, v) (diagonal3(m) * (v) + (m)[3].xyz)
vec3 toClipSpace3(vec3 viewSpacePosition) {
    return projMAD(gbufferProjection, viewSpacePosition) / -viewSpacePosition.z * 0.5 + 0.5;
}
vec4 iProjDiag = vec4(gbufferProjectionInverse[0].x, gbufferProjectionInverse[1].y, gbufferProjectionInverse[2].zw);
vec3 toScreenSpace(vec3 p) {
        vec3 p3 = p * 2. - 1.;
        vec4 fragposition = iProjDiag * p3.xyzz + gbufferProjectionInverse[3];
        return fragposition.xyz / fragposition.w;
}

vec3 closestToCamera3x3()
{
	vec2 texelSize = 1.0/vec2(viewWidth,viewHeight);
	vec2 du = vec2(texelSize.x, 0.0);
	vec2 dv = vec2(0.0, texelSize.y);

	vec3 dtl = vec3(texcoord,0.) + vec3(-texelSize, texture2D(depthtex0, texcoord - dv - du).x);
	vec3 dtc = vec3(texcoord,0.) + vec3( 0.0, -texelSize.y, texture2D(depthtex0, texcoord - dv).x);
	vec3 dtr = vec3(texcoord,0.) +  vec3( texelSize.x, -texelSize.y, texture2D(depthtex0, texcoord - dv + du).x);

	vec3 dml = vec3(texcoord,0.) +  vec3(-texelSize.x, 0.0, texture2D(depthtex0, texcoord - du).x);
	vec3 dmc = vec3(texcoord,0.) + vec3( 0.0, 0.0, texture2D(depthtex0, texcoord).x);
	vec3 dmr = vec3(texcoord,0.) + vec3( texelSize.x, 0.0, texture2D(depthtex0, texcoord + du).x);

	vec3 dbl = vec3(texcoord,0.) + vec3(-texelSize.x, texelSize.y, texture2D(depthtex0, texcoord + dv - du).x);
	vec3 dbc = vec3(texcoord,0.) + vec3( 0.0, texelSize.y, texture2D(depthtex0, texcoord + dv).x);
	vec3 dbr = vec3(texcoord,0.) + vec3( texelSize.x, texelSize.y, texture2D(depthtex0, texcoord + dv + du).x);

	vec3 dmin = dmc;
	
	dmin = dmin.z > dtc.z? dtc : dmin;
	dmin = dmin.z > dtr.z? dtr : dmin;

	dmin = dmin.z > dml.z? dml : dmin;
	dmin = dmin.z > dtl.z? dtl : dmin;
	dmin = dmin.z > dmr.z? dmr : dmin;

	dmin = dmin.z > dbl.z? dbl : dmin;
	dmin = dmin.z > dbc.z? dbc : dmin;
	dmin = dmin.z > dbr.z? dbr : dmin;

	return dmin;
}

float luma(vec3 color) {
	return dot(color,vec3(0.299, 0.587, 0.114));
}
vec3 fpDither(vec3 color,float dither){
	const vec3 mantissaBits = vec3(6.,6.,5.);
	vec3 exponent = floor(log2(color));
	return color + dither*exp2(-mantissaBits)*exp2(exponent);
}
void main() {

    vec3 albedoCurrent0 = texture2D(colortex0, texcoord).rgb;
    vec3 albedoCurrent1 = texture2D(colortex0, texcoord + vec2(1.0/viewWidth,1.0/viewHeight)).rgb;
	vec3 albedoCurrent2 = texture2D(colortex0, texcoord + vec2(1.0/viewWidth,-1.0/viewHeight)).rgb;
	vec3 albedoCurrent3 = texture2D(colortex0, texcoord + vec2(-1.0/viewWidth,-1.0/viewHeight)).rgb;
	vec3 albedoCurrent4 = texture2D(colortex0, texcoord + vec2(-1.0/viewWidth,1.0/viewHeight)).rgb;
	vec3 albedoCurrent5 = texture2D(colortex0, texcoord + vec2(0.0/viewWidth,1.0/viewHeight)).rgb;
	vec3 albedoCurrent6 = texture2D(colortex0, texcoord + vec2(0.0/viewWidth,-1.0/viewHeight)).rgb;
	vec3 albedoCurrent7 = texture2D(colortex0, texcoord + vec2(-1.0/viewWidth,0.0/viewHeight)).rgb;
	vec3 albedoCurrent8 = texture2D(colortex0, texcoord + vec2(1.0/viewWidth,0.0/viewHeight)).rgb;
	

	
	vec3 closestToCamera = closestToCamera3x3();
	//use velocity from the nearest texel from camera in a 3x3 box in order to improve edge quality in motion
	vec3 fragposition = toScreenSpace(closestToCamera);
	fragposition = mat3(gbufferModelViewInverse) * fragposition + gbufferModelViewInverse[3].xyz + (cameraPosition - previousCameraPosition);
	vec4 previousPosition = gbufferPreviousModelView * vec4(fragposition,1.);
	previousPosition = gbufferPreviousProjection * previousPosition;

	vec2 prevPos = vec2(previousPosition.xy/previousPosition.w*0.5+0.5);
	
	vec2 velocity = prevPos - closestToCamera.xy;
	
	prevPos = texcoord + velocity;
	//reduce blur caused by texture filtering (when reprojecting it is never going to have a fixed texel unless not moving)
	vec2 d = 0.5-abs(fract(prevPos.xy*vec2(viewWidth,viewHeight)-texcoord*vec2(viewWidth,viewHeight))-0.5);
	float mixFactor = pow(length(d*2.)/sqrt(2.),1.)*0.5;
	if (prevPos.x < 0.0 || prevPos.y < 0.0 || prevPos.x > 1.0 || prevPos.y > 1.0) mixFactor = 1.0;
	
	
	vec3 albedoPrev = texture2D(colortex4, prevPos.xy).xyz;

	vec3 cMax1 = max(max(max(albedoCurrent0,albedoCurrent1),albedoCurrent2),max(albedoCurrent3,max(albedoCurrent4,max(albedoCurrent5,max(albedoCurrent6,max(albedoCurrent7,albedoCurrent8))))));
	vec3 cMin1 = min(min(min(albedoCurrent0,albedoCurrent1),albedoCurrent2),min(albedoCurrent3,min(albedoCurrent4,min(albedoCurrent5,min(albedoCurrent6,min(albedoCurrent7,albedoCurrent8))))));
	
	//
	vec2 reProj = d/vec2(viewWidth,viewHeight);
	float weight = exp(-2.29*dot(reProj,reProj));
	
	//extend clipping range in high frequency areas to reduce flicker
	vec3 cMax = cMax1*1.25-cMin1*0.25;
	vec3 cMin = cMin1*1.25-cMax1*0.25;
	//fallof when leaving standard 3x3 clip range, reduces blurring at the cost of flickering
	vec3 noClipFinal = mix(albedoPrev,albedoCurrent0,mixFactor+1./16.*weight);
	vec3 clrToMin = min(noClipFinal-cMin1,0.)/(cMin-cMin1);
	vec3 clrToMax = max(noClipFinal-cMax,0.)/(cMax-cMax1);
	float d1 = max(max(clrToMax.r,clrToMax.g),clrToMax.b);
	float d2 = max(max(clrToMin.r,clrToMin.g),clrToMin.b);

	mixFactor += d1*0.5+d2*0.5;


	//scale with exposure
	albedoCurrent0 *= 1.0/100.;
	//clip
	vec3 finalcAcc = albedoPrev/100.;
	
	//do luma based weighting (from ue4 taa presentation)
	float historyLum = 1.0/(1.0+luma(finalcAcc));
	float currLum = 1.0/(1.0+luma(albedoCurrent0.xyz));
	
	vec3 supersampled =  mix(finalcAcc*historyLum,albedoCurrent0*currLum,clamp(mixFactor+1./10.,0.,1.));
	float newWeight = mix(historyLum,currLum,clamp(mixFactor+1./10.,0.,1.));
	
	
	supersampled = supersampled/newWeight;

	//clipping after temporal resolve gives better results
	vec3 color = clip_aabb(cMin/100.,cMax/100.,cMin*0.5+cMax*0.5,supersampled)*100.;
/* DRAWBUFFERS:04 */

gl_FragData[1] = vec4(clamp(fpDither(color,triangWhiteNoise(texcoord)),0.,65000.),1.0);
gl_FragData[1].xy = (gl_FragCoord.x < 1.0 && gl_FragCoord.y < 1.) ? texture2D(colortex4,0.5/vec2(viewWidth,viewHeight)).xy : gl_FragData[1].xy;


	vec3 dirtyLens = pow(texture2D(colortex5,texcoord).rgb,vec3(2.2))*10.;
	
		
	float len = length(texcoord.xy-vec2(.5));
	float len2 = distratio(texcoord.xy,vec2(.5));

	float dc = mix(len,len2,0.1);
    float vignette = smStep(VIGNETTE_END, VIGNETTE_START,  dc);
	float vignetteC = pow(vignette,1.5);

	
	color = color*vignetteC;

	color = mix(color,vec3(0.0),pow(dirtyLens,vec3(0.1))*0.4*pow(1.0-vignetteC,0.4545));


	//color *= vec3(1.78,1.75,1.8)/6.7;

	color = ACESFilm(color/100.);

gl_FragData[0] = vec4(fpDither(color,triangWhiteNoise(texcoord)),1.);

}