Compare commits

...

15 Commits

Author SHA1 Message Date
1076e6dcaf rtiow: BVHTriangles use binning to speed up BVH building. 2023-02-12 16:52:07 -08:00
7d9750b9d0 rtiow: AABB handle infinite bounds better. 2023-02-12 16:51:25 -08:00
88b8c547e0 rtiow: cleanup elapsed time logging. 2023-02-12 14:09:11 -08:00
ac555beafc rtiow: BVHTriangles use a fixed 100 divisions for split planes. 2023-02-12 13:47:29 -08:00
0158f9ea15 rtiow: BVHTriangles use BVHNode::cost for readability. 2023-02-12 13:26:45 -08:00
450342c3d4 rtiow: BVHTriangles refactor part of subdivide into find_best_split_plane. 2023-02-12 13:14:02 -08:00
41f9fa2742 rtiow: add proptest. 2023-02-12 13:05:01 -08:00
7ec30d8557 rtiow: BVHTriangles faster BVH traversal. 2023-02-12 13:04:08 -08:00
f51d3396f4 rtiow: tweak scenes and cleanup lint 2023-02-12 13:03:01 -08:00
a2f9166b5a rtiow: extend progress interval from 1s -> 5s. 2023-02-12 12:46:53 -08:00
f73a471cb6 rtiow: AABB add hit_distance to return Option<f32> instead of bool. 2023-02-12 12:46:10 -08:00
cd149755cb rtiow: pretty print CLI options. 2023-02-11 11:16:37 -08:00
9188ce17fa rtiow: print BVH stats. 2023-02-11 11:16:24 -08:00
63975bad96 rtiow: BVHTriangles use SAH for division and leave original triangles untouched. 2023-02-10 17:04:23 -08:00
df928e1779 rtiow: aabb add area/grow methods and infinite constructor. 2023-02-10 17:04:01 -08:00
10 changed files with 534 additions and 216 deletions

81
rtiow/Cargo.lock generated
View File

@ -357,6 +357,21 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "bit-set"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@ -1550,6 +1565,12 @@ version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "libm"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
[[package]] [[package]]
name = "link-cplusplus" name = "link-cplusplus"
version = "1.0.8" version = "1.0.8"
@ -1758,7 +1779,7 @@ dependencies = [
"log 0.4.17", "log 0.4.17",
"mime 0.2.6", "mime 0.2.6",
"mime_guess 1.8.8", "mime_guess 1.8.8",
"quick-error", "quick-error 1.2.3",
"rand 0.6.5", "rand 0.6.5",
"safemem", "safemem",
"tempfile", "tempfile",
@ -1869,6 +1890,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [ dependencies = [
"autocfg 1.1.0", "autocfg 1.1.0",
"libm",
] ]
[[package]] [[package]]
@ -2155,12 +2177,39 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "proptest"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29f1b898011ce9595050a68e60f90bad083ff2987a695a42357134c8381fba70"
dependencies = [
"bit-set",
"bitflags",
"byteorder",
"lazy_static 1.4.0",
"num-traits",
"quick-error 2.0.1",
"rand 0.8.5",
"rand_chacha 0.3.1",
"rand_xorshift 0.3.0",
"regex-syntax",
"rusty-fork",
"tempfile",
"unarray",
]
[[package]] [[package]]
name = "quick-error" name = "quick-error"
version = "1.2.3" version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]] [[package]]
name = "quote" name = "quote"
version = "0.6.13" version = "0.6.13"
@ -2472,6 +2521,7 @@ dependencies = [
"log 0.4.17", "log 0.4.17",
"num_cpus", "num_cpus",
"pretty_assertions", "pretty_assertions",
"proptest",
"rand 0.8.5", "rand 0.8.5",
"serde", "serde",
"serde_derive", "serde_derive",
@ -2490,7 +2540,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11834e137f3b14e309437a8276714eed3a80d1ef894869e510f2c0c0b98b9f4a" checksum = "11834e137f3b14e309437a8276714eed3a80d1ef894869e510f2c0c0b98b9f4a"
dependencies = [ dependencies = [
"hostname", "hostname",
"quick-error", "quick-error 1.2.3",
] ]
[[package]] [[package]]
@ -2514,6 +2564,18 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
[[package]]
name = "rusty-fork"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f"
dependencies = [
"fnv",
"quick-error 1.2.3",
"tempfile",
"wait-timeout",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.12" version = "1.0.12"
@ -3347,6 +3409,12 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "unarray"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]] [[package]]
name = "unicase" name = "unicase"
version = "1.4.2" version = "1.4.2"
@ -3536,6 +3604,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "wait-timeout"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "walkdir" name = "walkdir"
version = "2.3.2" version = "2.3.2"

View File

@ -34,6 +34,7 @@ strum_macros = "0.24.3"
[dev-dependencies] [dev-dependencies]
criterion = "0.4" criterion = "0.4"
pretty_assertions = "1.3.0" pretty_assertions = "1.3.0"
proptest = "1.1.0"
[features] [features]
profile = ["cpuprofiler"] profile = ["cpuprofiler"]

View File

