diff --git a/img2.png b/img2.png new file mode 100644 index 0000000..546942a Binary files /dev/null and b/img2.png differ diff --git a/rhai/mesh.rhai b/rhai/mesh.rhai index d1a9ff9..0fdc561 100644 --- a/rhai/mesh.rhai +++ b/rhai/mesh.rhai @@ -1,8 +1,8 @@ let scene = Scene(); -let material = Material(V(0.2,0.2,0.2), V(0.2, 0.2, 0.2), 10.0); +let material = Material(V(0.9,0.2,0.7), V(0.7, 0.2, 0.7), 10.0); scene.addMaterial("material", material); -let material2 = Material(V(0.2,0.7,0.2), V(0.2, 0.2, 0.2), 10.0); +let material2 = Material(V(0.2,0.7,0.2), V(0.7, 0.7, 0.7), 10.0); scene.addMaterial("mat2", material2); let camera = Camera(P(0.0,0.0,2.0), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); diff --git a/rhai/scene.rhai b/rhai/scene.rhai index c5d6516..e662416 100644 --- a/rhai/scene.rhai +++ b/rhai/scene.rhai @@ -14,27 +14,27 @@ scene.addCamera("-Y Cam", camera); let camera = Camera( P(-distance,0.0,0.0), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); scene.addCamera("-X Cam", camera); -let material = Material(V(0.2,0.2,0.2), V(0.2, 0.8, 0.8), 10.0); +let material = Material(V(0.2,0.9,0.8), V(0.3, 0.8, 0.8), 10.0); scene.addMaterial("bluegreen", material); let light = Light(P(0.0,7.0,0.0), V(0.0,0.0,1.0), V(0.1, 0.01, 0.001)); light.active(false); scene.addLight("blue", light); -let light = Light( P(2.0,7.0,0.0), V(0.0,1.0,0.0), V(0.1, 0.01, 0.001)); -light.active(false); -scene.addLight("green", light); +// let light = Light( P(2.0,7.0,0.0), V(0.0,1.0,0.0), V(0.1, 0.01, 0.001)); +// light.active(false); +// scene.addLight("green", light); -let light = Light( P(2.0,7.0,2.0), V(1.0,0.0,0.0), V(0.1, 0.01, 0.001)); -scene.addLight("red", light); +// let light = Light( P(2.0,7.0,2.0), V(1.0,0.5,0.5), V(0.0, 0.00, 0.001)); +// scene.addLight("red", light); let light = Ambient(V(0.1,0.1,0.1)); scene.addLight("ambient", light); -let sphere = Sphere(P(0.0,0.0,0.0), 1.0 ); -let sphere_node = Node(sphere, material); - scene.addNode("sphere", sphere_node); +// let sphere = Sphere(P(0.0,0.0,0.0), 1.0 ); +// let sphere_node = Node( sphere, material); +// scene.addNode("sphere",sphere_node); //let mesh = Mesh("obj/cow.obj" ); //let mesh_node = Node(mesh); @@ -42,7 +42,7 @@ let sphere_node = Node(sphere, material); for i in 0..6 { let sphere = Sphere(P(0.0,0.0,0.0), 2.0 ); - let sphere_node = Node(sphere, material); + let sphere_node = Node( sphere, material); sphere_node.translate(4.0*cos(i.to_float()), -4.0, 4.0*sin(i.to_float())); scene.addNode(i.to_string(), sphere_node); } @@ -51,8 +51,8 @@ for i in 0..6 { //scene.addNode(child); let cube = CubeUnit(); -let cube_node = Node(cube, material); -scene.addNode("cube", cube_node); +let cube_node = Node( cube, material); +scene.addNode("cube", cube_node); //let gnonom = Gnonom(); //let gnonom_node = Node(gnonom); diff --git a/src/bvh.rs b/src/bvh.rs index 23eeb7e..9a8a1e7 100644 --- a/src/bvh.rs +++ b/src/bvh.rs @@ -1,7 +1,14 @@ use crate::{node::Node, ray::*, EPSILON}; -use nalgebra::{Point3, Vector3}; +use nalgebra::{distance, point, Matrix4, Point3, Vector3}; use std::collections::HashMap; -use std::ops::Index; +use std::fmt; + +// Debuging statics +static mut STATIC0: i32 = 0; +static mut STATIC1: i32 = 0; +static mut STATIC2: i32 = 0; +static mut STATIC3: i32 = 0; +static mut STATIC4: i32 = 0; // BOUNDING BOX ----------------------------------------------------------------- #[derive(Clone)] @@ -16,18 +23,28 @@ impl AABB { pub fn new(bln: Point3, trf: Point3) -> AABB { let bln = bln + Vector3::new(EPSILON, EPSILON, EPSILON); let trf = trf - Vector3::new(EPSILON, EPSILON, EPSILON); - let centroid = bln + (bln - trf) / 2.0; + let centroid = bln + (trf - bln) / 2.0; AABB { bln, trf, centroid } } + //Empty box pub fn empty() -> AABB { AABB { - bln: Point3::new(0.0, 0.0, 0.0), - trf: Point3::new(0.0, 0.0, 0.0), + bln: Point3::new(f64::MAX, f64::MAX, f64::MAX), + trf: Point3::new(f64::MIN, f64::MIN, f64::MIN), centroid: Point3::new(0.0, 0.0, 0.0), } } + //Apply a matrix transformation to a box + pub fn transform_mut(&mut self, mat: &Matrix4) { + let bln = &mut self.bln; + let trf = &mut self.trf; + let centroid = &mut self.centroid; + self.bln = mat.transform_point(bln); + self.trf = mat.transform_point(trf); + self.centroid = mat.transform_point(centroid); + } // Intersect bounding box exactly - pub fn intersect_bounding_box(&self, ray: &Ray) -> bool { + pub fn intersect_ray(&self, ray: &Ray) -> bool { let bln = &self.bln; let trf = &self.trf; let t1 = (bln - ray.a).component_div(&ray.b); @@ -53,7 +70,7 @@ impl AABB { false } // Intersect way with some epsilon term - pub fn intersect_bounding_box_aprox(&self, ray: &Ray) -> bool { + pub fn intersect_ray_aprox(&self, ray: &Ray) -> bool { let bln = &self.bln; let trf = &self.trf; let t1 = (bln - ray.a).component_div(&ray.b); @@ -146,91 +163,26 @@ impl AABB { let size = self.size(); 2.0 * (size.x * size.y + size.x * size.z + size.y * size.z) } + pub fn area(&self) -> f64 { + let extent = self.trf - self.bln; + return extent.x * extent.y + extent.y * extent.z + extent.z * extent.x; + } // Volume of the AABB pub fn volume(&self) -> f64 { let size = self.size(); size.x * size.y * size.z } } - -// Index implemntation of the BVH tree -// pub enum BVHNode { -// Leaf { -// p_idx: usize, //Parent index -// depth: usize, //Depth in BVH tree -// n_idx: usize, //Node index in corrosponding Vec -// }, -// Node { -// p_idx: usize, //Parent index -// l_idx: usize, //Left child index -// l_aabb: AABB, //Left AABB -// r_idx: usize, //Right child index -// r_aabb: AABB, //Right AABB -// depth: usize, //Depth in BVH tree -// }, -// } -// impl BVHNode { -// //Get parent -// fn get_parent(&self) -> usize { -// match *self { -// BVHNode::Node { p_idx, .. } | BVHNode::Leaf { p_idx, .. } => p_idx, -// } -// } -// //Get the left child of a node -// fn get_child_l(&self) -> usize { -// match *self { -// BVHNode::Leaf { .. } => panic!("Cannot get child of leaf node"), -// BVHNode::Node { l_idx, .. } => l_idx, -// } -// } -// // Get right child -// fn get_child_r(&self) -> usize { -// match *self { -// BVHNode::Leaf { .. } => panic!("Cannot get child of leaf node"), -// BVHNode::Node { r_idx, .. } => r_idx, -// } -// } -// // Get the depth of selected node -// pub fn depth(&self) -> usize { -// match *self { -// BVHNode::Node { depth, .. } | BVHNode::Leaf { depth, .. } => depth, -// } -// } -// // Get the aabb of the current node, if leaf return the primitives aabb -// // If node return the join of the two child nodes -// pub fn get_node_aabb(&self, nodes: &Vec) -> AABB { -// match *self { -// BVHNode::Node { l_aabb, r_aabb, .. } => l_aabb.join(&r_aabb), -// BVHNode::Leaf { aabb, .. } => aabb, -// } -// } -// } -// //Implementation of the BVH -// pub struct BVHTree<'a> { -// pub nodes: &'a HashMap, -// pub bvh_nodes: Vec, -// } - -// impl<'a> BVHTree<'a> { -// //Generate a BVH tree given a vector of nodes -// pub fn new(nodes: &HashMap) -> BVHTree { -// //We will make an aabb that bounds all shapes -// let mut root_aabb = AABB::empty(); -// let mut root_centroid = AABB::empty(); -// for (_, node) in nodes { -// let node_aabb = node.primitive.get_aabb(); -// root_aabb.join_mut(&node_aabb); -// root_centroid.grow_mut(&node_aabb.get_centroid()); -// } - -// //We will make an aabb that bounds all centroids -// return BVHTree { -// nodes: &HashMap::new(), -// bvh_nodes: vec![], -// }; -// } -// } - +impl fmt::Display for AABB { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.bln[0] == f64::MAX || self.trf[0] == f64::MIN { + writeln!(f, "Empty aabb") + } else { + writeln!(f, "bln: {}\ntrf: {}", self.bln, self.trf) + } + } +} +#[derive(Clone)] pub struct BVHNode { aabb: AABB, //The nodes bounding box l_idx: usize, //Child node l, the right node is alway l_idx + 1 @@ -238,129 +190,282 @@ pub struct BVHNode { prim_count: usize, //Number of primitives the node encapsulates } +impl BVHNode { + pub fn default() -> BVHNode { + BVHNode { + aabb: AABB::empty(), + l_idx: 0, + first_prim: 0, + prim_count: 0, + } + } +} + +impl fmt::Display for BVHNode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "l_idx: {}", self.l_idx)?; + writeln!(f, "First Prim: {}", self.first_prim)?; + writeln!(f, "Prim Count: {}", self.prim_count)?; + writeln!(f, "aabb: {}", self.aabb) + } +} + pub struct BVH { bvh_nodes: Vec, //BVH nodes with AABBs nodes: Vec, //Nodes with primitives nodes_used: usize, - root_node_index: usize, } impl BVH { //Build a bvh by subdividing recursively - fn build(in_nodes: HashMap) -> BVH { - //Make our own vec of nodes so that we can refer to it by index - //Might be long to copy scene, so alternative methods may be prefered - let nodes = vec![]; + pub fn build(in_nodes: &HashMap) -> BVH { + /* + Make our own vec of nodes so that we can refer to it by index + This might be expensive so another method is preferred + */ + let mut nodes = vec![]; for (_, node) in in_nodes { - nodes.push(node); + nodes.push(node.clone()); } //A BVH tree will be maximum size of 2*n + 1 + //Initialise an empty BVHNode with empty AABB let n = nodes.len(); - let mut bvh_nodes: Vec = Vec::with_capacity(2 * n + 1); + let bvh_nodes: Vec = vec![BVHNode::default(); 2 * n + 1]; //Begin constructing our BVH tree - let root_node_index = 0; + //One node used to begin with (The root node) let nodes_used = 1; - let tree = BVH { + let mut tree = BVH { nodes, bvh_nodes, - root_node_index, nodes_used, }; - - // Get the root node and assign it to index 0 - let mut root = &bvh_nodes[root_node_index]; - root.l_idx = 0; //Root node has no children to begin with + // Get the root node at index 0 + let root = &mut tree.bvh_nodes[0]; + root.l_idx = 0; //Root node has no left or right child to begin (root.first_prim, root.prim_count) = (0, n); //Make root include all n nodes - tree.update_bvh_node_aabb(root_node_index); //Fit the root nodes AABB - tree.subdivide(root_node_index); + tree.update_bvh_node_aabb(0); //Create the root nodes AABB on the n primitives + tree.subdivide(0); //Sub divide the root node tree } - // Will update the node's AABB at bvh[index] + // Will update the node's AABB at bvh_nodes[index] fn update_bvh_node_aabb(&mut self, index: usize) { // We will make his node bound all its primitives - let bvh_node = &self.bvh_nodes[index]; //Get the BVHNode we are working - let bvh_node_aabb = AABB::empty(); //Create the BVHNode's AABB + let bvh_node = &mut self.bvh_nodes[index]; // Current BVHNode + let bvh_node_aabb = &mut bvh_node.aabb; //Current node AABB - let start_index = bvh_node.first_prim; //Start index of the first primitive the node contains - let count = bvh_node.prim_count; //Number of primitives within the nodes aabb + let first_prim = bvh_node.first_prim; //Start index of prim + let prim_count = bvh_node.prim_count; //Number of primitives within the nodes aabb - for i in 0..count { - let primitive = &self.nodes[start_index + i].primitive; //Get the primitive from the Vec - let node_aabb = primitive.get_aabb(); //Get the primitives aabb - bvh_node_aabb.join_mut(&node_aabb); //Join it with the bvh_nodes aabb + for i in 0..prim_count { + let node = &self.nodes[first_prim + i]; //Get the node from the Vec + let mut node_aabb = node.primitive.get_aabb(); //Get the primitive's AABB + node_aabb.transform_mut(&node.model); //Transform the AABB to world coordinates + bvh_node_aabb.join_mut(&node_aabb); //Join it with the BVH node's AABB } - } + // unsafe { + // println!("UPDATE TO AABB ---- {STATIC0}"); + // STATIC0 += 1; + // let bvh_node = &mut self.bvh_nodes[index]; //Get the BVHNode we are working on + // println!("{bvh_node}"); + // } + } + // Subdivision, will subdivide a split fn subdivide(&mut self, index: usize) { + //Get the bvh_node we will be altering // Determine the axis and position of the split plane // Split the group of primitives in two halves using the split plane // Create child nodes for each half // Recurse into each of the child nodes. - // Get information about the node we want to subdivide - let bvh_node = &self.bvh_nodes[index]; //Get the BVHNode we are working - - /* ----------------- SUBDIVIDE BY CENTROID --------------------- */ - // let bvh_node_centroid_aabb = AABB::empty(); //Create the BVHNode's AABB - // let start_index = bvh_node.first_prim; //Start index of the first primitive the node contains - // let count = bvh_node.prim_count; //Number of primitives within the nodes aabb - // for i in 0..count { - // let primitive = &self.nodes[start_index + i].primitive; //Get the primitive from the Vec - // let node_aabb_centroid = primitive.get_aabb().get_centroid(); //Get the primitives aabb centroid - // bvh_node_centroid_aabb.grow_mut(&node_aabb_centroid); // Grow the aabb to include the all centroids - // } + //Leaf node case, we cannot sub-divide any more + if self.bvh_nodes[index].prim_count == 1 { + return; + }; /* ------------ SUBDIVIDE BY LONGEST AXIS ------------ */ - - let (bln, trf) = (bvh_node.aabb.bln, bvh_node.aabb.trf); + //Get information about the node we want to subdivide + let (bln, trf) = ( + self.bvh_nodes[index].aabb.bln, + self.bvh_nodes[index].aabb.trf, + ); let extent = trf - bln; - let axis = 0; // Assume that x is longest + let mut axis = 0; // Assume that x is longest if extent.y > extent.x { - axis = 1 // Split y if longer + axis = 1; // Split y if longest }; if extent.z > extent[axis] { - axis = 2 // Split z if loner + axis = 2; // Split z if longest }; - let split_pos = bln[axis] + extent[axis] * 0.5; //Final split along this axis + let split_pos = bln[axis] + extent[axis] * 0.5; // Final split down the middle of AABB - //Perform a quicksort our nodes - let i = bvh_node.first_prim; - let j = i + bvh_node.prim_count - 1; - while i <= j { - let centroid = self.nodes[i].primitive.get_aabb().get_centroid(); - if centroid[axis] < split_pos { - i += 1; //If it is on left split remain in place - } else { - self.nodes.swap(i, j); //Move to right split - j -= 1; + /* --------- SUBDIVIDE BY Surface Area Heuristic ---------*/ + // let mut best_axis: Option = None; + // let mut best_pos = 0.0; + // let mut best_cost = 1e30; + // let first_prim_idx = self.bvh_nodes[index].first_prim; + // for axis in 0..2 { + // for i in 0..self.bvh_nodes[index].prim_count { + // let node = &self.nodes[first_prim_idx + i]; + // //Get the centroid of the bounding box + // let centroid = node.primitive.get_aabb().get_centroid(); + // //Place the centroid into world coordinates + // let world_centroid = node.model.transform_point(¢roid); + // //Get the candidate position + // let candidate_pos = world_centroid[axis]; + // let cost = self.evaluate_sah(&self.bvh_nodes[index], axis, candidate_pos); + // if cost < best_cost { + // best_pos = candidate_pos; + // best_axis = Some(axis); + // best_cost = cost; + // } + // } + // } + // let axis = match best_axis { + // Some(axis) => axis, + // None => 0, + // }; + // let split_pos = best_pos; + + let left_count; + let right_count; + let mut i; + let mut j; + { + let bvh_node = &mut self.bvh_nodes[index]; + i = bvh_node.first_prim; //Start of array + j = i + bvh_node.prim_count - 1; //End of array + while i <= j { + //Perform a quicksort dependent on location + let node = &self.nodes[i]; // Node we would like to sort + let centroid = node.primitive.get_aabb().get_centroid(); //Centroid of node we would like to sort + let centroid = node.model.transform_point(¢roid); //Transform to world coordinates + if centroid[axis] < split_pos { + i += 1; // On Left-Hand-Side + } else { + self.nodes.swap(i, j); + j -= 1; // On Right-Hand-Side + } + } + //Now we have two splits + //The lhs of the array is in the left split 0..left_count + //The rhs of the array is on the right split left_count + 1..n + left_count = i - bvh_node.first_prim; //Number of prims on lhs + right_count = bvh_node.prim_count - left_count; + //println!("SPLIT INTO: {left_count} {right_count}"); + if left_count == 0 || left_count == bvh_node.prim_count { + //Split did nothing + return; } } - //Now we have two children, the lhs of the array is in the left split, and the rhs of the array is on the right split - let left_count = i - bvh_node.first_prim; //Number of prims on lhs - if left_count == 0 || left_count == bvh_node.prim_count { - return; //If we have no more on the left, disregard - } + // unsafe { + // println!("SUBDIVIDE: {STATIC1}"); + // println!("SPLIT INTO: {left_count} "); + // STATIC1 += 1; + // } + let l_idx = self.nodes_used; //Left child - self.nodes_used += 1; - let r_idx = self.nodes_used; //Right child - self.nodes_used += 1; + self.bvh_nodes[index].l_idx = l_idx; + self.nodes_used = self.nodes_used + 2; - bvh_node.l_idx = l_idx; + //Set left node information + self.bvh_nodes[l_idx].first_prim = self.bvh_nodes[index].first_prim; //Left split begins at parent split + self.bvh_nodes[l_idx].prim_count = left_count; // Left prims - self.bvh_nodes[l_idx].first_prim = bvh_node.first_prim; //Set left split - self.bvh_nodes[l_idx].prim_count = left_count; //We know this info from our quicksort + //Set right node information + self.bvh_nodes[l_idx + 1].first_prim = i; // Right split start index + self.bvh_nodes[l_idx + 1].prim_count = right_count; - self.bvh_nodes[r_idx].first_prim = i; //Set right split information - self.bvh_nodes[r_idx].prim_count = bvh_node.prim_count - left_count; - bvh_node.prim_count = 0; + //Current node is not a leaf node + self.bvh_nodes[index].prim_count = 0; self.update_bvh_node_aabb(l_idx); //Update AABB for left of split - self.update_bvh_node_aabb(r_idx); //Update AABB for right of split + self.update_bvh_node_aabb(l_idx + 1); //Update AABB for right of split //Recurse - self.subdivide(l_idx); - self.subdivide(r_idx); + self.subdivide(l_idx); // Subdivide left index + self.subdivide(l_idx + 1); // SUbdivide right index + } + // Traverse the BVH, 0 will be needed to start at root node + pub fn traverse(&self, ray: &Ray, idx: usize) -> Option<(&Node, Intersection)> { + let bvh_node = &self.bvh_nodes[idx]; + if !bvh_node.aabb.intersect_ray(ray) { + // No intersection with BVH in world coordinates + return None; + } + if bvh_node.prim_count > 0 { + // Leaf node intersection + let node_idx = bvh_node.first_prim; + let node = &self.nodes[node_idx]; + if !node.active { + return None; + } + let ray = ray.transform(&node.inv_model); //Transform ray to model coords + if let Some(intersect) = node.primitive.intersect_ray(&ray) { + if intersect.distance < EPSILON { + return None; + } else { + // Convert intersect back to world coords + let intersect = intersect.transform(&node.model, &node.inv_model); + return Some((node, intersect)); + } + } + return None; + } else { + //Recurse down the BVH + //Recurse down the BVH right node + let intersect_l = self.traverse(ray, bvh_node.l_idx); + let intersect_r = self.traverse(ray, bvh_node.l_idx + 1); + + match (intersect_l, intersect_r) { + (None, None) => return None, + (Some(intersect), None) => return Some(intersect), + (None, Some(intersect)) => return Some(intersect), + (Some((node_l, inter_l)), Some((node_r, inter_r))) => { + //Compare intersect distance + let dist_l = distance(&ray.a, &inter_l.point); + let dist_r = distance(&ray.a, &inter_r.point); + if dist_l < dist_r { + return Some((node_l, inter_l)); + } else { + return Some((node_r, inter_r)); + } + } + } + } + } + fn evaluate_sah(&self, node: &BVHNode, axis: usize, pos: f64) -> f64 { + // determine triangle counts and bounds for this split candidate + let mut l_aabb = AABB::empty(); + let mut r_aabb = AABB::empty(); + let mut l_count = 0; + let mut r_count = 0; + for i in 0..node.prim_count { + let aabb = self.nodes[node.first_prim + i].primitive.get_aabb(); + if aabb.trf[axis] < pos { + l_count += 1; + l_aabb.grow_mut(&aabb.trf); + } else { + r_count += 1; + r_aabb.grow_mut(&aabb.bln); + } + } + let cost = l_count as f64 * l_aabb.area() + r_count as f64 * r_aabb.area(); + match cost > 0.0 { + true => 0.0, + false => 1e30, + } + } +} + +impl fmt::Display for BVH { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for (i, node) in self.bvh_nodes.iter().enumerate() { + writeln!(f, "Node: {i}")?; + writeln!(f, "{node}")?; + } + write!(f, "") } } diff --git a/src/gui.rs b/src/gui.rs index 48213d1..a6ba376 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -5,7 +5,7 @@ use crate::{ node::*, primitive::*, scene::*, - state::{INIT_FILE, SAVE_FILE}, + state::{RaytracingOption, INIT_FILE, SAVE_FILE}, }; use imgui::*; use nalgebra::{Point3, Vector3}; @@ -14,14 +14,24 @@ use rhai::Engine; use std::time::Instant; //BUFFER CONSTANTS -const BUFFER_PROPORTION_INIT: f32 = 0.2; const BUFFER_PROPORTION_MIN: f32 = 0.1; const BUFFER_PROPORTION_MAX: f32 = 1.0; //RAY CONSTANTS -const RAYS_INIT: i32 = 100; -const RAYS_MIN: i32 = 100; -const RAYS_MAX: i32 = 10000; +const RAYS_MIN: u32 = 100; +const RAYS_MAX: u32 = 10000; +const MIN_DEPTH: u8 = 5; +const MAX_DEPTH: u8 = 100; +const MIN_SAMPLES: u32 = 5; +const MAX_SAMPLES: u32 = 100; +const MIN_RANDOM: f64 = 100.0; +const MAX_RANDOM: f64 = 1000.0; + +//DIFFUSE CONSTANTS +const MIN_DIFFUSE_RAYS: u8 = 5; +const MAX_DIFFUSE_RAYS: u8 = 100; +const MIN_DIFFUSE_COEFFICIENT: f32 = 0.0; +const MAX_DIFFUSE_COEFFICIENT: f32 = 1.0; //MATERIAL CONSTANTS const MIN_D: f32 = 0.0; @@ -47,14 +57,14 @@ const MAX_ROTATION: f64 = 180.0; const MAX_TRANSLATE: f64 = 10.0; // CAMERA CONSTANTS -const MIN_FOV: f32 = 10.0; -const MAX_FOV: f32 = 160.0; +const MIN_FOV: f64 = 10.0; +const MAX_FOV: f64 = 160.0; //const CAMERA_INIT: f32 = 5.0; /// Manages all state required for rendering Dear ImGui over `Pixels`test. pub enum GuiEvent { - BufferResize(f32, f32), - CameraUpdate(Camera, f32), + RaytracerOption(RaytracingOption), + CameraUpdate(Camera), SceneLoad(Scene), SaveImage(String), } @@ -72,12 +82,9 @@ pub struct Gui { engine: Engine, scene: Scene, - pub ray_num: i32, - - buffer_proportion: f32, + raytracing_option: RaytracingOption, camera: Camera, - camera_fov: f32, image_filename: String, } @@ -122,7 +129,7 @@ impl Gui { let renderer = imgui_wgpu::Renderer::new(&mut imgui, device, queue, config); // Return GUI context - Self { + let mut gui = Self { imgui, platform, renderer, @@ -135,14 +142,29 @@ impl Gui { engine: init_engine(), scene: Scene::empty(), - ray_num: RAYS_INIT, - buffer_proportion: BUFFER_PROPORTION_INIT, + raytracing_option: RaytracingOption::default(), camera: Camera::unit(), - camera_fov: 110.0, image_filename: String::from(SAVE_FILE), + }; + + // ------------ TESTING CODE (LOAD SCENE ON START) ----------------- + match std::fs::read_to_string(&mut gui.script_filename) { + Ok(script) => { + gui.script = script; + } + Err(e) => println!("{}", e), } + match gui.engine.eval(&gui.script) { + Ok(scene) => { + gui.scene = scene; + gui.event = Some(GuiEvent::SceneLoad(gui.scene.clone())); + } + Err(e) => println!("{e}"), + } + // ------------ TESTING CODE (LOAD SCENE ON START) ----------------- + gui } /// Prepare Dear ImGui. @@ -183,23 +205,73 @@ impl Gui { //Raytracing options ------------------------------------------- if CollapsingHeader::new("Raytracer").build(ui) { - // Numbers of rays to render - ui.slider("# Rays: ", RAYS_MIN, RAYS_MAX, &mut self.ray_num); + // Numbers of rays to render per pass + ui.slider( + "Rays Per Pass", + RAYS_MIN, + RAYS_MAX, + &mut self.raytracing_option.rays_per_pass, + ); // Proportion of the window the buffer occupies ui.slider( "% Buffer: ", BUFFER_PROPORTION_MIN, BUFFER_PROPORTION_MAX, - &mut self.buffer_proportion, + &mut self.raytracing_option.buffer_proportion, + ); + //Clear colour for scene + ui.slider_config("Clear Colour", 0, 255) + .build_array(&mut self.raytracing_option.clear_color); + //Clear colour if no intersect + ui.slider_config("Pixel Clear Colour", 0, 255) + .build_array(&mut self.raytracing_option.pixel_clear); + //Ray depth slider + ui.slider( + "Ray Depth", + MIN_DEPTH, + MAX_DEPTH, + &mut self.raytracing_option.ray_depth, + ); + //Ray samples slider + ui.slider( + "Ray Samples", + MIN_SAMPLES, + MAX_SAMPLES, + &mut self.raytracing_option.ray_samples, + ); + //Ray randomness + ui.slider( + "Ray Randomness", + MIN_RANDOM, + MAX_RANDOM, + &mut self.raytracing_option.ray_randomness, + ); + //Number of diffuse rays + ui.slider( + "Diffuse Rays", + MIN_DIFFUSE_RAYS, + MAX_DIFFUSE_RAYS, + &mut self.raytracing_option.diffuse_rays, + ); + //Diffuse Coefficient + ui.slider( + "Diffuse Coefficient", + MIN_DIFFUSE_COEFFICIENT, + MAX_DIFFUSE_COEFFICIENT, + &mut self.raytracing_option.diffuse_coefficient, ); // Fov of the buffer - ui.slider("fov", MIN_FOV, MAX_FOV, &mut self.camera_fov); + ui.slider( + "fov", + MIN_FOV, + MAX_FOV, + &mut self.raytracing_option.buffer_fov, + ); + // Enable BVH + ui.checkbox("Enable BVH", &mut self.raytracing_option.bvh_active); // Apply stored changes if ui.button("Apply") { - self.event = Some(GuiEvent::BufferResize( - self.buffer_proportion, - self.camera_fov, - )); + self.event = Some(GuiEvent::RaytracerOption(self.raytracing_option.clone())); }; } // CAMERA OPTIONS ---------------------------------------- @@ -214,7 +286,7 @@ impl Gui { .build_array(self.camera.up.as_mut_slice()); if ui.button("Apply Camera") { println!("Camera changed"); - self.event = Some(GuiEvent::CameraUpdate(self.camera.clone(), self.camera_fov)); + self.event = Some(GuiEvent::CameraUpdate(self.camera.clone())); } } // SCRIPTING -------------------------------------------- @@ -318,7 +390,7 @@ impl Gui { for (label, camera) in &self.scene.cameras { if ui.button(label) { self.camera = camera.clone(); - self.event = Some(GuiEvent::CameraUpdate(camera.clone(), self.camera_fov)); + self.event = Some(GuiEvent::CameraUpdate(camera.clone())); } } } diff --git a/src/node.rs b/src/node.rs index a8cc085..b243bfe 100644 --- a/src/node.rs +++ b/src/node.rs @@ -14,7 +14,7 @@ pub struct Node { //Model matricies pub model: Matrix4, pub inv_model: Matrix4, - + //If the node is active pub active: bool, } diff --git a/src/ray.rs b/src/ray.rs index 6d60594..a58b596 100644 --- a/src/ray.rs +++ b/src/ray.rs @@ -1,11 +1,7 @@ -use crate::{node::Node, scene::Scene, EPSILON}; +use crate::{bvh::BVH, node::Node, scene::Scene, state::RaytracingOption, EPSILON}; use nalgebra::{distance, Matrix4, Point3, Vector3}; use rand; -const MAX_DEPTH: u8 = 5; -const DIFFUSE_RAYS: i8 = 5; -const DIFFUSE_COEFFICIENT: f32 = 0.5; - fn random_vec() -> Vector3 { Vector3::new(rand::random(), rand::random(), rand::random()) } @@ -67,6 +63,7 @@ impl Ray { } } //This function will determine if the ray hits an object in the scene + //This is not optimised as it does not include bounding boxes pub fn hit_scene(&self, scene: &Scene) -> bool { for (_, node) in &scene.nodes { if !node.active { @@ -74,17 +71,14 @@ impl Ray { } // Transform ray into local model cordinates let ray = self.transform(&node.inv_model); - // Check bounding box intersection - if node.primitive.intersect_bounding_box(&ray) { - // Check primitive intersection - if node.primitive.intersect_ray(&ray).is_some() { - return true; - } + if node.primitive.intersect_ray(&ray).is_some() { + return true; } } false } //This function find the closest intersection point of a ray with an object in the scene + //Also not optimised, as it does not include bounding boxes pub fn closest_intersect<'a>(&'a self, scene: &'a Scene) -> Option<(&Node, Intersection)> { let mut closest_distance = f64::MAX; let mut closest_intersect: Option<(&Node, Intersection)> = None; @@ -94,38 +88,55 @@ impl Ray { } // Transform ray into local model cordinates let ray = self.transform(&node.inv_model); - // Check bounding box intersection - if node.primitive.intersect_bounding_box(&ray) { - // Check primitive intersection - if let Some(intersect) = node.primitive.intersect_ray(&ray) { - // Dont intersect with itself - if intersect.distance < EPSILON { - continue; - } - // Check for closest distance by converting to world coords - let intersect = intersect.transform(&node.model, &node.inv_model); - let distance = distance(&ray.a, &intersect.point); - if distance < closest_distance { - closest_distance = distance; - closest_intersect = Some((node, intersect)); - } + // Check primitive intersection + if let Some(intersect) = node.primitive.intersect_ray(&ray) { + // Dont intersect with itself + if intersect.distance < EPSILON { + continue; + } + // Check for closest distance by converting to world coords + let intersect = intersect.transform(&node.model, &node.inv_model); + let distance = distance(&ray.a, &intersect.point); + if distance < closest_distance { + closest_distance = distance; + closest_intersect = Some((node, intersect)); } } } closest_intersect } // This function takes a scene and returns the color of the point where the ray intersects the scene - pub fn shade_ray(&self, scene: &Scene, depth: u8) -> Option> { - if depth == MAX_DEPTH { + pub fn shade_ray( + &self, + scene: &Scene, + depth: u8, + options: &RaytracingOption, + sbvh: &Option, + ) -> Option> { + if depth == options.ray_depth { return None; } - match self.closest_intersect(scene) { - Some((node, intersect)) => { - Some(Ray::phong_shade_point( - &scene, &self, &node, &intersect, depth, - )) // If there is an intersection, shade it + match sbvh { + Some(bvh) => { + //Intersect the scene with the bvh + if let Some((node, intersect)) = bvh.traverse(&self, 0) { + return Some(Ray::phong_shade_point( + &scene, &self, &node, &intersect, depth, options, sbvh, + )); + } + return None; + } + None => { + //No BVH given so intersect normally + match self.closest_intersect(scene) { + Some((node, intersect)) => { + Some(Ray::phong_shade_point( + &scene, &self, &node, &intersect, depth, options, sbvh, + )) // If there is an intersection, shade it + } + None => None, // If there is no intersection, return None + } } - None => None, // If there is no intersection, return None } } @@ -136,9 +147,11 @@ impl Ray { node: &Node, intersect: &Intersection, depth: u8, + options: &RaytracingOption, + bvh: &Option, ) -> Vector3 { let normal = &intersect.normal; - let point = intersect.point; + let point = intersect.point + normal * 0.0001; let incidence = &ray.b; let material = &node.material; @@ -172,18 +185,18 @@ impl Ray { let mut reflect = Vector3::zeros(); let reflect_dir = incidence - 2.0 * incidence.dot(&normal) * normal; let reflect_ray = Ray::new(point, reflect_dir); - if let Some(col) = reflect_ray.shade_ray(scene, depth + 1) { + if let Some(col) = reflect_ray.shade_ray(scene, depth + 1, options, bvh) { reflect += col.component_mul(&material.kr) } //Diffuse component (Lambertian) let mut diffuse = Vector3::zeros(); diffuse += material.kd * n_dot_l; - for _ in 0..DIFFUSE_RAYS { + for _ in 0..options.diffuse_rays { let diffuse_dir = random_unit_vec(); let diffuse_ray = Ray::new(point.clone(), diffuse_dir + normal); - if let Some(col) = diffuse_ray.shade_ray(scene, depth + 1) { - diffuse += col * DIFFUSE_COEFFICIENT; + if let Some(col) = diffuse_ray.shade_ray(scene, depth + 1, options, bvh) { + diffuse += col * options.diffuse_coefficient; } } @@ -215,13 +228,11 @@ impl Ray { continue; } let ray = self.transform(&node.inv_model); - if node.primitive.intersect_bounding_box(&ray) { - if node.primitive.intersect_ray(&ray).is_some() { - return true; - } + if node.primitive.intersect_ray(&ray).is_some() { + return true; } } - false + return false; } //Cast a set of rays pub fn cast_rays( diff --git a/src/state.rs b/src/state.rs index 653025a..9768b36 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,5 +1,6 @@ //Use linear algebra module +use crate::bvh::BVH; use crate::camera::Camera; use crate::ray::Ray; use crate::{gui::Gui, scene::Scene}; @@ -21,16 +22,45 @@ use winit::window::{Window, WindowBuilder}; const START_WIDTH: i32 = 1200; const START_HEIGHT: i32 = 700; -const RAY_SAMPLES: i8 = 5; -const RAY_RANDOMNESS: f64 = 100.0; -const COLOUR_CLEAR: [u8; 4] = [0x22, 0x00, 0x11, 0x55]; -const PIXEL_CLEAR: [u8; 4] = [0x55, 0x00, 0x22, 0x55]; pub const INIT_FILE: &str = "rhai/scene.rhai"; pub const SAVE_FILE: &str = "img.png"; +#[derive(Clone)] +pub struct RaytracingOption { + pub ray_samples: u32, + pub ray_randomness: f64, + pub clear_color: [u8; 4], + pub pixel_clear: [u8; 4], + pub rays_per_pass: u32, + pub buffer_proportion: f32, + pub buffer_fov: f64, + pub ray_depth: u8, + pub diffuse_rays: u8, + pub diffuse_coefficient: f32, + pub bvh_active: bool, +} +impl RaytracingOption { + pub fn default() -> RaytracingOption { + RaytracingOption { + ray_samples: 10, + ray_randomness: 100.0, + clear_color: [0x22, 0x00, 0x11, 0x55], + pixel_clear: [0x55, 0x00, 0x22, 0x55], + rays_per_pass: 200, + buffer_proportion: 1.0, + buffer_fov: 110.0, + ray_depth: 5, + diffuse_rays: 5, + diffuse_coefficient: 0.5, + bvh_active: false, + } + } +} + pub struct State { scene: Scene, + bvh: Option, camera: Camera, window: Window, @@ -42,6 +72,7 @@ pub struct State { rays: Vec, ray_queue: Vec, + raytracing_options: RaytracingOption, } impl State { @@ -53,39 +84,46 @@ impl State { Self { scene, + bvh: None, camera, window, buffer_width: window_size.width as u32, buffer_height: window_size.height as u32, - pixels: pixels, + pixels, gui, rays, ray_queue: Vec::new(), + raytracing_options: RaytracingOption::default(), } } fn update(&mut self) -> Result<(), Box> { if let Some(event) = self.gui.event.take() { match event { - GuiEvent::BufferResize(proportion, fov) => { - self.resize_buffer(proportion, fov as f64)? + GuiEvent::RaytracerOption(options) => { + self.raytracing_options = options; + match self.raytracing_options.bvh_active { + true => self.bvh = Some(BVH::build(&mut self.scene.nodes)), + false => self.bvh = None, + } + self.resize_buffer()? } - GuiEvent::CameraUpdate(camera, fovy) => { + GuiEvent::CameraUpdate(camera) => { self.rays = Ray::cast_rays( &camera.eye, &camera.target, &camera.up, - fovy as f64, + self.raytracing_options.buffer_fov, self.buffer_width, self.buffer_height, ); self.camera = camera; - self.clear()?; + self.clear_buffer()?; self.reset_queue(); } GuiEvent::SceneLoad(scene) => { self.scene = scene; - self.clear()?; + self.clear_buffer()?; self.reset_queue(); } GuiEvent::SaveImage(filename) => { @@ -103,14 +141,16 @@ impl State { Ok(()) } - fn resize_buffer(&mut self, proportion: f32, fovy: f64) -> Result<(), Box> { + fn resize_buffer(&mut self) -> Result<(), Box> { // Calculate new buffer dimensions based on proportion let size = self.window.inner_size(); + let proportion = &self.raytracing_options.buffer_proportion; + let fovy = self.raytracing_options.buffer_fov; self.buffer_width = (size.width as f32 * proportion) as u32; self.buffer_height = (size.height as f32 * proportion) as u32; // Clear the buffer and reset the ray queue - self.clear()?; + self.clear_buffer()?; self.reset_queue(); // Recalculate rays with new buffer dimensions @@ -149,7 +189,10 @@ impl State { fn draw(&mut self) -> Result<(), Box> { //Draw ray_num in a block let frame = self.pixels.frame_mut(); - for _ in 0..self.gui.ray_num { + let randomness = &self.raytracing_options.ray_randomness; + let samples = &self.raytracing_options.ray_samples; + let samples_f32 = *samples as f32; + for _ in 0..self.raytracing_options.rays_per_pass { //Get random index from queue let index = match self.ray_queue.pop() { Some(index) => index, @@ -157,39 +200,45 @@ impl State { }; //Shade colour for selected ray let mut colour = Vector3::zeros(); - for _ in 0..RAY_SAMPLES { + for _ in 0..*samples { let ray = &self.rays[index]; let point = ray.a; let dir = ray.b; - let rx = (random::() - 0.5) / RAY_RANDOMNESS; - let ry = (random::() - 0.5) / RAY_RANDOMNESS; - let rz = (random::() - 0.5) / RAY_RANDOMNESS; + let rx = (random::() - 0.5) / randomness; + let ry = (random::() - 0.5) / randomness; + let rz = (random::() - 0.5) / randomness; let nx = dir.x + rx; let ny = dir.y + ry; let nz = dir.z + rz; let rand_ray = Ray::new(point, Vector3::new(nx, ny, nz)); - if let Some(ray_colour) = rand_ray.shade_ray(&self.scene, 0) { + if let Some(ray_colour) = + rand_ray.shade_ray(&self.scene, 0, &self.raytracing_options, &self.bvh) + { colour += ray_colour; }; } - colour = (colour / RAY_SAMPLES as f32) * 255.0; + colour = (colour / samples_f32) * 255.0; let rgba = [colour.x as u8, colour.y as u8, colour.z as u8, 0xff]; frame[index * 4..(index + 1) * 4].copy_from_slice(&rgba); } Ok(()) } - fn clear(&mut self) -> Result<(), Box> { + fn clear_buffer(&mut self) -> Result<(), Box> { let frame = self.pixels.frame_mut(); for pixel in frame.chunks_exact_mut(4) { - pixel.copy_from_slice(&COLOUR_CLEAR); + pixel.copy_from_slice(&self.raytracing_options.pixel_clear); } Ok(()) } fn reset_queue(&mut self) { + match self.raytracing_options.bvh_active { + true => self.bvh = Some(BVH::build(&mut self.scene.nodes)), + false => self.bvh = None, + } let size = self.buffer_height as usize * self.buffer_width as usize; let mut ray_queue: Vec = (0..size).collect(); ray_queue.shuffle(&mut thread_rng()); @@ -234,7 +283,7 @@ pub fn run() -> Result<(), Box> { let gui = Gui::new(&window, &pixels); let mut state = State::new(window, pixels, gui); - state.resize_buffer(1.0, 90.0)?; + state.resize_buffer()?; event_loop.run(move |event, _, control_flow| { state.gui.handle_event(&state.window, &event);