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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+