diff --git a/Cargo.toml b/Cargo.toml index bfac7a2..4a26250 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "raytracer" version = "0.2.0" edition = "2021" authors = ["Scott Brugmans "] -respository = "https://github.com/scott223/raytracer/" +repository = "https://github.com/scott223/raytracer/" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/input/config.json b/input/config.json index b6024c5..9232a03 100644 --- a/input/config.json +++ b/input/config.json @@ -1,8 +1,8 @@ { "img_width": 600.0, "img_height": 600.0, - "samples": 8, - "max_depth": 5, + "samples": 12, + "max_depth": 8, "sky_color": { "r": 0.3, "g": 0.5, diff --git a/input/scene.json b/input/scene.json index 7a61fd1..9d1cacd 100644 --- a/input/scene.json +++ b/input/scene.json @@ -14,36 +14,8 @@ "camera_defocus_angle": 0.0, "camera_focus_dist": 800.0 }, - "elements": [ - { - "JSONObj": { - "filepath": "input/obj/teapot.obj", - "material": { - "Lambertian": { - "albedo": { - "r": 0.43, - "g": 0.53, - "b": 0.43 - } - } - }, - "transpose" : { - "x": 220.0, - "y": 0.0, - "z": 200.0 - }, - "rotate" : { - "theta_x": 0.0, - "theta_y": -3.2, - "theta_z": 0.0 - }, - "scale" : { - "x" : 20.0, - "y" : 20.0, - "z" : 20.0 - } - } - }, + "elements": + [ { "JSONBox": { "a": { @@ -419,26 +391,26 @@ { "JSONQuad": { "q": { - "x": 343.0, + "x": 383.0, "y": 554.0, - "z": 332.0 + "z": 305.0 }, "u": { - "x": -130.0, + "x": -210.0, "y": 0.0, "z": 0.0 }, "v": { "x": 0.0, "y": 0.0, - "z": -105.0 + "z": -155.0 }, "material": { "DiffuseLight": { "albedo": { - "r": 8.0, - "g": 8.0, - "b": 8.0 + "r": 5.0, + "g": 5.0, + "b": 5.0 } } }, diff --git a/renders/render.png b/renders/render.png index a2b73e6..225409b 100644 Binary files a/renders/render.png and b/renders/render.png differ diff --git a/src/bvh/aabb.rs b/src/bvh/aabb.rs index 070e46e..9d07495 100644 --- a/src/bvh/aabb.rs +++ b/src/bvh/aabb.rs @@ -13,8 +13,9 @@ pub struct Aabb { x: Interval, y: Interval, z: Interval, - min: Vec3, - max: Vec3, + pub min: Vec3, + pub max: Vec3, + pub centroid: Vec3, } impl Default for Aabb { @@ -34,19 +35,25 @@ impl Default for Aabb { }, min: Vec3::new(0.0, 0.0, 0.0), max: Vec3::new(0.0, 0.0, 0.0), + centroid: Vec3::new(0.0, 0.0, 0.0), } } } impl Aabb { pub fn new_from_intervals(x: Interval, y: Interval, z: Interval) -> Self { - Aabb { + + let mut bbox = Aabb { x, y, z, min: Vec3::new(x.interval_min, y.interval_min, z.interval_min), max: Vec3::new(x.interval_max, y.interval_max, z.interval_max), - } + centroid: Vec3::new(0.0, 0.0, 0.0), + }; + + bbox.centroid = calculate_centroid(bbox); + bbox } pub fn new_from_points(p: Vec3, q: Vec3) -> Self { @@ -54,13 +61,17 @@ impl Aabb { let y: Interval = Interval::new(q.y().min(p.y()), q.y().max(p.y())); let z: Interval = Interval::new(q.z().min(p.z()), q.z().max(p.z())); - Aabb { + let mut bbox = Aabb { x, y, z, min: Vec3::new(x.interval_min, y.interval_min, z.interval_min), max: Vec3::new(x.interval_max, y.interval_max, z.interval_max), - } + centroid: Vec3::new(0.0, 0.0, 0.0), + }; + + bbox.centroid = calculate_centroid(bbox); + bbox } pub fn new_from_aabbs(a: Aabb, b: Aabb) -> Self { @@ -68,13 +79,17 @@ impl Aabb { let y: Interval = Interval::new_from_intervals(a.y, b.y); let z: Interval = Interval::new_from_intervals(a.z, b.z); - Aabb { + let mut bbox = Aabb { x, y, z, min: Vec3::new(x.interval_min, y.interval_min, z.interval_min), max: Vec3::new(x.interval_max, y.interval_max, z.interval_max), - } + centroid: Vec3::new(0.0, 0.0, 0.0), + }; + + bbox.centroid = calculate_centroid(bbox); + bbox } // return an AABB that has no side narrower than some delta, padding if necessary @@ -97,15 +112,20 @@ impl Aabb { self.z.expand(delta) }; - Aabb { + let mut bbox = Aabb { x: new_x, y: new_y, z: new_z, min: Vec3::new(new_x.interval_min, new_y.interval_min, new_z.interval_min), max: Vec3::new(new_x.interval_max, new_y.interval_max, new_z.interval_max), - } + centroid: Vec3::new(0.0, 0.0, 0.0), + }; + + bbox.centroid = calculate_centroid(bbox); + bbox } + #[inline(always)] pub fn axis(&self, n: usize) -> Interval { match n { 0 => self.x, @@ -115,6 +135,25 @@ impl Aabb { } } + #[inline(always)] + pub fn largest_axis(&self) -> usize { + let size = self.max - self.min; + + if size.x() > size.y() && size.x() > size.z() { + 0 + } else if size.y() > size.z() { + 1 + } else { + 2 + } + } + + /// Returns the position of the centriod of the Aabb, + #[inline(always)] + pub fn centroid(&self) -> Vec3 { + calculate_centroid(*self) + } + // checks if we have a hit with the aabb, in a given interval // source: https://docs.rs/bvh/latest/src/bvh/ray.rs.html#168-188 @@ -136,6 +175,8 @@ impl Aabb { ray_min.max(0.0) <= ray_max } + + #[inline(always)] pub fn grow(mut self, b: T) { self.x = Interval::new_from_intervals(self.x, b.bounding_box().x); self.y = Interval::new_from_intervals(self.y, b.bounding_box().y); @@ -143,6 +184,14 @@ impl Aabb { } } +pub fn calculate_centroid(bbox: Aabb) -> Vec3 { + Vec3::new( + bbox.min.x() + (bbox.max.x() - bbox.min.x())/2., + bbox.min.y() + (bbox.max.y() - bbox.min.y())/2., + bbox.min.z() + (bbox.max.z() - bbox.min.z())/2., + ) +} + /// Display trait for Aabb impl fmt::Display for Aabb { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -260,4 +309,40 @@ mod tests { assert_eq!(aabb_p.hit(&ray_two, &mut int), false); } -} + + #[test_log::test] + fn test_centroid() { + let p: Vec3 = Vec3::new(1.0, 1.0, 1.0); + let q: Vec3 = Vec3::new(2.0, 2.0, 2.0); + + let aabb_p: Aabb = Aabb::new_from_points(p, q); + + let centroid = aabb_p.centroid(); + + assert_eq!(centroid.x(), 1.5); + assert_eq!(centroid.y(), 1.5); + assert_eq!(centroid.z(), 1.5); + } + + + #[test_log::test] + fn test_longest_axis() { + let p: Vec3 = Vec3::new(1.0, 1.0, 1.0); + let q: Vec3 = Vec3::new(2.0, 3.0, 2.0); + + let aabb_p: Aabb = Aabb::new_from_points(p, q); + + let largest_axis = aabb_p.largest_axis(); + + assert_eq!(largest_axis, 1); + + let p: Vec3 = Vec3::new(1.0, 1.0, 1.0); + let q: Vec3 = Vec3::new(2.0, 3.0, 5.0); + + let aabb_p: Aabb = Aabb::new_from_points(p, q); + + let largest_axis = aabb_p.largest_axis(); + + assert_eq!(largest_axis, 2); + } +} \ No newline at end of file diff --git a/src/bvh/bvh_node_sah.rs b/src/bvh/bvh_node_sah.rs new file mode 100644 index 0000000..7e2917a --- /dev/null +++ b/src/bvh/bvh_node_sah.rs @@ -0,0 +1,276 @@ + + +use std::f64::EPSILON; + +use super::aabb::Aabb; +use crate::elements::*; + + +// this is a node for the BHV tree, and it consists of objects with a hittable trait (either another node, or an element) +// it will only have a left or a right object +#[allow(non_camel_case_types)] + +pub enum BVHNode_SAH { + + Leaf { + parent_index: usize, + depth: u32, + element_index: usize, + }, + Node { + parent_index: usize, + depth: u32, + /// Index of the left subtree's root node. + child_l_index: usize, + + /// The aabb of the left element. + child_l_aabb: Aabb, + + /// Index of the right subtree's root node. + child_r_index: usize, + + /// The aabb of the left element. + child_r_aabb: Aabb, + } +} + +pub struct BVH_SAH { + /// The list of nodes of the [`BVH`]. + /// + /// [`BVH`]: struct.BVH.html + /// + pub nodes: Vec, +} + +impl BVH_SAH { + pub fn build(objects: &Vec) -> BVH_SAH { + let indices = (0..objects.len()).collect::>(); + let expected_node_count = objects.len() * 2; + let mut nodes = Vec::with_capacity(expected_node_count); + BVHNode_SAH::build(objects, &indices, &mut nodes, 0, 0); + BVH_SAH { nodes } + } +} + +impl BVHNode_SAH { + + /// The build function sometimes needs to add nodes while their data is not available yet. + /// A dummy created by this function serves the purpose of being changed later on. + fn create_dummy() -> BVHNode_SAH { + BVHNode_SAH::Leaf { + parent_index: 0, + depth: 0, + element_index: 0, + } + } + + pub fn build( + objects: &Vec, + indices: &[usize], + nodes: &mut Vec, + parent_index: usize, + depth: u32, + ) -> usize { + + match indices.len() { + 0 => { 0 }, + 1 => { + // only one more indice left + let element_index = indices[0]; + + // this will be the index of the new leaf + let node_index = nodes.len(); + + // add the leaf + nodes.push(BVHNode_SAH::Leaf { + parent_index, + depth, + element_index, + }); + + return node_index; + }, + 2 => { + let element_l_indice = indices[0]; + let element_r_indice = indices[1]; + + let node_index = nodes.len(); + + // add the root + let child_l_aabb = Aabb::default(); + let child_r_aabb = Aabb::default(); + + nodes.push(BVHNode_SAH::Node { + parent_index, + depth, + child_l_aabb, + child_l_index: node_index+1, + child_r_aabb, + child_r_index: node_index+2, + }); + + // add the first leaf + nodes.push(BVHNode_SAH::Leaf { + parent_index: node_index, + depth, + element_index: element_l_indice, + }); + + // add the second leaf + nodes.push(BVHNode_SAH::Leaf { + parent_index: node_index, + depth, + element_index: element_r_indice, + }); + + return node_index; + + } + _ => { + + // From here on we handle the recursive case. This dummy is required, because the children + // must know their parent, and it's easier to update one parent node than the child nodes. + let node_index = nodes.len(); + nodes.push(BVHNode_SAH::create_dummy()); + + // create the bounding box for this entire set + let mut aabb_objects = objects[indices[0]].bounding_box(); + + for index in indices { + aabb_objects = Aabb::new_from_aabbs(aabb_objects, objects[*index].bounding_box()); + } + + //create the bounding box for the centroids + let mut aabb_centroids = Aabb::new_from_points(objects[indices[0]].bounding_box().centroid, objects[indices[1]].bounding_box().centroid); + + for index in indices { + + aabb_centroids = Aabb::new_from_aabbs(aabb_centroids, Aabb::new_from_points(objects[*index].bounding_box().centroid,objects[*index].bounding_box().centroid)); + } + + let mut child_l_indices: Vec = Vec::with_capacity(indices.len()/2); + let mut child_r_indices: Vec = Vec::with_capacity(indices.len()/2); + + let sort_axis = aabb_centroids.largest_axis(); + let sort_axis_size = aabb_centroids.max[sort_axis] - aabb_centroids.min[sort_axis]; + + if sort_axis_size < EPSILON { + // axis is too small, lets just divide into 2 + + let mid = indices.len() / 2 as usize; + + for (i, index) in indices.iter().enumerate() { + if i < mid { + child_l_indices.push(*index); + } else { + child_r_indices.push(*index); + } + } + + } else { + + //axis is big enough to split + // here we need to add the SAH logic! + + let splitpoint = aabb_centroids.min[sort_axis] + sort_axis_size / 2.0; + + print!("size: {}, splitpoint {}", sort_axis_size, splitpoint); + + for index in indices { + + println!("point {}", objects[*index].bounding_box().centroid[sort_axis]); + + if objects[*index].bounding_box().centroid[sort_axis] < splitpoint { + child_l_indices.push(*index); + } else { + child_r_indices.push(*index); + } + + } + + } + + println!("l indices {:?}, r indices {:?}", child_l_indices, child_r_indices); + + let child_l_index = + BVHNode_SAH::build(objects, &child_l_indices, nodes, node_index, depth + 1); + let child_r_index = + BVHNode_SAH::build(objects, &child_r_indices, nodes, node_index, depth + 1); + + let mut child_l_aabb = Aabb::default(); + let mut child_r_aabb = Aabb::default(); + + nodes[node_index] = BVHNode_SAH::Node { + parent_index, + depth, + child_l_aabb, + child_l_index, + child_r_aabb, + child_r_index, + }; + + node_index + + } + + } + + } +} + +/* +impl Hittable for BVHNode_SAH<'_> { + // recursive check for hits through the BHV nodes + fn hit(&self, ray: &Ray, ray_t: &mut Interval) -> Option { + // check if we hit the bounding box of this node, because if we dont, we can stop right here + // this is where the real speadup happens, as we dont have to do any fancy calculations, just check for big box at the start of the tree (of further down below) + if !self.bounding_box().hit(ray, ray_t) { + return None; + } + + let left_hit = self.left?.hit(ray, ray_t); + + //TODO we can do the next part smoother, where we immediatly do the right hit too (adjust the interval before) and then just make one match statement + + match left_hit { + Some(lh) => { + //there is a hit on the left path, so lets adjust the interval and see if we have one closer by on the right + ray_t.interval_max = lh.t; + let right_hit = self.right?.hit(ray, ray_t); + + match right_hit { + Some(rh) => { + // we have a closer hit on the right, so return the right hit + Some(rh) + } + _ => { + // there is no closer hit on the right, so return the left hit + Some(lh) + } + } + } + _ => { + // no hit on the left side, so lets try the right with the unmodified interval ray_t + // this function returns Some(rh) if a hit is found, and None if no hit is found + self.right?.hit(ray, ray_t) + } + } + + } + + fn bounding_box(&self) -> Aabb { + self.bounding_box //return the pre-set bounding box + } + + // need to implement these for the Hittable trait, but serve little function in the BHVNode + fn pdf_value(&self, _origin: Vec3, _direction: Vec3) -> f64 { + 0.0 + } + + // need to implement these for the Hittable trait, but serve little function in the BHVNode + fn random(&self, _origin: Vec3, _rng: &mut SmallRng) -> Vec3 { + Vec3::new(1.0, 0.0, 0.0) + } + +} +*/ diff --git a/src/bvh/mod.rs b/src/bvh/mod.rs index a17c2dd..40610aa 100644 --- a/src/bvh/mod.rs +++ b/src/bvh/mod.rs @@ -3,3 +3,6 @@ pub use aabb::Aabb; mod bvh_node; pub use bvh_node::BVHNode; + +mod bvh_node_sah; +pub use bvh_node_sah::{BVH_SAH, BVHNode_SAH}; diff --git a/src/linalg/vec3.rs b/src/linalg/vec3.rs index 52024f3..061eeec 100644 --- a/src/linalg/vec3.rs +++ b/src/linalg/vec3.rs @@ -1,6 +1,6 @@ use std::f64::consts::PI; use std::fmt; -use std::ops::{Add, Div, Mul, Neg, Sub}; +use std::ops::{Add, Div, Mul, Neg, Sub, Index}; use rand::Rng; @@ -178,6 +178,20 @@ impl fmt::Display for Vec3 { } } +impl Index for Vec3 { + type Output = f64; + + fn index(&self, index: usize) -> &f64 { + if index == 0 { + &self.x + } else if index == 1 { + &self.y + } else { + &self.z + } + } +} + // negative value impl Neg for Vec3 { type Output = Vec3; diff --git a/src/main.rs b/src/main.rs index df19210..a298164 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,37 +1,34 @@ use std::env; use dotenv; + use raytracer::render::RenderIntegrator; // App main function // load the JSON files for the Scene and for the Configuration, and calls the main raytracer function render from lib.rs fn main() { - dotenv::dotenv().ok(); - + dotenv::dotenv().ok(); env_logger::init(); log::info!( - "Program started: loading scene and config from JSON files located in /input and render will be located in /render. Base directory is {:?}", + "Program started: loading scene and config from JSON files located in /input and render will be located in /render. Current directory is {:?}", env::current_dir().unwrap() ); - let mut r: RenderIntegrator = - RenderIntegrator::new_from_json("input/scene.json", "input/config.json"); - - // execute the main render - match r.render() { - Ok(()) => { - // success, so save to png - match r.save_to_png("renders/render.png") { + //load the JSON into the integrator + match RenderIntegrator::new_from_json("input/scene.json", "input/config.json") { + Ok(mut r) => { + // succes, so execute the main render + match r.render() { Ok(()) => { - log::info!("File saved, closing program"); + // success, so save to png + match r.save_to_png("renders/render.png") { + Ok(()) => { log::info!("File saved, closing program"); } + Err(e) => { log::error!("Error in saving file: {}", e); } + } } - Err(e) => { - log::error!("Error in saving file: {}", e); - } - } - } - Err(e) => { - log::error!("Error in render function: {}", e); + Err(e) => { log::error!("Error in render: {}", e); } + }; } + Err(e) => { log::error!("Error in reading JSON: {}", e); } } -} //fn main +} //fn main \ No newline at end of file diff --git a/src/render/integrator.rs b/src/render/integrator.rs index 9adf97f..4d44176 100644 --- a/src/render/integrator.rs +++ b/src/render/integrator.rs @@ -12,6 +12,8 @@ use rayon::prelude::*; use crate::{ bvh::BVHNode, + bvh::BVH_SAH, + bvh::BVHNode_SAH, elements::{Element, Hittable, JSONElement}, materials::{Emmits, Reflects, Refracts, Scatterable}, render::camera::Camera, @@ -42,25 +44,22 @@ impl RenderIntegrator { } } - pub fn new_from_json(scene_path: &str, config_path: &str) -> Self { + pub fn new_from_json(scene_path: &str, config_path: &str) -> Result> { // Open the Scene file - let scene_file = File::open(scene_path).expect("Error reading input/scene.json, quitting"); - let scene_reader = BufReader::new(scene_file); + let scene_file: File = File::open(scene_path)?; + let scene_reader: BufReader = BufReader::new(scene_file); // Read the JSON contents of the file as an instance of `Scene`. - let scene: JSONScene = serde_json::from_reader(scene_reader) - .expect("Error parsing input/scene.json, quitting"); + let scene: JSONScene = serde_json::from_reader(scene_reader)?; // Open the Config file - let config_file = - File::open(config_path).expect("Error reading input/config.json, quitting"); - let config_reader = BufReader::new(config_file); + let config_file: File = File::open(config_path)?; + let config_reader: BufReader = BufReader::new(config_file); // Read the JSON contents of the file as an instance of `Config`. - let config: Config = serde_json::from_reader(config_reader) - .expect("Error parsing input/config.json, quitting"); + let config: Config = serde_json::from_reader(config_reader)?; - return RenderIntegrator::new(scene, config); + return Ok(RenderIntegrator::new(scene, config)); } pub fn save_to_png(&self, png_path: &str) -> Result<(), Box> { @@ -128,6 +127,8 @@ impl RenderIntegrator { let bhv_tree: Box = BVHNode::new(&mut objects, 0, end); log::info!("BHV tree generated"); + let bvh_sah_tree = BVH_SAH::build(&objects); + // create a refrence to the elements that are marked as an attractor let attractors: Vec<&Element> = objects.iter().filter(|e| e.is_attractor()).collect();