#version 330 core

uniform sampler2D dfg_tex;
uniform samplerCube prefiltered_envmap_tex;

const float MAX_PREFILTERED_LOD = 4.0;

// math.glsl
float pow5(float x);

vec3 get_reflected(vec3 N, vec3 V, mat4 view_matrix_inverse)
{
    return (view_matrix_inverse * vec4(reflect(-V, N), 0.0)).xyz;
}

/*
 * Fresnel term with included roughness to get a pleasant visual result.
 * See https://seblagarde.wordpress.com/2011/08/17/hello-world/
 */
vec3 F_Schlick_roughness(float NdotV, vec3 F0, float r)
{
    return F0 + (max(vec3(1.0 - r), F0) - F0) * pow5(max(1.0 - NdotV, 0.0));
}

/*
 * Evaluate the indirect diffuse irradiance.
 *
 * To get better results we should be precomputing the irradiance into a cubemap
 * or calculating spherical harmonics coefficients on the CPU.
 * Sampling the roughness=1 mipmap level of the prefiltered specular map works
 * fine too. :)
 */
vec3 ibl_eval_diffuse(vec3 N)
{
    int max_lod = int(MAX_PREFILTERED_LOD);
    ivec2 s = textureSize(prefiltered_envmap_tex, max_lod);
    float du = 1.0 / float(s.x);
    float dv = 1.0 / float(s.y);
    vec3 m0 = normalize(cross(N, vec3(0.0, 1.0, 0.0)));
    vec3 m1 = cross(m0, N);
    vec3 m0du = m0 * du;
    vec3 m1dv = m1 * dv;
    vec3 c;
    c  = textureLod(prefiltered_envmap_tex, N - m0du - m1dv, max_lod).rgb;
    c += textureLod(prefiltered_envmap_tex, N + m0du - m1dv, max_lod).rgb;
    c += textureLod(prefiltered_envmap_tex, N + m0du + m1dv, max_lod).rgb;
    c += textureLod(prefiltered_envmap_tex, N - m0du + m1dv, max_lod).rgb;
    return 0.25 * c;
}

/*
 * Evaluate the indirect specular.
 * Sample from the prefiltered environment map.
 */
vec3 ibl_eval_specular(float NdotV, vec3 refl, float roughness, vec3 f)
{
    float lod = roughness * float(MAX_PREFILTERED_LOD);
    vec3 prefiltered = textureLod(prefiltered_envmap_tex, refl, lod).rgb;
    vec2 env_brdf = texture(dfg_tex, vec2(NdotV, roughness)).rg;
    return prefiltered * (f * env_brdf.x + env_brdf.y);
}

/*
 * Evaluate the contribution of image-based lights, or indirect lighting.
 */
vec3 eval_ibl(vec3 base_color, float metallic, float roughness, vec3 f0,
              float occlusion, vec3 ws_N, vec3 ws_refl, float NdotV)
{
    roughness = max(roughness, 0.002025);
    vec3 F = F_Schlick_roughness(NdotV, f0, roughness);
    vec3 specular = ibl_eval_specular(NdotV, ws_refl, roughness, F);
    vec3 diffuse = ibl_eval_diffuse(ws_N) * base_color
        * (vec3(1.0) - F) * (1.0 - metallic);
    return (diffuse + specular) * occlusion;
}