let PI: f32 = 3.141592;

fn D_GGX(NoH: f32, roughness: f32) -> f32 {
  let a = roughness * roughness;
  let a2 = a * a;
  let NoH2 = NoH * NoH;
  let f = NoH * (a2 - 1.0) + 1.0;
  return a2 / (PI * f * f);
}

fn g1(NoV: f32, roughness: f32, k: f32) -> f32 {
  let denom = NoV * (1.0 - k) + k;
  return NoV / denom;
}

fn G_SmithGGXCorrelated(NoV: f32, NoL: f32, roughness: f32) -> f32 {
  let r = roughness + 1.0;
  let k = (r * r) / 8.0;

  let g1l = g1(NoV, roughness, k);
  let g1v = g1(NoL, roughness, k);

  return g1l * g1v;
}

fn F_Schlick(u: f32, f0: vec3<f32>) -> vec3<f32> {
  let f = pow(1.0 - u, 5.0);
  return f + f0 * (1.0 - f);
}

fn BRDF(
  l: vec3<f32>,      // normalized light direction
  n: vec3<f32>,      // normalized surface normal
  v: vec3<f32>,      // normalized view direction
  albedo: vec3<f32>, // surface albedo
  metallic: f32,     // surface metallic
  roughness: f32,    // surface roughness
) -> vec3<f32> {
  let h = normalize(v + l);
  let NoL = max(dot(n, l), 0.0);
  let NoV = max(dot(n, v), 0.0);
  let NoH = max(dot(n, h), 0.0);
  let LoH = max(dot(l, h), 0.0);

  // calculate reflectance at surface incidence
  let f0 = mix(vec3<f32>(0.04), albedo, metallic);

  // specular BRDF
  let D = D_GGX(NoH, roughness);
  let G = G_SmithGGXCorrelated(NoV, NoL, roughness);
  let F = F_Schlick(LoH, f0);

  let numerator = (D * G) * F;
  let denominator = 4.0 * NoV * NoL;

  let Fr = numerator / max(denominator, 0.01);

  // diffuse BRDF
  let diffuse_fresnel =
    (vec3<f32>(1.0) - F_Schlick(NoL, f0)) *
    (vec3<f32>(1.0) - F_Schlick(NoV, f0));

  let lambertian = albedo / PI;
  let Fd = diffuse_fresnel * lambertian;

  // TODO multiple scattering
  return (Fr + Fd) * NoL;
}