#ifndef _BRDF_GLSL_ #define _BRDF_GLSL_ #ifndef PI #define PI 3.1415926535 #endif #ifndef INV_PI #define INV_PI 0.3183098861 #endif #ifndef INV_TWO_PI #define INV_TWO_PI 0.1591549430 #endif #ifndef SCHLICK_A #define SCHLICK_A -5.55473 #endif #ifndef SCHLICK_B #define SCHLICK_B -6.98316 #endif #ifndef SRGB_GAMMA #define SRGB_GAMMA 2.2 #endif #define srgbToLinear(x) pow(x, vec3(SRGB_GAMMA)) // http://xlgames-inc.github.io/posts/improvedibl/ // http://blog.selfshadow.com/publications/s2013-shading-course/ vec3 f_schlick(const vec3 f0, const float vh) { return f0 + (1.0 - f0) * exp2((SCHLICK_A * vh + SCHLICK_B) * vh); } float v_smithschlick(const float nl, const float nv, const float a) { return 1.0 / ((nl * (1.0 - a) + a) * (nv * (1.0 - a) + a)); } //Uncorrelated masking/shadowing (info below) function //Because it is uncorrelated, G1(NdotL, a) gives us shadowing, and G1(NdotV, a) gives us masking function. //Approximation from: https://ubm-twvideo01.s3.amazonaws.com/o1/vault/gdc2017/Presentations/Hammon_Earl_PBR_Diffuse_Lighting.pdf float g1_approx(const float NdotX, const float alpha) { return (2.0 * NdotX) * (1.0 / (NdotX * (2.0 - alpha) + alpha)); } //Uncorrelated masking-shadowing function //Approximation from: https://ubm-twvideo01.s3.amazonaws.com/o1/vault/gdc2017/Presentations/Hammon_Earl_PBR_Diffuse_Lighting.pdf float g2_approx(const float NdotL, const float NdotV, const float alpha) { vec2 helper = (2.0 * vec2(NdotL, NdotV)) * (1.0 / (vec2(NdotL, NdotV) * (2.0 - alpha) + alpha)); return max(helper.x * helper.y, 0.0); //This can go negative, let's fix that } float d_ggx(const float nh, const float a) { float a2 = a * a; float denom = nh * nh * (a2 - 1.0) + 1.0; denom = max(denom * denom, 0.00006103515625 /* 2^-14 = smallest possible half float value, prevent div by zero */); return a2 * INV_PI / denom; } vec3 specularBRDF(const vec3 f0, const float roughness, const float nl, const float nh, const float nv, const float vh) { float a = roughness * roughness; vec3 result = d_ggx(nh, a) * g2_approx(nl, nv, a) * f_schlick(f0, vh) / max(4.0 * nv, 1e-5); //NdotL cancels out later return result; } // John Hable - Optimizing GGX Shaders // http://filmicworlds.com/blog/optimizing-ggx-shaders-with-dotlh/ vec3 specularBRDFb(const vec3 f0, const float roughness, const float dotNL, const float dotNH, const float dotLH) { // D float alpha = roughness * roughness; float alphaSqr = alpha * alpha; float denom = dotNH * dotNH * (alphaSqr - 1.0) + 1.0; float D = alphaSqr / (PI * denom * denom); // F const float F_a = 1.0; float F_b = pow(1.0 - dotLH, 5.0); // V float vis; float k = alpha / 2.0; float k2 = k * k; float invK2 = 1.0 - k2; vis = 1.0 / (dotLH * dotLH * invK2 + k2); vec2 FV_helper = vec2((F_a - F_b) * vis, F_b * vis); vec3 FV = f0 * FV_helper.x + FV_helper.y; vec3 specular = clamp(dotNL, 0.0, 1.0) * D * FV; return specular / 4.0; // TODO: get rid of / 4.0 } vec3 lambertDiffuseBRDF(const vec3 albedo, const float nl) { return albedo * INV_PI * nl; } #ifdef _BurleyDiffuse vec3 burleyDiffuseBRDF(const vec3 albedo, const float roughness, const float dotNL, const float dotNV, const float dotVH) { float nl = clamp(dotNL, 0.0, 1.0); float nv = clamp(dotNV, 0.0, 1.0); float energyBias = mix(0.0, 0.5, roughness); float energyFactor = mix(1.0, 1.0 / 1.51, roughness); float fd90 = energyBias + 2.0 * roughness * dotVH * dotVH; float lightScatter = 1.0 + (fd90 - 1.0) * pow(1.0 - nl, 5.0); float viewScatter = 1.0 + (fd90 - 1.0) * pow(1.0 - nv, 5.0); return albedo * INV_PI * lightScatter * viewScatter * energyFactor * nl; } #endif #ifdef _EONDiffuse const float constant1_FON = 0.5 - 2.0 / (3.0 * PI); const float constant2_FON = 2.0 / 3.0 - 28.0 / (15.0 * PI); float E_FON_approx(float mu, float r) { float mucomp = 1.0 - mu; const float g1 = 0.0571085289; const float g2 = 0.491881867; const float g3 = -0.332181442; const float g4 = 0.0714429953; float GoverPi = mucomp * (g1 + mucomp * (g2 + mucomp * (g3 + mucomp * g4))); return (1.0 + r * GoverPi) / (1.0 + constant1_FON * r); } vec3 eonDiffuseBRDF(const vec3 albedo, const float roughness, const float dotNL, const float dotNV, const float dotVH) { float r = roughness; float mu_i = clamp(dotNL, 0.0, 1.0); float mu_o = clamp(dotNV, 0.0, 1.0); if (mu_i < 1.0e-7 || mu_o < 1.0e-7) return vec3(0.0); float dotLV = 2.0 * dotVH * dotVH - 1.0; float s = dotLV - mu_i * mu_o; float sovertF = s > 0.0 ? s / max(mu_i, mu_o) : s; float AF = 1.0 / (1.0 + constant1_FON * r); vec3 f_ss = (albedo * INV_PI) * AF * (1.0 + r * sovertF); float EFo = E_FON_approx(mu_o, r); float EFi = E_FON_approx(mu_i, r); float avgEF = AF * (1.0 + constant2_FON * r); vec3 rho_ms = (albedo * albedo) * avgEF / max(vec3(1.0) - albedo * (1.0 - avgEF), vec3(1.0e-7)); const float eps = 1.0e-7; vec3 f_ms = (rho_ms * INV_PI) * max(eps, 1.0 - EFo) * max(eps, 1.0 - EFi) / max(eps, 1.0 - avgEF); return (f_ss + f_ms) * mu_i; } #endif #ifdef _GotandaDiffuse vec3 gotandaDiffuseBRDF(const vec3 albedo, const float roughness, const vec3 f0, const float dotNL, const float dotNV, const float dotVH) { float nl = clamp(dotNL, 0.0, 1.0); float nv = clamp(dotNV, 0.0, 1.0); float a = roughness * roughness; float a2 = a * a; float dotLV = 2.0 * dotVH * dotVH - 1.0; float Cosri = dotLV - nv * nl; float a2_13 = a2 + 1.36053; float Fr = (1.0 - (0.542026 * a2 + 0.303573 * a) / a2_13) * (1.0 - pow(1.0 - nv, 5.0 - 4.0 * a2) / a2_13) * ((-0.733996 * a2 * a + 1.50912 * a2 - 1.16402 * a) * pow(1.0 - nv, 1.0 + 1.0 / (39.0 * a2 * a2 + 1.0)) + 1.0); float Lm = (max(1.0 - 2.0 * a, 0.0) * (1.0 - pow(1.0 - nl, 5.0)) + min(2.0 * a, 1.0)) * (1.0 - 0.5 * a * (nl - 1.0)) * nl; float Vd = (a2 / ((a2 + 0.09) * (1.31072 + 0.995584 * nv))) * (1.0 - pow(1.0 - nl, (1.0 - 0.3726732 * nv * nv) / (0.188566 + 0.38841 * nv))); float Bp = Cosri < 0.0 ? 1.4 * nv * nl * Cosri : Cosri; vec3 Lr = (21.0 / 20.0) * (1.0 - f0) * (Fr * Lm + Vd + Bp); return max(albedo * INV_PI * Lr, vec3(0.0)); } #endif #ifdef _ChanDiffuse vec3 chanDiffuseBRDF(const vec3 albedo, const float roughness, const float dotNL, const float dotNV, const float dotVH) { float nl = clamp(dotNL, 0.0, 1.0); float nv = clamp(dotNV, 0.0, 1.0); float vh = clamp(dotVH, 0.0, 1.0); float a = roughness * roughness; float a2 = a * a; float g = clamp((1.0 / 18.0) * log2(2.0 / max(a2, 1e-7) - 1.0), 0.0, 1.0); float dotNH = clamp((nl + nv) / max(2.0 * vh, 1e-5), 0.0, 1.0); float F0 = vh + pow(1.0 - vh, 5.0); float FdV = 1.0 - 0.75 * pow(1.0 - nv, 5.0); float FdL = 1.0 - 0.75 * pow(1.0 - nl, 5.0); float Fd = mix(F0, FdV * FdL, clamp(2.2 * g - 0.5, 0.0, 1.0)); float Fb = ((34.5 * g - 59.0) * g + 24.5) * vh * exp2(-max(73.2 * g - 21.2, 8.9) * sqrt(dotNH)); float Lobe = clamp(Fd + Fb, 0.0, 1.0); return albedo * INV_PI * Lobe * nl; } #endif vec3 diffuseBRDF(const vec3 albedo, const float roughness, const vec3 f0, const float dotNL, const float dotNV, const float dotVH) { #ifdef _BurleyDiffuse return burleyDiffuseBRDF(albedo, roughness, dotNL, dotNV, dotVH); #elif defined(_EONDiffuse) return eonDiffuseBRDF(albedo, roughness, dotNL, dotNV, dotVH); #elif defined(_GotandaDiffuse) return gotandaDiffuseBRDF(albedo, roughness, f0, dotNL, dotNV, dotVH); #elif defined(_ChanDiffuse) return chanDiffuseBRDF(albedo, roughness, dotNL, dotNV, dotVH); #else return lambertDiffuseBRDF(albedo, dotNL); #endif } vec3 lambertDiffuseIBL(const vec3 albedo) { return albedo; } #ifdef _BurleyDiffuse vec3 burleyDiffuseIBL(const vec3 albedo, const float roughness, const float dotNV) { float nv = clamp(dotNV, 0.0, 1.0); float energyBias = mix(0.0, 0.5, roughness); float energyFactor = mix(1.0, 1.0 / 1.51, roughness); float fd90 = energyBias + 2.0 * roughness * nv * nv; float viewScatter = 1.0 + (fd90 - 1.0) * pow(1.0 - nv, 5.0); return albedo * viewScatter * energyFactor; } #endif #ifdef _EONDiffuse vec3 eonDiffuseIBL(const vec3 albedo, const float roughness, const float dotNV) { float r = roughness; float AF = 1.0 / (1.0 + constant1_FON * r); float EF = E_FON_approx(clamp(dotNV, 0.0, 1.0), r); float avgEF = AF * (1.0 + constant2_FON * r); vec3 rho_ms = (albedo * albedo) * avgEF / max(vec3(1.0) - albedo * (1.0 - avgEF), vec3(1.0e-7)); return max(albedo * EF + rho_ms * (1.0 - EF), vec3(0.0)); } #endif #ifdef _GotandaDiffuse vec3 gotandaDiffuseIBL(const vec3 albedo, const float roughness, const vec3 f0, const float dotNV) { float nv = clamp(dotNV, 0.0, 1.0); float a = roughness * roughness; float a2 = a * a; float a2_13 = a2 + 1.36053; float Fr = (1.0 - (0.542026 * a2 + 0.303573 * a) / a2_13) * (1.0 - pow(1.0 - nv, 5.0 - 4.0 * a2) / a2_13) * ((-0.733996 * a2 * a + 1.50912 * a2 - 1.16402 * a) * pow(1.0 - nv, 1.0 + 1.0 / (39.0 * a2 * a2 + 1.0)) + 1.0); float Lm = (max(1.0 - 2.0 * a, 0.0) * (1.0 - pow(1.0 - nv, 5.0)) + min(2.0 * a, 1.0)) * (1.0 - 0.5 * a * (nv - 1.0)) * nv; float Vd = (a2 / ((a2 + 0.09) * (1.31072 + 0.995584 * nv))) * (1.0 - pow(1.0 - nv, (1.0 - 0.3726732 * nv * nv) / (0.188566 + 0.38841 * nv))); float Cosri = 1.0 - nv * nv; float Bp = Cosri; vec3 Lr = (21.0 / 20.0) * (1.0 - f0) * (Fr * Lm + Vd + Bp); return max(albedo * Lr, vec3(0.0)); } #endif #ifdef _ChanDiffuse vec3 chanDiffuseIBL(const vec3 albedo, const float roughness, const float dotNV) { float nv = clamp(dotNV, 0.0, 1.0); float a = roughness * roughness; float a2 = a * a; float g = clamp((1.0 / 18.0) * log2(2.0 / max(a2, 1e-7) - 1.0), 0.0, 1.0); float FdV = 1.0 - 0.75 * pow(1.0 - nv, 5.0); float Fd = mix(1.0, FdV * FdV, clamp(2.2 * g - 0.5, 0.0, 1.0)); float Fb = ((34.5 * g - 59.0) * g + 24.5) * exp2(-max(73.2 * g - 21.2, 8.9) * sqrt(nv)); return albedo * clamp(Fd + Fb, 0.0, 1.0); } #endif vec3 diffuseIBL(const vec3 albedo, const float roughness, const vec3 f0, const float dotNV) { #ifdef _BurleyDiffuse return burleyDiffuseIBL(albedo, roughness, dotNV); #elif defined(_EONDiffuse) return eonDiffuseIBL(albedo, roughness, dotNV); #elif defined(_GotandaDiffuse) return gotandaDiffuseIBL(albedo, roughness, f0, dotNV); #elif defined(_ChanDiffuse) return chanDiffuseIBL(albedo, roughness, dotNV); #else return lambertDiffuseIBL(albedo); #endif } vec3 surfaceAlbedo(const vec3 baseColor, const float metalness) { return mix(baseColor, vec3(0.0), metalness); } vec3 surfaceF0(const vec3 baseColor, const float metalness) { return mix(vec3(0.04), baseColor, metalness); } float getMipFromRoughness(const float roughness, const float numMipmaps) { // First mipmap level = roughness 0, last = roughness = 1 return roughness * numMipmaps; } // https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile // vec3 EnvBRDFApprox(vec3 SpecularColor, float Roughness, float NoV) { // const vec4 c0 = { -1, -0.0275, -0.572, 0.022 }; // const vec4 c1 = { 1, 0.0425, 1.04, -0.04 }; // vec4 r = Roughness * c0 + c1; // float a004 = min( r.x * r.x, exp2( -9.28 * NoV ) ) * r.x + r.y; // vec2 AB = vec2( -1.04, 1.04 ) * a004 + r.zw; // return SpecularColor * AB.x + AB.y; // } // float EnvBRDFApproxNonmetal(float Roughness, float NoV) { // // Same as EnvBRDFApprox( 0.04, Roughness, NoV ) // const vec2 c0 = { -1, -0.0275 }; // const vec2 c1 = { 1, 0.0425 }; // vec2 r = Roughness * c0 + c1; // return min( r.x * r.x, exp2( -9.28 * NoV ) ) * r.x + r.y; // } float D_Approx(const float Roughness, const float RoL) { float a = Roughness * Roughness; float a2 = a * a; float rcp_a2 = 1.0 / a2;//rcp(a2); // 0.5 / ln(2), 0.275 / ln(2) float c = 0.72134752 * rcp_a2 + 0.39674113; return rcp_a2 * exp2( c * RoL - c ); } #ifdef _ClearCoat float brdf_coatF0; vec3 clearcoatBRDF(const float clearcoat, const float clearcoat_rough, const float coat_ior, const vec3 coatN, const vec3 l, const vec3 v, const vec3 h) { if (clearcoat <= 0.0) return vec3(0.0); float cdotNL = max(0.0, dot(coatN, l)); float cdotNH = max(0.0, dot(coatN, h)); float cdotNV = max(0.0, dot(coatN, v)); float cdotVH = max(0.0, dot(v, h)); float a = clearcoat_rough * clearcoat_rough; float F = brdf_coatF0 + (1.0 - brdf_coatF0) * exp2((SCHLICK_A * cdotVH + SCHLICK_B) * cdotVH); float D = d_ggx(cdotNH, a); float G = g2_approx(cdotNL, cdotNV, a); return vec3(clearcoat * D * G * F / max(4.0 * cdotNV, 1e-5)); } float coatAttenuation(const float clearcoat, const float coat_ior, const vec3 coatN, const vec3 v) { if (clearcoat <= 0.0) return 1.0; float cdotNV = max(0.0, dot(coatN, v)); float F = brdf_coatF0 + (1.0 - brdf_coatF0) * exp2((SCHLICK_A * cdotNV + SCHLICK_B) * cdotNV); return max(1.0 - F * clearcoat, 0.0); } vec3 coatTintAttenuation(const float clearcoat, const vec3 coat_tint, const vec3 coatN, const vec3 v) { if (clearcoat <= 0.0) return vec3(1.0); float cdotNV = max(0.0, dot(coatN, v)); float absorption = 1.0 / max(cdotNV, 0.3); return mix(vec3(1.0), clamp(coat_tint, 0.0, 1.0), clamp(absorption * 0.2, 0.0, 1.0)); } #endif #ifdef _Sheen float brdf_sheenAlbedo; // based on Blender sheen model/Frostbite PBR vec3 sheenBRDF(const float sheen, const float sheen_rough, const vec3 sheen_tint, const float dotNL, const float dotNH, const float dotNV) { if (sheen <= 0.0) return vec3(0.0); float rough = clamp(sheen_rough, 1e-3, 1.0); float a = rough * rough; float sinNH2 = 1.0 - dotNH * dotNH; float a2 = a * a; float denom = 1.0 + a2 * sinNH2; float D = (2.0 + a2) * sinNH2 * INV_TWO_PI / (denom * denom); float V = 1.0 / (4.0 * dotNL * dotNV + 1e-5); return sheen_tint * sheen * D * V * dotNL * brdf_sheenAlbedo; } float sheenAttenuation(const float sheen, const float sheen_rough, const vec3 sheen_tint, const float dotNV) { if (sheen <= 0.0) return 1.0; float maxComp = sheen * max(max(sheen_tint.r, sheen_tint.g), sheen_tint.b) * brdf_sheenAlbedo; return max(1.0 - maxComp, 0.0); } #endif #ifdef _Anisotropy // anisotropic GGX Burley 2012 vec3 anisotropicBRDF(const vec3 f0, const float roughness, const float anisotropy, const float aniso_rot, const vec3 tangent, const vec3 bitangent, const vec3 n, const vec3 l, const vec3 v, const float dotNL, const float dotNV) { if (abs(anisotropy) <= 0.001) return vec3(0.0); float rot = aniso_rot * PI * 2.0; float cr = cos(rot); float sr = sin(rot); vec3 t = normalize(tangent * cr + bitangent * sr); vec3 b = normalize(bitangent * cr - tangent * sr); float aniso_abs = abs(anisotropy); float at = max(roughness * (1.0 + aniso_abs), 1e-5); float ab = max(roughness * (1.0 - aniso_abs), 1e-5); if (anisotropy < 0.0) { vec3 tmp = t; t = b; b = tmp; } float at2 = at * at; float ab2 = ab * ab; vec3 h = normalize(l + v); float dotTH = dot(t, h); float dotBH = dot(b, h); float dotTV = dot(t, v); float dotBV = dot(b, v); float dotTL = dot(t, l); float dotBL = dot(b, l); float denom = max(dotTH * dotTH / at2 + dotBH * dotBH / ab2, 1e-7); float D = INV_PI / (at * ab * denom * denom); float V = 1.0 / max(dotNL * (dotTL / at + dotBL / ab) * (dotTV / at + dotBV / ab), 1e-5); float dotVH = max(dot(v, h), 0.0); vec3 F = f_schlick(f0, dotVH); return D * V * F / max(4.0 * dotNV, 1e-5); } #endif #ifdef _Transmission float brdf_transmissionF0; // Blenders microfacet glass/refraction model vec3 transmissionBRDF(const vec3 albedo, const float transmission, const float trans_rough, const float ior, const float thin_wall, const float dotNL, const float dotNV, const float dotVH) { if (transmission <= 0.0) return vec3(0.0); float F = brdf_transmissionF0 + (1.0 - brdf_transmissionF0) * exp2((SCHLICK_A * dotVH + SCHLICK_B) * dotVH); float transmittance = 1.0 - F; if (thin_wall > 0.5) { return albedo * transmission * transmittance * dotNL; } float a = trans_rough * trans_rough; float rough_atten = min(mix(1.0, 1.0 / max(dotNV, 0.1), a), 4.0); return albedo * transmission * transmittance * rough_atten * dotNL; } #endif #ifdef _ExtBRDF float brdf_sheenWeight = 1.0; float brdf_coatWeight = 1.0; vec3 brdf_coatTintAbsorb = vec3(1.0); vec3 applyExtBRDFLayers( const vec3 direct, const vec3 albedo, const vec3 f0, const float roughness, const float dotNL, const float dotNV, const float dotNH, const float dotVH, const vec3 n, const vec3 l, const vec3 v, const vec3 h, #ifdef _ClearCoat const float clearcoat, const float clearcoatRough, const float coatIOR, const vec3 coatTint, const vec3 coatN, #endif #ifdef _Sheen const float sheen, const float sheenRough, const vec3 sheenTint, #endif #ifdef _Transmission const float transmission, const float transRough, const float ior, const float thinWall, #endif out float layerWeight ) { float sheenWeight = brdf_sheenWeight; float coatWeight = brdf_coatWeight; #ifdef _Sheen vec3 sheenContrib = sheenBRDF(sheen, sheenRough, sheenTint, dotNL, dotNH, dotNV); #endif #ifdef _ClearCoat vec3 coatContrib = clearcoatBRDF(clearcoat, clearcoatRough, coatIOR, coatN, l, v, h); #endif layerWeight = sheenWeight * coatWeight; vec3 result = direct * layerWeight; #ifdef _Transmission result += transmissionBRDF(albedo, transmission, transRough, ior, thinWall, dotNL, dotNV, dotVH) * layerWeight; #endif #ifdef _ClearCoat result *= brdf_coatTintAbsorb; result += coatContrib * sheenWeight; #endif #ifdef _Sheen result += sheenContrib; #endif return result; } #endif #ifdef _ClearCoat float coatIBLFresnel(const float clearcoat, const float coat_ior, const float dotNV_coat) { if (clearcoat <= 0.0) return 0.0; float F = brdf_coatF0 + (1.0 - brdf_coatF0) * exp2((SCHLICK_A * dotNV_coat + SCHLICK_B) * dotNV_coat); return F * clearcoat; } #endif #ifdef _Sheen float sheenIBLAlbedo(const float sheen, const float sheen_rough, const float dotNV) { if (sheen <= 0.0) return 0.0; float rough = clamp(sheen_rough, 1e-3, 1.0); return sheen * (1.0 - 0.5 * rough) * mix(1.0, dotNV, 0.5); } #endif #ifdef _Anisotropy vec3 anisotropicIBLDirection(const vec3 n, const vec3 v, const vec3 tangent, const float anisotropy, const float roughness) { if (abs(anisotropy) <= 0.001 || dot(tangent, tangent) < 0.001) return reflect(-v, n); vec3 bitangent = normalize(cross(n, tangent)); vec3 r = reflect(-v, n); float aniso_abs = abs(anisotropy); vec3 stretchDir = anisotropy > 0.0 ? tangent : bitangent; float stretchAmt = aniso_abs * roughness; return normalize(r + stretchDir * stretchAmt * dot(r, stretchDir) * 0.5); } #endif #ifdef _Transmission float transmissionIBLFresnel(const float ior, const float dotNV) { return brdf_transmissionF0 + (1.0 - brdf_transmissionF0) * exp2((SCHLICK_A * dotNV + SCHLICK_B) * dotNV); } vec3 transmissionIBLDirection(const vec3 n, const vec3 v, const float ior) { float eta = 1.0 / ior; vec3 refrDir = refract(-v, n, eta); if (dot(refrDir, refrDir) < 0.001) { refrDir = reflect(-v, n); } return refrDir; } #endif #endif