@ -33,15 +33,23 @@ impl fmt::Debug for AABB {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!( write!(
f, f,
"AABB: <{} - {}> Vol: {}", "AABB: <{} - {}> Vol: {} Area: {}",
self.bounds[0], self.bounds[0],
self.bounds[1], self.bounds[1],
self.volume() self.volume(),
self.area()
) )
} }
} }
impl AABB { impl AABB {
// Create AABB with min = f32::MAX and max = -f32::MAX. It is expected the caller will use grow to create
// a vaild AABB.
pub fn infinite() -> AABB {
AABB {
bounds: [Vec3::from(f32::MAX), Vec3::from(f32::MIN)],
}
}
pub fn new<V: Into<Vec3>>(min: V, max: V) -> AABB { pub fn new<V: Into<Vec3>>(min: V, max: V) -> AABB {
let min: Vec3 = min.into(); let min: Vec3 = min.into();
let max: Vec3 = max.into(); let max: Vec3 = max.into();
@ -51,10 +59,35 @@ impl AABB {
AABB { bounds: [min, max] } AABB { bounds: [min, max] }
} }
pub fn area(&self) -> f32 {
if self.max().x == f32::MIN || self.min().x == f32::MAX {
return 0.;
}
let e = self.max() - self.min();
let v = e.x * e.y + e.y * e.z + e.z * e.x;
if v.is_finite() {
v
} else {
0.
}
}
pub fn volume(&self) -> f32 { pub fn volume(&self) -> f32 {
(self.min().x - self.max().x).abs() if self.max().x == f32::MIN || self.min().x == f32::MAX {
return 0.;
}
let v = (self.min().x - self.max().x).abs()
* (self.min().y - self.max().y).abs() * (self.min().y - self.max().y).abs()
* (self.min().z - self.max().z).abs() * (self.min().z - self.max().z).abs();
if v.is_finite() {
v
} else {
0.
}
}
pub fn grow(&mut self, v: Vec3) {
self.bounds[0] = vec3::min(self.bounds[0], v);
self.bounds[1] = vec3::max(self.bounds[1], v);
} }
pub fn longest_axis(&self) -> usize { pub fn longest_axis(&self) -> usize {
@ -86,10 +119,14 @@ impl AABB {
// TODO(wathiede): implement branchless https://tavianator.com/cgit/dimension.git/tree/libdimension/bvh/bvh.c#n194 // TODO(wathiede): implement branchless https://tavianator.com/cgit/dimension.git/tree/libdimension/bvh/bvh.c#n194
pub fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> bool { pub fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
self.hit_simd(r, t_min, t_max).is_some()
}
pub fn hit_distance(&self, r: Ray, t_min: f32, t_max: f32) -> Option<f32> {
self.hit_simd(r, t_min, t_max) self.hit_simd(r, t_min, t_max)
} }
pub fn hit_naive(&self, r: Ray, t_min: f32, t_max: f32) -> bool { pub fn hit_naive(&self, r: Ray, t_min: f32, t_max: f32) -> Option<f32> {
let mut t_min = t_min; let mut t_min = t_min;
let mut t_max = t_max; let mut t_max = t_max;
for axis in 0..3 { for axis in 0..3 {
@ -100,13 +137,13 @@ impl AABB {
t_min = t0.max(t_min); t_min = t0.max(t_min);
t_max = t1.min(t_max); t_max = t1.min(t_max);
if t_max <= t_min { if t_max <= t_min {
return false; return None;
} }
} }
true Some(t_min)
} }
pub fn hit2(&self, r: Ray, t_min: f32, t_max: f32) -> bool { pub fn hit2(&self, r: Ray, t_min: f32, t_max: f32) -> Option<f32> {
let mut t_min = t_min; let mut t_min = t_min;
let mut t_max = t_max; let mut t_max = t_max;
for axis in 0..3 { for axis in 0..3 {
@ -122,13 +159,13 @@ impl AABB {
t_min = max(t0, t_min); t_min = max(t0, t_min);
t_max = min(t1, t_max); t_max = min(t1, t_max);
if t_max <= t_min { if t_max <= t_min {
return false; return None;
} }
} }
true Some(t_min)
} }
pub fn hit_precompute(&self, r: Ray, t0: f32, t1: f32) -> bool { pub fn hit_precompute(&self, r: Ray, t0: f32, t1: f32) -> Option<f32> {
// TODO(wathiede): this has bugs. // TODO(wathiede): this has bugs.
let mut t_min = (self.bounds[r.sign[0]].x - r.origin.x) * r.inv_direction.x; let mut t_min = (self.bounds[r.sign[0]].x - r.origin.x) * r.inv_direction.x;
let mut t_max = (self.bounds[1 - r.sign[0]].x - r.origin.x) * r.inv_direction.x; let mut t_max = (self.bounds[1 - r.sign[0]].x - r.origin.x) * r.inv_direction.x;
@ -136,7 +173,7 @@ impl AABB {
let t_y_max = (self.bounds[1 - r.sign[0]].y - r.origin.y) * r.inv_direction.y; let t_y_max = (self.bounds[1 - r.sign[0]].y - r.origin.y) * r.inv_direction.y;
if t_min > t_y_max || t_y_min > t_max { if t_min > t_y_max || t_y_min > t_max {
return false; return None;
} }
if t_y_min > t_min { if t_y_min > t_min {
@ -149,7 +186,7 @@ impl AABB {
let t_z_min = (self.bounds[r.sign[2]].z - r.origin.z) * r.inv_direction.z; let t_z_min = (self.bounds[r.sign[2]].z - r.origin.z) * r.inv_direction.z;
let t_z_max = (self.bounds[1 - r.sign[2]].z - r.origin.z) * r.inv_direction.z; let t_z_max = (self.bounds[1 - r.sign[2]].z - r.origin.z) * r.inv_direction.z;
if t_min > t_z_max || t_z_min > t_max { if t_min > t_z_max || t_z_min > t_max {
return false; return None;
} }
if t_z_min > t_min { if t_z_min > t_min {
t_min = t_z_min; t_min = t_z_min;
@ -157,10 +194,14 @@ impl AABB {
if t_z_max < t_max { if t_z_max < t_max {
t_max = t_z_max; t_max = t_z_max;
} }
t_min < t1 && t_max > t0 if t_min < t1 && t_max > t0 {
Some(t_min)
} else {
None
}
} }
pub fn hit_simd(&self, r: Ray, t_min: f32, t_max: f32) -> bool { pub fn hit_simd(&self, r: Ray, t_min: f32, t_max: f32) -> Option<f32> {
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
unsafe { unsafe {
use std::arch::x86_64::*; use std::arch::x86_64::*;
@ -177,14 +218,18 @@ impl AABB {
let vmin4: (f32, f32, f32, f32) = std::mem::transmute(vmin4); let vmin4: (f32, f32, f32, f32) = std::mem::transmute(vmin4);
let tmax = min(min(vmax4.0, min(vmax4.1, vmax4.2)), t_max); let tmax = min(min(vmax4.0, min(vmax4.1, vmax4.2)), t_max);
let tmin = max(max(vmin4.0, max(vmin4.1, vmin4.2)), t_min); let tmin = max(max(vmin4.0, max(vmin4.1, vmin4.2)), t_min);
tmin <= tmax if tmin <= tmax {
Some(tmin)
} else {
None
}
} }
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
// TODO(wathiede): add NEON implementation. // TODO(wathiede): add NEON implementation.
self.hit2(r, t_min, t_max) self.hit2(r, t_min, t_max)
} }
pub fn hit_fast(&self, r: Ray, _t_min: f32, _t_max: f32) -> bool { pub fn hit_fast(&self, r: Ray, _t_min: f32, _t_max: f32) -> Option<f32> {
let b = self; let b = self;
let mut t1 = (b.min()[0] - r.origin[0]) * 1. / r.direction[0]; let mut t1 = (b.min()[0] - r.origin[0]) * 1. / r.direction[0];
let mut t2 = (b.max()[0] - r.origin[0]) * 1. / r.direction[0]; let mut t2 = (b.max()[0] - r.origin[0]) * 1. / r.direction[0];
@ -200,7 +245,11 @@ impl AABB {
tmax = tmax.min(t1.max(t2)); tmax = tmax.min(t1.max(t2));
} }
tmax > tmin.max(0.0) if tmax > tmin.max(0.0) {
Some(tmin)
} else {
None
}
} }
} }
@ -222,6 +271,13 @@ pub fn surrounding_box(box0: &AABB, box1: &AABB) -> AABB {
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn infinite() {
let bb = AABB::infinite();
assert_eq!(bb.area(), 0.);
assert_eq!(bb.volume(), 0.);
}
macro_rules! hit_test { macro_rules! hit_test {
($($name:ident,)*) => { ($($name:ident,)*) => {
$( $(
@ -238,74 +294,74 @@ mod tests {
fn hit_front() { fn hit_front() {
let bb = test_bb(); let bb = test_bb();
let r = Ray::new([-2., 0., 0.], [1., 0., 0.], 0.5); let r = Ray::new([-2., 0., 0.], [1., 0., 0.], 0.5);
assert!(bb.$name(r, T_MIN, T_MAX)); assert!(bb.$name(r, T_MIN, T_MAX).is_some());
} }
#[test] #[test]
fn hit_back() { fn hit_back() {
let bb = test_bb(); let bb = test_bb();
let r = Ray::new([2., 0., 0.], [-1., 0., 0.], 0.5); let r = Ray::new([2., 0., 0.], [-1., 0., 0.], 0.5);
assert!(bb.$name(r, T_MIN, T_MAX)); assert!(bb.$name(r, T_MIN, T_MAX).is_some());
} }
#[test] #[test]
fn hit_top() { fn hit_top() {
let bb = test_bb(); let bb = test_bb();
let r = Ray::new([0., 2., 0.], [0., -1., 0.], 0.5); let r = Ray::new([0., 2., 0.], [0., -1., 0.], 0.5);
assert!(bb.$name(r, T_MIN, T_MAX)); assert!(bb.$name(r, T_MIN, T_MAX).is_some());
} }
#[test] #[test]
fn hit_bottom() { fn hit_bottom() {
let bb = test_bb(); let bb = test_bb();
let r = Ray::new([0., -2., 0.], [0., 1., 0.], 0.5); let r = Ray::new([0., -2., 0.], [0., 1., 0.], 0.5);
assert!(bb.$name(r, T_MIN, T_MAX)); assert!(bb.$name(r, T_MIN, T_MAX).is_some());
} }
#[test] #[test]
fn hit_left() { fn hit_left() {
let bb = test_bb(); let bb = test_bb();
let r = Ray::new([0., 0., -2.], [0., 0., 1.], 0.5); let r = Ray::new([0., 0., -2.], [0., 0., 1.], 0.5);
assert!(bb.$name(r, T_MIN, T_MAX)); assert!(bb.$name(r, T_MIN, T_MAX).is_some());
} }
#[test] #[test]
fn hit_right() { fn hit_right() {
let bb = test_bb(); let bb = test_bb();
let r = Ray::new([0., 0., 2.], [0., 0., -1.], 0.5); let r = Ray::new([0., 0., 2.], [0., 0., -1.], 0.5);
assert!(bb.$name(r, T_MIN, T_MAX)); assert!(bb.$name(r, T_MIN, T_MAX).is_some());
} }
#[test] #[test]
fn miss_front() { fn miss_front() {
let bb = test_bb(); let bb = test_bb();
let r = Ray::new([0., 1.1, -1.1], [0., -1., 0.], 0.5); let r = Ray::new([0., 1.1, -1.1], [0., -1., 0.], 0.5);
assert!(!bb.$name(r, T_MIN, T_MAX)); assert_eq!(bb.$name(r, T_MIN, T_MAX), None);
} }
#[test] #[test]
fn miss_back() { fn miss_back() {
let bb = test_bb(); let bb = test_bb();
let r = Ray::new([0., 1.1, 1.1], [0., -1., 0.], 0.5); let r = Ray::new([0., 1.1, 1.1], [0., -1., 0.], 0.5);
assert!(!bb.$name(r, T_MIN, T_MAX)); assert_eq!(bb.$name(r, T_MIN, T_MAX), None);
} }
#[test] #[test]
fn miss_top() { fn miss_top() {
let bb = test_bb(); let bb = test_bb();
let r = Ray::new([0., 1.1, -1.1], [0., 0., 1.], 0.5); let r = Ray::new([0., 1.1, -1.1], [0., 0., 1.], 0.5);
assert!(!bb.$name(r, T_MIN, T_MAX)); assert_eq!(bb.$name(r, T_MIN, T_MAX), None);
} }
#[test] #[test]
fn miss_bottom() { fn miss_bottom() {
let bb = test_bb(); let bb = test_bb();
let r = Ray::new([0., -1.1, -1.1], [0., 0., 1.], 0.5); let r = Ray::new([0., -1.1, -1.1], [0., 0., 1.], 0.5);
assert!(!bb.$name(r, T_MIN, T_MAX)); assert_eq!(bb.$name(r, T_MIN, T_MAX), None);
} }
#[test] #[test]
fn miss_left() { fn miss_left() {
let bb = test_bb(); let bb = test_bb();
let r = Ray::new([-1.1, 0., -1.1], [0., 0., 1.], 0.5); let r = Ray::new([-1.1, 0., -1.1], [0., 0., 1.], 0.5);
assert!(!bb.$name(r, T_MIN, T_MAX)); assert_eq!(bb.$name(r, T_MIN, T_MAX), None);
} }
#[test] #[test]
fn miss_right() { fn miss_right() {
let bb = test_bb(); let bb = test_bb();
let r = Ray::new([1.1, 0., -1.1], [0., 0., 1.], 0.5); let r = Ray::new([1.1, 0., -1.1], [0., 0., 1.], 0.5);
assert!(!bb.$name(r, T_MIN, T_MAX)); assert_eq!(bb.$name(r, T_MIN, T_MAX), None);
} }
} }
)* )*
@ -314,8 +370,8 @@ mod tests {
hit_test! { hit_test! {
hit_naive, hit_naive,
hit2,
hit_fast,
hit_simd, hit_simd,
hit_fast,
hit2,
} }
} }

View File

@ -3,6 +3,7 @@
use std::f32::EPSILON; use std::f32::EPSILON;
use std::fmt; use std::fmt;
use log::info;
use stl::STL; use stl::STL;
use crate::{ use crate::{
@ -16,19 +17,24 @@ use crate::{
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
struct BVHNode { struct BVHNode {
aabb: AABB, aabb: AABB,
// When prim_count==0, left_first holds the left child's index in bvh_nodes. When >0 left_first // When tri_count==0, left_first holds the left child's index in bvh_nodes. When >0 left_first
// holds the index for the first triangle in triangles. // holds the index for the first triangle in triangles.
left_first: u32, left_first: u32,
prim_count: u32, tri_count: u32,
} }
impl BVHNode { impl BVHNode {
fn is_leaf(&self) -> bool { fn is_leaf(&self) -> bool {
self.prim_count > 0 self.tri_count > 0
}
fn cost(&self) -> f32 {
let area = self.aabb.area();
self.tri_count as f32 * area
} }
} }
#[derive(PartialEq)] #[derive(Clone, PartialEq)]
pub struct Triangle { pub struct Triangle {
centroid: Vec3, centroid: Vec3,
verts: [Vec3; 3], verts: [Vec3; 3],
@ -48,10 +54,17 @@ where
M: Material, M: Material,
{ {
pub triangles: Vec<Triangle>, pub triangles: Vec<Triangle>,
triangle_index: Vec<usize>,
material: M, material: M,
bvh_nodes: Vec<BVHNode>, bvh_nodes: Vec<BVHNode>,
} }
#[derive(Debug, Default)]
struct Bin {
bounds: AABB,
tri_count: usize,
}
impl<M> fmt::Debug for BVHTriangles<M> impl<M> fmt::Debug for BVHTriangles<M>
where where
M: Material, M: Material,
@ -80,7 +93,7 @@ where
} }
let n = &self.bvh_nodes[i]; let n = &self.bvh_nodes[i];
if n.is_leaf() { if n.is_leaf() {
for t_idx in n.left_first..(n.left_first + n.prim_count) { for t_idx in n.left_first..(n.left_first + n.tri_count) {
if f.alternate() { if f.alternate() {
write!(f, "\t")?; write!(f, "\t")?;
} }
@ -95,12 +108,21 @@ where
} }
} }
#[derive(Debug)]
struct SplitCost {
pos: f32,
axis: usize,
cost: f32,
}
const ROOT_NODE_IDX: usize = 0; const ROOT_NODE_IDX: usize = 0;
impl<M> BVHTriangles<M> impl<M> BVHTriangles<M>
where where
M: Material, M: Material,
{ {
pub fn new(stl: &STL, material: M) -> BVHTriangles<M> { pub fn new(stl: &STL, material: M) -> BVHTriangles<M> {
let now = std::time::Instant::now();
assert_eq!(std::mem::size_of::<BVHNode>(), 32); assert_eq!(std::mem::size_of::<BVHNode>(), 32);
let div3 = 1. / 3.; let div3 = 1. / 3.;
let triangles: Vec<_> = stl let triangles: Vec<_> = stl
@ -117,15 +139,61 @@ where
} }
}) })
.collect(); .collect();
let triangle_index = (0..triangles.len()).collect();
let n = 2 * triangles.len() - 2; let n = 2 * triangles.len() - 2;
let bvh_nodes = Vec::with_capacity(n); let bvh_nodes = Vec::with_capacity(n);
let mut bvh = BVHTriangles { let mut bvh = BVHTriangles {
triangles, triangles,
triangle_index,
bvh_nodes, bvh_nodes,
material, material,
}; };
bvh.build_bvh(); bvh.build_bvh();
info!(
"BVHTriangles build time {:0.3}s",
now.elapsed().as_secs_f32()
);
struct Stats {
nodes: usize,
leafs: usize,
min_tris: u32,
max_tris: u32,
}
impl Default for Stats {
fn default() -> Self {
Stats {
nodes: 0,
leafs: 0,
min_tris: u32::MAX,
max_tris: u32::MIN,
}
}
}
let stats = bvh.bvh_nodes.iter().fold(Stats::default(), |mut stats, n| {
stats.nodes += 1;
if n.is_leaf() {
stats.leafs += 1;
stats.min_tris = n.tri_count.min(stats.min_tris);
stats.max_tris = n.tri_count.max(stats.max_tris);
}
stats
});
info!("BVHTriangles build stats:");
info!(" Nodes: {}", stats.nodes);
info!(" Leaves: {}", stats.leafs);
info!(" Tris: {}", bvh.triangles.len());
info!(" Min Tri: {}", stats.min_tris);
info!(" Max Tri: {}", stats.max_tris);
info!(" Avg Tri: {}", bvh.triangles.len() / stats.leafs);
info!(" Predict: {}", bvh.bvh_nodes.capacity());
info!(" Actual: {}", bvh.bvh_nodes.len());
info!(
" Savings: {} bytes",
(bvh.bvh_nodes.capacity() - bvh.bvh_nodes.len()) * std::mem::size_of::<BVHNode>()
);
bvh.bvh_nodes.shrink_to_fit();
//dbg!(&bvh);
bvh bvh
} }
@ -134,7 +202,7 @@ where
let root = BVHNode { let root = BVHNode {
aabb: AABB::default(), aabb: AABB::default(),
left_first: 0, left_first: 0,
prim_count: self.triangles.len() as u32, tri_count: self.triangles.len() as u32,
}; };
self.bvh_nodes.push(root); self.bvh_nodes.push(root);
self.update_node_bounds(ROOT_NODE_IDX); self.update_node_bounds(ROOT_NODE_IDX);
@ -145,8 +213,9 @@ where
let node = &mut self.bvh_nodes[node_idx]; let node = &mut self.bvh_nodes[node_idx];
let mut aabb_min: Vec3 = f32::MAX.into(); let mut aabb_min: Vec3 = f32::MAX.into();
let mut aabb_max: Vec3 = f32::MIN.into(); let mut aabb_max: Vec3 = f32::MIN.into();
for i in node.left_first..(node.left_first + node.prim_count) { for i in node.left_first..(node.left_first + node.tri_count) {
let leaf_tri = &self.triangles[i as usize]; let leaf_tri_idx = self.triangle_index[i as usize];
let leaf_tri = &self.triangles[leaf_tri_idx];
aabb_min = vec3::min(aabb_min, leaf_tri.verts[0]); aabb_min = vec3::min(aabb_min, leaf_tri.verts[0]);
aabb_min = vec3::min(aabb_min, leaf_tri.verts[1]); aabb_min = vec3::min(aabb_min, leaf_tri.verts[1]);
aabb_min = vec3::min(aabb_min, leaf_tri.verts[2]); aabb_min = vec3::min(aabb_min, leaf_tri.verts[2]);
@ -156,37 +225,118 @@ where
} }
node.aabb = AABB::new(aabb_min, aabb_max); node.aabb = AABB::new(aabb_min, aabb_max);
} }
fn find_best_split_plane(&self, node: &BVHNode) -> SplitCost {
let mut best = SplitCost {
pos: 0.,
cost: f32::MAX,
axis: usize::MAX,
};
for axis in 0..3 {
let mut bounds_min = f32::MAX;
let mut bounds_max = f32::MIN;
for i in 0..node.tri_count {
let triangle = &self.triangles[self.triangle_index[(node.left_first + i) as usize]];
bounds_min = bounds_min.min(triangle.centroid[axis]);
bounds_max = bounds_max.max(triangle.centroid[axis]);
}
if bounds_min == bounds_max {
continue;
}
const NUM_BINS: usize = 8;
let mut bins: Vec<_> = (0..NUM_BINS)
.map(|_| Bin {
bounds: AABB::infinite(),
tri_count: 0,
})
.collect();
let scale = bins.len() as f32 / (bounds_max - bounds_min);
// populate the bins
for i in 0..node.tri_count {
let triangle = &self.triangles[self.triangle_index[(node.left_first + i) as usize]];
let bin_idx = (triangle.centroid[axis] - bounds_min) * scale;
let bin_idx = (bins.len() - 1).min(bin_idx as usize);
bins[bin_idx].tri_count += 1;
bins[bin_idx].bounds.grow(triangle.verts[0]);
bins[bin_idx].bounds.grow(triangle.verts[1]);
bins[bin_idx].bounds.grow(triangle.verts[2]);
}
// gather data for the 7 planes between the 8 bins
let mut left_area: Vec<_> = (0..bins.len() - 1).map(|_| 0.).collect();
let mut right_area: Vec<_> = (0..bins.len() - 1).map(|_| 0.).collect();
let mut left_count: Vec<_> = (0..bins.len() - 1).map(|_| 0).collect();
let mut right_count: Vec<_> = (0..bins.len() - 1).map(|_| 0).collect();
let mut left_box = AABB::infinite();
let mut right_box = AABB::infinite();
let mut left_sum = 0;
let mut right_sum = 0;
for i in 0..(bins.len() - 1) {
left_sum += bins[i].tri_count;
left_count[i] = left_sum;
left_box.grow(bins[i].bounds.min());
left_box.grow(bins[i].bounds.max());
left_area[i] = left_box.area();
right_sum += bins[bins.len() - 1 - i].tri_count;
right_count[bins.len() - 2 - i] = right_sum;
right_box.grow(bins[bins.len() - 1 - i].bounds.min());
right_box.grow(bins[bins.len() - 1 - i].bounds.max());
right_area[bins.len() - 2 - i] = right_box.area();
}
let scale = (bounds_max - bounds_min) / bins.len() as f32;
// calculate SAH cost for the 7 planes
for i in 0..(bins.len() - 1) {
let plane_cost =
left_count[i] as f32 * left_area[i] + right_count[i] as f32 * right_area[i];
if plane_cost < best.cost {
best.axis = axis;
best.pos = bounds_min + scale * (i as f32 + 1.);
best.cost = plane_cost;
}
}
}
best
}
fn subdivide(&mut self, idx: usize) { fn subdivide(&mut self, idx: usize) {
// Early out if we're down to just 2 or less triangles let (left_first, tri_count, left_count, i) = {
if self.bvh_nodes[idx].prim_count <= 2 { let node = &self.bvh_nodes[idx];
let split = self.find_best_split_plane(&node);
let no_split_cost = node.cost();
// Stop subdividing if it isn't getting any better.
if split.cost >= no_split_cost {
return; return;
} }
let (left_first, prim_count, left_count, i) = {
let node = &self.bvh_nodes[idx];
// Compute split plane and position.
let extent = node.aabb.max() - node.aabb.min();
let axis = node.aabb.longest_axis();
let split_pos = node.aabb.min()[axis] + extent[axis] * 0.5;
// Split the group in two halves. // Split the group in two halves.
let mut i = node.left_first as isize; let mut i = node.left_first as isize;
let mut j = i + node.prim_count as isize - 1; let mut j = i + node.tri_count as isize - 1;
while i <= j { while i <= j {
if self.triangles[i as usize].centroid[axis] < split_pos { if self.triangles[self.triangle_index[i as usize]].centroid[split.axis] < split.pos
{
i += 1; i += 1;
} else { } else {
self.triangles.swap(i as usize, j as usize); self.triangles.swap(
self.triangle_index[i as usize],
self.triangle_index[j as usize],
);
j -= 1; j -= 1;
} }
} }
// Create child nodes for each half. // Create child nodes for each half.
let left_count = i as u32 - node.left_first; let left_count = i as u32 - node.left_first;
if left_count == 0 || left_count == node.prim_count { if left_count == 0 || left_count == node.tri_count {
return; return;
} }
(node.left_first, node.prim_count, left_count, i) (node.left_first, node.tri_count, left_count, i)
}; };
// create child nodes // create child nodes
@ -195,75 +345,92 @@ where
let left = BVHNode { let left = BVHNode {
aabb: AABB::default(), aabb: AABB::default(),
left_first, left_first,
prim_count: left_count as u32, tri_count: left_count as u32,
}; };
let right = BVHNode { let right = BVHNode {
aabb: AABB::default(), aabb: AABB::default(),
left_first: i as u32, left_first: i as u32,
prim_count: (prim_count - left_count) as u32, tri_count: (tri_count - left_count) as u32,
}; };
self.bvh_nodes.push(left); self.bvh_nodes.push(left);
self.bvh_nodes.push(right); self.bvh_nodes.push(right);
let node = &mut self.bvh_nodes[idx]; let node = &mut self.bvh_nodes[idx];
node.left_first = left_child_idx; node.left_first = left_child_idx;
node.prim_count = 0; node.tri_count = 0;
// Recurse // Recurse
self.update_node_bounds(left_child_idx as usize); self.update_node_bounds(left_child_idx as usize);
self.update_node_bounds(right_child_idx as usize);
self.subdivide(left_child_idx as usize); self.subdivide(left_child_idx as usize);
self.update_node_bounds(right_child_idx as usize);
self.subdivide(right_child_idx as usize); self.subdivide(right_child_idx as usize);
} }
fn intersect_bvh(&self, r: Ray, node_idx: u32, t_min: f32, t_max: f32) -> Option<HitRecord> { fn intersect_bvh(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
let node = &self.bvh_nodes[node_idx as usize]; let mut node = &self.bvh_nodes[ROOT_NODE_IDX];
if !node.aabb.hit(r, t_min, t_max) { let mut stack = Vec::with_capacity(2);
return None; let mut nearest = None;
} loop {
//dbg!(&self);
if node.is_leaf() { if node.is_leaf() {
return self let canditate = (node.left_first..(node.left_first + node.tri_count))
.triangles // Map from idx to Triangle
.iter() .map(|idx| &self.triangles[self.triangle_index[idx as usize]])
.map(|tri| { // Try to hit all triangles for this node, filtering out misses.
if let Some(RayTriangleResult { t, p }) = intersect_tri(r, tri) { .filter_map(|tri| intersect_tri(r, &tri))
// We don't support UV (yet?). // Find the nearest hit (if any).
let uv = (0.5, 0.5);
let v0 = tri.verts[0];
let v1 = tri.verts[1];
let v2 = tri.verts[2];
let v0v1 = v1 - v0;
let v0v2 = v2 - v0;
let normal = cross(v0v1, v0v2).unit_vector();
//println!("hit triangle {tri:?}");
Some(HitRecord {
t,
uv,
p,
normal,
material: &self.material,
})
} else {
None
}
})
.filter_map(|hr| hr)
.min_by(|a, b| a.t.partial_cmp(&b.t).unwrap()); .min_by(|a, b| a.t.partial_cmp(&b.t).unwrap());
// merge candidate with nearest.
nearest = match (&canditate, &nearest) {
(Some(_), None) => canditate,
(None, Some(_)) => nearest,
(Some(c), Some(n)) => {
//info!("merging {c:#?} and {n:#?}");
if c.t < n.t {
canditate
} else { } else {
let r1 = self.intersect_bvh(r, node.left_first, t_min, t_max); nearest
let r2 = self.intersect_bvh(r, node.left_first + 1, t_min, t_max);
// Merge results, if both hit, take the one closest to the ray origin (smallest t
// value).
match (&r1, &r2) {
(Some(_), None) => return r1,
(None, Some(_)) => return r2,
(None, None) => (),
(Some(rp1), Some(rp2)) => return if rp1.t < rp2.t { r1 } else { r2 },
} }
} }
None (None, None) => None,
};
if stack.is_empty() {
break;
}
node = stack.pop().unwrap();
continue;
}
let child1 = &self.bvh_nodes[node.left_first as usize];
let child2 = &self.bvh_nodes[node.left_first as usize + 1];
let dist1 = child1.aabb.hit_distance(r, t_min, t_max);
let dist2 = child2.aabb.hit_distance(r, t_min, t_max);
// Swap c1/c2 & d1/d2 based on d1/d2.
let (child1, child2, dist1, dist2) = match (dist1, dist2) {
(Some(d1), Some(d2)) if d1 > d2 => (child2, child1, dist2, dist1),
(None, Some(_)) => (child2, child1, dist2, dist1),
_ => (child1, child2, dist1, dist2),
};
// dist1/child1 should now be the nearest hit.
// If we missed dist1/child1, then we implicitly missed dist2/child2, so pop a child
// from the stack or exit the function.
if dist1.is_none() {
if stack.is_empty() {
break;
}
node = stack.pop().unwrap();
} else {
// We hit child1, so process it next.
node = child1;
// If we also hit child2 save it on the stack so we can process it later.
if dist2.is_some() {
stack.push(child2);
}
}
}
nearest
.and_then(|rtr: RayTriangleResult| Some(rtr.hit_record_with_material(&self.material)))
} }
} }
@ -325,6 +492,7 @@ fn intersect_tri(r: Ray, tri: &Triangle) -> Option<RayTriangleResult> {
return Some(RayTriangleResult { return Some(RayTriangleResult {
t, t,
p: r.point_at_parameter(t), p: r.point_at_parameter(t),
tri: tri.clone(),
}); });
} }
None None
@ -335,17 +503,41 @@ where
M: Material, M: Material,
{ {
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> { fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
self.intersect_bvh(r, 0, t_min, t_max) self.intersect_bvh(r, t_min, t_max)
} }
fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option<AABB> { fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option<AABB> {
Some(self.bvh_nodes[0].aabb) Some(self.bvh_nodes[ROOT_NODE_IDX].aabb)
} }
} }
#[derive(Debug)]
struct RayTriangleResult { struct RayTriangleResult {
t: f32, t: f32,
p: Vec3, p: Vec3,
tri: Triangle,
}
impl RayTriangleResult {
fn hit_record_with_material<'m>(self, material: &'m dyn Material) -> HitRecord<'m> {
// We don't support UV (yet?).
let uv = (0.5, 0.5);
let v0 = self.tri.verts[0];
let v1 = self.tri.verts[1];
let v2 = self.tri.verts[2];
let v0v1 = v1 - v0;
let v0v2 = v2 - v0;
let normal = cross(v0v1, v0v2).unit_vector();
//println!("hit triangle {tri:?}");
HitRecord {
t: self.t,
uv,
p: self.p,
normal,
material,
}
}
} }
#[cfg(test)] #[cfg(test)]
@ -360,67 +552,13 @@ mod tests {
texture::ConstantTexture, texture::ConstantTexture,
}; };
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use proptest::prelude::*;
use std::{ use std::{
io::{BufReader, Cursor}, io::{BufReader, Cursor},
sync::Arc, sync::Arc,
}; };
use stl::STL; use stl::STL;
/*
#[test]
fn build_bvh() {
let stl_triangles: Vec<_> = (0..4)
.flat_map(|y| {
(0..2).map(move |x| {
let x = x as f32;
let y = y as f32;
stl::Triangle {
normal: [1., 0., 0.].into(),
verts: [
[2. * x + 0., 2. * y + 0., 0.].into(),
[2. * x + 1., 2. * y + 0., 0.].into(),
[2. * x + 1., 2. * y + 1., 0.].into(),
],
attr: 0,
}
})
})
.collect();
let stl = STL {
header: [0; 80],
triangles: stl_triangles,
};
/*
let mut bvh_triangles: Vec<_> = stl_triangles
.iter()
.map(|tri| {
let div3 = 1. / 3.;
let v0 = tri.verts[0];
let v1 = tri.verts[1];
let v2 = tri.verts[2];
let centroid = (v0 + v1 + v2) * div3;
Triangle {
centroid,
verts: tri.verts,
}
})
.collect();
bvh_triangles.sort_by(|a, b| a.centroid.y.partial_cmp(&b.centroid.y).unwrap());
let material = Lambertian::new(ConstantTexture::new([0., 0., 0.]));
let bvh_nodes = Default::default();
let want = BVHTriangles {
triangles: bvh_triangles,
bvh_nodes,
material,
};
*/
let material = Lambertian::new(ConstantTexture::new([0., 0., 0.]));
let bvh = BVHTriangles::new(&stl, material);
dbg!(&bvh);
assert_eq!(bvh.bvh_nodes.len(), 2 * bvh.triangles.len() - 2);
}
*/
#[test] #[test]
fn compare_cuboid() { fn compare_cuboid() {
let c = Cuboid::new( let c = Cuboid::new(
@ -478,7 +616,6 @@ mod tests {
}) })
}) })
.collect(); .collect();
// These currently differ between STL and cuboid.
if false { if false {
// Outward in at an angle. // Outward in at an angle.
let sqrt2 = 2f32.sqrt(); let sqrt2 = 2f32.sqrt();
@ -490,8 +627,6 @@ mod tests {
]); ]);
} }
// TODO(wathiede): proptest this, it's still not perfectly equal when rendering.
for r in rays.into_iter() { for r in rays.into_iter() {
let c_hit = c let c_hit = c
.hit(r, 0., f32::MAX) .hit(r, 0., f32::MAX)
@ -515,4 +650,62 @@ mod tests {
); );
} }
} }
proptest! {
// TODO(wathiede): make this work.
//#[test]
fn compare_cuboid_proptest(
ox in -20.0f32..40.0,
oy in -20.0f32..40.0,
oz in -20.0f32..40.0,
dx in -1.0f32..1.0,
dy in -1.0f32..1.0,
dz in -1.0f32..1.0,
) {
let r = Ray::new([ox,oy,oz].into(), Vec3::new(dx, dy, dz).unit_vector(), 0.5);
let c = Cuboid::new(
[0., 0., 0.].into(),
[20., 20., 20.].into(),
Arc::new(Dielectric::new(1.5)),
);
let stl_cube = STL::parse(
BufReader::new(Cursor::new(include_bytes!("../stls/cube.stl"))),
false,
)
.expect("failed to parse cube");
let s = BVHTriangles::new(
&stl_cube,
Dielectric::new(1.5),
//Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.2),
//Lambertian::new(ConstantTexture::new(Vec3::new(1.0, 0.2, 0.2))),
);
let c_hit = c .hit(r, 0., f32::MAX);
let s_hit = s .hit(r, 0., f32::MAX);
match (c_hit, s_hit) {
(Some(_), None)=>assert!(false, "hit cuboid but not STL"),
(None, Some(_))=>assert!(false, "hit STL but not cuboid"),
(Some(c_hit), Some(s_hit))=> {
assert!(
(c_hit.t - s_hit.t).abs() < 0.00001,
"{r:?} [t] c_hit: {c_hit:#?}, s_hit: {s_hit:#?}"
);
// uv isn't valid for BVHTriangles.
// assert_eq!( c_hit.uv, s_hit.uv, "{i}: [uv] c_hit: {c_hit:?}, s_hit: {s_hit:?}");
assert_eq!(
c_hit.p, s_hit.p,
"{r:?}: [p] c_hit: {c_hit:#?}, s_hit: {s_hit:#?}"
);
assert_eq!(
c_hit.normal, s_hit.normal,
"{r:?}: [normal] c_hit: {c_hit:?}, s_hit: {s_hit:?}"
);
},
// It's okay if they both miss.
(None,None)=>(),
}
}
}
} }

