1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
//! The MERL BRDF represents the surface's properties through data loaded from a
//! [MERL BRDF Database file](http://www.merl.com/brdf/). The BRDF itself just stores
//! the data loaded from the BRDF file while actual loading is done by the MERL material
//! when it's created.

use std::f32;
use enum_set::EnumSet;

use linalg::{self, Vector};
use film::Colorf;
use bxdf::{self, BxDF, BxDFType};

/// BRDF that uses measured data to model the surface reflectance properties.
/// The measured data is from "A Data-Driven Reflectance Model",
/// by Wojciech Matusik, Hanspeter Pfister, Matt Brand and Leonard McMillan,
/// in ACM Transactions on Graphics 22, 3(2003), 759-769
#[derive(Copy, Clone)]
pub struct Merl<'a> {
    /// Vec containing the BRDF values for various incident/exiting angles
    brdf: &'a [f32],
    /// Number of theta_h measurements in `brdf`
    n_theta_h: usize,
    /// Number of theta_d measurements in `brdf`
    n_theta_d: usize,
    /// Number of phi_d measurements in `brdf`
    n_phi_d: usize,
}

impl<'a> Merl<'a> {
    /// Create a MERL BRDF to use data loaded from a MERL BRDF data file
    pub fn new(brdf: &'a [f32], n_theta_h: usize, n_theta_d: usize, n_phi_d: usize) -> Merl<'a> {
        Merl { brdf: brdf, n_theta_h: n_theta_h, n_theta_d: n_theta_d, n_phi_d: n_phi_d }
    }
    /// Re-map values from an angular value to the index in the MERL data table
    fn map_index(val: f32, max: f32, n_vals: usize) -> usize {
        linalg::clamp((val / max * n_vals as f32) as usize, 0, n_vals - 1)
    }
}

impl<'a> BxDF for Merl<'a> {
    fn bxdf_type(&self) -> EnumSet<BxDFType> {
        let mut e = EnumSet::new();
        e.insert(BxDFType::Glossy);
        e.insert(BxDFType::Reflection);
        e
    }
    fn eval(&self, w_oi: &Vector, w_ii: &Vector) -> Colorf {
        // Find the half-vector and transform into the half angle coordinate system used by MERL
        // BRDF files
        let mut w_i = *w_ii;
        let mut w_h = *w_oi + w_i;
        if w_h.z < 0.0 {
            w_i = -w_i;
            w_h = -w_h;
        }
        if w_h.length_sqr() == 0.0 {
            return Colorf::black();
        }

        let w_h = w_h.normalized();
        // Directly compute the rows of the matrix performing the rotation of w_h to (0, 0, 1)
        let theta_h = linalg::spherical_theta(&w_h);
        let cos_phi_h = bxdf::cos_phi(&w_h);
        let sin_phi_h = bxdf::sin_phi(&w_h);
        let cos_theta_h = bxdf::cos_theta(&w_h);
        let sin_theta_h = bxdf::sin_theta(&w_h);
        let w_hx = Vector::new(cos_phi_h * cos_theta_h, sin_phi_h * cos_theta_h, -sin_theta_h);
        let w_hy = Vector::new(-sin_phi_h, cos_phi_h, 0.0);
        let w_d = Vector::new(linalg::dot(&w_i, &w_hx), linalg::dot(&w_i, &w_hy), linalg::dot(&w_i, &w_h));
        let theta_d = linalg::spherical_theta(&w_d);
        // Wrap phi_d if needed to keep it in range
        let phi_d = match linalg::spherical_phi(&w_d) {
            d if d > f32::consts::PI => d - f32::consts::PI,
            d => d,
        };
        let theta_h_idx = Merl::map_index(f32::sqrt(f32::max(0.0, 2.0 * theta_h / f32::consts::PI)), 1.0, self.n_theta_h);
        let theta_d_idx = Merl::map_index(theta_d, f32::consts::PI / 2.0, self.n_theta_d);
        let phi_d_idx = Merl::map_index(phi_d, f32::consts::PI, self.n_phi_d);
        let i = phi_d_idx + self.n_phi_d * (theta_d_idx + theta_h_idx * self.n_theta_d);
        assert!(i < self.brdf.len());
        Colorf::new(self.brdf[3 * i], self.brdf[3 * i + 1], self.brdf[3 * i + 2])
    }
}