pbrt_r3/integrators/
volpath.rs

1use crate::core::prelude::*;
2
3use std::sync::Arc;
4use std::sync::RwLock;
5
6thread_local!(static PATH_LENGTH: StatIntDistribution = StatIntDistribution::new("Integrator/Path length"));
7thread_local!(static VOLUME_INTERACTIONS: StatCounter = StatCounter::new("Integrator/Volume interactions"));
8thread_local!(static SURFACE_INTERACTIONS: StatCounter = StatCounter::new("Integrator/Surface interactions"));
9
10pub struct VolPathIntegrator {
11    pub base: BaseSamplerIntegrator,
12    pub max_depth: u32,
13    pub rr_threshold: Float,
14    pub light_sample_strategy: String,
15    pub light_distribution: Option<Arc<dyn LightDistribution>>,
16}
17
18impl VolPathIntegrator {
19    pub fn new(
20        max_depth: u32,
21        camera: &Arc<dyn Camera>,
22        sampler: &Arc<RwLock<dyn Sampler>>,
23        pixel_bounds: &Bounds2i,
24        rr_threshold: Float,
25        light_sample_strategy: String,
26    ) -> Self {
27        let base = BaseSamplerIntegrator::new(camera, sampler, pixel_bounds);
28        VolPathIntegrator {
29            base,
30            max_depth,
31            rr_threshold,
32            light_sample_strategy,
33            light_distribution: None,
34        }
35    }
36}
37
38impl Integrator for VolPathIntegrator {
39    fn render(&mut self, scene: &Scene) {
40        BaseSamplerIntegrator::render(self, scene);
41    }
42    fn get_camera(&self) -> Arc<dyn Camera> {
43        self.base.camera.clone()
44    }
45}
46
47unsafe impl Sync for VolPathIntegrator {}
48
49impl SamplerIntegrator for VolPathIntegrator {
50    fn preprocess(&mut self, scene: &Scene, _sampler: &mut dyn Sampler) {
51        match create_light_sample_distribution(&self.light_sample_strategy, scene) {
52            Ok(distrib) => {
53                self.light_distribution = Some(distrib);
54            }
55            Err(e) => {
56                println!("{:}", e);
57            }
58        }
59    }
60
61    fn li(
62        &self,
63        r: &RayDifferential,
64        scene: &Scene,
65        sampler: &mut dyn Sampler,
66        arena: &mut MemoryArena,
67        _depth: i32,
68    ) -> Spectrum {
69        let _p = ProfilePhase::new(Prof::SamplerIntegratorLi);
70
71        let rr_threshold = self.rr_threshold;
72        let mut l = Spectrum::zero();
73        let mut beta = Spectrum::one();
74        let mut ray = r.clone();
75        let mut specular_bounce = false;
76
77        let light_distr = self.light_distribution.clone();
78        let light_distr = light_distr.unwrap();
79
80        // Added after book publication: etaScale tracks the accumulated effect
81        // of radiance scaling due to rays passing through refractive
82        // boundaries (see the derivation on p. 527 of the third edition). We
83        // track this value in order to remove it from beta when we apply
84        // Russian roulette; this is worthwhile, since it lets us sometimes
85        // avoid terminating refracted rays that are about to be refracted back
86        // out of a medium and thus have their beta value increased.
87        let mut eta_scale = 1.0;
88
89        let mut bounces = 0;
90        loop {
91            // Find next path vertex and accumulate contribution
92
93            // Intersect _ray_ with scene and store intersection in _isect_
94            let mut found_intersection = scene.intersect(&ray.ray);
95
96            let mut mi = None;
97            // Sample the participating medium, if present
98            if let Some(medium) = ray.ray.medium.as_ref() {
99                //println!("MediumInteraction: {:?}", medium);
100                let (spec, m) = medium.sample(&ray.ray, sampler, arena);
101                if let Some(mut m) = m {
102                    m.medium_interface = MediumInterface::from(medium);
103                    mi = Some(m);
104                }
105                beta *= spec;
106            }
107            if beta.is_black() {
108                break;
109            }
110
111            if let Some(mi) = mi {
112                // Terminate path if ray escaped or _maxDepth_ was reached
113                if bounces >= self.max_depth {
114                    break;
115                }
116
117                VOLUME_INTERACTIONS.with(|c| c.inc());
118
119                // Handle scattering at point in medium for volumetric path tracer
120                let light_distrib = light_distr.lookup(&mi.p);
121                let mit = Interaction::from(&mi);
122                let ld = beta
123                    * uniform_sample_one_light(
124                        &mit,
125                        scene,
126                        arena,
127                        sampler,
128                        true,
129                        Some(&light_distrib),
130                    );
131                l += ld;
132                let wo = -ray.ray.d;
133                let (_pdf, wi) = mi.phase.sample_p(&wo, &sampler.get_2d());
134                ray = mi.spawn_ray(&wi).into();
135                specular_bounce = false;
136            } else {
137                SURFACE_INTERACTIONS.with(|c| c.inc());
138                // Handle scattering at point on surface for volumetric path tracer
139
140                // Possibly add emitted light at intersection
141                if bounces == 0 || specular_bounce {
142                    // Add emitted light at path vertex or from the environment
143                    if let Some(isect) = found_intersection.as_ref() {
144                        l += beta * isect.le(&-ray.ray.d);
145                    } else {
146                        for light in scene.infinite_lights.iter() {
147                            l += beta * light.le(&ray);
148                        }
149                    }
150                }
151
152                // Terminate path if ray escaped or _maxDepth_ was reached
153                if found_intersection.is_none() || bounces >= self.max_depth {
154                    break;
155                }
156
157                // Compute scattering functions and skip over medium boundaries
158                let isect = found_intersection.as_mut().unwrap();
159                //if let Some(isect) = found_intersection.as_mut() {
160                isect.compute_scattering_functions(&ray, arena, TransportMode::Radiance, true);
161                if isect.bsdf.is_none() {
162                    ray = isect.spawn_ray(&ray.ray.d).into();
163                    continue;
164                }
165                let distrib = light_distr.lookup(&isect.p);
166
167                // Sample illumination from lights to find path contribution.
168                {
169                    let tisect = Interaction::from(isect as &SurfaceInteraction);
170                    let ld = beta
171                        * uniform_sample_one_light(
172                            &tisect,
173                            scene,
174                            arena,
175                            sampler,
176                            true,
177                            Some(&distrib),
178                        );
179                    l += ld;
180                }
181
182                // Sample BSDF to get new path direction
183                let bsdf = isect.bsdf.as_ref().unwrap();
184                let wo = -ray.ray.d;
185                if let Some((f, wi, pdf, flags)) = bsdf.sample_f(&wo, &sampler.get_2d(), BSDF_ALL) {
186                    if f.is_black() || pdf == 0.0 {
187                        break;
188                    }
189                    beta *= f * (wi.abs_dot(&isect.shading.n) / pdf);
190                    //beta *= 0.1;
191
192                    specular_bounce = (flags & BSDF_SPECULAR) != 0;
193                    let flag_s = (flags & BSDF_SPECULAR) != 0;
194                    let flag_t = (flags & BSDF_TRANSMISSION) != 0;
195                    if flag_s && flag_t {
196                        let eta = bsdf.eta;
197                        // Update the term that tracks radiance scaling for refraction
198                        // depending on whether the ray is entering or leaving the
199                        // medium.
200                        if Vector3f::dot(&wo, &isect.n) > 0.0 {
201                            eta_scale *= eta * eta;
202                        } else {
203                            eta_scale *= 1.0 / (eta * eta);
204                        }
205                    }
206
207                    ray = isect.spawn_ray(&wi).into();
208
209                    // Account for subsurface scattering, if applicable
210                    if let Some(bssrdf) = isect.bssrdf.as_ref() {
211                        if flag_t {
212                            // Importance sample the BSSRDF
213                            if let Some((s, pi, pdf)) = bssrdf.as_ref().sample_s(
214                                scene,
215                                sampler.get_1d(),
216                                &sampler.get_2d(),
217                                arena,
218                            ) {
219                                if s.is_black() || pdf == 0.0 {
220                                    break;
221                                }
222                                beta *= s / pdf;
223                                //beta *= 0.1;
224
225                                // Account for the direct subsurface scattering component
226                                let distrib = light_distr.lookup(&pi.p);
227                                let pit = Interaction::from(&pi);
228                                l += beta
229                                    * uniform_sample_one_light(
230                                        &pit,
231                                        scene,
232                                        arena,
233                                        sampler,
234                                        true,
235                                        Some(&distrib),
236                                    );
237
238                                // Account for the indirect subsurface scattering component
239                                if let Some(pi_bsdf) = pi.bsdf.as_ref() {
240                                    if let Some((f, wi, pdf, flags)) =
241                                        pi_bsdf.sample_f(&pi.wo, &sampler.get_2d(), BSDF_ALL)
242                                    {
243                                        if f.is_black() || pdf == 0.0 {
244                                            break;
245                                        }
246                                        beta *= f * (wi.abs_dot(&pi.shading.n) / pdf);
247                                        //beta *= 0.1;
248                                        specular_bounce = (flags & BSDF_SPECULAR) != 0;
249                                        ray = pi.spawn_ray(&wi).into();
250                                    } else {
251                                        break;
252                                    }
253                                } else {
254                                    break;
255                                }
256                            }
257                        }
258                    }
259                } else {
260                    break;
261                }
262            }
263
264            // Possibly terminate the path with Russian roulette.
265            // Factor out radiance scaling due to refraction in rrBeta.
266            let rr_beta = beta * eta_scale;
267            if rr_beta.max_component_value() < rr_threshold && bounces > 3 {
268                let q = Float::max(0.05, 1.0 - rr_beta.max_component_value());
269                if sampler.get_1d() < q {
270                    break;
271                }
272                beta /= 1.0 - q;
273                assert!(beta.max_component_value().is_finite());
274            }
275
276            bounces += 1;
277        } // end loop
278
279        PATH_LENGTH.with(|c| c.add(bounces as u64));
280
281        return l;
282    }
283
284    fn get_sampler(&self) -> Arc<RwLock<dyn Sampler>> {
285        return self.base.sampler.clone();
286    }
287
288    fn get_pixel_bounds(&self) -> Bounds2i {
289        return self.base.pixel_bounds;
290    }
291}
292
293pub fn create_volpath_integrator(
294    params: &ParamSet,
295    sampler: &Arc<RwLock<dyn Sampler>>,
296    camera: &Arc<dyn Camera>,
297) -> Result<Arc<RwLock<dyn Integrator>>, PbrtError> {
298    let max_depth = params.find_one_int("maxdepth", 5) as u32;
299
300    let pixel_bounds = camera.get_film().read().unwrap().get_sample_bounds();
301
302    let rr_threshold = params.find_one_float("rrthreshold", 1.0);
303    let light_sample_strategy = params.find_one_string("lightsamplestrategy", "spatial");
304    return Ok(Arc::new(RwLock::new(VolPathIntegrator::new(
305        max_depth,
306        camera,
307        sampler,
308        &pixel_bounds,
309        rr_threshold,
310        light_sample_strategy,
311    ))));
312}