diff --git a/libraries/bxdf/mx39_open_pbr_surface.mtlx b/libraries/bxdf/mx39_open_pbr_surface.mtlx new file mode 100644 index 0000000000..42df378691 --- /dev/null +++ b/libraries/bxdf/mx39_open_pbr_surface.mtlx @@ -0,0 +1,679 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libraries/pbrlib/genglsl/lib/mx39_microfacet.glsl b/libraries/pbrlib/genglsl/lib/mx39_microfacet.glsl new file mode 100644 index 0000000000..befa82b413 --- /dev/null +++ b/libraries/pbrlib/genglsl/lib/mx39_microfacet.glsl @@ -0,0 +1,30 @@ +#include "libraries/pbrlib/genglsl/lib/mx_microfacet.glsl" + +float mx39_pow6(float x) +{ + float x2 = mx_square(x); + return mx_square(x2) * x2; +} + +// Generate a cosine-weighted sample on the unit hemisphere. +vec3 mx39_cosine_sample_hemisphere(vec2 Xi) +{ + float phi = 2.0 * M_PI * Xi.x; + float cosTheta = sqrt(Xi.y); + float sinTheta = sqrt(1.0 - Xi.y); + return vec3(cos(phi) * sinTheta, + sin(phi) * sinTheta, + cosTheta); +} + +// Construct an orthonormal basis from a unit vector. +// https://graphics.pixar.com/library/OrthonormalB/paper.pdf +mat3 mx39_orthonormal_basis(vec3 N) +{ + float sign = (N.z < 0.0) ? -1.0 : 1.0; + float a = -1.0 / (sign + N.z); + float b = N.x * N.y * a; + vec3 X = vec3(1.0 + sign * N.x * N.x * a, sign * b, -sign * N.x); + vec3 Y = vec3(b, sign + N.y * N.y * a, -N.y); + return mat3(X, Y, N); +} diff --git a/libraries/pbrlib/genglsl/lib/mx39_microfacet_diffuse.glsl b/libraries/pbrlib/genglsl/lib/mx39_microfacet_diffuse.glsl new file mode 100644 index 0000000000..e55ed70229 --- /dev/null +++ b/libraries/pbrlib/genglsl/lib/mx39_microfacet_diffuse.glsl @@ -0,0 +1,88 @@ +#include "mx39_microfacet.glsl" +#include "libraries/pbrlib/genglsl/lib/mx_microfacet_diffuse.glsl" + +const float FUJII_CONSTANT_1 = 0.5 - 2.0 / (3.0 * M_PI); +const float FUJII_CONSTANT_2 = 2.0 / 3.0 - 28.0 / (15.0 * M_PI); + +// Qualitative Oren-Nayar diffuse with simplified math: +// https://www1.cs.columbia.edu/CAVE/publications/pdfs/Oren_SIGGRAPH94.pdf +float mx39_oren_nayar_diffuse(float NdotV, float NdotL, float LdotV, float roughness) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return A + B * stinv; +} + +// Rational quadratic fit to Monte Carlo data for Oren-Nayar directional albedo. +float mx39_oren_nayar_diffuse_dir_albedo_analytic(float NdotV, float roughness) +{ + vec2 r = vec2(1.0, 1.0) + + vec2(-0.4297, -0.6076) * roughness + + vec2(-0.7632, -0.4993) * NdotV * roughness + + vec2(1.4385, 2.0315) * mx_square(roughness); + return r.x / r.y; +} + +float mx39_oren_nayar_diffuse_dir_albedo(float NdotV, float roughness) +{ + float dirAlbedo = mx39_oren_nayar_diffuse_dir_albedo_analytic(NdotV, roughness); + return clamp(dirAlbedo, 0.0, 1.0); +} + +// Improved Oren-Nayar diffuse from Fujii: +// https://mimosa-pudica.net/improved-oren-nayar.html +float mx39_oren_nayar_fujii_diffuse_dir_albedo(float cosTheta, float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + float B = roughness * A; + float Si = sqrt(max(0.0, 1.0 - mx_square(cosTheta))); + float G = Si * (acos(clamp(cosTheta, -1.0, 1.0)) - Si * cosTheta) + + 2.0 * ((Si / cosTheta) * (1.0 - Si * Si * Si) - Si) / 3.0; + return A + (B * G * M_PI_INV); +} + +float mx39_oren_nayar_fujii_diffuse_avg_albedo(float roughness) +{ + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + return A * (1.0 + FUJII_CONSTANT_2 * roughness); +} + +// Energy-compensated Oren-Nayar diffuse from OpenPBR Surface: +// https://academysoftwarefoundation.github.io/OpenPBR/ +vec3 mx39_oren_nayar_compensated_diffuse(float NdotV, float NdotL, float LdotV, float roughness, vec3 color) +{ + float s = LdotV - NdotL * NdotV; + float stinv = (s > 0.0) ? s / max(NdotL, NdotV) : s; + + // Compute the single-scatter lobe. + float A = 1.0 / (1.0 + FUJII_CONSTANT_1 * roughness); + vec3 lobeSingleScatter = color * A * (1.0 + roughness * stinv); + + // Compute the multi-scatter lobe. + float dirAlbedoV = mx39_oren_nayar_fujii_diffuse_dir_albedo(NdotV, roughness); + float dirAlbedoL = mx39_oren_nayar_fujii_diffuse_dir_albedo(NdotL, roughness); + float avgAlbedo = mx39_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + vec3 lobeMultiScatter = colorMultiScatter * + max(M_FLOAT_EPS, 1.0 - dirAlbedoV) * + max(M_FLOAT_EPS, 1.0 - dirAlbedoL) / + max(M_FLOAT_EPS, 1.0 - avgAlbedo); + + // Return the sum. + return lobeSingleScatter + lobeMultiScatter; +} + +vec3 mx39_oren_nayar_compensated_diffuse_dir_albedo(float cosTheta, float roughness, vec3 color) +{ + float dirAlbedo = mx39_oren_nayar_fujii_diffuse_dir_albedo(cosTheta, roughness); + float avgAlbedo = mx39_oren_nayar_fujii_diffuse_avg_albedo(roughness); + vec3 colorMultiScatter = mx_square(color) * avgAlbedo / + (vec3(1.0) - color * max(0.0, 1.0 - avgAlbedo)); + return mix(colorMultiScatter, color, dirAlbedo); +} diff --git a/libraries/pbrlib/genglsl/lib/mx39_microfacet_sheen.glsl b/libraries/pbrlib/genglsl/lib/mx39_microfacet_sheen.glsl new file mode 100644 index 0000000000..f14079aa72 --- /dev/null +++ b/libraries/pbrlib/genglsl/lib/mx39_microfacet_sheen.glsl @@ -0,0 +1,99 @@ +#include "mx39_microfacet.glsl" +#include "libraries/pbrlib/genglsl/lib/mx_microfacet_sheen.glsl" + +// The following functions are adapted from https://github.com/tizian/ltc-sheen. +// "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines", Zeltner et al. + +// Gaussian fit to directional albedo table. +float mx39_zeltner_sheen_dir_albedo(float x, float y) +{ + float s = y*(0.0206607 + 1.58491*y)/(0.0379424 + y*(1.32227 + y)); + float m = y*(-0.193854 + y*(-1.14885 + y*(1.7932 - 0.95943*y*y)))/(0.046391 + y); + float o = y*(0.000654023 + (-0.0207818 + 0.119681*y)*y)/(1.26264 + y*(-1.92021 + y)); + return exp(-0.5*mx_square((x - m)/s))/(s*sqrt(2.0*M_PI)) + o; +} + +// Rational fits to LTC matrix coefficients. +float mx39_zeltner_sheen_ltc_aInv(float x, float y) +{ + return (2.58126*x + 0.813703*y)*y/(1.0 + 0.310327*x*x + 2.60994*x*y); +} + +float mx39_zeltner_sheen_ltc_bInv(float x, float y) +{ + return sqrt(1.0 - x)*(y - 1.0)*y*y*y/(0.0000254053 + 1.71228*x - 1.71506*x*y + 1.34174*y*y); +} + +// V and N are assumed to be unit vectors. +mat3 mx39_orthonormal_basis_ltc(vec3 V, vec3 N, float NdotV) +{ + // Generate a tangent vector in the plane of V and N. + // This required to correctly orient the LTC lobe. + vec3 X = V - N*NdotV; + float lenSqr = dot(X, X); + if (lenSqr > 0.0) + { + X *= inversesqrt(lenSqr); + vec3 Y = cross(N, X); + return mat3(X, Y, N); + } + + // If lenSqr == 0, then V == N, so any orthonormal basis will do. + return mx39_orthonormal_basis(N); +} + +// Multiplication by directional albedo is handled by the calling function. +float mx39_zeltner_sheen_brdf(vec3 L, vec3 V, vec3 N, float NdotV, float roughness) +{ + mat3 toLTC = transpose(mx39_orthonormal_basis_ltc(V, N, NdotV)); + vec3 w = toLTC * L; + + float aInv = mx39_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx39_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform w to original configuration (clamped cosine). + // |aInv 0 bInv| + // wo = M^-1 . w = | 0 aInv 0| . w + // | 0 0 1| + vec3 wo = vec3(aInv*w.x + bInv*w.z, aInv * w.y, w.z); + float lenSqr = dot(wo, wo); + + // D(w) = Do(M^-1.w / ||M^-1.w||) . |M^-1| / ||M^-1.w||^3 + // = Do(M^-1.w) . |M^-1| / ||M^-1.w||^4 + // = Do(wo) . |M^-1| / dot(wo, wo)^2 + // = Do(wo) . aInv^2 / dot(wo, wo)^2 + // = Do(wo) . (aInv / dot(wo, wo))^2 + return max(wo.z, 0.0) * M_PI_INV * mx_square(aInv / lenSqr); +} + +vec3 mx39_zeltner_sheen_importance_sample(vec2 Xi, vec3 V, vec3 N, float roughness, out float pdf) +{ + float NdotV = clamp(dot(N, V), 0.0, 1.0); + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 wo = mx39_cosine_sample_hemisphere(Xi); + + float aInv = mx39_zeltner_sheen_ltc_aInv(NdotV, roughness); + float bInv = mx39_zeltner_sheen_ltc_bInv(NdotV, roughness); + + // Transform wo from original configuration (clamped cosine). + // |1/aInv 0 -bInv/aInv| + // w = M . wo = | 0 1/aInv 0| . wo + // | 0 0 1| + vec3 w = vec3(wo.x/aInv - wo.z*bInv/aInv, wo.y / aInv, wo.z); + + float lenSqr = dot(w, w); + w *= inversesqrt(lenSqr); + + // D(w) = Do(wo) . ||M.wo||^3 / |M| + // = Do(wo / ||M.wo||) . ||M.wo||^4 / |M| + // = Do(w) . ||M.wo||^4 / |M| (possible because M doesn't change z component) + // = Do(w) . dot(w, w)^2 * aInv^2 + // = Do(w) . (aInv * dot(w, w))^2 + pdf = max(w.z, 0.0) * M_PI_INV * mx_square(aInv * lenSqr); + + mat3 fromLTC = mx39_orthonormal_basis_ltc(V, N, NdotV); + w = fromLTC * w; + + return w; +} diff --git a/libraries/pbrlib/genglsl/lib/mx39_microfacet_specular.glsl b/libraries/pbrlib/genglsl/lib/mx39_microfacet_specular.glsl new file mode 100644 index 0000000000..03fb671b17 --- /dev/null +++ b/libraries/pbrlib/genglsl/lib/mx39_microfacet_specular.glsl @@ -0,0 +1,419 @@ +#include "mx39_microfacet.glsl" +#include "libraries/pbrlib/genglsl/lib/mx_microfacet_specular.glsl" + +const int MX39_FRESNEL_MODEL_DIELECTRIC = 0; +const int MX39_FRESNEL_MODEL_CONDUCTOR = 1; +const int MX39_FRESNEL_MODEL_SCHLICK = 2; + +// Parameters for Fresnel calculations +struct Mx39FresnelData +{ + // Fresnel model + int model; + bool airy; + + // Physical Fresnel + vec3 ior; + vec3 extinction; + + // Generalized Schlick Fresnel + vec3 F0; + vec3 F82; + vec3 F90; + float exponent; + + // Thin film + float tf_thickness; + float tf_ior; + + // Refraction + bool refraction; +}; + +// Convert a real-valued index of refraction to normal-incidence reflectivity. +float mx39_ior_to_f0(float ior) +{ + return mx_square((ior - 1.0) / (ior + 1.0)); +} + +// Convert normal-incidence reflectivity to real-valued index of refraction. +float mx39_f0_to_ior(float F0) +{ + float sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (1.0 + sqrtF0) / (1.0 - sqrtF0); +} +vec3 mx39_f0_to_ior(vec3 F0) +{ + vec3 sqrtF0 = sqrt(clamp(F0, 0.01, 0.99)); + return (vec3(1.0) + sqrtF0) / (vec3(1.0) - sqrtF0); +} + +// https://renderwonk.com/publications/wp-generalization-adobe/gen-adobe.pdf +vec3 mx39_fresnel_hoffman_schlick(float cosTheta, Mx39FresnelData fd) +{ + const float COS_THETA_MAX = 1.0 / 7.0; + const float COS_THETA_FACTOR = 1.0 / (COS_THETA_MAX * pow(1.0 - COS_THETA_MAX, 6.0)); + + float x = clamp(cosTheta, 0.0, 1.0); + vec3 a = mix(fd.F0, fd.F90, pow(1.0 - COS_THETA_MAX, fd.exponent)) * (vec3(1.0) - fd.F82) * COS_THETA_FACTOR; + return mix(fd.F0, fd.F90, pow(1.0 - x, fd.exponent)) - a * x * mx39_pow6(1.0 - x); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +float mx39_fresnel_dielectric(float cosTheta, float ior) +{ + float c = cosTheta; + float g2 = ior*ior + c*c - 1.0; + if (g2 < 0.0) + { + // Total internal reflection + return 1.0; + } + + float g = sqrt(g2); + return 0.5 * mx_square((g - c) / (g + c)) * + (1.0 + mx_square(((g + c) * c - 1.0) / ((g - c) * c + 1.0))); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +vec2 mx39_fresnel_dielectric_polarized(float cosTheta, float ior) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + + float t0 = max(ior * ior - sinTheta2, 0.0); + float t1 = t0 + cosTheta2; + float t2 = 2.0 * sqrt(t0) * cosTheta; + float Rs = (t1 - t2) / (t1 + t2); + + float t3 = cosTheta2 * t0 + sinTheta2 * sinTheta2; + float t4 = t2 * sinTheta2; + float Rp = Rs * (t3 - t4) / (t3 + t4); + + return vec2(Rp, Rs); +} + +// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/ +void mx39_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs) +{ + float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0)); + float sinTheta2 = 1.0 - cosTheta2; + vec3 n2 = n * n; + vec3 k2 = k * k; + + vec3 t0 = n2 - k2 - vec3(sinTheta2); + vec3 a2plusb2 = sqrt(t0 * t0 + 4.0 * n2 * k2); + vec3 t1 = a2plusb2 + vec3(cosTheta2); + vec3 a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0)); + vec3 t2 = 2.0 * a * cosTheta; + Rs = (t1 - t2) / (t1 + t2); + + vec3 t3 = cosTheta2 * a2plusb2 + vec3(sinTheta2 * sinTheta2); + vec3 t4 = t2 * sinTheta2; + Rp = Rs * (t3 - t4) / (t3 + t4); +} + +vec3 mx39_fresnel_conductor(float cosTheta, vec3 n, vec3 k) +{ + vec3 Rp, Rs; + mx39_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs); + return 0.5 * (Rp + Rs); +} + +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +void mx39_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS) +{ + vec3 k2 = kappa2 / eta2; + vec3 sinThetaSqr = vec3(1.0) - cosTheta * cosTheta; + vec3 A = eta2*eta2*(vec3(1.0)-k2*k2) - eta1*eta1*sinThetaSqr; + vec3 B = sqrt(A*A + mx_square(2.0*eta2*eta2*k2)); + vec3 U = sqrt((A+B)/2.0); + vec3 V = max(vec3(0.0), sqrt((B-A)/2.0)); + + phiS = atan(2.0*eta1*V*cosTheta, U*U + V*V - mx_square(eta1*cosTheta)); + phiP = atan(2.0*eta1*eta2*eta2*cosTheta * (2.0*k2*U - (vec3(1.0)-k2*k2) * V), + mx_square(eta2*eta2*(vec3(1.0)+k2*k2)*cosTheta) - eta1*eta1*(U*U+V*V)); +} + +// A Practical Extension to Microfacet Theory for the Modeling of Varying Iridescence +// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +vec3 mx39_fresnel_airy(float cosTheta, Mx39FresnelData fd) +{ + // XYZ to CIE 1931 RGB color space (using neutral E illuminant) + const mat3 XYZ_TO_RGB = mat3(2.3706743, -0.5138850, 0.0052982, -0.9000405, 1.4253036, -0.0146949, -0.4706338, 0.0885814, 1.0093968); + + // Assume vacuum on the outside + float eta1 = 1.0; + float eta2 = max(fd.tf_ior, eta1); + vec3 eta3 = (fd.model == MX39_FRESNEL_MODEL_SCHLICK) ? mx39_f0_to_ior(fd.F0) : fd.ior; + vec3 kappa3 = (fd.model == MX39_FRESNEL_MODEL_SCHLICK) ? vec3(0.0) : fd.extinction; + float cosThetaT = sqrt(1.0 - (1.0 - mx_square(cosTheta)) * mx_square(eta1 / eta2)); + + // First interface + vec2 R12 = mx39_fresnel_dielectric_polarized(cosTheta, eta2 / eta1); + if (cosThetaT <= 0.0) + { + // Total internal reflection + R12 = vec2(1.0); + } + vec2 T121 = vec2(1.0) - R12; + + // Second interface + vec3 R23p, R23s; + if (fd.model == MX39_FRESNEL_MODEL_SCHLICK) + { + vec3 f = mx39_fresnel_hoffman_schlick(cosThetaT, fd); + R23p = 0.5 * f; + R23s = 0.5 * f; + } + else + { + mx39_fresnel_conductor_polarized(cosThetaT, eta3 / eta2, kappa3 / eta2, R23p, R23s); + } + + // Phase shift + float cosB = cos(atan(eta2 / eta1)); + vec2 phi21 = vec2(cosTheta < cosB ? 0.0 : M_PI, M_PI); + vec3 phi23p, phi23s; + if (fd.model == MX39_FRESNEL_MODEL_SCHLICK) + { + phi23p = vec3((eta3[0] < eta2) ? M_PI : 0.0, + (eta3[1] < eta2) ? M_PI : 0.0, + (eta3[2] < eta2) ? M_PI : 0.0); + phi23s = phi23p; + } + else + { + mx39_fresnel_conductor_phase_polarized(cosThetaT, eta2, eta3, kappa3, phi23p, phi23s); + } + vec3 r123p = max(sqrt(R12.x*R23p), 0.0); + vec3 r123s = max(sqrt(R12.y*R23s), 0.0); + + // Iridescence term + vec3 I = vec3(0.0); + vec3 Cm, Sm; + + // Optical path difference + float distMeters = fd.tf_thickness * 1.0e-9; + float opd = 2.0 * eta2 * cosThetaT * distMeters; + + // Iridescence term using spectral antialiasing for Parallel polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rs = (mx_square(T121.x) * R23p) / (vec3(1.0) - R12.x*R23p); + I += R12.x + Rs; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121.x; + for (int m=1; m<=2; m++) + { + Cm *= r123p; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23p+vec3(phi21.x))); + I += Cm*Sm; + } + + // Iridescence term using spectral antialiasing for Perpendicular polarization + + // Reflectance term for m=0 (DC term amplitude) + vec3 Rp = (mx_square(T121.y) * R23s) / (vec3(1.0) - R12.y*R23s); + I += R12.y + Rp; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rp - T121.y; + for (int m=1; m<=2; m++) + { + Cm *= r123s; + Sm = 2.0 * mx_eval_sensitivity(float(m) * opd, float(m)*(phi23s+vec3(phi21.y))); + I += Cm*Sm; + } + + // Average parallel and perpendicular polarization + I *= 0.5; + + // Convert back to RGB reflectance + I = clamp(XYZ_TO_RGB * I, 0.0, 1.0); + + return I; +} + +Mx39FresnelData mx39_init_fresnel_dielectric(float ior, float tf_thickness, float tf_ior) +{ + Mx39FresnelData fd; + fd.model = MX39_FRESNEL_MODEL_DIELECTRIC; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(ior); + fd.extinction = vec3(0.0); + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +Mx39FresnelData mx39_init_fresnel_conductor(vec3 ior, vec3 extinction, float tf_thickness, float tf_ior) +{ + Mx39FresnelData fd; + fd.model = MX39_FRESNEL_MODEL_CONDUCTOR; + fd.airy = tf_thickness > 0.0; + fd.ior = ior; + fd.extinction = extinction; + fd.F0 = vec3(0.0); + fd.F82 = vec3(0.0); + fd.F90 = vec3(0.0); + fd.exponent = 0.0; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +Mx39FresnelData mx39_init_fresnel_schlick(vec3 F0, vec3 F82, vec3 F90, float exponent, float tf_thickness, float tf_ior) +{ + Mx39FresnelData fd; + fd.model = MX39_FRESNEL_MODEL_SCHLICK; + fd.airy = tf_thickness > 0.0; + fd.ior = vec3(0.0); + fd.extinction = vec3(0.0); + fd.F0 = F0; + fd.F82 = F82; + fd.F90 = F90; + fd.exponent = exponent; + fd.tf_thickness = tf_thickness; + fd.tf_ior = tf_ior; + fd.refraction = false; + return fd; +} + +vec3 mx39_compute_fresnel(float cosTheta, Mx39FresnelData fd) +{ + if (fd.airy) + { + return mx39_fresnel_airy(cosTheta, fd); + } + else if (fd.model == MX39_FRESNEL_MODEL_DIELECTRIC) + { + return vec3(mx39_fresnel_dielectric(cosTheta, fd.ior.x)); + } + else if (fd.model == MX39_FRESNEL_MODEL_CONDUCTOR) + { + return mx39_fresnel_conductor(cosTheta, fd.ior, fd.extinction); + } + else + { + return mx39_fresnel_hoffman_schlick(cosTheta, fd); + } +} + +#ifdef MX39_USING_ENVIRONMENT_NONE +vec3 mx39_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, Mx39FresnelData fd) +{ + return vec3(0.0); +} +#endif + +#ifdef MX39_USING_ENVIRONMENT_PREFILTER +vec3 mx39_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, Mx39FresnelData fd) +{ + N = mx_forward_facing_normal(N, V); + vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, N, fd.ior.x) : -reflect(V, N); + + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + float avgAlpha = mx_average_alpha(alpha); + vec3 F = mx39_compute_fresnel(NdotV, fd); + float G = mx_ggx_smith_G2(NdotV, NdotV, avgAlpha); + vec3 FG = fd.refraction ? vec3(1.0) - (F * G) : F * G; + + vec3 Li = mx_latlong_map_lookup(L, $envMatrix, mx_latlong_alpha_to_lod(avgAlpha), $envRadiance); + return Li * FG * $envLightIntensity; +} +#endif + +#ifdef MX39_USING_ENVIRONMENT_FIS +vec3 mx39_environment_radiance(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, Mx39FresnelData fd) +{ + // Generate tangent frame. + X = normalize(X - dot(X, N) * N); + vec3 Y = cross(N, X); + mat3 tangentToWorld = mat3(X, Y, N); + + // Transform the view vector to tangent space. + V = vec3(dot(V, X), dot(V, Y), dot(V, N)); + + // Compute derived properties. + float NdotV = clamp(V.z, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(alpha); + float G1V = mx_ggx_smith_G1(NdotV, avgAlpha); + + // Integrate outgoing radiance using filtered importance sampling. + // http://cgg.mff.cuni.cz/~jaroslav/papers/2008-egsr-fis/2008-egsr-fis-final-embedded.pdf + vec3 radiance = vec3(0.0); + int envRadianceSamples = $envRadianceSamples; + for (int i = 0; i < envRadianceSamples; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, envRadianceSamples); + + // Compute the half vector and incoming light direction. + vec3 H = mx_ggx_importance_sample_VNDF(Xi, V, alpha); + vec3 L = fd.refraction ? mx_refraction_solid_sphere(-V, H, fd.ior.x) : -reflect(V, H); + + // Compute dot products for this sample. + float NdotL = clamp(L.z, M_FLOAT_EPS, 1.0); + float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + // Sample the environment light from the given direction. + vec3 Lw = tangentToWorld * L; + float pdf = mx_ggx_NDF(H, alpha) * G1V / (4.0 * NdotV); + float lod = mx_latlong_compute_lod(Lw, pdf, float($envRadianceMips - 1), envRadianceSamples); + vec3 sampleColor = mx_latlong_map_lookup(Lw, $envMatrix, lod, $envRadiance); + + // Compute the Fresnel term. + vec3 F = mx39_compute_fresnel(VdotH, fd); + + // Compute the geometric term. + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + // Compute the combined FG term, which is inverted for refraction. + vec3 FG = fd.refraction ? vec3(1.0) - (F * G) : F * G; + + // Add the radiance contribution of this sample. + // From https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + // incidentLight = sampleColor * NdotL + // microfacetSpecular = D * F * G / (4 * NdotL * NdotV) + // pdf = D * G1V / (4 * NdotV); + // radiance = incidentLight * microfacetSpecular / pdf + radiance += sampleColor * FG; + } + + // Apply the global component of the geometric term and normalize. + radiance /= G1V * float(envRadianceSamples); + + // Return the final radiance. + return radiance * $envLightIntensity; +} +#endif + +#ifdef MX39_USING_TRANSMISSION_OPACITY +vec3 mx39_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, Mx39FresnelData fd, vec3 tint) +{ + return tint; +} +#endif + +#ifdef MX39_USING_TRANSMISSION_REFRACT +vec3 mx39_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distribution, Mx39FresnelData fd, vec3 tint) +{ + // Approximate the appearance of surface transmission as glossy + // environment map refraction, ignoring any scene geometry that might + // be visible through the surface. + fd.refraction = true; + if ($refractionTwoSided) + { + tint = mx_square(tint); + } + return mx39_environment_radiance(N, V, X, alpha, distribution, fd) * tint; +} +#endif diff --git a/libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl b/libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl index 0b28f3645f..6bfc64571e 100644 --- a/libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl +++ b/libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl @@ -67,3 +67,5 @@ vec3 mx_environment_irradiance(vec3 N) vec3 Li = mx_latlong_map_lookup(N, $envMatrix, 0.0, $envIrradiance); return Li * $envLightIntensity; } + +#define MX39_USING_ENVIRONMENT_FIS diff --git a/libraries/pbrlib/genglsl/lib/mx_environment_none.glsl b/libraries/pbrlib/genglsl/lib/mx_environment_none.glsl index f0a1da5989..f535c84757 100644 --- a/libraries/pbrlib/genglsl/lib/mx_environment_none.glsl +++ b/libraries/pbrlib/genglsl/lib/mx_environment_none.glsl @@ -9,3 +9,5 @@ vec3 mx_environment_irradiance(vec3 N) { return vec3(0.0); } + +#define MX39_USING_ENVIRONMENT_NONE diff --git a/libraries/pbrlib/genglsl/lib/mx_environment_prefilter.glsl b/libraries/pbrlib/genglsl/lib/mx_environment_prefilter.glsl index 778742c449..89e898a1e1 100644 --- a/libraries/pbrlib/genglsl/lib/mx_environment_prefilter.glsl +++ b/libraries/pbrlib/genglsl/lib/mx_environment_prefilter.glsl @@ -28,3 +28,5 @@ vec3 mx_environment_irradiance(vec3 N) vec3 Li = mx_latlong_map_lookup(N, $envMatrix, 0.0, $envIrradiance); return Li * $envLightIntensity; } + +#define MX39_USING_ENVIRONMENT_PREFILTER diff --git a/libraries/pbrlib/genglsl/lib/mx_transmission_opacity.glsl b/libraries/pbrlib/genglsl/lib/mx_transmission_opacity.glsl index 2861d06194..54bef92ff4 100644 --- a/libraries/pbrlib/genglsl/lib/mx_transmission_opacity.glsl +++ b/libraries/pbrlib/genglsl/lib/mx_transmission_opacity.glsl @@ -4,3 +4,5 @@ vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distributio { return tint; } + +#define MX39_USING_TRANSMISSION_OPACITY diff --git a/libraries/pbrlib/genglsl/lib/mx_transmission_refract.glsl b/libraries/pbrlib/genglsl/lib/mx_transmission_refract.glsl index 64e496a384..4493321b4b 100644 --- a/libraries/pbrlib/genglsl/lib/mx_transmission_refract.glsl +++ b/libraries/pbrlib/genglsl/lib/mx_transmission_refract.glsl @@ -12,3 +12,5 @@ vec3 mx_surface_transmission(vec3 N, vec3 V, vec3 X, vec2 alpha, int distributio } return mx_environment_radiance(N, V, X, alpha, distribution, fd) * tint; } + +#define MX39_USING_TRANSMISSION_REFRACT diff --git a/libraries/pbrlib/genglsl/mx39_compensating_oren_nayar_diffuse_bsdf.glsl b/libraries/pbrlib/genglsl/mx39_compensating_oren_nayar_diffuse_bsdf.glsl new file mode 100644 index 0000000000..b93b5b8cd0 --- /dev/null +++ b/libraries/pbrlib/genglsl/mx39_compensating_oren_nayar_diffuse_bsdf.glsl @@ -0,0 +1,42 @@ +#include "lib/mx39_microfacet_diffuse.glsl" + +void mx39_compensating_oren_nayar_diffuse_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 normal, bool energy_compensation, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + float NdotV = clamp(dot(normal, V), M_FLOAT_EPS, 1.0); + float NdotL = clamp(dot(normal, L), M_FLOAT_EPS, 1.0); + float LdotV = clamp(dot(L, V), M_FLOAT_EPS, 1.0); + + vec3 diffuse = energy_compensation ? + mx39_oren_nayar_compensated_diffuse(NdotV, NdotL, LdotV, roughness, color) : + mx39_oren_nayar_diffuse(NdotV, NdotL, LdotV, roughness) * color; + bsdf.response = diffuse * occlusion * weight * NdotL * M_PI_INV; +} + +void mx39_compensating_oren_nayar_diffuse_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, bool energy_compensation, inout BSDF bsdf) +{ + bsdf.throughput = vec3(0.0); + + if (weight < M_FLOAT_EPS) + { + return; + } + + normal = mx_forward_facing_normal(normal, V); + + float NdotV = clamp(dot(normal, V), M_FLOAT_EPS, 1.0); + + vec3 diffuse = energy_compensation ? + mx39_oren_nayar_compensated_diffuse_dir_albedo(NdotV, roughness, color) : + mx39_oren_nayar_diffuse_dir_albedo(NdotV, roughness) * color; + vec3 Li = mx_environment_irradiance(normal); + bsdf.response = Li * diffuse * weight; +} diff --git a/libraries/pbrlib/genglsl/mx39_dielectric_tf_bsdf.glsl b/libraries/pbrlib/genglsl/mx39_dielectric_tf_bsdf.glsl new file mode 100644 index 0000000000..3dc668c4fa --- /dev/null +++ b/libraries/pbrlib/genglsl/mx39_dielectric_tf_bsdf.glsl @@ -0,0 +1,92 @@ +#include "lib/mx39_microfacet_specular.glsl" + +void mx39_dielectric_tf_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 tint, float ior, vec2 roughness, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + N = mx_forward_facing_normal(N, V); + + X = normalize(X - dot(X, N) * N); + vec3 Y = cross(N, X); + vec3 H = normalize(L + V); + + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + vec3 safeTint = max(tint, 0.0); + Mx39FresnelData fd = mx39_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior); + vec3 F = mx39_compute_fresnel(VdotH, fd); + float D = mx_ggx_NDF(Ht, safeAlpha); + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + float F0 = mx39_ior_to_f0(ior); + vec3 comp = mx_ggx_energy_compensation(NdotV, avgAlpha, F); + vec3 dirAlbedo = mx_ggx_dir_albedo(NdotV, avgAlpha, F0, 1.0) * comp; + bsdf.throughput = 1.0 - dirAlbedo * weight; + + // Note: NdotL is cancelled out + bsdf.response = D * F * G * comp * safeTint * occlusion * weight / (4.0 * NdotV); +} + +void mx39_dielectric_tf_bsdf_transmission(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + vec3 safeTint = max(tint, 0.0); + Mx39FresnelData fd = mx39_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior); + vec3 F = mx39_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx39_ior_to_f0(ior); + vec3 comp = mx_ggx_energy_compensation(NdotV, avgAlpha, F); + vec3 dirAlbedo = mx_ggx_dir_albedo(NdotV, avgAlpha, F0, 1.0) * comp; + bsdf.throughput = 1.0 - dirAlbedo * weight; + + if (scatter_mode != 0) + { + bsdf.response = mx39_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeTint) * weight; + } +} + +void mx39_dielectric_tf_bsdf_indirect(vec3 V, float weight, vec3 tint, float ior, vec2 roughness, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + N = mx_forward_facing_normal(N, V); + + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + vec3 safeTint = max(tint, 0.0); + Mx39FresnelData fd = mx39_init_fresnel_dielectric(ior, thinfilm_thickness, thinfilm_ior); + vec3 F = mx39_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + float F0 = mx39_ior_to_f0(ior); + vec3 comp = mx_ggx_energy_compensation(NdotV, avgAlpha, F); + vec3 dirAlbedo = mx_ggx_dir_albedo(NdotV, avgAlpha, F0, 1.0) * comp; + bsdf.throughput = 1.0 - dirAlbedo * weight; + + vec3 Li = mx39_environment_radiance(N, V, X, safeAlpha, distribution, fd); + bsdf.response = Li * safeTint * comp * weight; +} diff --git a/libraries/pbrlib/genglsl/mx39_generalized_schlick_tf_82_bsdf.glsl b/libraries/pbrlib/genglsl/mx39_generalized_schlick_tf_82_bsdf.glsl new file mode 100644 index 0000000000..512245784a --- /dev/null +++ b/libraries/pbrlib/genglsl/mx39_generalized_schlick_tf_82_bsdf.glsl @@ -0,0 +1,98 @@ +#include "lib/mx39_microfacet_specular.glsl" + +void mx39_generalized_schlick_tf_82_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color0, vec3 color82, vec3 color90, float exponent, vec2 roughness, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + N = mx_forward_facing_normal(N, V); + + X = normalize(X - dot(X, N) * N); + vec3 Y = cross(N, X); + vec3 H = normalize(L + V); + + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + float VdotH = clamp(dot(V, H), M_FLOAT_EPS, 1.0); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + vec3 Ht = vec3(dot(H, X), dot(H, Y), dot(H, N)); + + vec3 safeColor0 = max(color0, 0.0); + vec3 safeColor82 = max(color82, 0.0); + vec3 safeColor90 = max(color90, 0.0); + Mx39FresnelData fd = mx39_init_fresnel_schlick(safeColor0, safeColor82, safeColor90, exponent, thinfilm_thickness, thinfilm_ior); + vec3 F = mx39_compute_fresnel(VdotH, fd); + float D = mx_ggx_NDF(Ht, safeAlpha); + float G = mx_ggx_smith_G2(NdotL, NdotV, avgAlpha); + + vec3 comp = mx_ggx_energy_compensation(NdotV, avgAlpha, F); + vec3 dirAlbedo = mx_ggx_dir_albedo(NdotV, avgAlpha, safeColor0, safeColor90) * comp; + float avgDirAlbedo = dot(dirAlbedo, vec3(1.0 / 3.0)); + bsdf.throughput = vec3(1.0 - avgDirAlbedo * weight); + + // Note: NdotL is cancelled out + bsdf.response = D * F * G * comp * occlusion * weight / (4.0 * NdotV); +} + +void mx39_generalized_schlick_tf_82_bsdf_transmission(vec3 V, float weight, vec3 color0, vec3 color82, vec3 color90, float exponent, vec2 roughness, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + vec3 safeColor0 = max(color0, 0.0); + vec3 safeColor82 = max(color82, 0.0); + vec3 safeColor90 = max(color90, 0.0); + Mx39FresnelData fd = mx39_init_fresnel_schlick(safeColor0, safeColor82, safeColor90, exponent, thinfilm_thickness, thinfilm_ior); + vec3 F = mx39_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + + vec3 comp = mx_ggx_energy_compensation(NdotV, avgAlpha, F); + vec3 dirAlbedo = mx_ggx_dir_albedo(NdotV, avgAlpha, safeColor0, safeColor90) * comp; + float avgDirAlbedo = dot(dirAlbedo, vec3(1.0 / 3.0)); + bsdf.throughput = vec3(1.0 - avgDirAlbedo * weight); + + if (scatter_mode != 0) + { + float avgF0 = dot(safeColor0, vec3(1.0 / 3.0)); + fd.ior = vec3(mx39_f0_to_ior(avgF0)); + bsdf.response = mx39_surface_transmission(N, V, X, safeAlpha, distribution, fd, safeColor0) * weight; + } +} + +void mx39_generalized_schlick_tf_82_bsdf_indirect(vec3 V, float weight, vec3 color0, vec3 color82, vec3 color90, float exponent, vec2 roughness, float thinfilm_thickness, float thinfilm_ior, vec3 N, vec3 X, int distribution, int scatter_mode, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + vec3 safeColor0 = max(color0, 0.0); + vec3 safeColor82 = max(color82, 0.0); + vec3 safeColor90 = max(color90, 0.0); + Mx39FresnelData fd = mx39_init_fresnel_schlick(safeColor0, safeColor82, safeColor90, exponent, thinfilm_thickness, thinfilm_ior); + vec3 F = mx39_compute_fresnel(NdotV, fd); + + vec2 safeAlpha = clamp(roughness, M_FLOAT_EPS, 1.0); + float avgAlpha = mx_average_alpha(safeAlpha); + vec3 comp = mx_ggx_energy_compensation(NdotV, avgAlpha, F); + vec3 dirAlbedo = mx_ggx_dir_albedo(NdotV, avgAlpha, safeColor0, safeColor90) * comp; + float avgDirAlbedo = dot(dirAlbedo, vec3(1.0 / 3.0)); + bsdf.throughput = vec3(1.0 - avgDirAlbedo * weight); + + vec3 Li = mx39_environment_radiance(N, V, X, safeAlpha, distribution, fd); + bsdf.response = Li * comp * weight; +} diff --git a/libraries/pbrlib/genglsl/mx39_genglsl_impl.mtlx b/libraries/pbrlib/genglsl/mx39_genglsl_impl.mtlx new file mode 100644 index 0000000000..3626896222 --- /dev/null +++ b/libraries/pbrlib/genglsl/mx39_genglsl_impl.mtlx @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/libraries/pbrlib/genglsl/mx39_sheen_zeltner_bsdf.glsl b/libraries/pbrlib/genglsl/mx39_sheen_zeltner_bsdf.glsl new file mode 100644 index 0000000000..c6a874a5da --- /dev/null +++ b/libraries/pbrlib/genglsl/mx39_sheen_zeltner_bsdf.glsl @@ -0,0 +1,38 @@ +#include "lib/mx39_microfacet_sheen.glsl" + +void mx39_sheen_zeltner_bsdf_reflection(vec3 L, vec3 V, vec3 P, float occlusion, float weight, vec3 color, float roughness, vec3 N, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + + vec3 fr = color * mx39_zeltner_sheen_brdf(L, V, N, NdotV, roughness); + float dirAlbedo = mx39_zeltner_sheen_dir_albedo(NdotV, roughness); + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + bsdf.response = dirAlbedo * fr * occlusion * weight; +} + +void mx39_sheen_zeltner_bsdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 N, inout BSDF bsdf) +{ + if (weight < M_FLOAT_EPS) + { + return; + } + + N = mx_forward_facing_normal(N, V); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + + float dirAlbedo; + roughness = clamp(roughness, 0.01, 1.0); // Clamp to range of original impl. + dirAlbedo = mx39_zeltner_sheen_dir_albedo(NdotV, roughness); + + vec3 Li = mx_environment_irradiance(N); + bsdf.throughput = vec3(1.0 - dirAlbedo * weight); + bsdf.response = Li * color * dirAlbedo * weight; +} diff --git a/libraries/pbrlib/pbrlib_defs.mtlx b/libraries/pbrlib/pbrlib_defs.mtlx index e63625a45d..1417491681 100644 --- a/libraries/pbrlib/pbrlib_defs.mtlx +++ b/libraries/pbrlib/pbrlib_defs.mtlx @@ -417,4 +417,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +