// Port of glsl/pathtracing/fuzz_brdf.glsl (Zeltner sheen via LTC).
struct BsdfEval {
f: vec3<f32>,
pdf: f32,
/// dielectric lobes: Fresnel reflectance at the microfacet (used for joint R/T sampling)
fresnel: f32,
};
struct BsdfSample {
f: vec3<f32>,
woutputL: vec3<f32>,
pdf: f32,
fresnel: f32,
};
fn zeltner_sheen_dir_albedo(x: f32, y: f32) -> f32 {
let s = y * (0.0206607 + 1.58491 * y) / (0.0379424 + y * (1.32227 + y));
let m = y * (-0.193854 + y * (-1.14885 + y * (1.7932 - 0.95943 * y * y))) / (0.046391 + y);
let o = y * (0.000654023 + (-0.0207818 + 0.119681 * y) * y) / (1.26264 + y * (-1.92021 + y));
return exp(-0.5 * sqr((x - m) / s)) / (s * sqrt(2.0 * PI)) + o;
}
fn zeltner_sheen_ltc_aInv(x: f32, y: f32) -> f32 {
return (2.58126 * x + 0.813703 * y) * y / (1.0 + 0.310327 * x * x + 2.60994 * x * y);
}
fn zeltner_sheen_ltc_bInv(x: f32, y: f32) -> f32 {
return sqrt(1.0 - x) * (y - 1.0) * y * y * y / (0.0000254053 + 1.71228 * x - 1.71506 * x * y + 1.34174 * y * y);
}
fn fuzz_brdf_evaluate(winputL: vec3<f32>, woutputL: vec3<f32>) -> BsdfEval {
var out: BsdfEval;
out.f = vec3<f32>(0.0);
out.pdf = 0.0;
if (winputL.z < DENOM_TOLERANCE || woutputL.z < DENOM_TOLERANCE) { return out; }
let NdotV = min(winputL.z, 1.0);
let roughness = clamp(fuzz_roughness, 0.01, 1.0);
let toLTC = transpose(orthonormal_basis_ltc(winputL));
let w = toLTC * woutputL;
let aInv = zeltner_sheen_ltc_aInv(NdotV, roughness);
let bInv = zeltner_sheen_ltc_bInv(NdotV, roughness);
let wo = vec3<f32>(aInv * w.x + bInv * w.z, aInv * w.y, w.z);
let lenSqr = dot(wo, wo);
let jacobian = sqr(aInv / lenSqr);
out.pdf = max(wo.z, 0.0) * RECIPROCAL_PI * jacobian;
let albedo = zeltner_sheen_dir_albedo(NdotV, roughness);
let NdotL = max(abs(woutputL.z), FLT_EPSILON);
out.f = fuzz_color * albedo * out.pdf / NdotL;
return out;
}
fn fuzz_brdf_sample(winputL: vec3<f32>) -> BsdfSample {
var out: BsdfSample;
out.f = vec3<f32>(0.0);
out.woutputL = vec3<f32>(0.0, 0.0, 1.0);
out.pdf = 0.0;
if (winputL.z < DENOM_TOLERANCE) { return out; }
let NdotV = min(winputL.z, 1.0);
let roughness = clamp(fuzz_roughness, 0.01, 1.0);
let wo = sampleHemisphereCosineWeighted().dir;
let aInv = zeltner_sheen_ltc_aInv(NdotV, roughness);
let bInv = zeltner_sheen_ltc_bInv(NdotV, roughness);
var w = vec3<f32>(wo.x / aInv - wo.z * bInv / aInv, wo.y / aInv, wo.z);
let lenSqr = dot(w, w);
w *= inverseSqrt(lenSqr);
let jacobian = sqr(aInv * lenSqr);
out.pdf = max(w.z, 0.0) * RECIPROCAL_PI * jacobian;
let fromLTC = orthonormal_basis_ltc(winputL);
out.woutputL = fromLTC * w;
let albedo = zeltner_sheen_dir_albedo(NdotV, roughness);
out.f = fuzz_color * albedo * RECIPROCAL_PI * jacobian;
return out;
}
fn fuzz_brdf_albedo(winputL: vec3<f32>) -> vec3<f32> {
return vec3<f32>(zeltner_sheen_dir_albedo(winputL.z, fuzz_roughness));
}