// Port of glsl/pathtracing/diffuse_brdf.glsl + diffuse_btdf.glsl
// EON: "A practical energy-preserving rough diffuse BRDF" (Portsmouth, Kutz, Hill)
const constant1_FON: f32 = 0.5 - 2.0 / (3.0 * PI);
const constant2_FON: f32 = 2.0 / 3.0 - 28.0 / (15.0 * PI);
fn E_FON_exact(mu: f32, r: f32) -> f32 {
let AF = 1.0 / (1.0 + constant1_FON * r);
let BF = r * AF;
let Si = sqrt(1.0 - (mu * mu));
let G = Si * (acos(clamp(mu, -1.0, 1.0)) - Si * mu)
+ (2.0 / 3.0) * ((Si / mu) * (1.0 - (Si * Si * Si)) - Si);
return AF + (BF / PI) * G;
}
fn E_FON_approx(mu: f32, r: f32) -> f32 {
let mucomp = 1.0 - mu;
let mucomp2 = mucomp * mucomp;
let Gcoeffs = mat2x2<f32>(vec2<f32>(0.0571085289, -0.332181442), vec2<f32>(0.491881867, 0.0714429953));
let GoverPi = dot(Gcoeffs * vec2<f32>(mucomp, mucomp2), vec2<f32>(1.0, mucomp2));
return (1.0 + r * GoverPi) / (1.0 + constant1_FON * r);
}
fn f_EON(rho: vec3<f32>, r: f32, wi_local: vec3<f32>, wo_local: vec3<f32>, exact: bool) -> vec3<f32> {
let mu_i = wi_local.z;
let mu_o = wo_local.z;
let s = dot(wi_local, wo_local) - mu_i * mu_o;
let sovertF = select(s, s / max(mu_i, mu_o), s > 0.0);
let AF = 1.0 / (1.0 + constant1_FON * r);
let f_ss = (rho / PI) * AF * (1.0 + r * sovertF);
let EFo = select(E_FON_approx(mu_o, r), E_FON_exact(mu_o, r), exact);
let EFi = select(E_FON_approx(mu_i, r), E_FON_exact(mu_i, r), exact);
let avgEF = AF * (1.0 + constant2_FON * r);
let rho_ms = (rho * rho) * avgEF / (vec3<f32>(1.0) - rho * (1.0 - avgEF));
let eps = 1.0e-7;
let f_ms = (rho_ms / PI) * max(eps, 1.0 - EFo) * max(eps, 1.0 - EFi) / max(eps, 1.0 - avgEF);
return f_ss + f_ms;
}
fn E_EON(rho: vec3<f32>, r: f32, wi_local: vec3<f32>, exact: bool) -> vec3<f32> {
let mu_i = wi_local.z;
let AF = 1.0 / (1.0 + constant1_FON * r);
let EF = select(E_FON_approx(mu_i, r), E_FON_exact(mu_i, r), exact);
let avgEF = AF * (1.0 + constant2_FON * r);
let rho_ms = (rho * rho) * avgEF / (vec3<f32>(1.0) - rho * (1.0 - avgEF));
return rho * EF + rho_ms * (1.0 - EF);
}
struct LtcTerms {
a: f32,
b: f32,
c: f32,
d: f32,
};
fn ltc_terms(mu: f32, r: f32) -> LtcTerms {
var t: LtcTerms;
t.a = 1.0 + r * (0.303392 + (-0.518982 + 0.111709 * mu) * mu + (-0.276266 + 0.335918 * mu) * r);
t.b = r * (-1.16407 + 1.15859 * mu + (0.150815 - 0.150105 * mu) * r) / (mu * mu * mu - 1.43545);
t.c = 1.0 + (0.20013 + (-0.506373 + 0.261777 * mu) * mu) * r;
t.d = ((0.540852 + (-1.01625 + 0.475392 * mu) * mu) * r) / (-1.0743 + mu * (0.0725628 + mu));
return t;
}
fn cltc_sample(wo_local: vec3<f32>, r: f32, u1: f32, u2: f32) -> vec4<f32> {
let t = ltc_terms(wo_local.z, r);
let R = sqrt(u1);
let phi = 2.0 * PI * u2;
var x = R * cos(phi);
let y = R * sin(phi);
let vz = 1.0 / sqrt(t.d * t.d + 1.0);
let s = 0.5 * (1.0 + vz);
x = -mix(sqrt(1.0 - y * y), x, s);
let wh = vec3<f32>(x, y, sqrt(max(1.0 - (x * x + y * y), 0.0)));
let pdf_wh = wh.z / (PI * s);
var wi = vec3<f32>(t.a * wh.x + t.b * wh.z, t.c * wh.y, t.d * wh.x + wh.z);
let len = length(wi);
let detM = t.c * (t.a - t.b * t.d);
let pdf_wi = pdf_wh * len * len * len / detM;
let fromLTC = orthonormal_basis_ltc(wo_local);
wi = normalize(fromLTC * wi);
return vec4<f32>(wi, pdf_wi);
}
fn cltc_pdf(wo_local: vec3<f32>, wi_local: vec3<f32>, r: f32) -> f32 {
let toLTC = transpose(orthonormal_basis_ltc(wo_local));
let wi = toLTC * wi_local;
let t = ltc_terms(wo_local.z, r);
let detM = t.c * (t.a - t.b * t.d);
let wh = vec3<f32>(t.c * (wi.x - t.b * wi.z), (t.a - t.b * t.d) * wi.y, -t.c * (t.d * wi.x - t.a * wi.z));
let lensq = dot(wh, wh);
let vz = 1.0 / sqrt(t.d * t.d + 1.0);
let s = 0.5 * (1.0 + vz);
let pdf = sqr(detM / lensq) * max(wh.z, 0.0) / (PI * s);
return pdf;
}
fn uniform_lobe_sample(u1: f32, u2: f32) -> vec3<f32> {
let sinT = sqrt(1.0 - u1 * u1);
let phi = 2.0 * PI * u2;
return vec3<f32>(sinT * cos(phi), sinT * sin(phi), u1);
}
fn sample_EON(wo_local: vec3<f32>, r: f32, u1_in: f32, u2: f32) -> vec4<f32> {
let mu = wo_local.z;
let P_u = pow(r, 0.1) * (0.162925 + mu * (-0.372058 + (0.538233 - 0.290822 * mu) * mu));
let P_c = 1.0 - P_u;
var wi: vec4<f32>;
var pdf_c: f32;
var u1 = u1_in;
if (u1 <= P_u) {
u1 = u1 / P_u;
let d = uniform_lobe_sample(u1, u2);
wi = vec4<f32>(d, 0.0);
pdf_c = cltc_pdf(wo_local, d, r);
} else {
u1 = (u1 - P_u) / P_c;
wi = cltc_sample(wo_local, r, u1, u2);
pdf_c = wi.w;
}
let pdf_u = 1.0 / (2.0 * PI);
wi.w = P_u * pdf_u + P_c * pdf_c;
return wi;
}
fn pdf_EON(wo_local: vec3<f32>, wi_local: vec3<f32>, r: f32) -> f32 {
let mu = wo_local.z;
let P_u = pow(r, 0.1) * (0.162925 + mu * (-0.372058 + (0.538233 - 0.290822 * mu) * mu));
let P_c = 1.0 - P_u;
let pdf_c = cltc_pdf(wo_local, wi_local, r);
let pdf_u = 1.0 / (2.0 * PI);
return P_u * pdf_u + P_c * pdf_c;
}
// ---------------------------------------------------------------------------
// diffuse BRDF
// ---------------------------------------------------------------------------
// f = diffuse_w_color * f_EON(rho = base_color) + diffuse_w_white * f_EON(rho = 1)
fn diffuse_lobe_f(winputL: vec3<f32>, woutputL: vec3<f32>) -> vec3<f32> {
var f = vec3<f32>(0.0);
if (maxComponent(diffuse_w_color) > 0.0) {
f += diffuse_w_color * f_EON(base_color, base_diffuse_roughness, winputL, woutputL, true);
}
if (maxComponent(diffuse_w_white) > 0.0) {
f += diffuse_w_white * f_EON(vec3<f32>(1.0), base_diffuse_roughness, winputL, woutputL, true);
}
return f;
}
fn diffuse_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; }
out.pdf = pdf_EON(winputL, woutputL, base_diffuse_roughness);
out.f = diffuse_lobe_f(winputL, woutputL);
return out;
}
fn diffuse_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 u1 = rand();
let u2 = rand();
let wiP = sample_EON(winputL, base_diffuse_roughness, u1, u2);
out.woutputL = wiP.xyz;
out.pdf = wiP.w;
out.f = diffuse_lobe_f(winputL, out.woutputL);
return out;
}
fn diffuse_brdf_albedo(winputL: vec3<f32>) -> vec3<f32> {
if (winputL.z < DENOM_TOLERANCE) { return vec3<f32>(0.0); }
return diffuse_w_color * E_EON(base_color, base_diffuse_roughness, winputL, true)
+ diffuse_w_white * E_EON(vec3<f32>(1.0), base_diffuse_roughness, winputL, true);
}
// ---------------------------------------------------------------------------
// diffuse BTDF: EON lobe flipped into the negative hemisphere (thin-walled SSS)
// ---------------------------------------------------------------------------
fn diffuse_btdf_evaluate(winputL: vec3<f32>, woutputL_in: vec3<f32>) -> BsdfEval {
var out: BsdfEval;
out.f = vec3<f32>(0.0);
let reflected = woutputL_in.z * winputL.z > 0.0;
if (reflected) {
out.pdf = 0.0; // (viewer: 1.0 — see specular_btdf.wgsl)
return out;
}
var woutputL = woutputL_in;
woutputL.z *= -1.0;
out.pdf = pdf_EON(winputL, woutputL, base_diffuse_roughness);
out.f = f_EON(vec3<f32>(1.0), base_diffuse_roughness, winputL, woutputL, true);
return out;
}
fn diffuse_btdf_sample(winputL: vec3<f32>) -> BsdfSample {
var out: BsdfSample;
let u1 = rand();
let u2 = rand();
let wiP = sample_EON(winputL, base_diffuse_roughness, u1, u2);
var woutputL = wiP.xyz;
out.pdf = wiP.w;
out.f = f_EON(vec3<f32>(1.0), base_diffuse_roughness, winputL, woutputL, true);
woutputL.z *= -1.0;
out.woutputL = woutputL;
return out;
}
fn diffuse_btdf_albedo(winputL: vec3<f32>) -> vec3<f32> {
return E_EON(vec3<f32>(1.0), base_diffuse_roughness, winputL, true);
}