255 lines
8.2 KiB
HLSL
255 lines
8.2 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Iridescence
|
|
|
|
// Depolarization functions for natural light
|
|
float ThinFilmDepol(float2 polV) { return 0.5 * (polV.x + polV.y); }
|
|
float3 ThinFilmDepolColor(float3 colS, float3 colP) { return 0.5 * (colS + colP); }
|
|
|
|
// Fresnel equations for dielectric/dielectric interfaces.
|
|
void ThinFilmFresnelDielectric(
|
|
in float ct1,
|
|
in float n1,
|
|
in float n2,
|
|
out float2 R,
|
|
out float2 phi)
|
|
{
|
|
float st1 = (1 - ct1 * ct1); // Sinus theta1 'squared'
|
|
float nr = n1 / n2;
|
|
|
|
if (Pow2(nr) * st1 > 1) // Total reflection
|
|
{
|
|
R = float2(1, 1);
|
|
phi = 2.0 * atan(float2(
|
|
-Pow2(nr) * sqrt(st1 - 1.0 / Pow2(nr)) / ct1,
|
|
-sqrt(st1 - 1.0 / Pow2(nr)) / ct1));
|
|
}
|
|
else // Transmission & Reflection
|
|
{
|
|
|
|
float ct2 = sqrt(1 - Pow2(nr) * st1);
|
|
float2 r = float2(
|
|
(n2 * ct1 - n1 * ct2) / (n2 * ct1 + n1 * ct2),
|
|
(n1 * ct1 - n2 * ct2) / (n1 * ct1 + n2 * ct2));
|
|
phi.x = (r.x < 0.0) ? PI : 0.0;
|
|
phi.y = (r.y < 0.0) ? PI : 0.0;
|
|
R = Pow2(r);
|
|
}
|
|
}
|
|
|
|
// Fresnel equations for dielectric/conductor interfaces.
|
|
void ThinFilmFresnelConductor(
|
|
in float ct1,
|
|
in float n1,
|
|
in float n2,
|
|
in float k,
|
|
out float2 R,
|
|
out float2 phi)
|
|
{
|
|
if (k == 0)
|
|
{ // use dielectric formula to avoid numerical issues
|
|
ThinFilmFresnelDielectric(ct1, n1, n2, R, phi);
|
|
}
|
|
else
|
|
{
|
|
float A = Pow2(n2) * (1 - Pow2(k)) - Pow2(n1) * (1 - Pow2(ct1));
|
|
float B = sqrt(Pow2(A) + Pow2(2 * Pow2(n2) * k));
|
|
float U = sqrt((A + B) / 2.0);
|
|
float V = sqrt((B - A) / 2.0);
|
|
|
|
R.y = (Pow2(n1 * ct1 - U) + Pow2(V)) / (Pow2(n1 * ct1 + U) + Pow2(V));
|
|
phi.y = atan2(2 * n1 * V * ct1, Pow2(U) + Pow2(V) - Pow2(n1 * ct1)) + PI;
|
|
|
|
R.x = (Pow2(Pow2(n2) * (1 - Pow2(k)) * ct1 - n1 * U) + Pow2(2 * Pow2(n2) * k * ct1 - n1 * V)) / (Pow2(Pow2(n2) * (1 - Pow2(k)) * ct1 + n1 * U) + Pow2(2 * Pow2(n2) * k * ct1 + n1 * V));
|
|
phi.x = atan2(2 * n1 * Pow2(n2) * ct1 * (2 * k * U - (1 - Pow2(k)) * V), Pow2(Pow2(n2) * (1 + Pow2(k)) * ct1) - Pow2(n1) * (Pow2(U) + Pow2(V)));
|
|
}
|
|
}
|
|
|
|
// Evaluation XYZ sensitivity curves in Fourier space
|
|
float3 ThinFilmEvalSensitivity(float opd, float shift)
|
|
{
|
|
// Use Gaussian fits, given by 3 parameters: val, pos and var
|
|
float phase = 2 * PI * opd * 1.0e-6;
|
|
float3 val = float3(5.4856e-13, 4.4201e-13, 5.2481e-13);
|
|
float3 pos = float3(1.6810e+06, 1.7953e+06, 2.2084e+06);
|
|
float3 var = float3(4.3278e+09, 9.3046e+09, 6.6121e+09);
|
|
float3 xyz = val * sqrt(2 * PI * var) * cos(pos * phase + shift) * exp(-var * phase * phase);
|
|
xyz.x += 9.7470e-14 * sqrt(2 * PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift) * exp(-4.5282e+09 * phase * phase);
|
|
return xyz / 1.0685e-7;
|
|
// As pointed by the suplemental material the oupout is not in the correct space, we need to tranfser
|
|
}
|
|
|
|
|
|
// [Gulbrandsen, "Artist Friendly Metallic Fresnel"]
|
|
// Compute complex index of refraction N = n + ik from F0 and edge tint.
|
|
void ComputeComplexIORFromF0AndEdgeTint(in float3 Reflectivity, in float3 EdgeTint, inout float3 n, inout float3 k)
|
|
{
|
|
const float3 g = EdgeTint;
|
|
const float3 r = clamp(Reflectivity, 0.0, 0.999);
|
|
const float3 SqrtR = sqrt(r);
|
|
|
|
n = g * (1 - r) / (1 + r) + (1 - g) * (1 + SqrtR) / (1 - SqrtR);
|
|
|
|
const float3 k2 = ((n + 1) * (n + 1) * r - (n - 1) * (n - 1)) / (1 - r);
|
|
k = sqrt(k2);
|
|
}
|
|
|
|
// Fresnel term for iridescent microfacet BRDF model
|
|
// This is a reference implementation adapted from the original paper's source code
|
|
float3 F_ThinFilmRef(float NoV, float NoL, float VoH, float3 F0, float3 F90, float ThinFilmIOR, float ThinFilmTickness)
|
|
{
|
|
// Translate F0/F90 to eta/kappa form
|
|
float3 n3 = 1;
|
|
float3 k3 = 0;
|
|
ComputeComplexIORFromF0AndEdgeTint(F0, F90, n3, k3);
|
|
|
|
// Thin film is limited (for now) to the top most layer (i.e., the upper layer need to be air)
|
|
// |
|
|
// n1=1 / k1=0 | Air
|
|
// -----------------
|
|
// n2 / k2=0 | Thin film
|
|
// -----------------
|
|
// n3 / k3 | Regular material
|
|
// |
|
|
//
|
|
// Min Max Default
|
|
// float Dinc 0.0 10.0 0.5
|
|
// float eta2 1.0 5.0 2.0
|
|
// float eta3 1.0 5.0 3.0
|
|
// float kappa3 0.0 5.0 0.0
|
|
//
|
|
// n1 is fixed to air (~1.f)
|
|
float3 Avg = 1.f / 3.f;
|
|
float eta2 = clamp(ThinFilmIOR, 1.f, 3.f);
|
|
float eta3 = clamp(dot(Avg,n3), 1.f, 3.f);
|
|
float kappa3 = clamp(dot(Avg,k3), 1.f, 5.f);
|
|
float Dinc = ThinFilmTickness * 10; // ThinFilmTickness is in micrometer. Range is 0 - 10um i.e, 0 - 10 000nm
|
|
|
|
// Force eta_2 -> 1.0 when Dinc -> 0.0
|
|
const float eta_2 = lerp(1.0, eta2, smoothstep(0.0, 0.03, Dinc));
|
|
|
|
// Compute dot products
|
|
const bool bIsValid = NoL > 0 && NoV > 0;
|
|
|
|
float3 I = 0;
|
|
if (bIsValid)
|
|
{
|
|
float cosTheta1 = VoH;
|
|
float cosTheta2 = sqrt(1.0 - Pow2(1.0 / eta_2) * (1 - Pow2(cosTheta1)));
|
|
|
|
// First interface
|
|
float2 R12, phi12;
|
|
ThinFilmFresnelDielectric(cosTheta1, 1.0, eta_2, R12, phi12);
|
|
float2 R21 = R12;
|
|
float2 T121 = float2(1, 1) - R12;
|
|
float2 phi21 = float2(PI, PI) - phi12;
|
|
|
|
// Second interface
|
|
float2 R23, phi23;
|
|
ThinFilmFresnelConductor(cosTheta2, eta_2, eta3, kappa3, R23, phi23);
|
|
|
|
// Phase shift
|
|
float OPD = Dinc * cosTheta2;
|
|
float2 phi2 = phi21 + phi23;
|
|
|
|
// Compound terms
|
|
float2 R123 = R12 * R23;
|
|
float2 r123 = sqrt(R123);
|
|
float2 Rs = Pow2(T121) * R23 / (1 - R123);
|
|
|
|
// Reflectance term for m=0 (DC term amplitude)
|
|
float2 C0 = R12 + Rs;
|
|
float3 S0 = ThinFilmEvalSensitivity(0.0, 0.0);
|
|
I += ThinFilmDepol(C0) * S0;
|
|
|
|
// Reflectance term for m>0 (pairs of diracs)
|
|
float2 Cm = Rs - T121;
|
|
for (int m = 1; m <= 3; ++m)
|
|
{
|
|
Cm *= r123;
|
|
float3 SmS = 2.0 * ThinFilmEvalSensitivity(m * OPD, m * phi2.x);
|
|
float3 SmP = 2.0 * ThinFilmEvalSensitivity(m * OPD, m * phi2.y);
|
|
I += ThinFilmDepolColor(Cm.x * SmS, Cm.y * SmP);
|
|
}
|
|
|
|
// Convert back to RGB reflectance
|
|
// XYZ to CIE 1931 RGB color space (using neutral E illuminant)
|
|
const float3x3 ThinFilm_XYZ_TO_RGB = float3x3(2.3706743, -0.5138850, 0.0052982, -0.9000405, 1.4253036, -0.0146949, -0.4706338, 0.0885814, 1.0093968);
|
|
I = clamp(mul(I, ThinFilm_XYZ_TO_RGB), float3(0, 0, 0), float3(1, 1, 1));
|
|
}
|
|
return I;
|
|
}
|
|
|
|
// Fresnel term for iridescent microfacet BRDF model
|
|
// Simplified version which relies on Schlick's Fresnel and de facto does not take into
|
|
// account Fresnel phase shift & polarization.
|
|
float3 F_ThinFilm(float NoV, float NoL, float VoH, float3 F0, float3 F90, float ThinFilmIOR, float ThinFilmTickness)
|
|
{
|
|
// Thin film is limited (for now) to the top most layer (i.e., the upper layer need to be air)
|
|
// |
|
|
// n1=1 / k1=0 | Air
|
|
// -----------------
|
|
// n2 / k2=0 | Thin film
|
|
// -----------------
|
|
// n3 / k3 | Regular material
|
|
// |
|
|
|
|
float Dinc = ThinFilmTickness * 10; // ThinFilmTickness is in micrometer. Range is 0 - 10um i.e, 0 - 10 000nm
|
|
|
|
// Force eta2 -> 1.0 when Dinc -> 0.0
|
|
const float eta2 = lerp(1.0, ThinFilmIOR, smoothstep(0.0, 0.03, Dinc));
|
|
|
|
// Compute dot products
|
|
const bool bIsValid = NoL > 0 && NoV > 0;
|
|
|
|
float3 I = 0;
|
|
if (bIsValid)
|
|
{
|
|
float cosTheta1 = VoH;
|
|
float cosTheta2 = sqrt(1.0 - Pow2(1.0 / eta2) * (1 - Pow2(cosTheta1)));
|
|
|
|
// First interface is dieletric so it is achromatic
|
|
float R12 = F_Schlick(DielectricIorToF0(ThinFilmIOR), VoH).x;
|
|
float phi12 = 0;
|
|
float R21 = R12;
|
|
float T121 = 1 - R12;
|
|
float phi21 = PI - phi12;
|
|
|
|
// Second interface is either conductor or dieletric, so can be chromatic.
|
|
// Ideally we should recompute F0 (R23) by taking into account the thin layer IOR
|
|
float3 R23 = F0;
|
|
float phi23 = 0;
|
|
|
|
// Phase shift
|
|
float OPD = Dinc * cosTheta2;
|
|
float phi2 = phi21 + phi23;
|
|
|
|
// Compound terms
|
|
float3 R123 = R12 * R23;
|
|
float3 r123 = sqrt(R123);
|
|
float3 Rs = Pow2(T121) * R23 / (1 - R123);
|
|
|
|
// Reflectance term for m=0 (DC term amplitude)
|
|
float3 C0 = R12 + Rs;
|
|
float3 S0 = ThinFilmEvalSensitivity(0.0, 0.0);
|
|
I += C0 * S0;
|
|
|
|
// Reflectance term for m>0 (pairs of diracs)
|
|
// Simplified based as we don't take into account polarization
|
|
float3 Cm = Rs - T121;
|
|
for (int m = 1; m <= 3; ++m)
|
|
{
|
|
Cm *= r123;
|
|
I += Cm * 2.0 * ThinFilmEvalSensitivity(m * OPD, m * phi2);
|
|
}
|
|
|
|
// Convert back to RGB reflectance
|
|
// XYZ to CIE 1931 RGB color space (using neutral E illuminant)
|
|
const float3x3 ThinFilm_XYZ_TO_RGB = float3x3(2.3706743, -0.5138850, 0.0052982, -0.9000405, 1.4253036, -0.0146949, -0.4706338, 0.0885814, 1.0093968);
|
|
I = saturate(mul(I, ThinFilm_XYZ_TO_RGB));
|
|
}
|
|
return I;
|
|
} |