rust_pathtracer/
tracer.rs

1use rayon::{slice::ParallelSliceMut, iter::{IndexedParallelIterator, ParallelIterator}};
2use rand::{thread_rng, Rng, rngs::ThreadRng};
3use crate::prelude::*;
4
5pub struct Tracer {
6    eps                 : F,
7
8    scene               : Box<dyn Scene>,
9}
10
11impl Tracer {
12
13    pub fn new(scene: Box<dyn Scene>) -> Self {
14        Self {
15
16            eps         : 0.005,
17            scene,
18        }
19    }
20
21    /// Render one frame and accumulate into the pixels buffer
22    pub fn render(&mut self, buffer: &mut ColorBuffer) {
23
24        const LINES: usize = 1;
25
26        let width = buffer.width;
27        let height = buffer.height as F;
28
29        buffer.pixels
30            .par_rchunks_exact_mut(width * LINES * 4)
31            .enumerate()
32            .for_each(|(j, line)| {
33                for (i, pixel) in line.chunks_exact_mut(4).enumerate() {
34                    let i = j * width * LINES + i;
35
36                    let x = (i % width) as F;
37                    let y = height - (i / width) as F;
38
39                    let xx = (x as F) / width as F;
40                    let yy = ((y) as F) / height as F;
41
42                    // Camera
43
44                    let mut rng: ThreadRng = thread_rng();
45                    let cam_offset = F2::new(rng.gen(), rng.gen());
46                    let coord = F2::new(xx, 1.0 - yy);
47                    let mut ray = self.scene.camera().gen_ray(coord, cam_offset, width as F, height);
48
49                    // -
50
51                    let mut radiance = F3::new(0.0, 0.0, 0.0);
52                    let mut throughput = F3::new(1.0, 1.0, 1.0);
53                    let mut state = State::new();
54                    let mut light_sample = LightSampleRec::new();
55                    let mut scatter_sample = ScatterSampleRec::new();
56
57                    state.depth = self.scene.recursion_depth();
58
59                    let alpha = 1.0;
60
61                    for _i in 0..state.depth {
62
63                        state.material = Material::new();
64                        let hit = self.scene.closest_hit(&ray, &mut state, &mut light_sample);
65
66                        if !hit {
67                            radiance += self.scene.background(&ray) * throughput;
68                            break;
69                        }
70
71                        // State and material post-processing
72                        state.finalize(&ray);
73
74                        radiance += state.material.emission * throughput;
75
76                        // Gather radiance from light and use scatterSample.pdf from previous bounce for MIS
77                        if state.is_emitter {
78                            let mut mis_weight = 1.0;
79
80                            if state.depth > 0 {
81                                mis_weight = self.power_heuristic(&scatter_sample.pdf, &light_sample.pdf);
82                            }
83
84                            radiance += mis_weight * light_sample.emission * throughput;
85
86                            break;
87                        }
88
89                        radiance += self.direct_light(&ray, &state, true, &mut rng) * throughput;
90
91                        // Sample BSDF for color and outgoing direction
92                        scatter_sample.f = self.disney_sample(&state, -ray.direction, &state.ffnormal, &mut scatter_sample.l, &mut scatter_sample.pdf, &mut rng);
93                        if scatter_sample.pdf > 0.0 {
94                           throughput = throughput * (scatter_sample.f / F3::new_x(scatter_sample.pdf));
95                        } else {
96                           break;
97                        }
98
99                        // Move ray origin to hit point and set direction for next bounce
100                        ray.direction = scatter_sample.l;
101                        ray.origin = state.fhp + self.eps * ray.direction;
102
103                    }
104
105                    let color = [radiance.x, radiance.y, radiance.z, alpha];
106
107                    #[inline(always)]
108                    pub fn mix_color(a: &[F], b: &[F], v: F) -> [F; 4] {
109                        [   (1.0 - v) * a[0] + b[0] * v,
110                            (1.0 - v) * a[1] + b[1] * v,
111                            (1.0 - v) * a[2] + b[2] * v,
112                            (1.0 - v) * a[3] + b[3] * v ]
113                    }
114
115                    let mix = mix_color(pixel, &color, 1.0 / (buffer.frames + 1) as F);
116
117                    pixel.copy_from_slice(&mix);
118                }
119            });
120
121        buffer.frames += 1;
122
123    }
124
125    /// Sample direct lights
126    fn direct_light(&self, ray: &Ray, state: &State, _is_surface: bool, rng: &mut ThreadRng) -> F3 {
127
128        let mut ld = F3::zeros();
129        let li;
130
131        let scatter_pos = state.fhp + self.eps * state.ffnormal;
132        let mut scatter_sample = ScatterSampleRec::new();
133
134        let number_lights = self.scene.number_of_lights();
135
136        if number_lights > 0 {
137            let mut random : F = rng.gen();
138            random *= self.scene.number_of_lights() as F;
139            let index = random as usize;
140
141            let analytical = self.scene.light_at(index);
142
143            let mut light_sample = LightSampleRec::new();
144
145            self.sample_light(&analytical.light, &scatter_pos, &mut light_sample, rng);
146            li = light_sample.emission;
147
148            if dot(&light_sample.direction, &light_sample.normal) < 0.0 {// Required for quad lights with single sided emission
149
150                let shadow_ray: Ray = Ray::new(scatter_pos, light_sample.direction);
151
152                let in_shawdow = self.scene.any_hit(&shadow_ray, light_sample.dist - self.eps);
153
154                if in_shawdow == false {
155                    scatter_sample.f = self.disney_eval(state, -ray.direction, &state.ffnormal, &light_sample.direction, &mut scatter_sample.pdf);
156
157                    let mut mis_weight = 1.0;
158                    if analytical.light.area > 0.0 {// No MIS for distant light
159                        mis_weight = self.power_heuristic(&light_sample.pdf, &scatter_sample.pdf);
160                    }
161
162                    if scatter_sample.pdf > 0.0 {
163                        ld += mis_weight * li * (scatter_sample.f / F3::new_x(light_sample.pdf));
164                    }
165                }
166            }
167        }
168
169        ld
170    }
171
172    /// Sample one light
173    fn sample_light(&self, light: &Light, scatter_pos: &F3, light_sample: &mut LightSampleRec, rng: &mut ThreadRng) {
174
175        match light.light_type {
176            LightType::Spherical => {
177
178                fn uniform_sample_hemisphere(r1: &F, r2: &F) -> F3 {
179                    let r = 0.0_f32.max(1.0 - r1 * r1).sqrt();
180                    let phi = crate::TWO_PI * r2;
181                    F3::new(r * phi.cos(), r * phi.sin(), *r1)
182                }
183
184                pub fn onb(n: F3, t: &mut F3, b: &mut F3) {
185                    let up = if n.z.abs() < 0.999 { F3::new(0.0, 0.0, 1.0) } else { F3::new(1.0, 0.0, 0.0) };
186
187                    *t = normalize(&cross(&up, &n));
188                    *b = cross(&n, &t);
189                }
190
191                let r1 : F = rng.gen();
192                let r2 : F = rng.gen();
193
194                let mut sphere_center_to_surface = *scatter_pos - light.position;
195                let dist_to_sphere_center = length(&sphere_center_to_surface);
196                let mut sampled_dir = uniform_sample_hemisphere(&r1, &r2);
197
198                sphere_center_to_surface /= F3::new_x(dist_to_sphere_center);
199
200                let mut t = F3::new(0.0, 0.0, 0.0);
201                let mut b = F3::new(0.0, 0.0, 0.0);
202
203                onb(sphere_center_to_surface, &mut t, &mut b);
204                sampled_dir = sampled_dir.x * t + sampled_dir.y * b + sampled_dir.z * sphere_center_to_surface;
205
206                let light_surface_pos = light.position + light.radius * sampled_dir;
207
208                light_sample.direction = light_surface_pos - *scatter_pos;
209                light_sample.dist = length(&light_sample.direction);
210                let dist_sq = light_sample.dist * light_sample.dist;
211
212                light_sample.direction /= F3::new_x(light_sample.dist);
213                light_sample.normal = normalize(&(light_surface_pos - light.position));
214                light_sample.emission = self.scene.number_of_lights() as F * light.emission;
215                light_sample.pdf = dist_sq / (light.area * 0.5 * dot(&light_sample.normal, &light_sample.direction).abs());
216            },
217            _ => {},
218        }
219
220    }
221
222    #[inline(always)]
223    fn power_heuristic(&self, a: &F, b: &F) -> F {
224        let t = a * a;
225        return t / (b * b + t);
226    }
227
228    #[inline(always)]
229    fn mix_ptf(&self, a: &F, b: &F, v: F) -> F {
230        (1.0 - v) * a + b * v
231    }
232
233    fn gtr1(&self, ndoth: &F, a: F) -> F {
234        if a >= 1.0 {
235            return crate::INV_PI;
236        }
237        let a2 = a * a;
238        let t = 1.0 + (a2 - 1.0) * ndoth * ndoth;
239        return (a2 - 1.0) / (crate::PI * (a2).log2() * t);
240    }
241
242    fn sample_gtr1(&self, rgh: F, r1: F, _r2: F) -> F3 {
243        let a = 0.001_f32.max(rgh);
244        let a2 = a * a;
245
246        let phi = r1 * crate::TWO_PI;
247
248        let cos_theta = ((1.0 - a2.powf(1.0 - r1)) / (1.0 - a2)).sqrt();
249        let sin_theta = (1.0 - (cos_theta * cos_theta)).sqrt().clamp(0.0, 1.0);
250        let sin_phi = phi.sin();
251        let cos_phi = phi.cos();
252
253        F3::new(sin_theta * cos_phi, sin_theta * sin_phi, cos_theta)
254    }
255
256    fn sample_ggxvndf(&self, v: &F3, ax: F, ay: F, r1: F, r2: F) -> F3
257    {
258        let vh = normalize(&F3::new(ax * v.x, ay * v.y, v.z));
259
260        let lensq = vh.x * vh.x + vh.y * vh.y;
261        let t_1 = if lensq > 0.0 { F3::new(-vh.y, vh.x, 0.0).mult_f(&(1.0 / lensq.sqrt())) } else { F3::new(1.0, 0.0, 0.0) };
262        let t_2 = cross(&vh, &t_1);
263
264        let r = r1.sqrt();
265        let phi = 2.0 * crate::PI * r2;
266        let t1 = r * phi.cos();
267        let mut t2 = r * phi.sin();
268        let s = 0.5 * (1.0 + vh.z);
269        t2 = (1.0 - s) * (1.0 - t1 * t1).sqrt() + s * t2;
270
271        let nh = t1 * t_1 + t2 * t_2 + (0.0_f32.max(1.0 - t1 * t1 - t2 * t2)).sqrt() * vh;
272
273        normalize(&F3::new(ax * nh.x, ay * nh.y, 0.0_f32.max(nh.z)))
274    }
275
276    fn smithg(&self, ndotv: &F, alphag: F) -> F {
277        let a = alphag * alphag;
278        let b = ndotv * ndotv;
279        (2.0 * ndotv) / (ndotv + (a + b - a * b).sqrt())
280    }
281
282    // Disney
283
284    fn luminance(&self, c: &F3) -> F {
285        0.212671 * c.x + 0.715160 * c.y + 0.072169 * c.z
286    }
287
288    fn schlick_fresnel(&self, u: F) -> F {
289        let m = (1.0 - u).clamp(0.0, 1.0);
290        let m2 = m * m;
291        m2 * m2 * m
292    }
293
294    fn gtr2aniso(&self, ndoth: &F, hdotx: &F, hdoty: &F, ax: &F, ay: &F) -> F {
295        let a = hdotx / ax;
296        let b = hdoty / ay;
297        let c = a * a + b * b + ndoth * ndoth;
298        1.0 / (crate::PI * ax * ay * c * c)
299    }
300
301    fn smithganiso(&self, ndotv: &F, vdotx: &F, vdoty: &F, ax: &F, ay: &F) -> F {
302        let a = vdotx * ax;
303        let b = vdoty * ay;
304        let c = ndotv;
305        (2.0 * ndotv) / (ndotv + (a * a + b * b + c * c).sqrt())
306    }
307
308    fn dielectric_fresnel(&self, cos_theta_i: F, eta: F) -> F {
309        let sin_theta_tsq = eta * eta * (1.0 - cos_theta_i * cos_theta_i);
310
311        // Total internal reflection
312        if sin_theta_tsq > 1.0 {
313            return 1.0;
314        }
315
316        let cos_theta_t = (1.0 - sin_theta_tsq).max(0.0).sqrt();
317
318        let rs = (eta * cos_theta_t - cos_theta_i) / (eta * cos_theta_t + cos_theta_i);
319        let rp = (eta * cos_theta_i - cos_theta_t) / (eta * cos_theta_i + cos_theta_t);
320
321        0.5 * (rs * rs + rp * rp)
322    }
323
324    fn cosine_sample_hemisphere(&self, r1: F, r2: F) -> F3
325    {
326        let mut dir = F3::zeros();
327        let r = r1.sqrt();
328        let phi = crate::TWO_PI * r2;
329        dir.x = r * phi.cos();
330        dir.y = r * phi.sin();
331        dir.z = 0.0_f32.max(1.0 - dir.x * dir.x - dir.y * dir.y).sqrt();
332        dir
333    }
334
335    fn get_spec_color(&self, material: &Material, eta: F, spec_col: &mut F3, sheen_col: &mut F3) {
336        let lum = self.luminance(&material.rgb);
337        let ctint = if lum > 0.0 { material.rgb / F3::new_x(lum) } else { F3::new(1.0, 1.0, 1.0) };
338        let f0 = (1.0 - eta) / (1.0 + eta);
339        *spec_col = mix(&(f0 * f0 * mix(&F3::new(1.0, 1.0, 1.0), &ctint, &material.specular_tint)), &material.rgb, &material.metallic);
340        *sheen_col = mix(&F3::new(1.0, 1.0, 1.0), &ctint, &material.sheen_tint);
341    }
342
343    fn eval_diffuse(&self, material: &Material, c_sheen: &F3, v: &F3, l: &F3, h: &F3, pdf: &mut F) -> F3 {
344        *pdf = 0.0;
345        if l.z <= 0.0 {
346            return F3::zeros();
347        }
348
349        // Diffuse
350        let fl = self.schlick_fresnel(l.z);
351        let fv = self.schlick_fresnel(v.z);
352        let fh = self.schlick_fresnel(dot(&l, &h));
353        let fd90 = 0.5 + 2.0 * dot(&l, &h) * dot(&l, &h) * material.roughness;
354        let fd = self.mix_ptf(&1.0, &fd90, fl) * self.mix_ptf(&1.0, &fd90, fv);
355
356        // Fake Subsurface TODO: Replace with volumetric scattering
357        let fss90 = dot(&l, &h) * dot(&l, &h) * material.roughness;
358        let fss = self.mix_ptf(&1.0, &fss90, fl) * self.mix_ptf(&1.0, &fss90, fv);
359        let ss = 1.25 * (fss * (1.0 / (l.z + v.z) - 0.5) + 0.5);
360
361        // Sheen
362        let fsheen = fh * material.sheen * *c_sheen;
363
364        *pdf = l.z * crate::INV_PI;
365        (1.0 - material.metallic) * (1.0 - material.spec_trans) * (crate::INV_PI * self.mix_ptf(&fd, &ss, material.subsurface) * material.rgb + fsheen)
366    }
367
368    fn eval_spec_reflection(&self, material: &Material, eta: F, spec_col: &F3, v: &F3, l: &F3, h: &F3, pdf: &mut F) -> F3 {
369        *pdf = 0.0;
370        if l.z <= 0.0 {
371            return F3::zeros();
372        }
373
374        let fm = self.disney_fresnel(material, eta, dot(&l, &h), dot(&v, &h));
375        let f = mix(&spec_col, &F3::new(1.0, 1.0, 1.0), &fm);
376        let d = self.gtr2aniso(&h.z, &h.x, &h.y, &material.ax, &material.ay);
377        let g1 = self.smithganiso(&v.z.abs(), &v.x, &v.y, &material.ax, &material.ay);
378        let g2 = g1 * self.smithganiso(&l.z.abs(), &l.x, &l.y, &material.ax, &material.ay);
379
380        *pdf = g1 * d / (4.0 * v.z);
381        d * g2 * f / F3::new_x(4.0 * l.z * v.z)
382    }
383
384    fn eval_spec_refraction(&self, material: &Material, eta: F, v: &F3, l: &F3, h: &F3, pdf: &mut F) -> F3 {
385        *pdf = 0.0;
386        if l.z >= 0.0 {
387            return F3::zeros();
388        }
389
390        let f = self.dielectric_fresnel(dot(&v, &h).abs(), eta);
391        let d = self.gtr2aniso(&h.z, &h.x, &h.y, &material.ax, &material.ay);
392        let g1 = self.smithganiso(&v.z.abs(), &v.x, &v.y, &material.ax, &material.ay);
393        let g2 = g1 * self.smithganiso(&l.z.abs(), &l.x, &l.y, &material.ax, &material.ay);
394        let mut denom = dot(&l, &h) + dot(&v, &h) * eta;
395        denom *= denom;
396        let eta2 = eta * eta;
397        let jacobian = dot(&l, &h).abs() / denom;
398
399        *pdf = g1 * 0.0_f32.max(dot(&v, &h)) * d * jacobian / v.z;
400
401        (1.0 - material.metallic) * material.spec_trans * (1.0 - f) * d * g2 * dot(&v, &h).abs() * jacobian * eta2 / (l.z * v.z).abs() * pow(&material.rgb, &F3::new(0.5, 0.5, 0.5))
402    }
403
404    fn eval_clearcoat(&self, material: &Material, v: &F3, l: &F3, h: &F3, pdf: &mut F) -> F3
405    {
406        *pdf = 0.0;
407        if l.z <= 0.0 {
408            return F3::zeros();
409        }
410
411        let fh = self.dielectric_fresnel(dot(&v, &h), 1.0 / 1.5);
412        let f = self.mix_ptf(&0.04, &1.0, fh);
413        let d = self.gtr1(&h.z, material.clearcoat_roughness);
414        let g = self.smithg(&l.z, 0.25) * self.smithg(&v.z, 0.25);
415        let jacobian = 1.0 / (4.0 * dot(&v, &h));
416
417        *pdf = d * h.z * jacobian;
418        material.clearcoat * f * d * g / (4.0 * l.z * v.z) * F3::new(0.25, 0.25, 0.25)
419    }
420
421    fn get_lobe_probabilities(&self, material: &Material, _eta: &F3, spec_col: &F3, approx_fresnel: F, diffuse_wt: &mut F, spec_reflect_wt: &mut F,  spec_refract_wt: &mut F, clearcoat_wt: &mut F)
422    {
423        *diffuse_wt = self.luminance(&material.rgb) * (1.0 - material.metallic) * (1.0 - material.spec_trans);
424        *spec_reflect_wt = self.luminance(&mix(&spec_col, &F3::new(1.0, 1.0, 1.0), &approx_fresnel));
425        *spec_refract_wt = (1.0 - approx_fresnel) * (1.0 - material.metallic) * material.spec_trans * self.luminance(&material.rgb);
426        *clearcoat_wt = 0.25 * material.clearcoat * (1.0 - material.metallic);
427        let total_wt = *diffuse_wt + *spec_reflect_wt + *spec_refract_wt + *clearcoat_wt;
428
429        *diffuse_wt /= total_wt;
430        *spec_reflect_wt /= total_wt;
431        *spec_refract_wt /= total_wt;
432        *clearcoat_wt /= total_wt;
433    }
434
435    fn disney_fresnel(&self, material: &Material, eta: F, ldot_h: F, vdot_h: F) -> F {
436        let metallic_fresnel = self.schlick_fresnel(ldot_h);
437        let  dielectric_fresnel = self.dielectric_fresnel(vdot_h.abs(), eta);
438        self.mix_ptf(&dielectric_fresnel, &metallic_fresnel, material.metallic)
439    }
440
441    fn disney_sample(&self, state: &State, mut v: F3, n: &F3, l: &mut F3, pdf: &mut F, rng: &mut ThreadRng) -> F3 {
442
443        *pdf = 0.0;
444        let f;
445
446        let mut r1 : F = rng.gen();
447        let r2 : F = rng.gen();
448
449        fn onb(n: &F3, t: &mut F3, b: &mut F3) {
450            let up = if n.z.abs() < 0.999 { F3::new(0.0, 0.0, 1.0) } else { F3::new(1.0, 0.0, 0.0) };
451
452            *t = normalize(&cross(&up, n));
453            *b = cross(n, t);
454        }
455
456        fn to_local(x: &F3, y: &F3, z: &F3, v: &F3) -> F3 {
457                F3::new(dot(v, x), dot(v, y), dot(v, z))
458        }
459
460        fn to_world(x: &F3, y: &F3, z: &F3, v: &F3) -> F3 {
461            v.x * *x + v.y * *y + v.z * *z
462        }
463
464        fn reflect(i: F3, n: F3) -> F3 {
465            i - F3::new(2.0, 2.0, 2.0) * n * F3::new_x(dot(&n, &i))
466        }
467
468        fn refract(i: F3, n: F3, eta: F) -> F3 {
469            let k = 1.0 - eta * eta * (1.0 - dot(&n, &i) * dot(&n, &i));
470            if k < 0.0 {
471                F3::zeros()
472            } else {
473                eta * i - (eta * dot(&n, &i) + k.sqrt()) * n
474            }
475        }
476
477        let mut t = F3::zeros();
478        let mut b = F3::zeros();
479
480        onb(&n, &mut t, &mut b);
481        v = to_local(&t, &b, n, &v); // NDotL = L.z; NDotV = V.z; NDotH = H.z
482
483        // Specular and sheen color
484        let mut spec_col = F3::zeros();
485        let mut sheen_col = F3::zeros();
486        self.get_spec_color(&state.material, state.eta, &mut spec_col, &mut sheen_col);
487
488        // Lobe weights
489        let mut diffuse_wt = 0.0; let mut spec_reflect_wt = 0.0; let mut spec_refract_wt = 0.0; let mut clearcoat_wt = 0.0;
490
491        let approx_fresnel = self.disney_fresnel(&state.material, state.eta, v.z, v.z);
492        self.get_lobe_probabilities(&state.material, &F3::new_x(state.eta), &spec_col, approx_fresnel, &mut diffuse_wt, &mut spec_reflect_wt, &mut spec_refract_wt, &mut clearcoat_wt);
493
494        // CDF for picking a lobe
495        let mut cdf = [0.0, 0.0, 0.0, 0.0];
496        cdf[0] = diffuse_wt;
497        cdf[1] = cdf[0] + clearcoat_wt;
498        cdf[2] = cdf[1] + spec_reflect_wt;
499        cdf[3] = cdf[2] + spec_refract_wt;
500
501        if r1 < cdf[0] { // Diffuse Reflection Lobe
502            r1 /= cdf[0];
503            *l = self.cosine_sample_hemisphere(r1, r2);
504
505            let h = normalize(&(*l + v));
506            f = self.eval_diffuse(&state.material, &sheen_col, &v, &l, &h, pdf);
507            *pdf *= diffuse_wt;
508        } else
509        if r1 < cdf[1] {// Clearcoat Lobe
510            r1 = (r1 - cdf[0]) / (cdf[1] - cdf[0]);
511
512            let mut h = self.sample_gtr1(state.material.clearcoat_roughness, r1, r2);
513
514            if h.z < 0.0 {
515                h = -h;
516            }
517
518            *l = normalize(&reflect(-v, h));
519            f = self.eval_clearcoat(&state.material, &v, l, &h, pdf);
520            *pdf *= clearcoat_wt;
521        } else  // Specular Reflection/Refraction Lobes
522        {
523            r1 = (r1 - cdf[1]) / (1.0 - cdf[1]);
524            let mut h = self.sample_ggxvndf(&v, state.material.ax, state.material.ay, r1, r2);
525
526            if h.z < 0.0 {
527                h = -h;
528            }
529
530            // TODO: Refactor into metallic BRDF and specular BSDF
531            let fresnel = self.disney_fresnel(&state.material, state.eta, dot(l, &h), dot(&v, &h));
532            let ff = 1.0 - ((1.0 - fresnel) * state.material.spec_trans * (1.0 - state.material.metallic));
533
534            let rand : F = rng.gen();
535
536            if rand < ff {
537                *l = normalize(&reflect(-v, h));
538
539                f = self.eval_spec_reflection(&state.material, state.eta, &spec_col, &v, l, &h, pdf);
540                *pdf *= ff;
541            } else {
542                *l = normalize(&refract(-v, h, state.eta));
543
544                f = self.eval_spec_refraction(&state.material, state.eta, &v, l, &h, pdf);
545                *pdf *= 1.0 - ff;
546            }
547
548            *pdf *= spec_reflect_wt + spec_refract_wt;
549        }
550
551        *l = to_world(&t, &b, &n, &l);
552        dot(n, l).abs() * f
553    }
554
555    fn disney_eval(&self, state: &State, v: F3, n: &F3, l: &F3, bsdf_pdf: &mut F) -> F3 {
556        *bsdf_pdf = 0.0;
557        let mut f = F3::zeros();
558
559        fn onb(n: &F3, t: &mut F3, b: &mut F3) {
560            let up = if n.z.abs() < 0.999 { F3::new(0.0, 0.0, 1.0) } else { F3::new(1.0, 0.0, 0.0) };
561
562            *t = normalize(&cross(&up, &n));
563            *b = cross(&n, &t);
564        }
565
566        fn to_local(x: &F3, y: &F3, z: &F3, v: &F3) -> F3 {
567                F3::new(dot(v, x), dot(v, y), dot(v, z))
568        }
569
570        let mut t = F3::zeros();
571        let mut b = F3::zeros();
572
573        onb(n, &mut t, &mut b);
574        let v = to_local(&t, &b, n, &v); // NDotL = L.z; NDotV = V.z; NDotH = H.z
575        let l = to_local(&t, &b, n, l);
576
577        let mut h;
578        if l.z > 0.0 {
579            h = normalize(&(l + v));
580        } else {
581            h = normalize(&(l + state.eta * v));
582        }
583
584        if h.z < 0.0 {
585            h = -h;
586        }
587
588        // Specular and sheen color
589        let mut spec_col = F3::zeros();
590        let mut sheen_col = F3::zeros();
591        self.get_spec_color(&state.material, state.eta, &mut spec_col, &mut sheen_col);
592
593        // Lobe weights
594        let mut diffuse_wt = 0.0; let mut spec_reflect_wt = 0.0; let mut spec_refract_wt = 0.0; let mut clearcoat_wt = 0.0;
595
596        let fresnel = self.disney_fresnel(&state.material, state.eta, dot(&l, &h), dot(&v, &h));
597        self.get_lobe_probabilities(&state.material, &F3::new_x(state.eta), &spec_col, fresnel, &mut diffuse_wt, &mut spec_reflect_wt, &mut spec_refract_wt, &mut clearcoat_wt);
598
599        let mut pdf = 0.0;
600
601        // Diffuse
602        if diffuse_wt > 0.0 && l.z > 0.0 {
603            f += self.eval_diffuse(&state.material, &sheen_col, &v, &l, &h, &mut pdf);
604            *bsdf_pdf += pdf * diffuse_wt;
605        }
606
607        // Specular Reflection
608        if spec_reflect_wt > 0.0 && l.z > 0.0 && v.z > 0.0 {
609            f += self.eval_spec_reflection(&state.material, state.eta, &spec_col, &v, &l, &h, &mut pdf);
610            *bsdf_pdf += pdf * spec_reflect_wt;
611        }
612
613        // Specular Refraction
614        if spec_refract_wt > 0.0 && l.z < 0.0 {
615            f += self.eval_spec_refraction(&state.material, state.eta, &v, &l, &h, &mut pdf);
616            *bsdf_pdf += pdf * spec_refract_wt;
617        }
618
619        // Clearcoat
620        if clearcoat_wt > 0.0 && l.z > 0.0 && v.z > 0.0 {
621            f +=self.eval_clearcoat(&state.material, &v, &l, &h, &mut pdf);
622            *bsdf_pdf += pdf * clearcoat_wt;
623        }
624
625        l.z.abs() * f
626    }
627
628    /// Return a mutable reference to the scene.
629    pub fn scene(&mut self) -> &mut Box<dyn Scene> {
630        &mut self.scene
631    }
632
633}