#version 300 es
precision highp float;
out vec4 outColor;

in vec2 tc; // texture coordinate of pixel (interpolated)

uniform sampler2D normalTexture; // description="G-buffer surface normal texture"
uniform sampler2D positionTexture; // description="G-buffer position texture"
uniform sampler2D baseColorTexture; // description="G-buffer base color texture"
uniform sampler2D materialTexture; // description="G-buffer material texture"
uniform sampler2D emissionTexture; // description="G-buffer emission texture"
uniform vec4 lightColor; // description="color of light" defaultval="1.0, 1.0, 1.0, 1.0"
uniform float irradiPerp; // description="irradiance in perpendicular direction" defaultval="10.0"
uniform vec3 lightDirection; // light direction in world space
uniform vec3 cameraPosition; // camera position in world space

vec3 rgb2lin(vec3 rgb) { // sRGB to linear approximation
  return pow(rgb, vec3(2.2));
}

vec3 lin2rgb(vec3 lin) { // linear to sRGB approximation
  return pow(lin, vec3(1.0 / 2.2));
}

#define RECIPROCAL_PI 0.3183098861837907
#define RECIPROCAL_2PI 0.15915494309189535

vec3 modifiedPhongBRDF(vec3 lightDir, vec3 viewDir, vec3 normal, 
                vec3 phongDiffuseCol, vec3 phongSpecularCol, float phongShininess) {
  vec3 color = phongDiffuseCol * RECIPROCAL_PI;
  vec3 reflectDir = reflect(-lightDir, normal);
  float specDot = max(dot(reflectDir, viewDir), 0.001);
  float normalization = (phongShininess + 2.0) * RECIPROCAL_2PI; 
  color += pow(specDot, phongShininess) * normalization * phongSpecularCol;
  return color;
}

float roughnessToShininess( const in float roughness ) {
	return pow(1000.0, 1.0-roughness);
}

// from http://www.thetenthplanet.de/archives/1180
mat3 cotangentFrame(in vec3 N, in vec3 p, in vec2 uv)
{
    // get edge vectors of the pixel triangle
    vec3 dp1 = dFdx( p );
    vec3 dp2 = dFdy( p );
    vec2 duv1 = dFdx( uv );
    vec2 duv2 = dFdy( uv );
 
    // solve the linear system
    vec3 dp2perp = cross( dp2, N );
    vec3 dp1perp = cross( N, dp1 );
    vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
    vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;
 
    // construct a scale-invariant frame 
    float invmax = inversesqrt( max( dot(T,T), dot(B,B) ) );
    return mat3( T * invmax, B * invmax, N );
}

vec3 applyNormalMap(in vec3 normal, in vec3 viewVec, in vec2 texcoord)
{
    vec3 highResNormal = texture(normalTexture, texcoord).xyz;
    highResNormal = normalize(highResNormal * 2.0 - 1.0);
    mat3 TBN = cotangentFrame(normal, -viewVec, texcoord);
    return normalize(TBN * highResNormal);
}


vec3 fresnelSchlick(float cosTheta, vec3 F0) {
  return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}

float D_GGX(float NoH, float roughness) {
  float alpha = roughness * roughness;
  float alpha2 = alpha * alpha;
  float NoH2 = NoH * NoH;
  float b = (NoH2 * (alpha2 - 1.0) + 1.0);
  return alpha2 * RECIPROCAL_PI / (b * b);
}

float G1_GGX_Schlick(float NoV, float roughness) {
  float alpha = roughness * roughness;
  float k = alpha / 2.0;
  return max(NoV, 0.001) / (NoV * (1.0 - k) + k);
}

float G_Smith(float NoV, float NoL, float roughness) {
  return G1_GGX_Schlick(NoL, roughness) * G1_GGX_Schlick(NoV, roughness);
}

float fresnelSchlick90(float cosTheta, float F0, float F90) {
  return F0 + (F90 - F0) * pow(1.0 - cosTheta, 5.0);
} 

float disneyDiffuseFactor(float NoV, float NoL, float VoH, float roughness) {
  float alpha = roughness * roughness;
  float F90 = 0.5 + 2.0 * alpha * VoH * VoH;
  float F_in = fresnelSchlick90(NoL, 1.0, F90);
  float F_out = fresnelSchlick90(NoV, 1.0, F90);
  return F_in * F_out;
}

vec3 brdfMicrofacet(in vec3 L, in vec3 V, in vec3 N,
                        in float metallic, in float roughness, in vec3 baseColor, in float reflectance) {

  vec3 H = normalize(V + L);
  
  float NoV = clamp(dot(N, V), 0.0, 1.0);
  float NoL = clamp(dot(N, L), 0.0, 1.0);
  float NoH = clamp(dot(N, H), 0.0, 1.0);
  float VoH = clamp(dot(V, H), 0.0, 1.0);
  
  vec3 f0 = vec3(0.16 * (reflectance * reflectance));
  f0 = mix(f0, baseColor, metallic);
  
  vec3 F = fresnelSchlick(VoH, f0);
  float D = D_GGX(NoH, roughness);
  float G = G_Smith(NoV, NoL, roughness);
  
  vec3 spec = (F * D * G) / (4.0 * max(NoV, 0.001) * max(NoL, 0.001));
  
  vec3 rhoD = baseColor;
  
  // optionally
  rhoD *= vec3(1.0) - F;
  // rhoD *= disneyDiffuseFactor(NoV, NoL, VoH, roughness);
  
  rhoD *= (1.0 - metallic);
  
  vec3 diff = rhoD * RECIPROCAL_PI;
  
  return diff + spec;
  
  
}
  

void main() {
  vec3 n = texture(normalTexture, tc).rgb;
  vec3 vertPos = texture(positionTexture, tc).rgb;
  vec3 baseCol = rgb2lin(texture(baseColorTexture, tc).rgb);
  vec3 mat = texture(materialTexture, tc).rgb;
  float metallic = mat.r;
  float roughness = mat.g;
  float reflectance = mat.b;
  vec4 emission = texture(emissionTexture, tc);
  vec3 lightDir = normalize(-lightDirection); // towards light
  vec3 viewDir = normalize(cameraPosition - vertPos);

  vec3 radiance = rgb2lin(emission.rgb);
  
  float irradiance = max(dot(lightDir, n), 0.0) * irradiPerp; 
  if(irradiance > 0.0) { // if receives light
    vec3 brdf = brdfMicrofacet(lightDir, viewDir, n, metallic, roughness, baseCol, reflectance);
    // irradiance contribution from directional light
    radiance += brdf * irradiance * lightColor.rgb;
  }

  outColor.rgb = lin2rgb(radiance);
  outColor.a = 1.0;
}