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.
229 lines
6.3 KiB
229 lines
6.3 KiB
// The # of lobes to evaluate explicitly (R, TT, TRT, TRRT+..) before summing up the remainder with a residual lobe approximation.
|
|
#define PATH_MAX 3
|
|
|
|
#define SQRT_PI_OVER_8 0.62665706865775012560
|
|
|
|
// Precompute the factorials (should really precompute the squared value).
|
|
static const float FACTORIAL[11] = { 1.0,
|
|
1.0,
|
|
2.0,
|
|
6.0,
|
|
24.0,
|
|
120.0,
|
|
720.0,
|
|
5040.0,
|
|
40320.0,
|
|
362880.0,
|
|
3628800.0 };
|
|
|
|
struct ReferenceBSDFData
|
|
{
|
|
float h; // Intersection offset from fiber center.
|
|
float gammaO;
|
|
|
|
float eta; // Refraction Index
|
|
float3 sigmaA; // Absorption Coefficient of Cortex
|
|
float betaM; // Longitudinal Roughness
|
|
float betaN; // Azimuthal Roughness
|
|
|
|
float alpha; // Cuticle Tilt (Radians)
|
|
float sinAlpha[3];
|
|
float cosAlpha[3];
|
|
|
|
float s; // Logistic Scale Factor
|
|
float v[PATH_MAX + 1]; // Longitudinal Variance
|
|
};
|
|
|
|
struct ReferenceAngles
|
|
{
|
|
float sinThetaI;
|
|
float sinThetaO;
|
|
|
|
float cosThetaI;
|
|
float cosThetaO;
|
|
|
|
float phiI;
|
|
float phiO;
|
|
float phi;
|
|
};
|
|
|
|
// Ref: Light Scattering from Human Hair Fibers Eq. 3
|
|
float AzimuthalDirection(uint p, float gammaO, float gammaT)
|
|
{
|
|
return (2 * p * gammaT) - (2 * gammaO) + (p * PI);
|
|
}
|
|
|
|
float Logistic(float x, float s)
|
|
{
|
|
// Avoids numerical instability for large x / s ratios.
|
|
x = abs(x);
|
|
|
|
return exp(-x / s) / (s * Sq(1 + exp(-x / s)));
|
|
}
|
|
|
|
float LogisticCDF(float x, float s)
|
|
{
|
|
return 1 / (1 + exp(-x / s));
|
|
}
|
|
|
|
float TrimmedLogistic(float x, float s, float a, float b)
|
|
{
|
|
return Logistic(x, s) / ( LogisticCDF(b, s) - LogisticCDF(a, s) );
|
|
}
|
|
|
|
float TrimmedLogisticSampled(float u, float s, float a, float b)
|
|
{
|
|
float k = LogisticCDF(b, s) - LogisticCDF(a, s);
|
|
float x = -s * log(1 / (u * k + LogisticCDF(a, s)) - 1);
|
|
|
|
return clamp(x, a, b);
|
|
}
|
|
|
|
// Modified Bessel Function of the First Kind
|
|
float BesselI(float x)
|
|
{
|
|
float b = 0;
|
|
|
|
UNITY_UNROLL
|
|
for (int i = 0; i < 10; ++i)
|
|
{
|
|
const float f = FACTORIAL[i];
|
|
b += pow(abs(x), 2.0 * i) / (pow(4, i) * f * f);
|
|
}
|
|
|
|
return b;
|
|
}
|
|
|
|
float LogBesselI(float x)
|
|
{
|
|
float lnIO;
|
|
|
|
// The log of the bessel function may also be problematic for larger inputs (> ~12)...
|
|
if (x > 12)
|
|
{
|
|
// ...in which case it's approximated.
|
|
lnIO = x + 0.5 * (-log(TWO_PI) + log(rcp(x)) + rcp(8 * x));
|
|
}
|
|
else
|
|
{
|
|
lnIO = log(BesselI(x));
|
|
}
|
|
|
|
return lnIO;
|
|
}
|
|
|
|
void LongitudinalVarianceFromBeta(float beta, inout float v[PATH_MAX + 1])
|
|
{
|
|
// Ref: A Practical and Controllable Hair and Fur Model for Production Path Tracing Eq. 7
|
|
v[0] = Sq(0.726 * beta + 0.812 * Sq(beta) + 3.7 * pow(abs(beta), 20.0));
|
|
v[1] = 0.25 * v[0];
|
|
v[2] = 4.0 * v[0];
|
|
|
|
for (int p = 3; p <= PATH_MAX; p++)
|
|
v[p] = v[2];
|
|
}
|
|
|
|
// Ref: A Practical and Controllable Hair and Fur Model for Production Path Tracing Eq. 8
|
|
float LogisticScaleFromBeta(float beta)
|
|
{
|
|
return SQRT_PI_OVER_8 * ((0.265 * beta) + (1.194 * beta * beta) + (5.372 * pow(abs(beta), 22.0)));
|
|
}
|
|
|
|
void ApplyCuticleTilts(uint p, ReferenceAngles angles, ReferenceBSDFData data, out float sinThetaO, out float cosThetaO)
|
|
{
|
|
if (p == 0)
|
|
{
|
|
sinThetaO = angles.sinThetaO * data.cosAlpha[1] - angles.cosThetaO * data.sinAlpha[1];
|
|
cosThetaO = angles.cosThetaO * data.cosAlpha[1] + angles.sinThetaO * data.sinAlpha[1];
|
|
}
|
|
else if (p == 1)
|
|
{
|
|
sinThetaO = angles.sinThetaO * data.cosAlpha[0] + angles.cosThetaO * data.sinAlpha[0];
|
|
cosThetaO = angles.cosThetaO * data.cosAlpha[0] - angles.sinThetaO * data.sinAlpha[0];
|
|
}
|
|
else if (p == 2)
|
|
{
|
|
sinThetaO = angles.sinThetaO * data.cosAlpha[2] + angles.cosThetaO * data.sinAlpha[2];
|
|
cosThetaO = angles.cosThetaO * data.cosAlpha[2] - angles.sinThetaO * data.sinAlpha[2];
|
|
}
|
|
else
|
|
{
|
|
sinThetaO = angles.sinThetaO;
|
|
cosThetaO = angles.cosThetaO;
|
|
}
|
|
|
|
// Need to clamp for possible out of range after cuticle tilt application.
|
|
cosThetaO = abs(cosThetaO);
|
|
}
|
|
|
|
void GetAlphaScalesFromAlpha(float alpha, inout float sinAlpha[3], inout float cosAlpha[3])
|
|
{
|
|
sinAlpha[0] = sin(alpha);
|
|
cosAlpha[0] = SafeSqrt(1 - Sq(sinAlpha[0]));
|
|
|
|
// Get the lobe alpha terms by solving for the trigonometric double angle identities.
|
|
for (int i = 1; i < 3; ++i)
|
|
{
|
|
sinAlpha[i] = 2 * cosAlpha[i - 1] * sinAlpha[i - 1];
|
|
cosAlpha[i] = Sq(cosAlpha[i - 1]) - Sq(sinAlpha[i - 1]);
|
|
}
|
|
}
|
|
|
|
ReferenceBSDFData GetReferenceBSDFData(BSDFData bsdfData)
|
|
{
|
|
ReferenceBSDFData data;
|
|
ZERO_INITIALIZE(ReferenceBSDFData, data);
|
|
|
|
data.h = bsdfData.h;
|
|
data.gammaO = FastASin(data.h);
|
|
data.eta = 1.55;
|
|
data.sigmaA = bsdfData.absorption;
|
|
data.betaM = bsdfData.perceptualRoughness;
|
|
data.betaN = bsdfData.perceptualRoughnessRadial;
|
|
data.alpha = bsdfData.cuticleAngle;
|
|
data.s = LogisticScaleFromBeta(data.betaN);
|
|
|
|
// Clamp the absorption coefficient to prevent against some NaNs.
|
|
data.sigmaA = max(data.sigmaA, 1e-5);
|
|
|
|
// Fill the list of variances from beta
|
|
LongitudinalVarianceFromBeta(data.betaM, data.v);
|
|
|
|
// Fill the alpha terms for each lobe
|
|
GetAlphaScalesFromAlpha(data.alpha, data.sinAlpha, data.cosAlpha);
|
|
|
|
return data;
|
|
}
|
|
|
|
ReferenceAngles GetReferenceAngles(float3 wi, float3 wo)
|
|
{
|
|
ReferenceAngles angles;
|
|
ZERO_INITIALIZE(ReferenceAngles, angles);
|
|
|
|
angles.sinThetaI = wi.x;
|
|
angles.sinThetaO = wo.x;
|
|
|
|
// Small epsilon to suppress various compiler warnings + div-zero guard
|
|
const float epsilon = 1e-5;
|
|
|
|
angles.cosThetaI = SafeSqrt(1 - (Sq(angles.sinThetaI) + epsilon));
|
|
angles.cosThetaO = SafeSqrt(1 - (Sq(angles.sinThetaO) + epsilon));
|
|
|
|
angles.phiI = atan2(wi.z, wi.y + epsilon);
|
|
angles.phiO = atan2(wo.z, wo.y + epsilon);
|
|
angles.phi = angles.phiI - angles.phiO;
|
|
|
|
return angles;
|
|
}
|
|
|
|
// Quick utility to convert into a structure used in rasterizer.
|
|
CBSDF HairFtoCBSDF(float3 F)
|
|
{
|
|
CBSDF cbsdf;
|
|
ZERO_INITIALIZE(CBSDF, cbsdf);
|
|
|
|
// Transmission is baked into the model.
|
|
cbsdf.specR = F;
|
|
|
|
return cbsdf;
|
|
}
|