(
scenario: &Scenario,
primary: Ray,
max_bounces: u32,
rng: &mut Rng,
)
| 1770 | } |
| 1771 | |
| 1772 | fn trace_path( |
| 1773 | scenario: &Scenario, |
| 1774 | primary: Ray, |
| 1775 | max_bounces: u32, |
| 1776 | rng: &mut Rng, |
| 1777 | ) -> Vec3 { |
| 1778 | let mut radiance = Vec3::ZERO; |
| 1779 | let mut throughput = Vec3::ONE; |
| 1780 | let mut ray = primary; |
| 1781 | |
| 1782 | // Tracks the BRDF PDF used to generate the CURRENT ray — needed |
| 1783 | // to apply MIS when the ray escapes to the environment. On the |
| 1784 | // primary ray nothing sampled it via BRDF, so we mark it with a |
| 1785 | // sentinel of None (meaning "full weight, no MIS"). |
| 1786 | let mut last_brdf_pdf: Option<f32> = None; |
| 1787 | |
| 1788 | for bounce in 0..max_bounces { |
| 1789 | let hit = match intersect_bvh(&ray, scenario.scene, scenario.bvh) { |
| 1790 | Some(h) => h, |
| 1791 | None => { |
| 1792 | // BRDF-sampled ray escaped into the environment. Weight |
| 1793 | // with MIS against the env-importance-sampler we'd use |
| 1794 | // at the previous hit. The primary ray gets full weight. |
| 1795 | let env_radiance = scenario.environment.sample(ray.direction); |
| 1796 | let weight = match last_brdf_pdf { |
| 1797 | Some(brdf_pdf) => { |
| 1798 | let env_pdf = scenario.environment.pdf(ray.direction); |
| 1799 | mis_balance(brdf_pdf, env_pdf) |
| 1800 | } |
| 1801 | None => 1.0, |
| 1802 | }; |
| 1803 | radiance += throughput * env_radiance * weight; |
| 1804 | break; |
| 1805 | } |
| 1806 | }; |
| 1807 | |
| 1808 | let mut surface = surface_from_hit(scenario.scene, &ray, &hit); |
| 1809 | let view = -ray.direction; |
| 1810 | if surface.normal.dot(view) < 0.0 { |
| 1811 | surface.normal = -surface.normal; |
| 1812 | } |
| 1813 | |
| 1814 | // Emissive surfaces contribute directly. No NEE toward glTF |
| 1815 | // emissive surfaces yet — we treat them as diffuse light |
| 1816 | // that's only hit by BRDF paths. Phase 5 can add emissive |
| 1817 | // surface importance sampling if the reference needs it for |
| 1818 | // small area lights. |
| 1819 | radiance += throughput * surface.emissive; |
| 1820 | |
| 1821 | let shadow_origin = surface.position + surface.normal * 1e-4; |
| 1822 | |
| 1823 | // --- NEE A: delta sun light. MIS weight is 1.0 because no |
| 1824 | // continuous sampler can hit a zero-extent light; the |
| 1825 | // BRDF sampler cannot compete with a delta direction. |
| 1826 | if let Some(sun) = scenario.sun { |
| 1827 | let l = sun.direction_to_light; |
| 1828 | let n_dot_l = surface.normal.dot(l); |
| 1829 | if n_dot_l > 0.0 |
no test coverage detected