Skip to content

Commit

Permalink
Improvements to GLSL Fresnel
Browse files Browse the repository at this point in the history
- Simplify argument passing for FresnelData, and fix a missing initializer in MSL.
- Minor optimizations to mx_fresnel_dielectric_polarized and mx_fresnel_airy.
- Minor formatting improvements in GLSL Fresnel logic.
  • Loading branch information
jstone-lucasfilm committed Mar 9, 2024
1 parent cb9a99d commit e4dc8a1
Showing 1 changed file with 83 additions and 97 deletions.
180 changes: 83 additions & 97 deletions libraries/pbrlib/genglsl/lib/mx_microfacet_specular.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ const int FRESNEL_MODEL_SCHLICK = 2;
const int FRESNEL_MODEL_AIRY = 3;
const int FRESNEL_MODEL_SCHLICK_AIRY = 4;

// 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);

// Parameters for Fresnel calculations.
struct FresnelData
{
Expand All @@ -32,24 +29,29 @@ struct FresnelData
// Refraction
bool refraction;

#ifdef __METAL__
FresnelData(int _model = 0,
vec3 _ior = vec3(0.0f),
vec3 _extinction = vec3(0.0f),
vec3 _F0 = vec3(0.0f),
vec3 _F82 = vec3(0.0f),
vec3 _F90 = vec3(0.0f),
float _exponent = 0.0f,
#ifdef __METAL__
FresnelData(int _model = 0,
vec3 _ior = vec3(0.0f),
vec3 _extinction = vec3(0.0f),
vec3 _F0 = vec3(0.0f),
vec3 _F82 = vec3(0.0f),
vec3 _F90 = vec3(0.0f),
float _exponent = 0.0f,
float _tf_thickness = 0.0f,
float _tf_ior = 0.0f,
bool _refraction = false) :
model(_model),
ior(_ior),
extinction(_extinction),
F0(_F0), F90(_F90), exponent(_exponent),
tf_thickness(_tf_thickness),
tf_ior(_tf_ior),
refraction(_refraction) {}
float _tf_ior = 0.0f,
bool _refraction = false) :
model(_model),
ior(_ior),
extinction(_extinction),
F0(_F0),
F82(_F82),
F90(_F90),
exponent(_exponent),
tf_thickness(_tf_thickness),
tf_ior(_tf_ior),
refraction(_refraction)
{
}
#endif

};
Expand Down Expand Up @@ -224,22 +226,38 @@ float mx_f0_to_ior(float F0)
return (1.0 + sqrtF0) / (1.0 - sqrtF0);
}

vec3 mx_f0_to_ior_colored(vec3 F0)
vec3 mx_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 mx_fresnel_hoffman_schlick(float cosTheta, FresnelData 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 * mx_pow6(1.0 - x);
}

// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/
float mx_fresnel_dielectric(float cosTheta, float ior)
{
if (cosTheta < 0.0)
{
return 1.0;
}

float g = ior*ior + cosTheta*cosTheta - 1.0;

float g = ior*ior + cosTheta*cosTheta - 1.0;
// Check for total internal reflection
if (g < 0.0)
{
return 1.0;
}

g = sqrt(g);
float gmc = g - cosTheta;
Expand All @@ -249,51 +267,31 @@ float mx_fresnel_dielectric(float cosTheta, float ior)
return 0.5 * x * x * (1.0 + y * y);
}

// https://renderwonk.com/publications/wp-generalization-adobe/gen-adobe.pdf
vec3 mx_fresnel_hoffman_schlick(float cosTheta, vec3 F0, vec3 F82, vec3 F90, float exponent)
{
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(F0, F90, pow(1.0 - COS_THETA_MAX, exponent)) * (vec3(1.0) - F82) * COS_THETA_FACTOR;
return mix(F0, F90, pow(1.0 - x, exponent)) - a * x * mx_pow6(1.0 - x);
}

