Compare commits
22 Commits
a12938db95
...
2d696932e3
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d696932e3 | |||
| 27d6c1280b | |||
| 4506418706 | |||
| 1d8aff7905 | |||
| 585ad4805c | |||
| b7f163c5a9 | |||
| 4ab9425a97 | |||
| 468cba97b3 | |||
| b9ebc186fa | |||
| 3e9d900f1e | |||
| 9e81acfda9 | |||
| f8ec874d13 | |||
| a8756debb8 | |||
| a0fb4637b5 | |||
| 6069bf9a65 | |||
| eeb7813243 | |||
| c644299726 | |||
| e6db61543b | |||
| 39eeb79409 | |||
| 54e72cd81d | |||
| 2d91f781f3 | |||
| 24e8b4f9cf |
654
rtiow/Cargo.lock
generated
654
rtiow/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -5,9 +5,8 @@ members = [
|
|||||||
"noise_explorer_warp",
|
"noise_explorer_warp",
|
||||||
"renderer",
|
"renderer",
|
||||||
"tracer",
|
"tracer",
|
||||||
|
"vec3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile]
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
||||||
|
|
||||||
|
|||||||
@ -2,19 +2,19 @@
|
|||||||
name = "noise_explorer"
|
name = "noise_explorer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Bill Thiede <git@xinu.tv>"]
|
authors = ["Bill Thiede <git@xinu.tv>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "0.7.8"
|
actix-web = "0.7.19"
|
||||||
askama = "0.7.1"
|
askama = "0.7.2"
|
||||||
image = "0.22.3"
|
image = "0.22.5"
|
||||||
log = "0.4.5"
|
log = "0.4.17"
|
||||||
rand = "0.8.3"
|
rand = "0.8.5"
|
||||||
rand_xorshift = "0.3.0"
|
rand_xorshift = "0.3.0"
|
||||||
renderer = { path = "../renderer" }
|
renderer = { path = "../renderer" }
|
||||||
serde = "1.0.79"
|
serde = "1.0.152"
|
||||||
serde_derive = "1.0.79"
|
serde_derive = "1.0.152"
|
||||||
stderrlog = "0.4.1"
|
stderrlog = "0.4.3"
|
||||||
structopt = "0.2.10"
|
structopt = "0.2.18"
|
||||||
|
|||||||
@ -2,18 +2,18 @@
|
|||||||
name = "noise_explorer_warp"
|
name = "noise_explorer_warp"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Bill Thiede <git@xinu.tv>"]
|
authors = ["Bill Thiede <git@xinu.tv>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
askama = "0.7.1"
|
askama = "0.7.2"
|
||||||
image = "0.22.3"
|
image = "0.22.5"
|
||||||
log = "0.4.5"
|
log = "0.4.17"
|
||||||
rand = "0.5.5"
|
rand = "0.5.6"
|
||||||
renderer = { path = "../renderer" }
|
renderer = { path = "../renderer" }
|
||||||
serde = "1.0.79"
|
serde = "1.0.152"
|
||||||
serde_derive = "1.0.79"
|
serde_derive = "1.0.152"
|
||||||
stderrlog = "0.4.1"
|
stderrlog = "0.4.3"
|
||||||
structopt = "0.2.10"
|
structopt = "0.2.18"
|
||||||
warp = "0.1.20"
|
warp = "0.1.23"
|
||||||
|
|||||||
@ -2,29 +2,35 @@
|
|||||||
name = "renderer"
|
name = "renderer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Bill Thiede <git@xinu.tv>"]
|
authors = ["Bill Thiede <git@xinu.tv>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
[[bench]]
|
[[bench]]
|
||||||
harness = false
|
harness = false
|
||||||
name = "spheres"
|
name = "spheres"
|
||||||
|
[[bench]]
|
||||||
|
harness = false
|
||||||
|
name = "aabb"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "*"
|
chrono = "*"
|
||||||
core_affinity = "0.5"
|
core_affinity = "0.5"
|
||||||
cpuprofiler = { version = "0.0.3", optional = true }
|
cpuprofiler = { version = "0.0.3", optional = true }
|
||||||
image = "0.22.3"
|
image = "0.22.5"
|
||||||
lazy_static = "1.1.0"
|
lazy_static = "1.4.0"
|
||||||
log = "0.4.5"
|
log = "0.4.17"
|
||||||
num_cpus = "1.8.0"
|
num_cpus = "1.15.0"
|
||||||
rand = "0.8.3"
|
rand = "0.8.5"
|
||||||
serde = "1.0.79"
|
serde = "1.0.152"
|
||||||
serde_derive = "1.0.79"
|
serde_derive = "1.0.152"
|
||||||
serde_json = "1.0.41"
|
serde_json = "1.0.91"
|
||||||
structopt = "0.2.10"
|
structopt = "0.2.18"
|
||||||
|
vec3 = {path = "../vec3"}
|
||||||
|
stl = {path = "../../../stl"}
|
||||||
|
#stl = {git = "https://git-private.z.xinu.tv/wathiede/stl"}
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.2"
|
criterion = "0.4"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
profile = ["cpuprofiler"]
|
profile = ["cpuprofiler"]
|
||||||
|
|||||||
47
rtiow/renderer/benches/aabb.rs
Normal file
47
rtiow/renderer/benches/aabb.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
use criterion::*;
|
||||||
|
use renderer::{aabb::AABB, ray::Ray};
|
||||||
|
|
||||||
|
fn bench(c: &mut Criterion) {
|
||||||
|
let bb = AABB::new([1., -1., -1.], [3., 1., 1.]);
|
||||||
|
let r_hit = Ray::new([0., 0., 0.], [1., 0., 0.], 0.);
|
||||||
|
let r_miss = Ray::new([0., 0., 0.], [-1., 0., 0.], 0.);
|
||||||
|
let t_min = 0.001;
|
||||||
|
let t_max = f32::MAX;
|
||||||
|
|
||||||
|
let mut group = c.benchmark_group("aabb");
|
||||||
|
group.throughput(Throughput::Elements(1));
|
||||||
|
group.bench_with_input(BenchmarkId::new("hit_naive", "r_hit"), &r_hit, |b, r| {
|
||||||
|
b.iter(|| bb.hit_naive(*r, t_min, t_max))
|
||||||
|
});
|
||||||
|
group.bench_with_input(BenchmarkId::new("hit2", "r_hit"), &r_hit, |b, r| {
|
||||||
|
b.iter(|| bb.hit2(*r, t_min, t_max))
|
||||||
|
});
|
||||||
|
//group.bench_with_input(BenchmarkId::new("hit_precompute", "r_hit"), &r_hit, |b, r| { b.iter(|| bb.hit_precompute(*r, t_min, t_max)) });
|
||||||
|
group.bench_with_input(BenchmarkId::new("hit_fast", "r_hit"), &r_hit, |b, r| {
|
||||||
|
b.iter(|| bb.hit_fast(*r, t_min, t_max))
|
||||||
|
});
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
group.bench_with_input(BenchmarkId::new("hit_simd", "r_hit"), &r_hit, |b, r| {
|
||||||
|
b.iter(|| bb.hit_simd(*r, t_min, t_max))
|
||||||
|
});
|
||||||
|
|
||||||
|
group.bench_with_input(BenchmarkId::new("hit_naive", "r_miss"), &r_miss, |b, r| {
|
||||||
|
b.iter(|| bb.hit_naive(*r, t_min, t_max))
|
||||||
|
});
|
||||||
|
group.bench_with_input(BenchmarkId::new("hit2", "r_miss"), &r_miss, |b, r| {
|
||||||
|
b.iter(|| bb.hit2(*r, t_min, t_max))
|
||||||
|
});
|
||||||
|
//group.bench_with_input(BenchmarkId::new("hit_precompute", "r_miss"), &r_miss, |b, r| { b.iter(|| bb.hit_precompute(*r, t_min, t_max)) });
|
||||||
|
group.bench_with_input(BenchmarkId::new("hit_fast", "r_miss"), &r_miss, |b, r| {
|
||||||
|
b.iter(|| bb.hit_fast(*r, t_min, t_max))
|
||||||
|
});
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
group.bench_with_input(BenchmarkId::new("hit_simd", "r_miss"), &r_miss, |b, r| {
|
||||||
|
b.iter(|| bb.hit_simd(*r, t_min, t_max))
|
||||||
|
});
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, bench);
|
||||||
|
criterion_main!(benches);
|
||||||
@ -1,9 +1,6 @@
|
|||||||
#[macro_use]
|
use criterion::*;
|
||||||
extern crate criterion;
|
|
||||||
|
|
||||||
use criterion::{Criterion, ParameterizedBenchmark};
|
use renderer::{
|
||||||
|
|
||||||
use rtiow::{
|
|
||||||
hitable::Hit, material::Lambertian, ray::Ray, sphere::Sphere, texture::ConstantTexture,
|
hitable::Hit, material::Lambertian, ray::Ray, sphere::Sphere, texture::ConstantTexture,
|
||||||
vec3::Vec3,
|
vec3::Vec3,
|
||||||
};
|
};
|
||||||
@ -20,14 +17,15 @@ fn criterion_benchmark(c: &mut Criterion) {
|
|||||||
// Miss
|
// Miss
|
||||||
Ray::new([0., 0., -2.], [0., 0., -1.], 0.),
|
Ray::new([0., 0., -2.], [0., 0., -1.], 0.),
|
||||||
];
|
];
|
||||||
c.bench(
|
let mut group = c.benchmark_group("sphere");
|
||||||
"sphere",
|
group.throughput(Throughput::Elements(1));
|
||||||
ParameterizedBenchmark::new(
|
group.bench_with_input(BenchmarkId::new("Sphere", "hit"), &rays[0], |b, r| {
|
||||||
"Sphere",
|
b.iter(|| sphere.hit(*r, 0., 1.))
|
||||||
move |b, r| b.iter(|| sphere.hit(*r, 0., 1.)),
|
});
|
||||||
rays,
|
group.bench_with_input(BenchmarkId::new("Sphere", "miss"), &rays[1], |b, r| {
|
||||||
),
|
b.iter(|| sphere.hit(*r, 0., 1.))
|
||||||
);
|
});
|
||||||
|
group.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
criterion_group!(benches, criterion_benchmark);
|
criterion_group!(benches, criterion_benchmark);
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use std::fmt;
|
|||||||
|
|
||||||
use crate::{ray::Ray, vec3::Vec3};
|
use crate::{ray::Ray, vec3::Vec3};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Default, Debug, Copy, Clone, PartialEq)]
|
||||||
pub struct AABB {
|
pub struct AABB {
|
||||||
bounds: [Vec3; 2],
|
bounds: [Vec3; 2],
|
||||||
}
|
}
|
||||||
@ -30,7 +30,12 @@ fn max(x: f32, y: f32) -> f32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AABB {
|
impl AABB {
|
||||||
pub fn new(min: Vec3, max: Vec3) -> AABB {
|
pub fn new<V: Into<Vec3>>(min: V, max: V) -> AABB {
|
||||||
|
let min: Vec3 = min.into();
|
||||||
|
let max: Vec3 = max.into();
|
||||||
|
assert!(min.x < max.x);
|
||||||
|
assert!(min.y < max.y);
|
||||||
|
assert!(min.z < max.z);
|
||||||
AABB { bounds: [min, max] }
|
AABB { bounds: [min, max] }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,10 +66,33 @@ impl AABB {
|
|||||||
pub fn min(&self) -> Vec3 {
|
pub fn min(&self) -> Vec3 {
|
||||||
self.bounds[0]
|
self.bounds[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn max(&self) -> Vec3 {
|
pub fn max(&self) -> Vec3 {
|
||||||
self.bounds[1]
|
self.bounds[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
|
||||||
|
self.hit_simd(r, t_min, t_max)
|
||||||
|
//self.hit_naive(r, t_min, t_max)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hit_naive(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
|
||||||
|
let mut t_min = t_min;
|
||||||
|
let mut t_max = t_max;
|
||||||
|
for axis in 0..3 {
|
||||||
|
let t0 = ((self.min()[axis] - r.origin[axis]) * r.inv_direction[axis])
|
||||||
|
.min((self.max()[axis] - r.origin[axis]) * r.inv_direction[axis]);
|
||||||
|
let t1 = ((self.min()[axis] - r.origin[axis]) * r.inv_direction[axis])
|
||||||
|
.max((self.max()[axis] - r.origin[axis]) * r.inv_direction[axis]);
|
||||||
|
t_min = t0.max(t_min);
|
||||||
|
t_max = t1.min(t_max);
|
||||||
|
if t_max <= t_min {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
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) -> bool {
|
||||||
let mut t_min = t_min;
|
let mut t_min = t_min;
|
||||||
let mut t_max = t_max;
|
let mut t_max = t_max;
|
||||||
@ -119,22 +147,27 @@ impl AABB {
|
|||||||
t_min < t1 && t_max > t0
|
t_min < t1 && t_max > t0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
|
pub fn hit_simd(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
|
||||||
let mut t_min = t_min;
|
#[cfg(target_arch = "x86_64")]
|
||||||
let mut t_max = t_max;
|
unsafe {
|
||||||
for axis in 0..3 {
|
use std::arch::x86_64::*;
|
||||||
let t0 = ((self.min()[axis] - r.origin[axis]) * r.inv_direction[axis])
|
let o4 = _mm_set_ps(0., r.origin.z, r.origin.y, r.origin.x);
|
||||||
.min((self.max()[axis] - r.origin[axis]) * r.inv_direction[axis]);
|
let d4 = _mm_set_ps(0., r.direction.z, r.direction.y, r.direction.x);
|
||||||
let t1 = ((self.min()[axis] - r.origin[axis]) * r.inv_direction[axis])
|
let bmin4 = _mm_set_ps(0., self.min().z, self.min().y, self.min().x);
|
||||||
.max((self.max()[axis] - r.origin[axis]) * r.inv_direction[axis]);
|
let bmax4 = _mm_set_ps(0., self.max().z, self.max().y, self.max().x);
|
||||||
t_min = t0.max(t_min);
|
let mask4 = _mm_cmpeq_ps(_mm_setzero_ps(), _mm_set_ps(1., 0., 0., 0.));
|
||||||
t_max = t1.min(t_max);
|
let t1 = _mm_mul_ps(_mm_sub_ps(_mm_and_ps(bmin4, mask4), o4), d4);
|
||||||
if t_max <= t_min {
|
let t2 = _mm_mul_ps(_mm_sub_ps(_mm_and_ps(bmax4, mask4), o4), d4);
|
||||||
return false;
|
let vmax4 = _mm_max_ps(t1, t2);
|
||||||
|
let vmin4 = _mm_min_ps(t1, t2);
|
||||||
|
let vmax4: (f32, f32, f32, f32) = std::mem::transmute(vmax4);
|
||||||
|
let vmin4: (f32, f32, f32, f32) = std::mem::transmute(vmin4);
|
||||||
|
let tmax = min(vmax4.0, min(vmax4.1, vmax4.2));
|
||||||
|
let tmin = max(vmin4.0, max(vmin4.1, vmin4.2));
|
||||||
|
//tmax >= tmin && tmin < r.time && tmax > t_min
|
||||||
|
t_min <= tmin && tmin <= t_max
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
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) -> bool {
|
||||||
let b = self;
|
let b = self;
|
||||||
@ -169,3 +202,48 @@ pub fn surrounding_box(box0: &AABB, box1: &AABB) -> AABB {
|
|||||||
);
|
);
|
||||||
AABB::new(min, max)
|
AABB::new(min, max)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
macro_rules! hit_test {
|
||||||
|
($($name:ident,)*) => {
|
||||||
|
mod hit {
|
||||||
|
use super::*;
|
||||||
|
$(
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
let t_min = 0.001;
|
||||||
|
let t_max = f32::MAX;
|
||||||
|
let bb = AABB::new([1., -1., -1.], [3., 1., 1.]);
|
||||||
|
// Hit
|
||||||
|
let r = Ray::new([0., 0., 0.], [1., 0., 0.], 0.5);
|
||||||
|
assert!(bb.$name(r, t_min, t_max));
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
mod miss {
|
||||||
|
use super::*;
|
||||||
|
$(
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
let t_min = 0.001;
|
||||||
|
let t_max = f32::MAX;
|
||||||
|
let bb = AABB::new([1., -1., -1.], [3., 1., 1.]);
|
||||||
|
// Miss
|
||||||
|
let r = Ray::new([0., 0., 0.], [-1., 0., 0.], 0.5);
|
||||||
|
assert!(!bb.$name(r, t_min, t_max));
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hit_test! {
|
||||||
|
hit_naive,
|
||||||
|
hit2,
|
||||||
|
hit_fast,
|
||||||
|
hit_simd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
289
rtiow/renderer/src/bvh_triangles.rs
Normal file
289
rtiow/renderer/src/bvh_triangles.rs
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
/// Implementation based on blog post @
|
||||||
|
/// https://jacco.ompf2.com/2022/04/13/how-to-build-a-bvh-part-1-basics/
|
||||||
|
use std::f32::EPSILON;
|
||||||
|
|
||||||
|
use stl::STL;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
aabb::AABB,
|
||||||
|
hitable::{Hit, HitRecord},
|
||||||
|
material::Material,
|
||||||
|
ray::Ray,
|
||||||
|
vec3::{cross, dot, Vec3},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct BVHNode {
|
||||||
|
aabb: AABB,
|
||||||
|
left_child: usize,
|
||||||
|
first_prim: usize,
|
||||||
|
prim_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BVHNode {
|
||||||
|
fn is_leaf(&self) -> bool {
|
||||||
|
self.prim_count > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Triangle {
|
||||||
|
centroid: Vec3,
|
||||||
|
verts: [Vec3; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BVHTriangles<M>
|
||||||
|
where
|
||||||
|
M: Material,
|
||||||
|
{
|
||||||
|
pub triangles: Vec<Triangle>,
|
||||||
|
material: M,
|
||||||
|
bvh_nodes: Vec<BVHNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ROOT_NODE_IDX: usize = 0;
|
||||||
|
impl<M> BVHTriangles<M>
|
||||||
|
where
|
||||||
|
M: Material,
|
||||||
|
{
|
||||||
|
pub fn new(stl: &STL, material: M) -> BVHTriangles<M> {
|
||||||
|
let triangles: Vec<_> = stl
|
||||||
|
.triangles
|
||||||
|
.iter()
|
||||||
|
.map(|t| {
|
||||||
|
let v0 = t.verts[0];
|
||||||
|
let v1 = t.verts[1];
|
||||||
|
let v2 = t.verts[2];
|
||||||
|
let centroid = (v0 + v1 + v2) * 0.3333;
|
||||||
|
Triangle {
|
||||||
|
centroid,
|
||||||
|
verts: [v0, v1, v2],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let n = 2 * triangles.len() - 2;
|
||||||
|
let bvh_nodes = Vec::with_capacity(n);
|
||||||
|
let mut bvh = BVHTriangles {
|
||||||
|
triangles,
|
||||||
|
bvh_nodes,
|
||||||
|
material,
|
||||||
|
};
|
||||||
|
bvh.build_bvh();
|
||||||
|
bvh
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_bvh(&mut self) {
|
||||||
|
// assign all triangles to root node
|
||||||
|
let root = BVHNode {
|
||||||
|
aabb: AABB::default(),
|
||||||
|
left_child: 0,
|
||||||
|
first_prim: 0,
|
||||||
|
prim_count: self.triangles.len() - 1,
|
||||||
|
};
|
||||||
|
self.bvh_nodes.push(root);
|
||||||
|
self.update_node_bounds(ROOT_NODE_IDX);
|
||||||
|
// subdivide recursively
|
||||||
|
self.subdivide(ROOT_NODE_IDX);
|
||||||
|
}
|
||||||
|
fn update_node_bounds(&mut self, node_idx: usize) {
|
||||||
|
let node = &mut self.bvh_nodes[node_idx];
|
||||||
|
let mut aabb_min: Vec3 = f32::MAX.into();
|
||||||
|
let mut aabb_max: Vec3 = f32::MIN.into();
|
||||||
|
for i in node.first_prim..node.prim_count {
|
||||||
|
let leaf_tri = &self.triangles[i];
|
||||||
|
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[2]);
|
||||||
|
aabb_max = vec3::max(aabb_max, leaf_tri.verts[0]);
|
||||||
|
aabb_max = vec3::max(aabb_max, leaf_tri.verts[1]);
|
||||||
|
aabb_max = vec3::max(aabb_max, leaf_tri.verts[2]);
|
||||||
|
}
|
||||||
|
node.aabb = AABB::new(aabb_min, aabb_max);
|
||||||
|
}
|
||||||
|
fn subdivide(&mut self, idx: usize) {
|
||||||
|
// Early out if we're down to just 2 or less triangles
|
||||||
|
if self.bvh_nodes[idx].prim_count <= 2 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let (first_prim, prim_count, left_count, i) = {
|
||||||
|
let node = &self.bvh_nodes[idx];
|
||||||
|
|
||||||
|
// Compute split plane and position.
|
||||||
|
let extent = node.aabb.min() - node.aabb.max();
|
||||||
|
let axis = node.aabb.longest_axis();
|
||||||
|
let split_pos = node.aabb.min()[axis] + extent[axis] * 0.5;
|
||||||
|
|
||||||
|
// Split the group in two halves.
|
||||||
|
let mut i = node.first_prim as isize;
|
||||||
|
let mut j = i + node.prim_count as isize - 1;
|
||||||
|
while i <= j {
|
||||||
|
if self.triangles[i as usize].centroid[axis] < split_pos {
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
self.triangles.swap(i as usize, j as usize);
|
||||||
|
j -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create child nodes for each half.
|
||||||
|
let left_count = i as usize - node.first_prim;
|
||||||
|
if left_count == 0 || left_count == node.prim_count {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(node.first_prim, node.prim_count, left_count, i)
|
||||||
|
};
|
||||||
|
|
||||||
|
// create child nodes
|
||||||
|
let left_child_idx = self.bvh_nodes.len();
|
||||||
|
let right_child_idx = left_child_idx + 1;
|
||||||
|
let left = BVHNode {
|
||||||
|
aabb: AABB::default(),
|
||||||
|
left_child: 0,
|
||||||
|
first_prim: first_prim,
|
||||||
|
prim_count: left_count,
|
||||||
|
};
|
||||||
|
let right = BVHNode {
|
||||||
|
aabb: AABB::default(),
|
||||||
|
left_child: 0,
|
||||||
|
first_prim: i as usize,
|
||||||
|
prim_count: prim_count - left_count,
|
||||||
|
};
|
||||||
|
self.bvh_nodes.push(left);
|
||||||
|
self.bvh_nodes.push(right);
|
||||||
|
let node = &mut self.bvh_nodes[idx];
|
||||||
|
node.left_child = left_child_idx;
|
||||||
|
node.prim_count = 0;
|
||||||
|
|
||||||
|
// Recurse
|
||||||
|
self.update_node_bounds(left_child_idx);
|
||||||
|
self.update_node_bounds(right_child_idx);
|
||||||
|
self.subdivide(left_child_idx);
|
||||||
|
self.subdivide(right_child_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect_bvh(&self, r: Ray, node_idx: usize, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||||
|
let node = &self.bvh_nodes[node_idx];
|
||||||
|
if !node.aabb.hit(r, t_min, t_max) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.is_leaf() {
|
||||||
|
for tri in &self.triangles {
|
||||||
|
if let Some(RayTriangleResult { t, p }) =
|
||||||
|
ray_triangle_intersect_moller_trumbore(r, tri)
|
||||||
|
{
|
||||||
|
//if let Some(RayTriangleResult { t, p }) = ray_triangle_intersect_geometric(r, tri) {
|
||||||
|
// We don't support UV (yet?).
|
||||||
|
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);
|
||||||
|
return Some(HitRecord {
|
||||||
|
t,
|
||||||
|
uv,
|
||||||
|
p,
|
||||||
|
normal,
|
||||||
|
material: &self.material,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let hr = self.intersect_bvh(r, node.left_child, t_min, t_max);
|
||||||
|
if hr.is_some() {
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
let hr = self.intersect_bvh(r, node.left_child + 1, t_min, t_max);
|
||||||
|
if hr.is_some() {
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> Hit for BVHTriangles<M>
|
||||||
|
where
|
||||||
|
M: Material,
|
||||||
|
{
|
||||||
|
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||||
|
self.intersect_bvh(r, 0, t_min, t_max)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option<AABB> {
|
||||||
|
Some(self.bvh_nodes[0].aabb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RayTriangleResult {
|
||||||
|
t: f32,
|
||||||
|
p: Vec3,
|
||||||
|
}
|
||||||
|
///
|
||||||
|
/// Based on https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection.html
|
||||||
|
fn ray_triangle_intersect_moller_trumbore(r: Ray, tri: &Triangle) -> Option<RayTriangleResult> {
|
||||||
|
// #ifdef MOLLER_TRUMBORE
|
||||||
|
// Vec3f v0v1 = v1 - v0;
|
||||||
|
// Vec3f v0v2 = v2 - v0;
|
||||||
|
// Vec3f pvec = dir.crossProduct(v0v2);
|
||||||
|
// float det = v0v1.dotProduct(pvec);
|
||||||
|
// #ifdef CULLING
|
||||||
|
// // if the determinant is negative, the triangle is 'back facing'
|
||||||
|
// // if the determinant is close to 0, the ray misses the triangle
|
||||||
|
// if (det < kEpsilon) return false;
|
||||||
|
// #else
|
||||||
|
// // ray and triangle are parallel if det is close to 0
|
||||||
|
// if (fabs(det) < kEpsilon) return false;
|
||||||
|
// #endif
|
||||||
|
// float invDet = 1 / det;
|
||||||
|
//
|
||||||
|
// Vec3f tvec = orig - v0;
|
||||||
|
// u = tvec.dotProduct(pvec) * invDet;
|
||||||
|
// if (u < 0 || u > 1) return false;
|
||||||
|
//
|
||||||
|
// Vec3f qvec = tvec.crossProduct(v0v1);
|
||||||
|
// v = dir.dotProduct(qvec) * invDet;
|
||||||
|
// if (v < 0 || u + v > 1) return false;
|
||||||
|
//
|
||||||
|
// t = v0v2.dotProduct(qvec) * invDet;
|
||||||
|
//
|
||||||
|
let v0 = tri.verts[0];
|
||||||
|
let v1 = tri.verts[1];
|
||||||
|
let v2 = tri.verts[2];
|
||||||
|
|
||||||
|
let v0v1 = v1 - v0;
|
||||||
|
let v0v2 = v2 - v0;
|
||||||
|
let p = cross(r.direction, v0v2);
|
||||||
|
let det = dot(v0v1, p);
|
||||||
|
if det < EPSILON {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let inv_det = 1. / det;
|
||||||
|
|
||||||
|
let t = r.origin - v0;
|
||||||
|
let u = dot(t, p) * inv_det;
|
||||||
|
if u < 0. || u > 1. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let q = cross(t, v0v1);
|
||||||
|
let v = dot(r.direction, q) * inv_det;
|
||||||
|
if v < 0. || u + v > 1. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let t = dot(v0v2, q) * inv_det;
|
||||||
|
|
||||||
|
if t > EPSILON {
|
||||||
|
return Some(RayTriangleResult {
|
||||||
|
t,
|
||||||
|
p: r.origin + r.direction * t,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
pub mod aabb;
|
pub mod aabb;
|
||||||
pub mod bvh;
|
pub mod bvh;
|
||||||
|
pub mod bvh_triangles;
|
||||||
pub mod camera;
|
pub mod camera;
|
||||||
pub mod constant_medium;
|
pub mod constant_medium;
|
||||||
pub mod cuboid;
|
pub mod cuboid;
|
||||||
@ -17,8 +18,10 @@ pub mod ray;
|
|||||||
pub mod rect;
|
pub mod rect;
|
||||||
pub mod renderer;
|
pub mod renderer;
|
||||||
pub mod rotate;
|
pub mod rotate;
|
||||||
|
pub mod scale;
|
||||||
pub mod scenes;
|
pub mod scenes;
|
||||||
pub mod sphere;
|
pub mod sphere;
|
||||||
pub mod texture;
|
pub mod texture;
|
||||||
pub mod translate;
|
pub mod translate;
|
||||||
|
pub mod triangles;
|
||||||
pub mod vec3;
|
pub mod vec3;
|
||||||
|
|||||||
@ -7,7 +7,8 @@ use std::{
|
|||||||
mpsc::{sync_channel, Receiver, SyncSender},
|
mpsc::{sync_channel, Receiver, SyncSender},
|
||||||
Arc, Mutex,
|
Arc, Mutex,
|
||||||
},
|
},
|
||||||
thread, time,
|
thread,
|
||||||
|
time::{self, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use core_affinity;
|
use core_affinity;
|
||||||
@ -41,6 +42,7 @@ pub enum Model {
|
|||||||
Mandelbrot,
|
Mandelbrot,
|
||||||
PerlinDebug,
|
PerlinDebug,
|
||||||
Spheramid,
|
Spheramid,
|
||||||
|
Stltest,
|
||||||
Test,
|
Test,
|
||||||
Tron,
|
Tron,
|
||||||
Tutorial,
|
Tutorial,
|
||||||
@ -58,6 +60,7 @@ impl Model {
|
|||||||
Model::Mandelbrot => scenes::mandelbrot::new(opt),
|
Model::Mandelbrot => scenes::mandelbrot::new(opt),
|
||||||
Model::PerlinDebug => scenes::perlin_debug::new(opt),
|
Model::PerlinDebug => scenes::perlin_debug::new(opt),
|
||||||
Model::Spheramid => scenes::spheramid::new(opt),
|
Model::Spheramid => scenes::spheramid::new(opt),
|
||||||
|
Model::Stltest => scenes::stltest::new(opt),
|
||||||
Model::Test => scenes::test::new(opt),
|
Model::Test => scenes::test::new(opt),
|
||||||
Model::Tron => scenes::tron::new(opt),
|
Model::Tron => scenes::tron::new(opt),
|
||||||
Model::Tutorial => scenes::tutorial::new(opt),
|
Model::Tutorial => scenes::tutorial::new(opt),
|
||||||
@ -87,6 +90,7 @@ impl str::FromStr for Model {
|
|||||||
"mandelbrot" => Ok(Model::Mandelbrot),
|
"mandelbrot" => Ok(Model::Mandelbrot),
|
||||||
"perlin_debug" => Ok(Model::PerlinDebug),
|
"perlin_debug" => Ok(Model::PerlinDebug),
|
||||||
"spheramid" => Ok(Model::Spheramid),
|
"spheramid" => Ok(Model::Spheramid),
|
||||||
|
"stltest" => Ok(Model::Stltest),
|
||||||
"test" => Ok(Model::Test),
|
"test" => Ok(Model::Test),
|
||||||
"tron" => Ok(Model::Tron),
|
"tron" => Ok(Model::Tron),
|
||||||
"tutorial" => Ok(Model::Tutorial),
|
"tutorial" => Ok(Model::Tutorial),
|
||||||
@ -107,6 +111,7 @@ impl std::string::ToString for Model {
|
|||||||
Model::Mandelbrot => "mandelbrot".to_string(),
|
Model::Mandelbrot => "mandelbrot".to_string(),
|
||||||
Model::PerlinDebug => "perlin_debug".to_string(),
|
Model::PerlinDebug => "perlin_debug".to_string(),
|
||||||
Model::Spheramid => "spheramid".to_string(),
|
Model::Spheramid => "spheramid".to_string(),
|
||||||
|
Model::Stltest => "stltest".to_string(),
|
||||||
Model::Test => "test".to_string(),
|
Model::Test => "test".to_string(),
|
||||||
Model::Tron => "tron".to_string(),
|
Model::Tron => "tron".to_string(),
|
||||||
Model::Tutorial => "tutorial".to_string(),
|
Model::Tutorial => "tutorial".to_string(),
|
||||||
@ -394,6 +399,7 @@ impl AddAssign for RenderStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn progress(
|
fn progress(
|
||||||
|
start_time: Instant,
|
||||||
last_stat: &RenderStats,
|
last_stat: &RenderStats,
|
||||||
current_stat: &RenderStats,
|
current_stat: &RenderStats,
|
||||||
time_diff: time::Duration,
|
time_diff: time::Duration,
|
||||||
@ -402,13 +408,18 @@ fn progress(
|
|||||||
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 start_diff = now - start_time;
|
||||||
|
let percent = 100. * current_stat.pixels as f32 / pixel_total as f32;
|
||||||
|
let eta = 100. * start_diff.as_secs_f32() / percent;
|
||||||
format!(
|
format!(
|
||||||
"{:7} / {:7}pixels ({:2}%) {:7}pixels/s {:7}rays/s",
|
"{:7} / {:7}pixels ({:2.0}%) {:7}pixels/s {:7}rays/s eta {:.0}s",
|
||||||
human.format(current_stat.pixels as f64),
|
human.format(current_stat.pixels as f64),
|
||||||
human.format(pixel_total as f64),
|
human.format(pixel_total as f64),
|
||||||
100 * current_stat.pixels / pixel_total,
|
percent,
|
||||||
human.format(pixel_diff as f64 / time_diff.as_secs_f64()),
|
human.format(pixel_diff as f64 / time_diff.as_secs_f64()),
|
||||||
human.format(ray_diff as f64 / time_diff.as_secs_f64())
|
human.format(ray_diff as f64 / time_diff.as_secs_f64()),
|
||||||
|
eta
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -576,10 +587,9 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
|
|||||||
drop(pixel_req_rx);
|
drop(pixel_req_rx);
|
||||||
drop(pixel_resp_tx);
|
drop(pixel_resp_tx);
|
||||||
|
|
||||||
let start_time = time::Instant::now();
|
|
||||||
let (w, h) = (scene.width, scene.height);
|
let (w, h) = (scene.width, scene.height);
|
||||||
handles.push(thread::spawn(move || {
|
handles.push(thread::spawn(move || {
|
||||||
let batch_line_requests = true;
|
let batch_line_requests = false;
|
||||||
if batch_line_requests {
|
if batch_line_requests {
|
||||||
for y in 0..h {
|
for y in 0..h {
|
||||||
pixel_req_tx
|
pixel_req_tx
|
||||||
@ -604,6 +614,7 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
|
|||||||
let mut last_time = time::Instant::now();
|
let mut last_time = 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();
|
||||||
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 } => {
|
||||||
@ -621,9 +632,15 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
|
|||||||
let now = time::Instant::now();
|
let now = time::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 > time::Duration::from_secs(1) {
|
||||||
info!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
progress(&last_stat, ¤t_stat, time_diff, pixel_total)
|
progress(
|
||||||
|
start_time,
|
||||||
|
&last_stat,
|
||||||
|
¤t_stat,
|
||||||
|
time_diff,
|
||||||
|
pixel_total
|
||||||
|
)
|
||||||
);
|
);
|
||||||
last_stat = current_stat;
|
last_stat = current_stat;
|
||||||
last_time = now;
|
last_time = now;
|
||||||
@ -633,10 +650,16 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
|
|||||||
thr.join().expect("thread join");
|
thr.join().expect("thread join");
|
||||||
}
|
}
|
||||||
let time_diff = time::Instant::now() - start_time;
|
let time_diff = time::Instant::now() - start_time;
|
||||||
info!(
|
println!(
|
||||||
"Runtime {} seconds {}",
|
"Runtime {} seconds {}",
|
||||||
time_diff.as_secs_f32(),
|
time_diff.as_secs_f32(),
|
||||||
progress(&Default::default(), ¤t_stat, time_diff, pixel_total)
|
progress(
|
||||||
|
start_time,
|
||||||
|
&Default::default(),
|
||||||
|
¤t_stat,
|
||||||
|
time_diff,
|
||||||
|
pixel_total
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
output::write_images(&scene, time_diff, output_dir)
|
output::write_images(&scene, time_diff, output_dir)
|
||||||
|
|||||||
53
rtiow/renderer/src/scale.rs
Normal file
53
rtiow/renderer/src/scale.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
use crate::{
|
||||||
|
aabb::AABB,
|
||||||
|
hitable::{Hit, HitRecord},
|
||||||
|
ray::Ray,
|
||||||
|
vec3::Vec3,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Scale<H>
|
||||||
|
where
|
||||||
|
H: Hit,
|
||||||
|
{
|
||||||
|
hitable: H,
|
||||||
|
scale: Vec3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H> Scale<H>
|
||||||
|
where
|
||||||
|
H: Hit,
|
||||||
|
{
|
||||||
|
pub fn new<V>(hitable: H, scale: V) -> Scale<H>
|
||||||
|
where
|
||||||
|
V: Into<Vec3>,
|
||||||
|
{
|
||||||
|
Scale {
|
||||||
|
hitable,
|
||||||
|
scale: scale.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H> Hit for Scale<H>
|
||||||
|
where
|
||||||
|
H: Hit,
|
||||||
|
{
|
||||||
|
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||||
|
let moved_r = Ray::new(r.origin / self.scale, r.direction, r.time);
|
||||||
|
if let Some(rec) = self.hitable.hit(moved_r, t_min, t_max) {
|
||||||
|
return Some(HitRecord {
|
||||||
|
p: rec.p * self.scale,
|
||||||
|
..rec
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounding_box(&self, t_min: f32, t_max: f32) -> Option<AABB> {
|
||||||
|
if let Some(bbox) = self.hitable.bounding_box(t_min, t_max) {
|
||||||
|
return Some(AABB::new(bbox.min() * self.scale, bbox.max() * self.scale));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ pub mod final_scene;
|
|||||||
pub mod mandelbrot;
|
pub mod mandelbrot;
|
||||||
pub mod perlin_debug;
|
pub mod perlin_debug;
|
||||||
pub mod spheramid;
|
pub mod spheramid;
|
||||||
|
pub mod stltest;
|
||||||
pub mod test;
|
pub mod test;
|
||||||
pub mod tron;
|
pub mod tron;
|
||||||
pub mod tutorial;
|
pub mod tutorial;
|
||||||
|
|||||||
126
rtiow/renderer/src/scenes/stltest.rs
Normal file
126
rtiow/renderer/src/scenes/stltest.rs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
use std::io::{BufReader, Cursor};
|
||||||
|
use stl::STL;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
bvh_triangles::BVHTriangles,
|
||||||
|
camera::Camera,
|
||||||
|
hitable::Hit,
|
||||||
|
hitable_list::HitableList,
|
||||||
|
kdtree::KDTree,
|
||||||
|
material::{DiffuseLight, Lambertian, Metal},
|
||||||
|
rect::{XYRect, XZRect},
|
||||||
|
renderer::{Opt, Scene},
|
||||||
|
scale::Scale,
|
||||||
|
sphere::Sphere,
|
||||||
|
texture::ConstantTexture,
|
||||||
|
vec3::Vec3,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn new(opt: &Opt) -> Scene {
|
||||||
|
let lookfrom = Vec3::new(-20., 100., -100.);
|
||||||
|
let lookat = Vec3::new(0., 10., 0.);
|
||||||
|
let dist_to_focus = 10.0;
|
||||||
|
let aperture = 0.0;
|
||||||
|
let time_min = 0.;
|
||||||
|
let time_max = 1.;
|
||||||
|
let camera = Camera::new(
|
||||||
|
lookfrom,
|
||||||
|
lookat,
|
||||||
|
Vec3::new(0., 1., 0.),
|
||||||
|
45.,
|
||||||
|
opt.width as f32 / opt.height as f32,
|
||||||
|
aperture,
|
||||||
|
dist_to_focus,
|
||||||
|
time_min,
|
||||||
|
time_max,
|
||||||
|
);
|
||||||
|
let ground_color = if opt.use_accel {
|
||||||
|
ConstantTexture::new(Vec3::new(1.0, 0.4, 0.4))
|
||||||
|
} else {
|
||||||
|
ConstantTexture::new(Vec3::new(0.4, 0.4, 0.4))
|
||||||
|
};
|
||||||
|
|
||||||
|
let stl_cube = STL::parse(
|
||||||
|
BufReader::new(Cursor::new(include_bytes!("../../stls/20mm cube.stl"))),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.expect("failed to parse cube");
|
||||||
|
let light_size = 50.;
|
||||||
|
let light_height = 200.;
|
||||||
|
let objects: Vec<Box<dyn Hit>> = vec![
|
||||||
|
// Light from above - white
|
||||||
|
Box::new(XZRect::new(
|
||||||
|
-light_size,
|
||||||
|
light_size,
|
||||||
|
-light_size,
|
||||||
|
light_size,
|
||||||
|
light_height,
|
||||||
|
DiffuseLight::new(ConstantTexture::new(Vec3::new(15., 15., 15.))),
|
||||||
|
)),
|
||||||
|
// Light from back - green
|
||||||
|
Box::new(XYRect::new(
|
||||||
|
-light_size,
|
||||||
|
light_size,
|
||||||
|
-light_size,
|
||||||
|
light_size,
|
||||||
|
-light_height,
|
||||||
|
DiffuseLight::new(ConstantTexture::new(Vec3::new(1., 15., 1.))),
|
||||||
|
)),
|
||||||
|
// Light from front - blue
|
||||||
|
Box::new(XYRect::new(
|
||||||
|
-light_size,
|
||||||
|
light_size,
|
||||||
|
-light_size,
|
||||||
|
light_size,
|
||||||
|
light_height,
|
||||||
|
DiffuseLight::new(ConstantTexture::new(Vec3::new(1., 1., 15.))),
|
||||||
|
)),
|
||||||
|
// Earth sized sphere
|
||||||
|
Box::new(Sphere::new(
|
||||||
|
Vec3::new(0., -1200., 0.),
|
||||||
|
1000.,
|
||||||
|
Lambertian::new(ground_color),
|
||||||
|
)),
|
||||||
|
/*
|
||||||
|
// Blue sphere
|
||||||
|
Box::new(Sphere::new(
|
||||||
|
Vec3::new(-40., 20., 0.),
|
||||||
|
20.,
|
||||||
|
Lambertian::new(ConstantTexture::new(Vec3::new(0.1, 0.2, 0.5))),
|
||||||
|
)),
|
||||||
|
*/
|
||||||
|
// Shiny sphere
|
||||||
|
Box::new(Sphere::new(
|
||||||
|
Vec3::new(40., 20., -40.),
|
||||||
|
20.,
|
||||||
|
Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.2),
|
||||||
|
)),
|
||||||
|
Box::new(Sphere::new(
|
||||||
|
Vec3::new(-40., 20., 40.),
|
||||||
|
20.,
|
||||||
|
Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.2),
|
||||||
|
)),
|
||||||
|
// STL Mesh
|
||||||
|
Box::new(Scale::new(
|
||||||
|
BVHTriangles::new(
|
||||||
|
&stl_cube,
|
||||||
|
Lambertian::new(ConstantTexture::new(Vec3::new(0.6, 0.6, 0.6))),
|
||||||
|
),
|
||||||
|
200.,
|
||||||
|
)),
|
||||||
|
];
|
||||||
|
let world: Box<dyn Hit> = if opt.use_accel {
|
||||||
|
Box::new(KDTree::new(objects, time_min, time_max))
|
||||||
|
} else {
|
||||||
|
Box::new(HitableList::new(objects))
|
||||||
|
};
|
||||||
|
Scene {
|
||||||
|
camera,
|
||||||
|
world,
|
||||||
|
subsamples: opt.subsamples,
|
||||||
|
num_threads: opt.num_threads,
|
||||||
|
width: opt.width,
|
||||||
|
height: opt.height,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
168
rtiow/renderer/src/triangles.rs
Normal file
168
rtiow/renderer/src/triangles.rs
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
use std::f32::EPSILON;
|
||||||
|
|
||||||
|
use stl::STL;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
aabb::AABB,
|
||||||
|
hitable::{Hit, HitRecord},
|
||||||
|
material::Material,
|
||||||
|
ray::Ray,
|
||||||
|
vec3::{cross, dot, Vec3},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Triangle {
|
||||||
|
normal: Vec3,
|
||||||
|
verts: [Vec3; 3],
|
||||||
|
// Precomputed data
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Triangles<M>
|
||||||
|
where
|
||||||
|
M: Material,
|
||||||
|
{
|
||||||
|
pub triangles: Vec<Triangle>,
|
||||||
|
pub bbox: AABB,
|
||||||
|
material: M,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> Triangles<M>
|
||||||
|
where
|
||||||
|
M: Material,
|
||||||
|
{
|
||||||
|
pub fn new(stl: &STL, material: M, scale_factor: f32) -> Triangles<M> {
|
||||||
|
let triangles: Vec<_> = stl
|
||||||
|
.triangles
|
||||||
|
.iter()
|
||||||
|
.map(|t| {
|
||||||
|
let v0 = t.verts[0] * scale_factor;
|
||||||
|
let v1 = t.verts[1] * scale_factor;
|
||||||
|
let v2 = t.verts[2] * scale_factor;
|
||||||
|
Triangle {
|
||||||
|
normal: t.normal,
|
||||||
|
verts: [v0, v1, v2],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let (min, max) = triangles.iter().fold(
|
||||||
|
(Vec3::from(f32::MAX), Vec3::from(f32::MIN)),
|
||||||
|
|(min, max), t| {
|
||||||
|
let t_min_x = t.verts[0].x.min(t.verts[1].x.min(t.verts[2].x));
|
||||||
|
let t_min_y = t.verts[0].y.min(t.verts[1].y.min(t.verts[2].y));
|
||||||
|
let t_min_z = t.verts[0].z.min(t.verts[1].z.min(t.verts[2].z));
|
||||||
|
let t_max_x = t.verts[0].x.max(t.verts[1].x.max(t.verts[2].x));
|
||||||
|
let t_max_y = t.verts[0].y.max(t.verts[1].y.max(t.verts[2].y));
|
||||||
|
let t_max_z = t.verts[0].z.max(t.verts[1].z.max(t.verts[2].z));
|
||||||
|
|
||||||
|
(
|
||||||
|
Vec3::from([min.x.min(t_min_x), min.y.min(t_min_y), min.z.min(t_min_z)]),
|
||||||
|
Vec3::from([max.x.max(t_max_x), max.y.max(t_max_y), max.z.max(t_max_z)]),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let bbox = AABB::new(min, max);
|
||||||
|
Triangles {
|
||||||
|
triangles,
|
||||||
|
bbox,
|
||||||
|
material,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Based on https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection.html
|
||||||
|
fn ray_triangle_intersect_moller_trumbore(r: Ray, tri: &Triangle) -> Option<RayTriangleResult> {
|
||||||
|
// #ifdef MOLLER_TRUMBORE
|
||||||
|
// Vec3f v0v1 = v1 - v0;
|
||||||
|
// Vec3f v0v2 = v2 - v0;
|
||||||
|
// Vec3f pvec = dir.crossProduct(v0v2);
|
||||||
|
// float det = v0v1.dotProduct(pvec);
|
||||||
|
// #ifdef CULLING
|
||||||
|
// // if the determinant is negative, the triangle is 'back facing'
|
||||||
|
// // if the determinant is close to 0, the ray misses the triangle
|
||||||
|
// if (det < kEpsilon) return false;
|
||||||
|
// #else
|
||||||
|
// // ray and triangle are parallel if det is close to 0
|
||||||
|
// if (fabs(det) < kEpsilon) return false;
|
||||||
|
// #endif
|
||||||
|
// float invDet = 1 / det;
|
||||||
|
//
|
||||||
|
// Vec3f tvec = orig - v0;
|
||||||
|
// u = tvec.dotProduct(pvec) * invDet;
|
||||||
|
// if (u < 0 || u > 1) return false;
|
||||||
|
//
|
||||||
|
// Vec3f qvec = tvec.crossProduct(v0v1);
|
||||||
|
// v = dir.dotProduct(qvec) * invDet;
|
||||||
|
// if (v < 0 || u + v > 1) return false;
|
||||||
|
//
|
||||||
|
// t = v0v2.dotProduct(qvec) * invDet;
|
||||||
|
//
|
||||||
|
let v0 = tri.verts[0];
|
||||||
|
let v1 = tri.verts[1];
|
||||||
|
let v2 = tri.verts[2];
|
||||||
|
|
||||||
|
let v0v1 = v1 - v0;
|
||||||
|
let v0v2 = v2 - v0;
|
||||||
|
let p = cross(r.direction, v0v2);
|
||||||
|
let det = dot(v0v1, p);
|
||||||
|
if det < EPSILON {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let inv_det = 1. / det;
|
||||||
|
|
||||||
|
let t = r.origin - v0;
|
||||||
|
let u = dot(t, p) * inv_det;
|
||||||
|
if u < 0. || u > 1. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let q = cross(t, v0v1);
|
||||||
|
let v = dot(r.direction, q) * inv_det;
|
||||||
|
if v < 0. || u + v > 1. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let t = dot(v0v2, q) * inv_det;
|
||||||
|
|
||||||
|
if t > EPSILON {
|
||||||
|
return Some(RayTriangleResult {
|
||||||
|
t,
|
||||||
|
p: r.origin + r.direction * t,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> Hit for Triangles<M>
|
||||||
|
where
|
||||||
|
M: Material,
|
||||||
|
{
|
||||||
|
fn hit(&self, r: Ray, _t_min: f32, _t_max: f32) -> Option<HitRecord> {
|
||||||
|
// TODO(wathiede): add an acceleration structure to more cheaply skip some triangles.
|
||||||
|
for tri in &self.triangles {
|
||||||
|
if let Some(RayTriangleResult { t, p }) = ray_triangle_intersect_moller_trumbore(r, tri)
|
||||||
|
{
|
||||||
|
//if let Some(RayTriangleResult { t, p }) = ray_triangle_intersect_geometric(r, tri) {
|
||||||
|
// We don't support UV (yet?).
|
||||||
|
let uv = (0.5, 0.5);
|
||||||
|
return Some(HitRecord {
|
||||||
|
t,
|
||||||
|
uv,
|
||||||
|
p,
|
||||||
|
normal: tri.normal,
|
||||||
|
material: &self.material,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option<AABB> {
|
||||||
|
Some(self.bbox)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RayTriangleResult {
|
||||||
|
t: f32,
|
||||||
|
p: Vec3,
|
||||||
|
}
|
||||||
@ -1,274 +1 @@
|
|||||||
use std::{
|
pub use vec3::*;
|
||||||
convert::From,
|
|
||||||
fmt,
|
|
||||||
num::ParseFloatError,
|
|
||||||
ops::{Add, Div, Index, Mul, Neg, Sub},
|
|
||||||
str,
|
|
||||||
};
|
|
||||||
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Default, Debug, Deserialize, Serialize, PartialEq, Copy, Clone)]
|
|
||||||
pub struct Vec3 {
|
|
||||||
pub x: f32,
|
|
||||||
pub y: f32,
|
|
||||||
pub z: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cross(v1: Vec3, v2: Vec3) -> Vec3 {
|
|
||||||
Vec3 {
|
|
||||||
x: v1.y * v2.z - v1.z * v2.y,
|
|
||||||
y: v1.z * v2.x - v1.x * v2.z,
|
|
||||||
z: v1.x * v2.y - v1.y * v2.x,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dot(v1: Vec3, v2: Vec3) -> f32 {
|
|
||||||
v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Vec3 {
|
|
||||||
pub fn new(x: f32, y: f32, z: f32) -> Vec3 {
|
|
||||||
Vec3 { x, y, z }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn min(self) -> f32 {
|
|
||||||
self.x.min(self.y).min(self.z)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn max(self) -> f32 {
|
|
||||||
self.x.max(self.y).max(self.z)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn length(self) -> f32 {
|
|
||||||
self.squared_length().sqrt()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn squared_length(self) -> f32 {
|
|
||||||
self.x * self.x + self.y * self.y + self.z * self.z
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unit_vector(self) -> Vec3 {
|
|
||||||
self / self.length()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn make_unit_vector(&mut self) {
|
|
||||||
*self = self.unit_vector();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<[f32; 3]> for Vec3 {
|
|
||||||
fn from(v: [f32; 3]) -> Self {
|
|
||||||
Vec3 {
|
|
||||||
x: v[0],
|
|
||||||
y: v[1],
|
|
||||||
z: v[2],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Vec3 {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{} {} {}", self.x, self.y, self.z)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl str::FromStr for Vec3 {
|
|
||||||
type Err = ParseFloatError;
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let coords: Vec<&str> = s.split(' ').collect();
|
|
||||||
Ok(Vec3 {
|
|
||||||
x: coords[0].parse::<f32>()?,
|
|
||||||
y: coords[1].parse::<f32>()?,
|
|
||||||
z: coords[2].parse::<f32>()?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Add<f32> for Vec3 {
|
|
||||||
type Output = Vec3;
|
|
||||||
|
|
||||||
fn add(self, r: f32) -> Vec3 {
|
|
||||||
Vec3 {
|
|
||||||
x: self.x + r,
|
|
||||||
y: self.y + r,
|
|
||||||
z: self.z + r,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Add for Vec3 {
|
|
||||||
type Output = Vec3;
|
|
||||||
|
|
||||||
fn add(self, r: Vec3) -> Vec3 {
|
|
||||||
Vec3 {
|
|
||||||
x: self.x + r.x,
|
|
||||||
y: self.y + r.y,
|
|
||||||
z: self.z + r.z,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Div<Vec3> for f32 {
|
|
||||||
type Output = Vec3;
|
|
||||||
|
|
||||||
fn div(self, r: Vec3) -> Vec3 {
|
|
||||||
Vec3 {
|
|
||||||
x: self / r.x,
|
|
||||||
y: self / r.y,
|
|
||||||
z: self / r.z,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Div<f32> for Vec3 {
|
|
||||||
type Output = Vec3;
|
|
||||||
|
|
||||||
fn div(self, r: f32) -> Vec3 {
|
|
||||||
Vec3 {
|
|
||||||
x: self.x / r,
|
|
||||||
y: self.y / r,
|
|
||||||
z: self.z / r,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Index<usize> for Vec3 {
|
|
||||||
type Output = f32;
|
|
||||||
fn index(&self, idx: usize) -> &f32 {
|
|
||||||
match idx {
|
|
||||||
0 => &self.x,
|
|
||||||
1 => &self.y,
|
|
||||||
2 => &self.z,
|
|
||||||
_ => panic!("idx {} out of range for vec3", idx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mul for Vec3 {
|
|
||||||
type Output = Vec3;
|
|
||||||
|
|
||||||
fn mul(self, r: Vec3) -> Vec3 {
|
|
||||||
Vec3 {
|
|
||||||
x: self.x * r.x,
|
|
||||||
y: self.y * r.y,
|
|
||||||
z: self.z * r.z,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mul<Vec3> for f32 {
|
|
||||||
type Output = Vec3;
|
|
||||||
|
|
||||||
fn mul(self, v: Vec3) -> Vec3 {
|
|
||||||
Vec3 {
|
|
||||||
x: v.x * self,
|
|
||||||
y: v.y * self,
|
|
||||||
z: v.z * self,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mul<f32> for Vec3 {
|
|
||||||
type Output = Vec3;
|
|
||||||
|
|
||||||
fn mul(self, r: f32) -> Vec3 {
|
|
||||||
Vec3 {
|
|
||||||
x: self.x * r,
|
|
||||||
y: self.y * r,
|
|
||||||
z: self.z * r,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Neg for Vec3 {
|
|
||||||
type Output = Vec3;
|
|
||||||
|
|
||||||
fn neg(self) -> Vec3 {
|
|
||||||
-1. * self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sub for Vec3 {
|
|
||||||
type Output = Vec3;
|
|
||||||
|
|
||||||
fn sub(self, r: Vec3) -> Vec3 {
|
|
||||||
Vec3 {
|
|
||||||
x: self.x - r.x,
|
|
||||||
y: self.y - r.y,
|
|
||||||
z: self.z - r.z,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::{f32::consts::PI, str::FromStr};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn vec_add() {
|
|
||||||
let v0 = Vec3::new(1., 2., 3.);
|
|
||||||
let v1 = Vec3::new(1., 1., 1.);
|
|
||||||
assert_eq!(v0 + v1, Vec3::new(2., 3., 4.));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn vec_div() {
|
|
||||||
let v0 = Vec3::new(1., 2., 4.);
|
|
||||||
assert_eq!(v0 / 2., Vec3::new(0.5, 1., 2.));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn vec_mul() {
|
|
||||||
let v0 = Vec3::new(1., 2., 4.);
|
|
||||||
assert_eq!(v0 * 0.5, Vec3::new(0.5, 1., 2.));
|
|
||||||
assert_eq!(v0 * v0, Vec3::new(1., 4., 16.));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn vec_sub() {
|
|
||||||
let v0 = Vec3::new(1., 2., 3.);
|
|
||||||
let v1 = Vec3::new(1., 1., 1.);
|
|
||||||
assert_eq!(v0 - v1, Vec3::new(0., 1., 2.));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn vec_idx() {
|
|
||||||
let v0 = Vec3::new(1., 2., 3.);
|
|
||||||
assert_eq!(v0[2], 3.);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn vec_display() {
|
|
||||||
let v0 = Vec3::new(1., 2., 3.);
|
|
||||||
assert_eq!(format!("{}", v0), "1 2 3".to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn vec_from_str() {
|
|
||||||
assert_eq!(Vec3::from_str("1. 2. 3.").unwrap(), Vec3::new(1., 2., 3.));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn vec_str_roundtrip() {
|
|
||||||
let v = Vec3::from_str("1 2 3").unwrap();
|
|
||||||
let s = format!("{}", v);
|
|
||||||
assert_eq!(v, Vec3::from_str(&s).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn vec_dot() {
|
|
||||||
let v0 = Vec3::new(1., 0., 0.);
|
|
||||||
let v1 = Vec3::new(-1., 0., 0.);
|
|
||||||
assert_eq!(dot(v0, v1), v0.length() * v1.length() * PI.cos());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn vec_cross() {
|
|
||||||
let v0 = Vec3::new(1., 0., 0.);
|
|
||||||
let v1 = Vec3::new(0., 1., 0.);
|
|
||||||
assert_eq!(cross(v0, v1), Vec3::new(0., 0., 1.));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
BIN
rtiow/renderer/stls/20mm cube.stl
Normal file
BIN
rtiow/renderer/stls/20mm cube.stl
Normal file
Binary file not shown.
@ -1,2 +0,0 @@
|
|||||||
imports_granularity = "Crate"
|
|
||||||
format_code_in_doc_comments = true
|
|
||||||
@ -2,12 +2,12 @@
|
|||||||
name = "tracer"
|
name = "tracer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Bill Thiede <git@xinu.tv>"]
|
authors = ["Bill Thiede <git@xinu.tv>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.4.5"
|
log = "0.4.17"
|
||||||
renderer = { path = "../renderer" }
|
renderer = { path = "../renderer" }
|
||||||
stderrlog = "0.4.1"
|
stderrlog = "0.4.3"
|
||||||
structopt = "0.2.10"
|
structopt = "0.2.18"
|
||||||
|
|||||||
10
rtiow/vec3/Cargo.toml
Normal file
10
rtiow/vec3/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "vec3"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1.0.152", features = ["serde_derive"] }
|
||||||
|
serde_derive = "1.0.152"
|
||||||
2
rtiow/vec3/src/lib.rs
Normal file
2
rtiow/vec3/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
mod vec3;
|
||||||
|
pub use vec3::*;
|
||||||
309
rtiow/vec3/src/vec3.rs
Normal file
309
rtiow/vec3/src/vec3.rs
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
use std::{
|
||||||
|
convert::From,
|
||||||
|
fmt,
|
||||||
|
num::ParseFloatError,
|
||||||
|
ops::{Add, Div, Index, Mul, Neg, Sub},
|
||||||
|
str,
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Deserialize, Serialize, PartialEq, Copy, Clone)]
|
||||||
|
pub struct Vec3 {
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub z: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a `Vec3` with the lowest of each component in v1 or v2.
|
||||||
|
pub fn min(v1: Vec3, v2: Vec3) -> Vec3 {
|
||||||
|
Vec3 {
|
||||||
|
x: v1.x.min(v2.x),
|
||||||
|
y: v1.y.min(v2.y),
|
||||||
|
z: v1.z.min(v2.z),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Return a `Vec3` with the greatest of each component in v1 or v2.
|
||||||
|
pub fn max(v1: Vec3, v2: Vec3) -> Vec3 {
|
||||||
|
Vec3 {
|
||||||
|
x: v1.x.max(v2.x),
|
||||||
|
y: v1.y.max(v2.y),
|
||||||
|
z: v1.z.max(v2.z),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cross(v1: Vec3, v2: Vec3) -> Vec3 {
|
||||||
|
Vec3 {
|
||||||
|
x: v1.y * v2.z - v1.z * v2.y,
|
||||||
|
y: v1.z * v2.x - v1.x * v2.z,
|
||||||
|
z: v1.x * v2.y - v1.y * v2.x,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dot(v1: Vec3, v2: Vec3) -> f32 {
|
||||||
|
v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vec3 {
|
||||||
|
pub fn new(x: f32, y: f32, z: f32) -> Vec3 {
|
||||||
|
Vec3 { x, y, z }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn min(self) -> f32 {
|
||||||
|
self.x.min(self.y).min(self.z)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max(self) -> f32 {
|
||||||
|
self.x.max(self.y).max(self.z)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn length(self) -> f32 {
|
||||||
|
self.squared_length().sqrt()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn squared_length(self) -> f32 {
|
||||||
|
self.x * self.x + self.y * self.y + self.z * self.z
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unit_vector(self) -> Vec3 {
|
||||||
|
self / self.length()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_unit_vector(&mut self) {
|
||||||
|
*self = self.unit_vector();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<f32> for Vec3 {
|
||||||
|
fn from(v: f32) -> Self {
|
||||||
|
Vec3 { x: v, y: v, z: v }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<[f32; 3]> for Vec3 {
|
||||||
|
fn from(v: [f32; 3]) -> Self {
|
||||||
|
Vec3 {
|
||||||
|
x: v[0],
|
||||||
|
y: v[1],
|
||||||
|
z: v[2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Vec3 {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{} {} {}", self.x, self.y, self.z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl str::FromStr for Vec3 {
|
||||||
|
type Err = ParseFloatError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let coords: Vec<&str> = s.split(' ').collect();
|
||||||
|
Ok(Vec3 {
|
||||||
|
x: coords[0].parse::<f32>()?,
|
||||||
|
y: coords[1].parse::<f32>()?,
|
||||||
|
z: coords[2].parse::<f32>()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<f32> for Vec3 {
|
||||||
|
type Output = Vec3;
|
||||||
|
|
||||||
|
fn add(self, r: f32) -> Vec3 {
|
||||||
|
Vec3 {
|
||||||
|
x: self.x + r,
|
||||||
|
y: self.y + r,
|
||||||
|
z: self.z + r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for Vec3 {
|
||||||
|
type Output = Vec3;
|
||||||
|
|
||||||
|
fn add(self, r: Vec3) -> Vec3 {
|
||||||
|
Vec3 {
|
||||||
|
x: self.x + r.x,
|
||||||
|
y: self.y + r.y,
|
||||||
|
z: self.z + r.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Div<Vec3> for Vec3 {
|
||||||
|
type Output = Vec3;
|
||||||
|
|
||||||
|
fn div(self, r: Vec3) -> Vec3 {
|
||||||
|
Vec3 {
|
||||||
|
x: self.x / r.x,
|
||||||
|
y: self.y / r.y,
|
||||||
|
z: self.z / r.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Div<Vec3> for f32 {
|
||||||
|
type Output = Vec3;
|
||||||
|
|
||||||
|
fn div(self, r: Vec3) -> Vec3 {
|
||||||
|
Vec3 {
|
||||||
|
x: self / r.x,
|
||||||
|
y: self / r.y,
|
||||||
|
z: self / r.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Div<f32> for Vec3 {
|
||||||
|
type Output = Vec3;
|
||||||
|
|
||||||
|
fn div(self, r: f32) -> Vec3 {
|
||||||
|
Vec3 {
|
||||||
|
x: self.x / r,
|
||||||
|
y: self.y / r,
|
||||||
|
z: self.z / r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<usize> for Vec3 {
|
||||||
|
type Output = f32;
|
||||||
|
fn index(&self, idx: usize) -> &f32 {
|
||||||
|
match idx {
|
||||||
|
0 => &self.x,
|
||||||
|
1 => &self.y,
|
||||||
|
2 => &self.z,
|
||||||
|
_ => panic!("idx {} out of range for vec3", idx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul for Vec3 {
|
||||||
|
type Output = Vec3;
|
||||||
|
|
||||||
|
fn mul(self, r: Vec3) -> Vec3 {
|
||||||
|
Vec3 {
|
||||||
|
x: self.x * r.x,
|
||||||
|
y: self.y * r.y,
|
||||||
|
z: self.z * r.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<Vec3> for f32 {
|
||||||
|
type Output = Vec3;
|
||||||
|
|
||||||
|
fn mul(self, v: Vec3) -> Vec3 {
|
||||||
|
Vec3 {
|
||||||
|
x: v.x * self,
|
||||||
|
y: v.y * self,
|
||||||
|
z: v.z * self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<f32> for Vec3 {
|
||||||
|
type Output = Vec3;
|
||||||
|
|
||||||
|
fn mul(self, r: f32) -> Vec3 {
|
||||||
|
Vec3 {
|
||||||
|
x: self.x * r,
|
||||||
|
y: self.y * r,
|
||||||
|
z: self.z * r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Neg for Vec3 {
|
||||||
|
type Output = Vec3;
|
||||||
|
|
||||||
|
fn neg(self) -> Vec3 {
|
||||||
|
-1. * self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub for Vec3 {
|
||||||
|
type Output = Vec3;
|
||||||
|
|
||||||
|
fn sub(self, r: Vec3) -> Vec3 {
|
||||||
|
Vec3 {
|
||||||
|
x: self.x - r.x,
|
||||||
|
y: self.y - r.y,
|
||||||
|
z: self.z - r.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::{f32::consts::PI, str::FromStr};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vec_add() {
|
||||||
|
let v0 = Vec3::new(1., 2., 3.);
|
||||||
|
let v1 = Vec3::new(1., 1., 1.);
|
||||||
|
assert_eq!(v0 + v1, Vec3::new(2., 3., 4.));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vec_div() {
|
||||||
|
let v0 = Vec3::new(1., 2., 4.);
|
||||||
|
assert_eq!(v0 / 2., Vec3::new(0.5, 1., 2.));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vec_mul() {
|
||||||
|
let v0 = Vec3::new(1., 2., 4.);
|
||||||
|
assert_eq!(v0 * 0.5, Vec3::new(0.5, 1., 2.));
|
||||||
|
assert_eq!(v0 * v0, Vec3::new(1., 4., 16.));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vec_sub() {
|
||||||
|
let v0 = Vec3::new(1., 2., 3.);
|
||||||
|
let v1 = Vec3::new(1., 1., 1.);
|
||||||
|
assert_eq!(v0 - v1, Vec3::new(0., 1., 2.));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vec_idx() {
|
||||||
|
let v0 = Vec3::new(1., 2., 3.);
|
||||||
|
assert_eq!(v0[2], 3.);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vec_display() {
|
||||||
|
let v0 = Vec3::new(1., 2., 3.);
|
||||||
|
assert_eq!(format!("{}", v0), "1 2 3".to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vec_from_str() {
|
||||||
|
assert_eq!(Vec3::from_str("1. 2. 3.").unwrap(), Vec3::new(1., 2., 3.));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vec_str_roundtrip() {
|
||||||
|
let v = Vec3::from_str("1 2 3").unwrap();
|
||||||
|
let s = format!("{}", v);
|
||||||
|
assert_eq!(v, Vec3::from_str(&s).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vec_dot() {
|
||||||
|
let v0 = Vec3::new(1., 0., 0.);
|
||||||
|
let v1 = Vec3::new(-1., 0., 0.);
|
||||||
|
assert_eq!(dot(v0, v1), v0.length() * v1.length() * PI.cos());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vec_cross() {
|
||||||
|
let v0 = Vec3::new(1., 0., 0.);
|
||||||
|
let v1 = Vec3::new(0., 1., 0.);
|
||||||
|
assert_eq!(cross(v0, v1), Vec3::new(0., 0., 1.));
|
||||||
|
}
|
||||||
|
}
|
||||||
56
zigrtiow/test/threads.zig
Normal file
56
zigrtiow/test/threads.zig
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Thread = std.Thread;
|
||||||
|
const info = std.log.info;
|
||||||
|
const Queue = std.atomic.Queue;
|
||||||
|
|
||||||
|
const Task = struct {
|
||||||
|
row_idx: usize,
|
||||||
|
row_pixels: []u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn worker(q: *Queue(Task)) void {
|
||||||
|
while (true) {
|
||||||
|
if (q.get()) |node| {
|
||||||
|
const task = node.data;
|
||||||
|
info("starting thread: {}", .{task.row_idx});
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() anyerror!void {
|
||||||
|
const allocator = std.heap.page_allocator;
|
||||||
|
const cpus = try Thread.getCpuCount();
|
||||||
|
const width = 512;
|
||||||
|
const height = 256;
|
||||||
|
var pixels: [width * height * 3]u8 = undefined;
|
||||||
|
|
||||||
|
var threads: std.ArrayList(std.Thread) = std.ArrayList(std.Thread).init(allocator);
|
||||||
|
var q = Queue(Task).init();
|
||||||
|
const Node = Queue(Task).Node;
|
||||||
|
var row: usize = 0;
|
||||||
|
while (row < height) : (row += 1) {
|
||||||
|
const node = try allocator.create(Node);
|
||||||
|
node.* = .{
|
||||||
|
.prev = undefined,
|
||||||
|
.next = undefined,
|
||||||
|
.data = Task{
|
||||||
|
.row_idx = row,
|
||||||
|
.row_pixels = pixels[row * width * 3 .. (row + 1) * width * 3],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
q.put(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
var t: usize = 0;
|
||||||
|
while (t < cpus) : (t += 1) {
|
||||||
|
try threads.append(try Thread.spawn(.{}, worker, .{&q}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all threads to finish.
|
||||||
|
for (threads.items) |thread| {
|
||||||
|
Thread.join(thread);
|
||||||
|
}
|
||||||
|
info("main done", .{});
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user