Skip to content

Commit

Permalink
Additional improvements to GLSL Fresnel
Browse files Browse the repository at this point in the history
- Align mx_fresnel_dielectric with the Lagarde reference implementation.
- Align mx_fresnel_airy with the Belcour reference implementation.
- Merge mx_fresnel_dielectric_phase_polarized into mx_fresnel_airy and remove unused paths.
- Remove an unused code path in mx_fresnel_conductor_phase_polarized.
  • Loading branch information
jstone-lucasfilm committed Mar 12, 2024
1 parent b9c126c commit a09b9a4
Showing 1 changed file with 50 additions and 97 deletions.
147 changes: 50 additions & 97 deletions libraries/pbrlib/genglsl/lib/mx_microfacet_specular.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -218,42 +218,38 @@ vec3 mx_fresnel_hoffman_schlick(float cosTheta, FresnelData fd)
// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/
float mx_fresnel_dielectric(float cosTheta, float ior)
{
if (cosTheta < 0.0)
float c = cosTheta;
float g2 = ior*ior + c*c - 1.0;
if (g2 < 0.0)
{
// Total internal reflection
return 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;
float gpc = g + cosTheta;
float x = gmc / gpc;
float y = (gpc * cosTheta - 1.0) / (gmc * cosTheta + 1.0);
return 0.5 * x * x * (1.0 + y * y);
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)));
}

void mx_fresnel_dielectric_polarized(float cosTheta, float ior, out float Rp, out float Rs)
// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/
vec2 mx_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;
Rs = (t1 - t2) / (t1 + t2);
float Rs = (t1 - t2) / (t1 + t2);

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

return vec2(Rp, Rs);
}

// https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/
void mx_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));
Expand All @@ -280,34 +276,9 @@ vec3 mx_fresnel_conductor(float cosTheta, vec3 n, vec3 k)
return 0.5 * (Rp + Rs);
}

// 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)
{
phiP = cosTheta < cosB ? M_PI : 0.0f;
phiS = 0.0f;
}
else
{
phiP = cosTheta < cosB ? 0.0f : M_PI;
phiS = M_PI;
}
}

// Phase shift due to a conducting material
// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html
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)
{
// Use dielectric formula to increase performance
float phiPx, phiSx;
mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx);
phiP = vec3(phiPx, phiPx, phiPx);
phiS = vec3(phiSx, phiSx, phiSx);
return;
}
vec3 k2 = kappa2 / eta2;
vec3 sinThetaSqr = vec3(1.0) - cosTheta * cosTheta;
vec3 A = eta2*eta2*(vec3(1.0)-k2*k2) - eta1*eta1*sinThetaSqr;
Expand All @@ -320,7 +291,7 @@ void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2,
mx_square(eta2*eta2*(vec3(1.0)+k2*k2)*cosTheta) - eta1*eta1*(U*U+V*V));
}

// Evaluation XYZ sensitivity curves in Fourier space
// https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html
vec3 mx_eval_sensitivity(float opd, vec3 shift)
{
// Use Gaussian fits, given by 3 parameters: val, pos and var
Expand All @@ -340,57 +311,39 @@ 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);

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

// Assume vacuum on the outside
float eta1 = 1.0;
float eta2 = max(fd.tf_ior, eta1);
vec3 eta3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? mx_f0_to_ior(fd.F0) : fd.ior;
vec3 kappa3 = (fd.model == FRESNEL_MODEL_SCHLICK) ? vec3(0.0) : fd.extinction;
float cosThetaT = sqrt(1.0 - (1.0 - mx_square(cosTheta)) * mx_square(eta1 / eta2));

// Compute the Spectral versions of the Fresnel reflectance and
// transmitance for each interface.
float R12p, T121p, R12s, T121s;
// First interface
vec2 R12 = mx_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;

// Reflected and transmitted parts in the thin film
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 (fd.model == FRESNEL_MODEL_SCHLICK)
{
vec3 f = mx_fresnel_hoffman_schlick(cosTheta2, fd);
vec3 f = mx_fresnel_hoffman_schlick(cosThetaT, fd);
R23p = 0.5 * f;
R23s = 0.5 * f;
}
else
{
mx_fresnel_conductor_polarized(cosTheta2, eta3 / eta2, kappa3 / eta2, R23p, R23s);
}

// Check for total internal reflection
if (cosThetaTSqr <= 0.0f)
{
R12p = 1.0;
R12s = 1.0;
mx_fresnel_conductor_polarized(cosThetaT, eta3 / eta2, kappa3 / eta2, R23p, R23s);
}

// Compute the transmission coefficients
T121p = 1.0 - R12p;
T121s = 1.0 - R12s;

// Optical path difference
float D = 2.0 * eta2 * d * cosTheta2;

float phi21p, phi21s;
vec3 phi23p, phi23s, r123s, r123p;

// Evaluate the phase shift
mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2, phi21p, phi21s);
// 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 == FRESNEL_MODEL_SCHLICK)
{
phi23p = vec3((eta3[0] < eta2) ? M_PI : 0.0,
Expand All @@ -400,54 +353,54 @@ vec3 mx_fresnel_airy(float cosTheta, FresnelData fd)
}
else
{
mx_fresnel_conductor_phase_polarized(cosTheta2, eta2, eta3, kappa3, phi23p, phi23s);
mx_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);

phi21p = M_PI - phi21p;
phi21s = M_PI - phi21s;

r123p = max(vec3(0.0), sqrt(R12p*R23p));
r123s = max(vec3(0.0), sqrt(R12s*R23s));

// Evaluate iridescence term
// 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 = (T121p*T121p*R23p) / (vec3(1.0) - R12p*R23p);
I += R12p + Rs;
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 - T121p;
Cm = Rs - T121.x;
for (int m=1; m<=2; m++)
{
Cm *= r123p;
Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23p+vec3(phi21p)));
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 = (T121s*T121s*R23s) / (vec3(1.0) - R12s*R23s);
I += R12s + Rp;
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 - T121s;
Cm = Rp - T121.y;
for (int m=1; m<=2; m++)
{
Cm *= r123s;
Sm = 2.0 * mx_eval_sensitivity(float(m)*D, float(m)*(phi23s+vec3(phi21s)));
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, vec3(0.0), vec3(1.0));
I = clamp(XYZ_TO_RGB * I, 0.0, 1.0);

return I;
}
Expand Down

0 comments on commit a09b9a4

Please sign in to comment.