vec3 GGX2(vec3 normal, vec3 viewDir, vec3 lightDir, float roughness, vec3 baseReflectance)
{
    vec3 halfVector = normalize(lightDir + viewDir);
    float NdotH = max(dot(normal, halfVector), 0.0),
          NdotL = max(dot(normal, lightDir), 0.0),
          NdotV = max(dot(normal, viewDir), 0.0),
          VdotH = max(dot(viewDir, halfVector), 0.0);

    vec3 fresnel = mix(baseReflectance, vec3(1.0), pow(1.0 - VdotH, 5.0));

    float adjustedRoughness = roughness + 1e-2,
          alpha = adjustedRoughness * adjustedRoughness,
          NdotH2 = NdotH * NdotH,
          distributionDenom = NdotH2 * (alpha - 1.0) + 1.0,
          distributionTerm = alpha / (PI * distributionDenom * distributionDenom),
          k = (adjustedRoughness + 1.0) * (adjustedRoughness + 1.0) * 0.125,
          geomVisibilityV = NdotV / (NdotV * (1.0 - k) + k),
          geomVisibilityL = NdotL / (NdotL * (1.0 - k) + k),
          denominator = max(4.0 * NdotV * NdotL, 1e-7);

    return max(distributionTerm * geomVisibilityV * geomVisibilityL * fresnel * (NdotL / denominator), 0.0);
}