void mx_fresnel_dielectric_polarized(float cosTheta, float n, out float Rp, out float Rs)
void mx_fresnel_dielectric_polarized(float cosTheta, float ior, out float Rp, out float Rs)
{
if (cosTheta < 0.0) {
if (cosTheta < 0.0)
{
Rp = 1.0;
Rs = 1.0;
return;
}

float cosTheta2 = cosTheta * cosTheta;
float sinTheta2 = 1.0 - cosTheta2;
float n2 = n * n;

float t0 = n2 - sinTheta2;
float a2plusb2 = sqrt(t0 * t0);
float t1 = a2plusb2 + cosTheta2;
float a = sqrt(max(0.5 * (a2plusb2 + t0), 0.0));
float t2 = 2.0 * a * cosTheta;
float t0 = max(ior * ior - sinTheta2, 0.0);
float t1 = t0 + cosTheta2;
float t2 = 2.0 * sqrt(t0) * cosTheta;
Rs = (t1 - t2) / (t1 + t2);

float t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2;
float t3 = cosTheta2 * t0 + sinTheta2 * sinTheta2;
float t4 = t2 * sinTheta2;
Rp = Rs * (t3 - t4) / (t3 + t4);
}

void mx_fresnel_dielectric_polarized(float cosTheta, float eta1, float eta2, out float Rp, out float Rs)
{
float n = eta2 / eta1;
mx_fresnel_dielectric_polarized(cosTheta, n, Rp, Rs);
}

void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp, out vec3 Rs)
{
cosTheta = clamp(cosTheta, 0.0, 1.0);
float cosTheta2 = cosTheta * cosTheta;
float cosTheta2 = mx_square(clamp(cosTheta, 0.0, 1.0));
float sinTheta2 = 1.0 - cosTheta2;
vec3 n2 = n * n;
vec3 k2 = k * k;
Expand All @@ -310,13 +308,6 @@ void mx_fresnel_conductor_polarized(float cosTheta, vec3 n, vec3 k, out vec3 Rp,
Rp = Rs * (t3 - t4) / (t3 + t4);
}

void mx_fresnel_conductor_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 Rp, out vec3 Rs)
{
vec3 n = eta2 / eta1;
vec3 k = kappa2 / eta1;
mx_fresnel_conductor_polarized(cosTheta, n, k, Rp, Rs);
}

vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k)
{
vec3 Rp, Rs;
Expand All @@ -327,11 +318,14 @@ vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k)
// Phase shift due to a dielectric material
void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta2, out float phiP, out float phiS)
{
float cosB = cos(atan(eta2 / eta1)); // Brewster's angle
if (eta2 > eta1) {
float cosB = cos(atan(eta2 / eta1)); // Brewster's angle
if (eta2 > eta1)
{
phiP = cosTheta < cosB ? M_PI : 0.0f;
phiS = 0.0f;
} else {
}
else
{
phiP = cosTheta < cosB ? 0.0f : M_PI;
phiS = M_PI;
}
Expand All @@ -340,7 +334,8 @@ void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta
// Phase shift due to a conducting material
void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS)
{
if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) {
if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z)
{
// Use dielectric formula to increase performance
float phiPx, phiSx;
mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx);
Expand Down Expand Up @@ -375,46 +370,51 @@ vec3 mx_eval_sensitivity(float opd, vec3 shift)

// 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 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickness, float tf_ior,
vec3 F0, vec3 F82, vec3 F90, float exponent, bool use_schlick)
vec3 mx_fresnel_airy(float cosTheta, FresnelData 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);

// Select between Schlick and physical models.
bool useSchlickAiry = fd.model == FRESNEL_MODEL_SCHLICK_AIRY;

// Convert nm -> m
float d = tf_thickness * 1.0e-9;
float d = fd.tf_thickness * 1.0e-9;

// Assume vacuum on the outside
float eta1 = 1.0;
float eta2 = max(tf_ior, eta1);
vec3 eta3 = use_schlick ? mx_f0_to_ior_colored(F0) : ior;
vec3 kappa3 = use_schlick ? vec3(0.0) : extinction;
float eta2 = max(fd.tf_ior, eta1);
vec3 eta3 = useSchlickAiry ? mx_f0_to_ior(fd.F0) : fd.ior;
vec3 kappa3 = useSchlickAiry ? vec3(0.0) : fd.extinction;

// Compute the Spectral versions of the Fresnel reflectance and
// transmitance for each interface.
float R12p, T121p, R12s, T121s;
vec3 R23p, R23s;

