Evaluate the BRDF at a surface for a given outgoing (toward-eye) and incoming (toward-light) direction pair. Returns (brdf * N·L) which is what the light-transport equation actually needs, plus the PDF the BRDF sampler would have chosen for this incoming direction — used by MIS to weight NEE vs BRDF samples.
(surface: &SurfaceSample, view: Vec3, light: Vec3)
| 1717 | /// the PDF the BRDF sampler would have chosen for this incoming |
| 1718 | /// direction — used by MIS to weight NEE vs BRDF samples. |
| 1719 | fn evaluate_brdf(surface: &SurfaceSample, view: Vec3, light: Vec3) -> (Vec3, f32) { |
| 1720 | let n = surface.normal; |
| 1721 | let n_dot_v = n.dot(view).max(0.0); |
| 1722 | let n_dot_l = n.dot(light).max(0.0); |
| 1723 | if n_dot_l <= 0.0 || n_dot_v <= 0.0 { |
| 1724 | return (Vec3::ZERO, 0.0); |
| 1725 | } |
| 1726 | let h = (view + light).normalize_or_zero(); |
| 1727 | let n_dot_h = n.dot(h).max(0.0); |
| 1728 | let v_dot_h = view.dot(h).max(0.0); |
| 1729 | |
| 1730 | let f0 = Vec3::splat(0.04).lerp(surface.base_color, surface.metallic); |
| 1731 | let alpha = surface.roughness * surface.roughness; |
| 1732 | |
| 1733 | // Specular: F * D * Vsmith. Vsmith is the height-correlated form |
| 1734 | // that already includes the 1/(4·N·V·N·L) term. |
| 1735 | let f = fresnel_schlick(v_dot_h, f0); |
| 1736 | let d = d_ggx(n_dot_h, alpha); |
| 1737 | let vsmith = v_smith(n_dot_v, n_dot_l, alpha); |
| 1738 | let specular = f * d * vsmith; |
| 1739 | |
| 1740 | // Diffuse: Burley (already 1/pi-normalized). Scale by (1 - F) and |
| 1741 | // (1 - metallic) for energy conservation. |
| 1742 | let fd = burley_diffuse(n_dot_l, n_dot_v, v_dot_h, surface.roughness); |
| 1743 | let diffuse_albedo = surface.base_color * (1.0 - surface.metallic) * (Vec3::ONE - f); |
| 1744 | let diffuse = diffuse_albedo * fd; |
| 1745 | |
| 1746 | let brdf_cos = (specular + diffuse) * n_dot_l; |
| 1747 | |
| 1748 | // Rough approximation of the BRDF sampler's PDF for MIS. Uses the |
| 1749 | // same spec/diff split heuristic as `sample_brdf`. |
| 1750 | let spec_weight = (f0.x + f0.y + f0.z) / 3.0; |
| 1751 | let diff_weight = (1.0 - spec_weight) * (1.0 - surface.metallic); |
| 1752 | let total = spec_weight + diff_weight + 1e-6; |
| 1753 | let p_spec = spec_weight / total; |
| 1754 | let p_diff = 1.0 - p_spec; |
| 1755 | |
| 1756 | // Spec PDF (GGX VNDF): D * G1(V) * max(0, V·H) / (4 * N·V * V·H). |
| 1757 | // We just approximate D·cos/(4·V·H) since we only need a |
| 1758 | // reasonable ratio for MIS — exact matching isn't required. |
| 1759 | let pdf_spec = d * n_dot_h / (4.0 * v_dot_h + 1e-6); |
| 1760 | let pdf_diff = n_dot_l / std::f32::consts::PI; |
| 1761 | let pdf = p_spec * pdf_spec + p_diff * pdf_diff; |
| 1762 | |
| 1763 | (brdf_cos, pdf.max(0.0)) |
| 1764 | } |
| 1765 | |
| 1766 | /// Balance heuristic for MIS — weights a sample from strategy A by |
| 1767 | /// p_a / (p_a + p_b). Standard in PBRT / Eric Veach's thesis. |
no test coverage detected