Skip to content

Commit 6197380

Browse files
committed
Start building a photon map
1 parent f864925 commit 6197380

14 files changed

+360751
-200
lines changed

foo.png

-53 KB
Loading

foo2.png

1.41 KB
Loading

foo3.png

367 KB
Loading

models/bunny.obj

Lines changed: 360309 additions & 0 deletions
Large diffs are not rendered by default.

rustfmt.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
max_width = 120
2+
comment_width = 80
3+
tab_spaces = 2
4+
newline_style = "Unix"
5+
brace_style = "SameLineWhere"
6+
fn_args_density = "Tall"
7+
trailing_comma = "Vertical"
8+
indent_style = "Block"
9+
report_todo = "Always"
10+
report_fixme = "Never"
11+
reorder_imports = false
12+
format_strings = true
389 KB
Loading

src/colour.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ impl Colour {
1515
let Colour::RGB(r, g, b) = *self;
1616
return Colour::RGB(r + rr, g + rg, b + rb);
1717
}
18+
pub fn intensity(&self) -> f64 {
19+
let Colour::RGB(r, g, b) = *self;
20+
return (r * r + g * g + b * b).sqrt();
21+
}
1822
}
1923

2024
impl From<Colour> for Vec4d {

src/kdtree.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#[derive(Debug)]
2+
struct KDTreeInnerNode<T> {
3+
children: Box<[KDTreeNode<T>; 2]>,
4+
axis: usize,
5+
value: f64,
6+
}
7+
8+
#[derive(Debug)]
9+
enum KDTreeNode<T> {
10+
Node(KDTreeInnerNode<T>),
11+
Leaf(Vec<T>),
12+
}
13+
14+
#[derive(Debug)]
15+
pub struct KDTree<T: Clone> {
16+
root: KDTreeNode<T>,
17+
}
18+
19+
fn build_tree<T: Clone>(elements: &[T]) -> KDTreeNode<T> {
20+
return KDTreeNode::Leaf(elements.to_vec());
21+
}
22+
23+
impl<T: Clone> KDTree<T> {
24+
pub fn new(elements: &[T]) -> Self {
25+
return KDTree {
26+
root: build_tree(elements),
27+
};
28+
}
29+
}

src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ mod colour;
1414
mod compound_object;
1515
mod fragment;
1616
mod intersectable;
17+
mod kdtree;
1718
mod material;
1819
mod mesh;
1920
mod objects;
21+
mod photon_map;
2022
mod ray;
2123
mod scene;
2224
mod shader;

src/photon_map.rs

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
use colour::Colour;
2+
use fragment::Fragment;
3+
use kdtree::KDTree;
4+
use material::MaterialCollisionInfo;
5+
use rand::{thread_rng, Rng};
6+
use ray::Ray;
7+
use scene::Scene;
8+
use shader::LightSample;
9+
use vectors::Vec4d;
10+
11+
#[derive(Clone, Debug)]
12+
struct Photon {
13+
colour: Colour,
14+
position: Vec4d,
15+
}
16+
17+
#[derive(Debug)]
18+
pub struct PhotonMap {
19+
tree: KDTree<Photon>,
20+
}
21+
22+
fn random(min: f64, max: f64) -> f64 {
23+
thread_rng().gen_range(min, max)
24+
}
25+
26+
fn random_in_hemisphere(normal: Vec4d) -> Vec4d {
27+
loop {
28+
let x = random(-1.0, 1.0);
29+
let y = random(-1.0, 1.0);
30+
let z = random(-1.0, 1.0);
31+
if (x * x + y * y + z * z) > 1.0 {
32+
continue;
33+
}
34+
let result = Vec4d::vector(x, y, z);
35+
if result.dot(normal) > 0.01 {
36+
return result;
37+
}
38+
}
39+
}
40+
41+
impl PhotonMap {
42+
pub fn new(scene: &Scene) -> PhotonMap {
43+
let mut photons: Vec<Photon> = vec![];
44+
let lights = &scene.get_lights();
45+
let mut virtual_lights: Vec<LightSample> = vec![];
46+
for light in lights {
47+
virtual_lights.append(&mut light.get_samples(1000, scene));
48+
}
49+
let mut bounces: usize = 0;
50+
let mut max_bounces: usize = 0;
51+
let mut paths = 0;
52+
let start = std::time::Instant::now();
53+
let photon_count = 1000000;
54+
while photons.len() < photon_count {
55+
'photon_loop: for sample in &virtual_lights {
56+
paths += 1;
57+
let mut light_dir = {
58+
let mut x;
59+
let mut y;
60+
let mut z;
61+
62+
loop {
63+
x = random(-1.0, 1.0);
64+
y = random(-1.0, 1.0);
65+
z = random(-1.0, 1.0);
66+
if (x * x + y * y + z * z) <= 1.0 {
67+
break;
68+
}
69+
}
70+
71+
Vec4d::vector(x, y, z).normalize() // this is a super awful/biased random, but whatever
72+
};
73+
74+
if sample.direction.is_none() || light_dir.dot(sample.direction.unwrap()) < 0.01 {
75+
continue 'photon_loop;
76+
}
77+
78+
let mut throughput = Colour::RGB(1.0, 1.0, 1.0);
79+
let mut photon_ray = Ray::new(sample.position + light_dir * 0.01, light_dir, None);
80+
let mut photon_colour = Colour::from(sample.specular);
81+
let mut path_length: usize = 0;
82+
'photon_bounce_loop: while photons.len() < photon_count {
83+
bounces += 1;
84+
path_length += 1;
85+
max_bounces = max_bounces.max(path_length);
86+
let (c, shadable) = match scene.intersect(&photon_ray) {
87+
None => continue 'photon_loop,
88+
Some(x) => x,
89+
};
90+
91+
let fragment: Fragment = shadable.compute_fragment(scene, &photon_ray, &c);
92+
let material = match fragment.material {
93+
Some(inner) => scene.get_material(inner),
94+
None => continue 'photon_loop,
95+
};
96+
97+
let surface: MaterialCollisionInfo = material.compute_surface_properties(scene, &photon_ray, &fragment);
98+
99+
photons.push(Photon {
100+
colour: photon_colour * surface.diffuse_colour,
101+
position: fragment.position,
102+
});
103+
104+
let mut next = {
105+
let mut selection = random(0.0, 1.0);
106+
let mut result = None;
107+
for (secondary_ray, secondary_colour, secondary_weight) in surface.secondaries {
108+
if selection > secondary_weight {
109+
selection -= secondary_weight;
110+
continue;
111+
}
112+
113+
result = Some((secondary_ray, secondary_colour))
114+
}
115+
result
116+
};
117+
if next.is_none() {
118+
let diffuse_direction = random_in_hemisphere(surface.normal);
119+
let diffuse_intensity = diffuse_direction.dot(surface.normal);
120+
121+
let specular_direction = fragment.view.reflect(surface.normal);
122+
let specular_intensity = specular_direction.dot(fragment.view).powf(20.0);
123+
let mut next_colour;
124+
let new_direction = if random(0.0, diffuse_intensity + specular_intensity) < diffuse_intensity {
125+
next_colour = surface.diffuse_colour;
126+
random_in_hemisphere(surface.normal)
127+
} else {
128+
next_colour = surface.diffuse_colour;
129+
fragment.view.reflect(surface.normal)
130+
};
131+
132+
next = Some((
133+
Ray::new(
134+
fragment.position + new_direction * 0.01,
135+
new_direction,
136+
Some(photon_ray.ray_context.clone()),
137+
),
138+
next_colour,
139+
));
140+
};
141+
142+
let (next_ray, next_colour) = next.unwrap();
143+
144+
// Now we know the colour and diretion of the next bounce, let's decide if we're keeping it.
145+
throughput = throughput * next_colour;
146+
let p = random(0.0, 1.0);
147+
if p > throughput.intensity() {
148+
continue 'photon_loop;
149+
}
150+
throughput = throughput * (1.0 / p);
151+
photon_colour = photon_colour * next_colour;
152+
photon_ray = next_ray;
153+
continue 'photon_bounce_loop;
154+
}
155+
}
156+
}
157+
158+
let end = std::time::Instant::now();
159+
160+
let delta = end - start;
161+
let time = (delta.as_secs() * 1000 + delta.subsec_millis() as u64) as f64 / 1000.0;
162+
println!("Time taken to generate photons: {}", time);
163+
let average_bounces = bounces as f64 / paths as f64;
164+
println!("Average path length: {}", average_bounces);
165+
println!("Total paths: {}", paths);
166+
println!("Max path length: {}", max_bounces);
167+
return PhotonMap {
168+
tree: KDTree::new(&photons),
169+
};
170+
}
171+
}

0 commit comments

Comments
 (0)