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
85
86
87
88
89
90
91
92
93
use super::*;
use crate::tracer::Color;
#[cfg(test)]
mod medium_test;
/// A homogenous participating media. Mediums represent space where rays get
/// absorbed and can scatter at random depending on density.
/// Examples of real life mediums include smoke, fog, and clouds.
pub struct Medium {
/// Transmittance of the medium, defined as `sigma_a + sigma_s`, where
/// `sigma_a` tells how much each RGB channel gets absorbed while
/// traversing the medium
sigma_t: Vec3,
/// Material of the medium
material: Material,
}
impl Medium {
/// Constructs a medium contained in an invisible solid.
///
/// # Arguments
/// * `absorption` - How much of each RGB channel gets absorbed while
/// traversing the medium. Value in `\[0,1\]^3`
/// * `scattering` - How much of each RGB channel gets scattered on hit
/// * `scatter_param` - Scattering parameter to Henyey-Greenstein in
/// `(-1,1)`
pub fn new(absorption: Vec3, scattering: Vec3, scatter_param: Float) -> Self {
assert!(-1.0 < scatter_param && scatter_param < 1.0);
assert!(scattering.min_element() >= 0.0);
assert!(absorption.max_element() <= 1.0
&& absorption.min_element() >= 0.0);
let sigma_s = Color::from(scattering);
let sigma_t = scattering + absorption;
Self {
sigma_t,
material: Material::Volumetric(scatter_param, sigma_t, sigma_s),
}
}
/// Computes the transmittance for the distance `t`.
pub fn transmittance(&self, t_delta: Float) -> Color {
// need to move some of the stuff to bsdf?
let transmittance = (-self.sigma_t * t_delta).exp();
let pdf = transmittance.dot(Vec3::ONE) / 3.0;
if pdf == 0.0 {
// this medium does not do much...
Color::WHITE
} else {
Color::from(transmittance / pdf)
}
}
}
impl Object for Medium {
fn hit(&self, ro: &Ray, t_min: Float, t_max: Float) -> Option<Hit> {
// choose a random color channel from density
let density = match 3.0 * rand_utils::rand_float() {
f if f < 1.0 => self.sigma_t.x,
f if f < 2.0 => self.sigma_t.y,
_ => self.sigma_t.z,
};
// this channel never gets hit
if density == 0.0 {
return None;
}
let ray_length = ro.dir.length();
let inside_dist = (t_max - t_min) * ray_length;
let hit_dist = -(1.0 - rand_utils::rand_float()).ln() / density;
// this way, the scale of the world matters.
// doubt there are alternative ways?
if hit_dist > inside_dist {
None
} else {
let t = t_min + hit_dist / ray_length;
let xi = ro.at(t);
// need shading normal to cancel out the dot product in integrator.
let ns = Normal::X;
let ng = Normal::ZERO;
let uv = Vec2::ZERO;
let err = Vec3::ZERO;
Hit::new(t, &self.material, -ng, xi, err, ns, ng, uv)
}
}
}