You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

221 lines
7.7 KiB

#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Hair/Reference/HairReferenceCommon.hlsl"
// Reference implementation of a Marschner-based energy conserving hair reflectance model with concepts from:
// "The Implementation of a Hair Scattering Model" (Pharr 2016)
// "A Practical and Controllable Hair and Fur Model for Production Path Tracing" (Chiang 2016)
// "Importance Sampling for Physically-Based Hair Fiber Models" (D'Eon 2012)
// "An Energy-Conserving Hair Reflectance Model" (d'Eon 2011)
// "Light Scattering from Human Hair Fibers" (Marschner 2003)
void ComputeFiberAttenuations(float cosThetaO, float eta, float h, float3 T, inout float3 A[PATH_MAX + 1])
{
// Reconstruct the incident angle.
float cosGammaO = SafeSqrt(1 - Sq(h));
float cosTheta = cosThetaO * cosGammaO;
float F0 = IorToFresnel0(eta);
float F = F_Schlick(F0, cosTheta);
// Solve for P == 0 (Reflection at the cuticle).
A[0] = F;
// Solve for P == 1 (Solves two air-hair boundary events and one transmission event).
A[1] = Sq(1 - F) * T;
// Solve for 2 < P < PMAX
for (uint p = 2; p < PATH_MAX; p++)
A[p] = A[p - 1] * T * F;
// Solve for the residual lobe PMAX < P < INFINITY
// Ref: A Practical and Controllable Hair and Fur Model for Production Eq. 6
A[PATH_MAX] = A[PATH_MAX - 1] * F * T / (1 - T * F);
}
void ComputeFiberAttenuationsPDF(float cosThetaO, float3 sigmaA, float eta, float h, inout float APDF[PATH_MAX + 1])
{
float sinThetaO = SafeSqrt(1 - Sq(cosThetaO));
// Compute the refracted ray
float sinThetaT = sinThetaO / eta;
float cosThetaT = SafeSqrt(1 - Sq(sinThetaT));
float etaP = sqrt(Sq(eta) - Sq(sinThetaO)) / cosThetaO;
float sinGammaT = h / etaP;
float cosGammaT = SafeSqrt(1 - Sq(sinGammaT));
// Compute transmittance of single path through the fiber using Beer's Law.
float3 T = exp(-sigmaA * (2 * cosGammaT / cosThetaT));
float3 A[PATH_MAX + 1];
ComputeFiberAttenuations(cosThetaO, eta, h, T, A);
float sumY = 0;
int i;
for (i = 0; i <= PATH_MAX; i++)
sumY += Luminance(A[i]);
for (i = 0; i <= PATH_MAX; i++)
APDF[i] = Luminance(A[i]) / sumY;
}
// Ref: An Energy-Conserving Hair Reflectance Model Eq. 7
// Plot: https://www.desmos.com/calculator/jmf1ofgfdv
float LongitudinalScattering(float cosThetaI, float cosThetaO, float sinThetaI, float sinThetaO, float v)
{
float M;
float a = cosThetaI * cosThetaO / v;
float b = sinThetaI * sinThetaO / v;
if (v < 0.1)
{
// Ref: [https://publons.com/review/414383/]
// Small variances (< ~0.1) produce numerical issues due to limited floating precision.
M = exp(LogBesselI(a) - b - rcp(v) + 0.6931 + log(rcp(2 * v)));
}
else
{
M = (exp(-b) * BesselI(a)) / (sinh(1 / v) * 2 * v);
}
return M;
}
float AzimuthalScattering(float phi, uint p, float s, float gammaO, float gammaT)
{
float dphi = phi - AzimuthalDirection(p, gammaO, gammaT);
// Remap Phi to fit in the -PI..PI domain for the logistic.
while (dphi > +PI) dphi -= TWO_PI;
while (dphi < -PI) dphi += TWO_PI;
return TrimmedLogistic(dphi, s, -PI, PI);
}
CBSDF EvaluateHairReference(float3 wo, float3 wi, BSDFData bsdfData)
{
// Initialize the BSDF invocation.
ReferenceBSDFData data = GetReferenceBSDFData(bsdfData);
ReferenceAngles angles = GetReferenceAngles(wi, wo);
// Find refracted ray angles.
float sinThetaT = angles.sinThetaO / data.eta;
float cosThetaT = SafeSqrt(1 - Sq(sinThetaT));
// Find the modified index of refraction.
float etaP = sqrt(Sq(data.eta) - Sq(angles.sinThetaO)) / angles.cosThetaO;
// Compute refracted angle gamma T (exploiting the Bravais properties of a cylinder).
float sinGammaT = data.h / etaP;
float cosGammaT = SafeSqrt(1 - Sq(sinGammaT));
float gammaT = clamp(FastASin(sinGammaT), -1, 1);
// Compute transmittance of single path through the fiber using Beer's Law.
float3 T = exp(-data.sigmaA * (2 * cosGammaT / cosThetaT));
// Compute the absorptions that occur in the fiber for every path.
float3 A[PATH_MAX + 1];
ComputeFiberAttenuations(angles.cosThetaO, data.eta, data.h, T, A);
float3 F = 0;
for (uint p = 0; p < PATH_MAX; ++p)
{
float sinThetaO, cosThetaO;
ApplyCuticleTilts(p, angles, data, sinThetaO, cosThetaO);
F += LongitudinalScattering(angles.cosThetaI, cosThetaO, angles.sinThetaI, sinThetaO, data.v[p]) * A[p] *
AzimuthalScattering(angles.phi, p, data.s, data.gammaO, gammaT);
}
// Compute the residual lobe
F += LongitudinalScattering(angles.cosThetaI, angles.cosThetaO, angles.sinThetaI, angles.sinThetaO, data.v[PATH_MAX]) * A[PATH_MAX] * INV_TWO_PI;
if(abs(wi.z) > 0)
F /= abs(wi.z + 1e-4);
return HairFtoCBSDF(max(F, 0));
}
CBSDF SampleHairReference(float3 wo, out float3 wi, out float pdf, float4 u, BSDFData bsdfData)
{
// Initialize the BSDF invocation.
ReferenceBSDFData data = GetReferenceBSDFData(bsdfData);
// Compute angles only for the currently known outgoing direction.
ReferenceAngles angles;
ZERO_INITIALIZE(ReferenceAngles, angles);
angles.sinThetaO = wo.x;
angles.cosThetaO = SafeSqrt(1 - Sq(angles.sinThetaO));
angles.phiO = FastAtan2(wo.z, wo.y);
// Determine the path to sample.
float APDF[PATH_MAX + 1];
ComputeFiberAttenuationsPDF(angles.cosThetaO, data.sigmaA, data.eta, data.h, APDF);
int p;
for (p = 0; p < PATH_MAX; p++)
{
if (u.x < APDF[p])
break;
u.x -= APDF[p];
}
float sinThetaO, cosThetaO;
ApplyCuticleTilts(p, angles, data, sinThetaO, cosThetaO);
// Note, clamping this sample seems required to prevent NaNs for very low (< ~0.1) variances.
u.y = max(u.y, 1e-4);
// Importance sample the longitudinal scattering function using an exponential function identity to handle low variance.
// Ref: "Importance Sampling for Physically-Based Hair Fiber Models" Eq. 6 & 7
// Ref: "Numerically stable sampling of the von Mises Fisher distribution on S2"
float sampleMP = 1 + data.v[p] * log(u.y + (1 - u.y) * exp(-2 / data.v[p]));
float sinThetaI = -sampleMP * sinThetaO + SafeSqrt(1 - Sq(sampleMP)) * cos(TWO_PI * u.z) * cosThetaO;
float cosThetaI = SafeSqrt(1 - Sq(sinThetaI));
// Importance sample the azimuthal scattering function
// Find the modified index of refraction.
float etaP = sqrt(Sq(data.eta) - Sq(angles.sinThetaO)) / angles.cosThetaO;
// Compute refracted angle gamma T (exploiting the Bravais properties of a cylinder).
float sinGammaT = data.h / etaP;
float gammaT = clamp(FastASin(sinGammaT), -1, 1);
float phi;
if (p < PATH_MAX)
phi = AzimuthalDirection(p, data.gammaO, gammaT) + TrimmedLogisticSampled(u.w, data.s, -PI, PI);
else
phi = TWO_PI * u.w;
// Construct the sampled direction wi.
float phiI = angles.phiO + phi;
wi = float3(sinThetaI, cosThetaI * cos(phiI), cosThetaI * sin(phiI));
// Solve the overall PDF
pdf = 0;
for (p = 0; p < PATH_MAX; p++)
{
float sinThetaOp, cosThetaOp;
ApplyCuticleTilts(p, angles, data, sinThetaOp, cosThetaOp);
pdf += LongitudinalScattering(cosThetaI, cosThetaOp, sinThetaI, sinThetaOp, data.v[p]) * APDF[p] *
AzimuthalScattering(phi, p, data.s, data.gammaO, gammaT);
}
// Don't forget the residual lobe
pdf += LongitudinalScattering(cosThetaI, angles.cosThetaO, sinThetaI, angles.sinThetaO, data.v[PATH_MAX]) * APDF[PATH_MAX] * INV_TWO_PI;
// Enforce a maximum pdf to prevent divide-by-zeros and NaN propagation in path tracer.
pdf = max(pdf, 1e-3);
return EvaluateHairReference(wo, wi, bsdfData);
}