View File

@ -52,7 +52,7 @@ impl Material for Box<dyn Material> {
} }
} }
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct Isotropic<T> pub struct Isotropic<T>
where where
T: Texture, T: Texture,
@ -83,7 +83,7 @@ where
} }
} }
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct Lambertian<T> pub struct Lambertian<T>
where where
T: Texture, T: Texture,
@ -115,7 +115,7 @@ where
} }
} }
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct Metal { pub struct Metal {
albedo: Vec3, albedo: Vec3,
fuzzy: f32, fuzzy: f32,
@ -170,7 +170,7 @@ fn schlick(cosine: f32, ref_idx: f32) -> f32 {
r0 + (1. - r0) * (1. - cosine).powf(5.) r0 + (1. - r0) * (1. - cosine).powf(5.)
} }
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct Dielectric { pub struct Dielectric {
ref_idx: f32, ref_idx: f32,
} }

View File

@ -8,7 +8,7 @@ use std::{
Arc, Mutex, Arc, Mutex,
}, },
thread, thread,
time::{self, Instant}, time::{Duration, Instant},
}; };
use core_affinity; use core_affinity;
@ -363,13 +363,13 @@ fn progress(
start_time: Instant, start_time: Instant,
last_stat: &RenderStats, last_stat: &RenderStats,
current_stat: &RenderStats, current_stat: &RenderStats,
time_diff: time::Duration, time_diff: Duration,
pixel_total: usize, pixel_total: usize,
) -> String { ) -> String {
let human = human::Formatter::new(); let human = human::Formatter::new();
let pixel_diff = current_stat.pixels - last_stat.pixels; let pixel_diff = current_stat.pixels - last_stat.pixels;
let ray_diff = current_stat.rays - last_stat.rays; let ray_diff = current_stat.rays - last_stat.rays;
let now = time::Instant::now(); let now = Instant::now();
let start_diff = now - start_time; let start_diff = now - start_time;
let ratio = current_stat.pixels as f32 / pixel_total as f32; let ratio = current_stat.pixels as f32 / pixel_total as f32;
let percent = ratio * 100.; let percent = ratio * 100.;
@ -390,6 +390,7 @@ fn progress(
enum Request { enum Request {
Pixel { x: usize, y: usize }, Pixel { x: usize, y: usize },
Line { width: usize, y: usize }, Line { width: usize, y: usize },
// TODO(wathiede): add Cohort that does 4x4 or 8x8 pixel chunks.
} }
enum Response { enum Response {
@ -576,10 +577,10 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
info!("Rendering with {} subsamples", scene.subsamples); info!("Rendering with {} subsamples", scene.subsamples);
let pixel_total = scene.width * scene.height; let pixel_total = scene.width * scene.height;
let mut last_time = time::Instant::now(); let mut last_time = Instant::now();
let mut last_stat: RenderStats = Default::default(); let mut last_stat: RenderStats = Default::default();
let mut current_stat: RenderStats = Default::default(); let mut current_stat: RenderStats = Default::default();
let start_time = time::Instant::now(); let render_start_time = Instant::now();
for resp in pixel_resp_rx { for resp in pixel_resp_rx {
match resp { match resp {
Response::Pixel { x, y, pixel, rs } => { Response::Pixel { x, y, pixel, rs } => {
@ -594,13 +595,13 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
} }
} }
let now = time::Instant::now(); let now = Instant::now();
let time_diff = now - last_time; let time_diff = now - last_time;
if time_diff > time::Duration::from_secs(1) { if time_diff > Duration::from_secs(5) {
println!( println!(
"{}", "{}",
progress( progress(
start_time, render_start_time,
&last_stat, &last_stat,
&current_stat, &current_stat,
time_diff, time_diff,
@ -614,12 +615,12 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
for thr in handles { for thr in handles {
thr.join().expect("thread join"); thr.join().expect("thread join");
} }
let time_diff = time::Instant::now() - start_time; let time_diff = Instant::now() - render_start_time;
println!( println!(
"Runtime {} seconds {}", "Render {} seconds {}",
time_diff.as_secs_f32(), time_diff.as_secs_f32(),
progress( progress(
start_time, render_start_time,
&Default::default(), &Default::default(),
&current_stat, &current_stat,
time_diff, time_diff,

View File

@ -1,14 +1,10 @@
use std::{ use std::io::{BufReader, Cursor};
io::{BufReader, Cursor},
sync::Arc,
};
use stl::STL; use stl::STL;
use crate::{ use crate::{
bvh_triangles::BVHTriangles, bvh_triangles::BVHTriangles,
camera::Camera, camera::Camera,
cuboid::Cuboid,
hitable::Hit, hitable::Hit,
hitable_list::HitableList, hitable_list::HitableList,
kdtree::KDTree, kdtree::KDTree,
@ -17,6 +13,7 @@ use crate::{
scale::Scale, scale::Scale,
sphere::Sphere, sphere::Sphere,
texture::{ConstantTexture, EnvMap}, texture::{ConstantTexture, EnvMap},
translate::Translate,
vec3::Vec3, vec3::Vec3,
}; };
@ -46,8 +43,7 @@ pub fn new(opt: &Opt) -> Scene {
let stl_cube = STL::parse( let stl_cube = STL::parse(
BufReader::new(Cursor::new(include_bytes!( BufReader::new(Cursor::new(include_bytes!(
"../../stls/cube.stl" "../../stls/stanford_dragon-lowres.stl" //"../../stls/stanford_dragon.stl"
//"../../stls/stanford_dragon-lowres.stl" //"../../stls/stanford_dragon.stl"
))), ))),
false, false,
) )
@ -81,20 +77,9 @@ pub fn new(opt: &Opt) -> Scene {
Lambertian::new(ConstantTexture::new(Vec3::new(1.0, 0.2, 0.2))), Lambertian::new(ConstantTexture::new(Vec3::new(1.0, 0.2, 0.2))),
)), )),
// STL Mesh // STL Mesh
Box::new(Scale::new( Box::new(Translate::new(
BVHTriangles::new( Scale::new(BVHTriangles::new(&stl_cube, Dielectric::new(1.5)), 250.),
&stl_cube, [0., -10., 0.],
Dielectric::new(1.5),
//Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.2),
//Lambertian::new(ConstantTexture::new(Vec3::new(1.0, 0.2, 0.2))),
),
1.,
//250.,
)),
Box::new(Cuboid::new(
[-20., 0., 0.].into(),
[0., 20., 20.].into(),
Arc::new(Dielectric::new(1.5)),
)), )),
]; ];
let world: Box<dyn Hit> = if opt.use_accel { let world: Box<dyn Hit> = if opt.use_accel {

View File

@ -12,7 +12,7 @@ use crate::{
hitable::Hit, hitable::Hit,
hitable_list::HitableList, hitable_list::HitableList,
kdtree::KDTree, kdtree::KDTree,
material::{Dielectric, Lambertian}, material::{Dielectric, Lambertian, Metal},
renderer::{Opt, Scene}, renderer::{Opt, Scene},
sphere::Sphere, sphere::Sphere,
texture::{ConstantTexture, EnvMap}, texture::{ConstantTexture, EnvMap},
@ -43,13 +43,20 @@ pub fn new(opt: &Opt) -> Scene {
ConstantTexture::new(Vec3::new(0.4, 0.4, 0.4)) ConstantTexture::new(Vec3::new(0.4, 0.4, 0.4))
}; };
let glass = Dielectric::new(1.5);
let metal = Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.2);
let red = Lambertian::new(ConstantTexture::new(Vec3::new(1.0, 0.2, 0.2)));
let box_material = glass;
let _ = glass;
let _ = metal;
let _ = red;
let stl_cube = STL::parse( let stl_cube = STL::parse(
BufReader::new(Cursor::new(include_bytes!("../../stls/cube.stl"))), BufReader::new(Cursor::new(include_bytes!("../../stls/cube.stl"))),
false, false,
) )
.expect("failed to parse cube"); .expect("failed to parse cube");
let _light_size = 50.;
let _light_height = 200.;
let objects: Vec<Box<dyn Hit>> = vec![ let objects: Vec<Box<dyn Hit>> = vec![
// Light from above - white // Light from above - white
// Earth sized sphere // Earth sized sphere
@ -77,16 +84,11 @@ pub fn new(opt: &Opt) -> Scene {
Lambertian::new(ConstantTexture::new(Vec3::new(1.0, 0.2, 0.2))), Lambertian::new(ConstantTexture::new(Vec3::new(1.0, 0.2, 0.2))),
)), )),
// STL Mesh // STL Mesh
Box::new(BVHTriangles::new( Box::new(BVHTriangles::new(&stl_cube, box_material.clone())),
&stl_cube,
Dielectric::new(1.5),
//Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.2),
//Lambertian::new(ConstantTexture::new(Vec3::new(1.0, 0.2, 0.2))),
)),
Box::new(Cuboid::new( Box::new(Cuboid::new(
[-20., 0., 0.].into(), [-20., 0., 0.].into(),
[0., 20., 20.].into(), [0., 20., 20.].into(),
Arc::new(Dielectric::new(1.5)), Arc::new(box_material),
)), )),
]; ];
let world: Box<dyn Hit> = if opt.use_accel { let world: Box<dyn Hit> = if opt.use_accel {

View File

@ -1,6 +1,6 @@
use crate::{texture::Texture, vec3::Vec3}; use crate::{texture::Texture, vec3::Vec3};
#[derive(Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct ConstantTexture { pub struct ConstantTexture {
color: Vec3, color: Vec3,
} }

View File

@ -1,5 +1,5 @@
#![warn(unused_extern_crates)] #![warn(unused_extern_crates)]
use std::fs; use std::{fs, time::Instant};
#[cfg(feature = "profile")] #[cfg(feature = "profile")]
use cpuprofiler::PROFILER; use cpuprofiler::PROFILER;
@ -36,6 +36,7 @@ impl MockProfiler {
static PROFILER: MockProfiler = MockProfiler {}; static PROFILER: MockProfiler = MockProfiler {};
fn main() -> Result<(), std::io::Error> { fn main() -> Result<(), std::io::Error> {
let start_time = Instant::now();
stderrlog::new() stderrlog::new()
.verbosity(3) .verbosity(3)
.timestamp(stderrlog::Timestamp::Millisecond) .timestamp(stderrlog::Timestamp::Millisecond)
@ -46,7 +47,7 @@ fn main() -> Result<(), std::io::Error> {
eprintln!("--model should be one of {:?}", Model::VARIANTS); eprintln!("--model should be one of {:?}", Model::VARIANTS);
return Ok(()); return Ok(());
} }
info!("{:?}", opt); info!("{:#?}", opt);
let scene = opt.model.as_ref().unwrap().scene(&opt); let scene = opt.model.as_ref().unwrap().scene(&opt);
fs::create_dir_all(&opt.output)?; fs::create_dir_all(&opt.output)?;
if opt.pprof.is_some() && !cfg!(feature = "profile") { if opt.pprof.is_some() && !cfg!(feature = "profile") {
@ -65,5 +66,7 @@ fn main() -> Result<(), std::io::Error> {
PROFILER.lock().unwrap().stop().unwrap(); PROFILER.lock().unwrap().stop().unwrap();
} }
let time_diff = Instant::now() - start_time;
info!("Total runtime {} seconds", time_diff.as_secs_f32());
res res
} }