Skip to main content

pbrt_r3/lights/
diffuse.rs

1use crate::core::prelude::*;
2
3use std::sync::Arc;
4
5pub struct DiffuseAreaLight {
6    base: BaseLight,
7    lemit: Spectrum,
8    shape: Arc<dyn Shape>,
9    two_sided: bool,
10    area: Float,
11}
12
13impl DiffuseAreaLight {
14    pub fn new(
15        light_to_world: &Transform,
16        medium_interface: &MediumInterface,
17        le: &Spectrum,
18        n_samples: u32,
19        shape: &Arc<dyn Shape>,
20        two_sided: bool,
21    ) -> Self {
22        let base = BaseLight::new(
23            LightFlags::Area as u32,
24            light_to_world,
25            medium_interface,
26            n_samples,
27        );
28        let lemit = *le;
29        let shape = shape.clone();
30        let area = shape.area();
31        DiffuseAreaLight {
32            base,
33            lemit,
34            shape,
35            two_sided,
36            area,
37        }
38    }
39
40    fn sample_wo(&self, _u1: &Vector2f, u2: &Vector2f) -> (Vector3f, Float) {
41        if self.two_sided {
42            let mut u = *u2;
43            // Choose a side to sample and then remap u[0] to [0,1] before
44            // applying cosine-weighted hemisphere sampling for the chosen side.
45            let mut w;
46            if u[0] < 0.5 {
47                u.x = Float::min(u[0] * 2.0, ONE_MINUS_EPSILON);
48                w = cosine_sample_hemisphere(&u);
49            } else {
50                u.x = Float::min((u[0] - 0.5) * 2.0, ONE_MINUS_EPSILON);
51                w = cosine_sample_hemisphere(&u);
52                w.z *= -1.0;
53            };
54            let pdf = 0.5 * cosine_hemisphere_pdf(Float::abs(w.z));
55            return (w, pdf);
56        } else {
57            let w = cosine_sample_hemisphere(u2);
58            let pdf = cosine_hemisphere_pdf(w.z);
59            return (w, pdf);
60        }
61    }
62}
63
64impl Light for DiffuseAreaLight {
65    fn power(&self) -> Spectrum {
66        let n = if self.two_sided { 2.0 } else { 1.0 };
67        return self.lemit * (n * self.area * PI);
68    }
69
70    fn sample_li(
71        &self,
72        inter: &Interaction,
73        u: &Point2f,
74    ) -> Option<(Spectrum, Vector3f, Float, VisibilityTester)> {
75        let _p = ProfilePhase::new(Prof::LightSample);
76
77        let shape = self.shape.as_ref();
78        let (mut p_shape, pdf) = shape.sample_from(inter, u)?;
79        p_shape.set_medium_interface(&self.base.medium_interface);
80        if pdf <= 0.0 || (p_shape.get_p() - inter.get_p()).length_squared() <= 0.0 {
81            return None;
82        }
83        let wi = (p_shape.get_p() - inter.get_p()).normalize();
84        let vis = VisibilityTester::from((inter, &p_shape));
85        let spec = self.l(&p_shape, &-wi);
86        return Some((spec, wi, pdf, vis));
87    }
88
89    fn pdf_li(&self, inter: &Interaction, wi: &Vector3f) -> Float {
90        let _p = ProfilePhase::new(Prof::LightPdf);
91
92        let shape = self.shape.as_ref();
93        return shape.pdf_from(inter, wi);
94    }
95
96    fn sample_le(
97        &self,
98        u1: &Point2f,
99        u2: &Point2f,
100        _time: Float,
101    ) -> Option<(Spectrum, Ray, Normal3f, Float, Float)> {
102        let _p = ProfilePhase::new(Prof::LightSample);
103
104        let shape = self.shape.as_ref();
105        let (mut p_shape, pdf_pos) = shape.sample(u1)?;
106        p_shape.set_medium_interface(&self.base.medium_interface);
107        let n = p_shape.get_n();
108
109        // Sample a cosine-weighted outgoing direction _w_ for area light
110        let (w, pdf_dir) = self.sample_wo(u1, u2);
111        let (v1, v2) = coordinate_system(&n);
112        let w = w.x * v1 + w.y * v2 + w.z * n;
113        let ray = p_shape.spawn_ray(&w);
114        let spec = self.l(&p_shape, &w);
115        return Some((spec, ray, n, pdf_pos, pdf_dir));
116    }
117
118    fn pdf_le(&self, ray: &Ray, n: &Normal3f) -> Option<(Float, Float)> {
119        let _p = ProfilePhase::new(Prof::LightPdf);
120
121        let shape = self.shape.as_ref();
122        let n = *n;
123        let wo = Vector3f::from(n);
124        let medium_interface = self.base.medium_interface.clone();
125        let it = Interaction::from((ray.o, n, Vector3f::zero(), wo, ray.time, medium_interface));
126        let pdf_pos = shape.pdf(&it);
127        let pdf_dir = if self.two_sided {
128            0.5 * cosine_hemisphere_pdf(Vector3::abs_dot(&n, &ray.d))
129        } else {
130            // pbrt-r3: Clamp to 0.0 in case of numerical error.
131            //cosine_hemisphere_pdf(Vector3f::dot(n, &ray.d))
132            cosine_hemisphere_pdf(Vector3f::dot(&n, &ray.d)).max(0.0)
133            // pbrt-r3:
134        };
135        if pdf_pos > 0.0 || pdf_dir > 0.0 {
136            return Some((pdf_pos, pdf_dir));
137        } else {
138            return None;
139        }
140    }
141
142    fn get_light_flags(&self) -> u32 {
143        return self.base.flags;
144    }
145
146    fn get_sample_count(&self) -> u32 {
147        return self.base.n_samples;
148    }
149
150    fn as_area_light(&self) -> Option<&dyn AreaLight> {
151        return Some(self);
152    }
153}
154
155impl AreaLight for DiffuseAreaLight {
156    fn l(&self, inter: &Interaction, w: &Vector3f) -> Spectrum {
157        if self.two_sided || Vector3f::dot(&inter.get_n(), w) > 0.0 {
158            return self.lemit;
159        } else {
160            return Spectrum::zero();
161        }
162    }
163}
164
165unsafe impl Sync for DiffuseAreaLight {}
166unsafe impl Send for DiffuseAreaLight {}
167
168pub fn create_diffuse_area_light(
169    light2world: &Transform,
170    medium: &Option<Arc<dyn Medium>>,
171    params: &ParamSet,
172    shape: &Arc<dyn Shape>,
173) -> Result<Arc<dyn Light>, PbrtError> {
174    let l = params.find_one_spectrum("L", &Spectrum::one());
175    let sc = params.find_one_spectrum("scale", &Spectrum::one());
176    let mi = MediumInterface::from(medium);
177    let mut n_samples = params.find_one_int("nsamples", 1) as u32;
178    let two_sided = params.find_one_bool("twosided", false);
179    {
180        let options = PbrtOptions::get();
181        if options.quick_render {
182            n_samples = (n_samples / 4).max(1);
183        }
184    }
185    return Ok(Arc::new(DiffuseAreaLight::new(
186        light2world,
187        &mi,
188        &(l * sc),
189        n_samples,
190        shape,
191        two_sided,
192    )));
193}