// Reflected and transmitted parts in the thin film
mx_fresnel_dielectric_polarized(cosTheta, eta1, eta2, R12p, R12s);
mx_fresnel_dielectric_polarized(cosTheta, eta2 / eta1, R12p, R12s);

// Reflected part by the base
float scale = eta1 / eta2;
float cosThetaTSqr = 1.0 - (1.0-cosTheta*cosTheta) * scale*scale;
float cosTheta2 = sqrt(cosThetaTSqr);
if (use_schlick)
if (useSchlickAiry)
{
vec3 f = mx_fresnel_hoffman_schlick(cosTheta2, F0, F82, F90, exponent);
vec3 f = mx_fresnel_hoffman_schlick(cosTheta2, fd);
R23p = 0.5 * f;
R23s = 0.5 * f;
}
else
{
mx_fresnel_conductor_polarized(cosTheta2, eta2, eta3, kappa3, R23p, R23s);
mx_fresnel_conductor_polarized(cosTheta2, eta3 / eta2, kappa3 / eta2, R23p, R23s);
}

// Check for total internal reflection
if (cosThetaTSqr <= 0.0f)
{
R12s = 1.0;
R12p = 1.0;
R12s = 1.0;
}

// Compute the transmission coefficients
Expand All @@ -429,12 +429,11 @@ vec3 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickne

// Evaluate the phase shift
mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s);
if (use_schlick)
if (useSchlickAiry)
{
phi23p = vec3(
(eta3[0] < eta2) ? M_PI : 0.0,
(eta3[1] < eta2) ? M_PI : 0.0,
(eta3[2] < eta2) ? M_PI : 0.0);
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
Expand All @@ -454,16 +453,14 @@ vec3 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickne

// Iridescence term using spectral antialiasing for Parallel polarization

vec3 S0 = vec3(1.0);

// Reflectance term for m=0 (DC term amplitude)
vec3 Rs = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p);
C0 = R12p + Rs;
I += C0 * S0;
I += C0;

// Reflectance term for m>0 (pairs of diracs)
Cm = Rs - T121p;
for (int m=1; m<=2; ++m)
for (int m=1; m<=2; m++)
{
Cm *= r123p;
Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p)));
Expand All @@ -475,11 +472,11 @@ vec3 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickne
// Reflectance term for m=0 (DC term amplitude)
vec3 Rp = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s);
C0 = R12s + Rp;
I += C0 * S0;
I += C0;

// Reflectance term for m>0 (pairs of diracs)
Cm = Rp - T121s ;
for (int m=1; m<=2; ++m)
Cm = Rp - T121s;
for (int m=1; m<=2; m++)
{
Cm *= r123s;
Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s)));
Expand Down Expand Up @@ -515,15 +512,6 @@ FresnelData mx_init_fresnel_conductor(vec3 ior, vec3 extinction)
return fd;
}

FresnelData mx_init_fresnel_schlick(vec3 F0)
{
FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK);
fd.F0 = F0;
fd.F90 = vec3(1.0);
fd.exponent = 5.0f;
return fd;
}

FresnelData mx_init_fresnel_schlick(vec3 F0, vec3 F82, vec3 F90, float exponent)
{
FresnelData fd = mx_init_fresnel_data(FRESNEL_MODEL_SCHLICK);
Expand Down Expand Up @@ -577,13 +565,11 @@ vec3 mx_compute_fresnel(float cosTheta, FresnelData fd)
}
else if (fd.model == FRESNEL_MODEL_SCHLICK)
{
return mx_fresnel_hoffman_schlick(cosTheta, fd.F0, fd.F82, fd.F90, fd.exponent);
return mx_fresnel_hoffman_schlick(cosTheta, fd);
}
else
{
return mx_fresnel_airy(cosTheta, fd.ior, fd.extinction, fd.tf_thickness, fd.tf_ior,
fd.F0, fd.F82, fd.F90, fd.exponent,
fd.model == FRESNEL_MODEL_SCHLICK_AIRY);
return mx_fresnel_airy(cosTheta, fd);
}
}

Expand Down

0 comments on commit e4dc8a1

Please sign in to comment.