diff --git a/rhai/mesh.rhai b/rhai/mesh.rhai index 0fdc561..c1e80f0 100644 --- a/rhai/mesh.rhai +++ b/rhai/mesh.rhai @@ -10,18 +10,20 @@ scene.addCamera("Cam", camera); let falloff = V(0.0,0.0,0.0); let light = Light(P(6.0,6.0,6.0), V(0.4,0.4,0.4), falloff); -light.active(false); +light.active(true); scene.addLight("white", light); -let light = Light(P(2.0,0.0,0.0), V(0.0,1.0,0.0), V(0.1, 0.01, 0.001)); +let light = Light(P(2.0,0.0,0.0), V(1.0,1.0,0.0), falloff); light.active(false); scene.addLight("green", light); -let light = Light(P(-2.0,0.0,0.0), V(1.0,0.0,0.0), V(0.1, 0.01, 0.001)); +let light = Light(P(-2.0,0.0,0.0), V(1.0,0.0,0.0), falloff); light.active(false); scene.addLight("red", light); let light = Ambient(V(0.3,0.3,0.3)); +light.active(false); scene.addLight("ambient", light); + let tri = TriangleUnit(); let tri_node = Node(tri, material); tri_node.active(false); @@ -39,7 +41,7 @@ scene.addNode("cone", cone_node); let torus = Torus(0.5, 1.5); let torus_node = Node(torus, material); -torus_node.active(true); +torus_node.active(false); scene.addNode("torus", torus_node); @@ -59,7 +61,8 @@ scene.addNode("ground", ground_node); let mesh = Mesh("obj/cat.obj"); let mesh_node = Node(mesh, material); -mesh_node.active(false); +mesh_node.active(true); +mesh_node.rotate(0.0,90.0,90.0); scene.addNode("mesh", mesh_node); diff --git a/rhai/scene.rhai b/rhai/scene.rhai index e662416..11cdbac 100644 --- a/rhai/scene.rhai +++ b/rhai/scene.rhai @@ -16,46 +16,46 @@ scene.addCamera("-X Cam", camera); let material = Material(V(0.2,0.9,0.8), V(0.3, 0.8, 0.8), 10.0); scene.addMaterial("bluegreen", material); +let material2 = Material(V(0.2,0.9,0.8), V(0.3, 0.8, 0.8), 25.0); +scene.addMaterial("bluegreen2", 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.5,0.5), V(0.0, 0.00, 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)); +let light = Ambient(V(0.5,0.5,0.5)); 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,-10.0,0.0), 10.0 ); +let sphere_node = Node( sphere, material); +scene.addNode("sphere",sphere_node); //let mesh = Mesh("obj/cow.obj" ); //let mesh_node = Node(mesh); //scene.addNode("mesh", mesh_node); -for i in 0..6 { - let sphere = Sphere(P(0.0,0.0,0.0), 2.0 ); - 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); - } + // let child = sphere_node.child(sphere); // child.translate(V(1.0,1.0,1.0)); //scene.addNode(child); let cube = CubeUnit(); -let cube_node = Node( cube, material); +let cube_node = Node( cube, material2); +cube_node.rotate(0.1,0.1,45.0); +cube_node.translate(0.0,1.0,0.0); scene.addNode("cube", cube_node); //let gnonom = Gnonom(); -//let gnonom_node = Node(gnonom); +//let gnonom_node = Node(gnonom, material); //scene.addNode("gnonom", gnonom_node); //let cylinder = Cylinder(2.0,1.0 ); diff --git a/src/bvh.rs b/src/bvh.rs index 9a8a1e7..7a07170 100644 --- a/src/bvh.rs +++ b/src/bvh.rs @@ -260,9 +260,7 @@ impl BVH { 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 + bvh_node_aabb.join_mut(&node.aabb); //Join it with the BVH node's AABB } // unsafe { @@ -310,9 +308,7 @@ impl BVH { // 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); + // let centroid = node.aabb.get_centroid(); // //Get the candidate position // let candidate_pos = world_centroid[axis]; // let cost = self.evaluate_sah(&self.bvh_nodes[index], axis, candidate_pos); @@ -340,8 +336,7 @@ impl BVH { 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 + let centroid = node.aabb.get_centroid(); //Centroid of node we would like to sort if centroid[axis] < split_pos { i += 1; // On Left-Hand-Side } else { diff --git a/src/gui.rs b/src/gui.rs index a6ba376..e3c8ccf 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -210,7 +210,7 @@ impl Gui { "Rays Per Pass", RAYS_MIN, RAYS_MAX, - &mut self.raytracing_option.rays_per_pass, + &mut self.raytracing_option.pixels_per_pass, ); // Proportion of the window the buffer occupies ui.slider( diff --git a/src/node.rs b/src/node.rs index b243bfe..4f5ec80 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,12 +1,13 @@ -use crate::{material::Material, primitive::*}; +use crate::{bvh::AABB, material::Material, primitive::*}; use nalgebra::{Matrix4, Vector3}; -use std::rc::Rc; +use std::sync::Arc; #[derive(Clone)] pub struct Node { //Primitive - pub primitive: Rc, + pub primitive: Arc, pub material: Material, + pub aabb: AABB, //Transformations pub rotation: [f64; 3], pub scale: [f64; 3], @@ -20,10 +21,12 @@ pub struct Node { impl Node { //New node with no transformations - pub fn new(primitive: Rc, material: Material) -> Node { + pub fn new(primitive: Arc, material: Material) -> Node { + let aabb = primitive.get_aabb(); Node { primitive, material, + aabb, rotation: [0.0, 0.0, 0.0], scale: [1.0, 1.0, 1.0], translation: [0.0, 0.0, 0.0], @@ -34,7 +37,7 @@ impl Node { } } //New node with parent transformations - pub fn child(self, primitive: Rc) -> Node { + pub fn child(self, primitive: Arc) -> Node { let mut child = self.clone(); child.primitive = primitive; child @@ -93,5 +96,6 @@ impl Node { self.model = (translation_matrix * rotation_matrix * scale_matrix).cast(); // Compute the inverse model matrix by inverting the model matrix self.inv_model = self.model.try_inverse().unwrap(); + self.aabb.transform_mut(&self.model); } } diff --git a/src/primitive.rs b/src/primitive.rs index 324c13c..059945a 100644 --- a/src/primitive.rs +++ b/src/primitive.rs @@ -9,9 +9,9 @@ use nalgebra::{distance, Point3, Vector3}; use roots::{find_roots_quadratic, find_roots_quartic, Roots}; use std::fs::File; use std::io::{BufRead, BufReader}; -use std::rc::Rc; +use std::sync::Arc; // PRIMITIVE TRAIT ----------------------------------------------------------------- -pub trait Primitive { +pub trait Primitive: Send + Sync { fn intersect_ray(&self, ray: &Ray) -> Option; fn get_aabb(&self) -> AABB; } @@ -24,11 +24,11 @@ pub struct Sphere { } impl Sphere { - pub fn new(position: Point3, radius: f64) -> Rc { - Rc::new(Sphere { position, radius }) + pub fn new(position: Point3, radius: f64) -> Arc { + Arc::new(Sphere { position, radius }) } - pub fn unit() -> Rc { + pub fn unit() -> Arc { Sphere::new(Point3::new(0.0, 0.0, 0.0), 1.0) } } @@ -88,10 +88,10 @@ pub struct Circle { } impl Circle { - pub fn new(position: Point3, radius: f64, normal: Vector3) -> Rc { + pub fn new(position: Point3, radius: f64, normal: Vector3) -> Arc { let normal = normal.normalize(); let constant = normal.dot(&position.coords); - Rc::new(Circle { + Arc::new(Circle { position, radius, normal, @@ -99,7 +99,18 @@ impl Circle { }) } - pub fn unit() -> Rc { + pub fn new_unboxed(position: Point3, radius: f64, normal: Vector3) -> Circle { + let normal = normal.normalize(); + let constant = normal.dot(&position.coords); + Circle { + position, + radius, + normal, + constant, + } + } + + pub fn unit() -> Arc { let position = Point3::new(0.0, 0.0, 0.0); let normal = Vector3::new(0.0, 0.0, -1.0); let radius = 1.0; @@ -147,23 +158,23 @@ impl Primitive for Circle { pub struct Cylinder { radius: f64, height: f64, - base_circle: Rc, - top_circle: Rc, + base_circle: Circle, + top_circle: Circle, } impl Cylinder { - pub fn new(radius: f64, height: f64) -> Rc { - let base_circle = Circle::new( + pub fn new(radius: f64, height: f64) -> Arc { + let base_circle = Circle::new_unboxed( Point3::new(0.0, 0.0, 0.0), radius, Vector3::new(0.0, -1.0, 0.0), ); - let top_circle = Circle::new( + let top_circle = Circle::new_unboxed( Point3::new(0.0, height, 0.0), radius, Vector3::new(0.0, 1.0, 0.0), ); - Rc::new(Cylinder { + Arc::new(Cylinder { radius, height, base_circle, @@ -264,24 +275,24 @@ impl Primitive for Cylinder { pub struct Cone { height: f64, constant: f64, - circle: Rc, + circle: Circle, } impl Cone { - pub fn new(radius: f64, height: f64) -> Rc { - let circle = Circle::new( + pub fn new(radius: f64, height: f64) -> Arc { + let circle = Circle::new_unboxed( Point3::new(0.0, 0.0, 0.0), radius, Vector3::new(0.0, -1.0, 0.0), ); let constant = radius * radius / (height * height); - Rc::new(Cone { + Arc::new(Cone { height, constant, circle, }) } - pub fn unit() -> Rc { + pub fn unit() -> Arc { Cone::new(0.5, 1.0) } @@ -378,11 +389,11 @@ impl Primitive for Cone { // width_direction: Vector3, // width: f64, // height: f64, -// ) -> Rc { +// ) -> Arc { // let normal = normal.normalize(); // let width_direction = width_direction.normalize(); // let height_direction = width_direction.cross(&normal); -// Rc::new(Rectangle { +// Arc::new(Rectangle { // position, // normal: normal.normalize(), // width_direction: width_direction.normalize(), @@ -390,7 +401,7 @@ impl Primitive for Cone { // height, // }) // } -// pub fn unit() -> Rc { +// pub fn unit() -> Arc { // Rectangle::new( // Point3::new(0.0, 0.0, 0.0), // Vector3::new(0.0, 1.0, 0.0), @@ -450,11 +461,15 @@ pub struct Cube { } impl Cube { - pub fn new(bln: Point3, trf: Point3) -> Rc { - Rc::new(Cube { bln, trf }) + pub fn new(bln: Point3, trf: Point3) -> Arc { + Arc::new(Cube { bln, trf }) } - pub fn unit() -> Rc { + pub fn new_unboxed(bln: Point3, trf: Point3) -> Cube { + Cube { bln, trf } + } + + pub fn unit() -> Arc { let bln = Point3::new(-1.0, -1.0, -1.0); let trf = Point3::new(1.0, 1.0, 1.0); Cube::new(bln, trf) @@ -479,12 +494,12 @@ impl Primitive for Cube { let intersect = ray.at_t(tmin); // Check if the intersection is outside the box - if intersect.x < bln.x - || intersect.x > trf.x - || intersect.y < bln.y - || intersect.y > trf.y - || intersect.z < bln.z - || intersect.z > trf.z + if intersect.x < bln.x - EPSILON + || intersect.x > trf.x + EPSILON + || intersect.y < bln.y - EPSILON + || intersect.y > trf.y + EPSILON + || intersect.z < bln.z - EPSILON + || intersect.z > trf.z + EPSILON { return None; // Intersection is outside the box } @@ -507,7 +522,7 @@ impl Primitive for Cube { Some(Intersection { point: intersect, - normal: normal, + normal, distance: tmin, }) } else { @@ -533,14 +548,14 @@ pub struct Triangle { } impl Triangle { - pub fn new(u: Point3, v: Point3, w: Point3) -> Rc { + pub fn new(u: Point3, v: Point3, w: Point3) -> Arc { let uv = v - u; let uw = w - u; let normal = uw.cross(&uv).normalize(); - Rc::new(Triangle { u, v, w, normal }) + Arc::new(Triangle { u, v, w, normal }) } #[allow(dead_code)] - pub fn unit() -> Rc { + pub fn unit() -> Arc { let u = Point3::new(-1.0, -1.0, 0.0); let v = Point3::new(0.0, 1.0, 0.0); let w = Point3::new(1.0, -1.0, 0.0); @@ -606,10 +621,10 @@ pub struct Mesh { } impl Mesh { - pub fn new(triangles: Vec) -> Rc { + pub fn new(triangles: Vec) -> Arc { // Calculate the bounding box for the entire mesh based on the bounding boxes of individual triangles let bounding_box = Mesh::compute_bounding_box(&triangles); - Rc::new(Mesh { triangles }) + Arc::new(Mesh { triangles }) } fn compute_bounding_box(triangles: &Vec) -> AABB { @@ -626,7 +641,7 @@ impl Mesh { AABB::new(bln, trf) } - pub fn from_file(filename: &str) -> Rc { + pub fn from_file(filename: &str) -> Arc { let mut triangles: Vec = Vec::new(); let mut vertices: Vec> = Vec::new(); @@ -713,9 +728,9 @@ pub struct Torus { } impl Torus { - pub fn new(inner_rad: f64, outer_rad: f64) -> Rc { + pub fn new(inner_rad: f64, outer_rad: f64) -> Arc { // I need to find the bounding box for this shape - Rc::new(Torus { + Arc::new(Torus { inner_rad, outer_rad, }) @@ -834,28 +849,28 @@ impl Primitive for Torus { // GNOMON ----------------------------------------------------------------- #[derive(Clone)] pub struct Gnonom { - x_cube: Rc, - y_cube: Rc, - z_cube: Rc, + x_cube: Cube, + y_cube: Cube, + z_cube: Cube, } impl Gnonom { const GNONOM_WIDTH: f64 = 0.1; const GNONOM_LENGTH: f64 = 2.0; - pub fn new() -> Rc { - let x_cube = Cube::new( + pub fn new() -> Arc { + let x_cube = Cube::new_unboxed( Point3::new(0.0, -Self::GNONOM_WIDTH, -Self::GNONOM_WIDTH), Point3::new(Self::GNONOM_LENGTH, Self::GNONOM_WIDTH, Self::GNONOM_WIDTH), ); - let y_cube = Cube::new( + let y_cube = Cube::new_unboxed( Point3::new(-Self::GNONOM_WIDTH, 0.0, -Self::GNONOM_WIDTH), Point3::new(Self::GNONOM_WIDTH, Self::GNONOM_LENGTH, Self::GNONOM_WIDTH), ); - let z_cube = Cube::new( + let z_cube = Cube::new_unboxed( Point3::new(-Self::GNONOM_WIDTH, -Self::GNONOM_WIDTH, 0.0), Point3::new(Self::GNONOM_WIDTH, Self::GNONOM_WIDTH, Self::GNONOM_LENGTH), ); - Rc::new(Gnonom { + Arc::new(Gnonom { x_cube, y_cube, z_cube, @@ -901,9 +916,9 @@ impl Primitive for Gnonom { pub struct CrossCap {} impl CrossCap { - pub fn new() -> Rc { + pub fn new() -> Arc { // I need to find the bounding box for this shape - Rc::new(CrossCap {}) + Arc::new(CrossCap {}) } } @@ -992,9 +1007,9 @@ pub struct CrossCap2 { } impl CrossCap2 { - pub fn new(p: f64, q: f64) -> Rc { + pub fn new(p: f64, q: f64) -> Arc { // I need to find the bounding box for this shape - Rc::new(CrossCap2 { p, q }) + Arc::new(CrossCap2 { p, q }) } } @@ -1105,9 +1120,9 @@ impl Primitive for CrossCap2 { pub struct Steiner {} impl Steiner { - pub fn new() -> Rc { + pub fn new() -> Arc { // I need to find the bounding box for this shape - Rc::new(Steiner {}) + Arc::new(Steiner {}) } } @@ -1182,9 +1197,9 @@ impl Primitive for Steiner { pub struct Steiner2 {} impl Steiner2 { - pub fn new() -> Rc { + pub fn new() -> Arc { // I need to find the bounding box for this shape - Rc::new(Steiner2 {}) + Arc::new(Steiner2 {}) } } @@ -1272,9 +1287,9 @@ pub struct Roman { } impl Roman { - pub fn new(k: f64) -> Rc { + pub fn new(k: f64) -> Arc { // I need to find the bounding box for this shape - Rc::new(Roman { k }) + Arc::new(Roman { k }) } } diff --git a/src/ray.rs b/src/ray.rs index a58b596..2adcaab 100644 --- a/src/ray.rs +++ b/src/ray.rs @@ -86,20 +86,23 @@ impl Ray { if !node.active { continue; } - // Transform ray into local model cordinates - let ray = self.transform(&node.inv_model); - // 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)); + + if node.aabb.intersect_ray(&self) { + // Transform ray into local model cordinates + let ray = self.transform(&node.inv_model); + // 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)); + } } } } @@ -113,10 +116,12 @@ impl Ray { options: &RaytracingOption, sbvh: &Option, ) -> Option> { + //If we have exceeded depth then return if depth == options.ray_depth { return None; } match sbvh { + //We have a bvh so use bvh traversal Some(bvh) => { //Intersect the scene with the bvh if let Some((node, intersect)) = bvh.traverse(&self, 0) { @@ -126,6 +131,7 @@ impl Ray { } return None; } + //We dont have a bvh so use generic algorithm None => { //No BVH given so intersect normally match self.closest_intersect(scene) { @@ -175,7 +181,7 @@ impl Ray { //Niave Shadows let to_light_ray = Ray::new(point, to_light); - if to_light_ray.light_blocked(scene, node) { + if to_light_ray.light_blocked(scene, node, bvh) { continue; } @@ -202,37 +208,55 @@ impl Ray { //Specular component let mut specular = Vector3::zeros(); - if n_dot_l < 0.0 { + if n_dot_l > 0.0 { let h = (to_light - incidence).normalize(); let n_dot_h = normal.dot(&h).max(0.0) as f32; specular = material.ks * n_dot_h.powf(material.shininess); } //Falloff - // let falloff = 1.0 - // / (1.0 - // + light.falloff[0] - // + light.falloff[1] * light_distance - // + light.falloff[2] * light_distance * light_distance); + let falloff = 1.0 + / ((1.0 + light.falloff[0]) + + light.falloff[1] * light_distance + + light.falloff[2] * light_distance * light_distance); - let intensity = light.colour.component_mul(&(diffuse + reflect + specular)); + let intensity = light.colour.component_mul(&(diffuse + reflect + specular)) * falloff; colour += &intensity; } colour } - pub fn light_blocked(&self, scene: &Scene, _node: &Node) -> bool { - for (_, node) in &scene.nodes { - if !node.active { - continue; + pub fn light_blocked(&self, scene: &Scene, _node: &Node, bvh: &Option) -> bool { + match bvh { + Some(bvh) => { + //We have a bvh so use bvh traversal + for (_, node) in &scene.nodes { + if !node.active { + continue; + } + match bvh.traverse(&self, 0) { + Some(_) => return true, + None => continue, + } + } + return false; } - let ray = self.transform(&node.inv_model); - if node.primitive.intersect_ray(&ray).is_some() { - return true; + None => { + for (_, node) in &scene.nodes { + if !node.active { + continue; + } + if node.aabb.intersect_ray(self) { + let ray = self.transform(&node.inv_model); + if node.primitive.intersect_ray(&ray).is_some() { + return true; + } + } + } + return false; } } - return false; } //Cast a set of rays pub fn cast_rays( diff --git a/src/scene.rs b/src/scene.rs index fadff89..af35fc2 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -1,5 +1,23 @@ use crate::{camera::Camera, light::Light, material::*, node::*}; use std::collections::HashMap; +use std::rc::Rc; + +// pub struct MultiThreadScene { +// pub nodes: Rc>, +// pub materials: Rc>, +// pub lights: Rc>, +// pub cameras: Rc>, +// } +// impl MultiThreadScene { +// pub fn from_scene(scene: &Scene) -> MultiThreadScene { +// MultiThreadScene { +// nodes: Rc::new(scene.nodes.clone()), +// materials: Rc::new(scene.materials.clone()), +// lights: Rc::new(scene.lights.clone()), +// cameras: Rc::new(scene.cameras.clone()), +// } +// } +// } #[derive(Clone)] pub struct Scene { diff --git a/src/state.rs b/src/state.rs index 9768b36..4feb55d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -6,12 +6,15 @@ use crate::ray::Ray; use crate::{gui::Gui, scene::Scene}; use crate::{gui::GuiEvent, log_error}; use std::path::Path; +use std::thread; use nalgebra::Vector3; use rand::seq::SliceRandom; use rand::{random, thread_rng}; use std::error::Error; +use std::sync::Arc; +use std::sync::Mutex; use anyhow::Result; use pixels::{Pixels, SurfaceTexture}; @@ -28,11 +31,12 @@ pub const SAVE_FILE: &str = "img.png"; #[derive(Clone)] pub struct RaytracingOption { + pub threads: u32, pub ray_samples: u32, pub ray_randomness: f64, pub clear_color: [u8; 4], pub pixel_clear: [u8; 4], - pub rays_per_pass: u32, + pub pixels_per_pass: u32, pub buffer_proportion: f32, pub buffer_fov: f64, pub ray_depth: u8, @@ -43,11 +47,12 @@ pub struct RaytracingOption { impl RaytracingOption { pub fn default() -> RaytracingOption { RaytracingOption { + threads: 12, ray_samples: 10, ray_randomness: 100.0, clear_color: [0x22, 0x00, 0x11, 0x55], pixel_clear: [0x55, 0x00, 0x22, 0x55], - rays_per_pass: 200, + pixels_per_pass: 200, buffer_proportion: 1.0, buffer_fov: 110.0, ray_depth: 5, @@ -59,32 +64,33 @@ impl RaytracingOption { } pub struct State { - scene: Scene, - bvh: Option, + scene: Arc, + bvh: Arc>, camera: Camera, window: Window, buffer_width: u32, buffer_height: u32, - pixels: Pixels, + pixels: Arc>, gui: Gui, - rays: Vec, - ray_queue: Vec, - raytracing_options: RaytracingOption, + rays: Arc>, + ray_queue: Arc>>, + raytracing_options: Arc, } impl State { pub fn new(window: Window, pixels: Pixels, gui: Gui) -> Self { - let scene = Scene::empty(); + let scene = Arc::new(Scene::empty()); let window_size = window.inner_size(); + let pixels = Arc::new(Mutex::new(pixels)); let camera = Camera::unit(); - let rays = Vec::new(); + let rays = Arc::new(Vec::new()); Self { scene, - bvh: None, + bvh: Arc::new(None), camera, window, buffer_width: window_size.width as u32, @@ -92,8 +98,8 @@ impl State { pixels, gui, rays, - ray_queue: Vec::new(), - raytracing_options: RaytracingOption::default(), + ray_queue: Arc::new(Mutex::new(Vec::new())), + raytracing_options: Arc::new(RaytracingOption::default()), } } @@ -101,33 +107,34 @@ impl State { if let Some(event) = self.gui.event.take() { match event { GuiEvent::RaytracerOption(options) => { - self.raytracing_options = options; + self.raytracing_options = Arc::new(options); match self.raytracing_options.bvh_active { - true => self.bvh = Some(BVH::build(&mut self.scene.nodes)), - false => self.bvh = None, + true => self.bvh = Arc::new(Some(BVH::build(&self.scene.nodes))), + false => self.bvh = Arc::new(None), } self.resize_buffer()? } GuiEvent::CameraUpdate(camera) => { - self.rays = Ray::cast_rays( + self.rays = Arc::new(Ray::cast_rays( &camera.eye, &camera.target, &camera.up, self.raytracing_options.buffer_fov, self.buffer_width, self.buffer_height, - ); + )); self.camera = camera; self.clear_buffer()?; self.reset_queue(); } GuiEvent::SceneLoad(scene) => { - self.scene = scene; + self.scene = Arc::new(scene); self.clear_buffer()?; self.reset_queue(); } GuiEvent::SaveImage(filename) => { - let frame = self.pixels.frame(); + let pixels = &self.pixels.as_ref().lock().unwrap(); + let frame = pixels.frame(); image::save_buffer( Path::new(&filename), frame, @@ -154,17 +161,17 @@ impl State { self.reset_queue(); // Recalculate rays with new buffer dimensions - self.rays = Ray::cast_rays( + self.rays = Arc::new(Ray::cast_rays( &self.camera.eye, &self.camera.target, &self.camera.up, fovy, self.buffer_width, self.buffer_height, - ); + )); // Resize buffer and surface - let pixels = &mut self.pixels; + let pixels = &mut self.pixels.as_ref().lock().unwrap(); pixels.resize_surface(size.width, size.height)?; pixels.resize_buffer(self.buffer_width, self.buffer_height)?; @@ -172,7 +179,8 @@ impl State { } fn resize(&mut self, size: &PhysicalSize) -> Result<(), Box> { - self.pixels.resize_surface(size.width, size.height)?; + let pixels = &mut self.pixels.as_ref().lock().unwrap(); + pixels.resize_surface(size.width, size.height)?; Ok(()) } @@ -188,46 +196,67 @@ impl State { fn draw(&mut self) -> Result<(), Box> { //Draw ray_num in a block - let frame = self.pixels.frame_mut(); - 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, - None => break, - }; - //Shade colour for selected ray - let mut colour = Vector3::zeros(); - for _ in 0..*samples { - let ray = &self.rays[index]; - let point = ray.a; - let dir = ray.b; - 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 randomness = self.raytracing_options.ray_randomness; + let samples = self.raytracing_options.ray_samples; + let samples_f32 = samples as f32; + let mut handles = vec![]; - let rand_ray = Ray::new(point, Vector3::new(nx, ny, nz)); - - if let Some(ray_colour) = - rand_ray.shade_ray(&self.scene, 0, &self.raytracing_options, &self.bvh) - { - colour += ray_colour; + for _ in 0..self.raytracing_options.pixels_per_pass { + for _ in 0..self.raytracing_options.threads { + //Get random index from queue + let queue = &mut self.ray_queue.clone(); + let index = match queue.lock().unwrap().pop() { + Some(index) => index, + None => break, }; + //Create a nre thread for this pixel + let handle = thread::spawn({ + let rays = self.rays.clone(); + let scene = self.scene.clone(); + let options = self.raytracing_options.clone(); + let bvh = self.bvh.clone(); + let rays = rays.clone(); + let pixels_mutex = self.pixels.clone(); + move || { + //Shade colour for selected ray + let mut colour: Vector3 = Vector3::zeros(); + for _ in 0..samples { + let ray = &rays[index]; + let point = ray.a; + let dir = ray.b; + 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(&scene, 0, &options, &bvh) + { + colour += ray_colour; + } + } + colour = (colour / samples_f32) * 255.0; + let rgba = [colour.x as u8, colour.y as u8, colour.z as u8, 0xff]; + let pixels = &mut pixels_mutex.lock().unwrap(); + let frame = pixels.frame_mut(); + frame[index * 4..(index + 1) * 4].copy_from_slice(&rgba); + } + }); + handles.push(handle); + } + for handle in handles.drain(..) { + handle.join().unwrap(); } - 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_buffer(&mut self) -> Result<(), Box> { - let frame = self.pixels.frame_mut(); + let pixels = &mut self.pixels.as_ref().lock().unwrap(); + let frame = pixels.frame_mut(); for pixel in frame.chunks_exact_mut(4) { pixel.copy_from_slice(&self.raytracing_options.pixel_clear); } @@ -236,33 +265,32 @@ impl State { 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, + true => self.bvh = Arc::new(Some(BVH::build(&self.scene.nodes))), + false => self.bvh = Arc::new(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()); - self.ray_queue = ray_queue; + self.ray_queue = Arc::new(Mutex::new(ray_queue)); } fn render(&mut self) -> Result<(), Box> { // Update state self.update()?; // Draw rays if we have remaining rays in queue - if !self.ray_queue.is_empty() { - match self.draw() { - Err(e) => { - println!("ERROR: {}", e); - } - _ => {} + match self.draw() { + Err(e) => { + println!("ERROR: {}", e); } + _ => {} } // Render Gui self.gui .prepare(&self.window) .expect("gui.prepare() failed"); // Try to render pixels - if let Err(e) = self.pixels.render_with(|encoder, render_target, context| { + let pixels = &mut self.pixels.as_ref().lock().unwrap(); + if let Err(e) = pixels.render_with(|encoder, render_target, context| { context.scaling_renderer.render(encoder, render_target); // Render pixels self.gui .render(&self.window, encoder, render_target, context)?;