#version 330

in vec3 fragPosition;
in vec3 fragNormal;
in vec2 fragTexCoord;
in vec4 fragColor;

uniform sampler2D texture0;
uniform sampler2D depth_tex;
uniform sampler2D normal_tex;
uniform float tan_half_fov;
uniform float aspect;

out vec4 finalColor;

// dither
const mat4 bayerMatrix = mat4(
	0.0/16.0, 12.0/16.0,  3.0/16.0, 15.0/16.0,
	8.0/16.0,  4.0/16.0, 11.0/16.0,  7.0/16.0,
	2.0/16.0, 14.0/16.0,  1.0/16.0, 13.0/16.0,
	10.0/16.0,  6.0/16.0,  9.0/16.0,  5.0/16.0
);

// ssao
const int   AO_SAMPLES  = 16;
const float AO_RADIUS   = 1.5;
const float AO_BIAS     = 0.05;
const float AO_STRENGTH = 0.6;

// pixelization + other
float pixel_size = 1.0;
float contrast = 1.5;
float levels = 32.0;
vec2 gamma = vec2(0.5, 1.5);

// isoteric.
const float near_plane = 0.01;
const float far_plane = 1000.0;
const float fog_far = 150.0;

float linearize_depth(float d) {
	float z = d * 2.0 - 1.0;
	float lin = (2.0 * near_plane * far_plane) / (far_plane + near_plane - z * (far_plane - near_plane));
	return clamp(lin / fog_far, 0.0, 1.0);
}

float view_dist(float d) {
    float z = d * 2.0 - 1.0;
    return (2.0 * near_plane * far_plane) / (far_plane + near_plane - z * (far_plane - near_plane));
}

vec3 view_pos(vec2 uv, float d) {
    float lz = view_dist(d);
    vec2 ndc = uv * 2.0 - 1.0;
    return vec3(ndc * vec2(aspect, 1.0) * tan_half_fov * lz, -lz);
}

float hash(vec2 p) {
    return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
}

float ssao(vec2 uv, float depth, vec2 texsize) {
    vec3 P = view_pos(uv, depth);
    vec3 N = normalize(texture(normal_tex, uv).xyz * 2.0 - 1.0);

    float ang = hash(floor(uv * texsize)) * 6.2831853;
    vec3 rvec = vec3(cos(ang), sin(ang), 0.0);
    vec3 T = normalize(rvec - N * dot(rvec, N));
    vec3 B = cross(N, T);
    mat3 TBN = mat3(T, B, N);

    float occ = 0.0;
    for (int i = 0; i < AO_SAMPLES; i++) {
        float e = (float(i) + 0.5) / float(AO_SAMPLES);
        float phi = float(i) * 2.399963;
        vec3 dir = vec3(cos(phi) * sqrt(e), sin(phi) * sqrt(e), sqrt(1.0 - e));

        float s = fract(float(i) * 0.618034 + 0.1);
        float scale = mix(0.1, 1.0, s * s);

        vec3 sp = P + TBN * dir * (AO_RADIUS * scale);
        if (sp.z >= -near_plane) continue;

        vec2 suv = (sp.xy / (-sp.z * vec2(aspect, 1.0) * tan_half_fov)) * 0.5 + 0.5;
        if (any(lessThan(suv, vec2(0.0))) || any(greaterThan(suv, vec2(1.0)))) continue;

        float sd = texture(depth_tex, suv).r;
        if (sd >= 0.99999) continue;

        float scene_dist  = view_dist(sd);
        float sample_dist = -sp.z;

        float hit = (scene_dist <= sample_dist - AO_BIAS) ? 1.0 : 0.0;
        float range = smoothstep(0.0, 1.0, AO_RADIUS / abs(view_dist(depth) - scene_dist));
        occ += hit * range;
    }
    return 1.0 - AO_STRENGTH * occ / float(AO_SAMPLES);
}

void main() {
	// pixelization
	vec2 texsize = textureSize(texture0, 0);
	vec2 pixel_c = texsize / (pixel_size * 2.0);
	vec2 pixel_uv = floor(fragTexCoord * pixel_c) / pixel_c;

	vec2 p = pixel_uv - vec2(0.5);
	float r = length(p);
	vec2 dist = p * (1.0 + 0.25 * r);
	pixel_uv = dist * 0.5 + vec2(0.5);

	float depth = texture(depth_tex, pixel_uv).r;
	vec3 normal = texture(normal_tex, pixel_uv).xyz * 2.0 - 1.0;
	vec4 color = texture(texture0, pixel_uv) * fragColor;

	// ssao
	if (depth < 1) {
		color.rgb *= ssao(pixel_uv, depth, texsize);
	}
	
	// dither
	{
		float lum = dot(color.rgb, vec3(0.299, 0.587, 0.114));
		int x = int(mod(gl_FragCoord.x, 4.0));
		int y = int(mod(gl_FragCoord.y, 4.0));
		float thres = bayerMatrix[x][y];
		color.rgb = (lum > thres) ? color.rgb * 2.0 : vec3(0.0);
	}

	// gamma + posterization
	color.rgb = pow(color.rgb, vec3(gamma.x));
	{
		float gray = max(color.r, max(color.g, color.b));
		float low = floor(gray * levels) / levels;
		float high = ceil(gray * levels) / levels;
		float level = abs(low - gray) < abs(high - gray) ? low : high;
		float adj = level / gray;
		color.rgb *= adj;
	}
	color.rgb = pow(color.rgb, vec3(gamma.y));

	// vignette
	float diff = length(fragTexCoord - vec2(0.5));
	color.rgb *= 1.0 - diff;
	color.rgb *= pow(1.0 - linearize_depth(depth), 4.0);

	finalColor = color;
}