propper multithreading

This commit is contained in:
STP
2023-12-03 22:12:46 -05:00
parent d8488f24f7
commit 0eff7fc694
10 changed files with 391 additions and 201 deletions

View File

@@ -3,41 +3,43 @@ let scene = Scene();
let distance = 10.0; let distance = 10.0;
let camera = Camera( P(0.0,0.0,distance), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); let camera = Camera( P(0.0,0.0,distance), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("+Z Cam", camera); scene.addCamera("+Z Cam", camera);
let camera = Camera( P(0.0,distance,0.1), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); // let camera = Camera( P(0.0,distance,0.1), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("+Y Cam", camera); // 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)); // 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); // scene.addCamera("+X Cam", camera);
let camera = Camera( P(0.0,0.0,-distance), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); // let camera = Camera( P(0.0,0.0,-distance), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("-Z Cam", camera); // scene.addCamera("-Z Cam", camera);
let camera = Camera( P(0.0,-distance,0.1), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); // let camera = Camera( P(0.0,-distance,0.1), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("-Y Cam", camera); // 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)); // 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); // scene.addCamera("-X Cam", camera);
let material = Material(V(0.2,0.9,0.8), V(0.3, 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); scene.addMaterial("mat1", material);
let material2 = Material(V(0.2,0.9,0.8), V(0.3, 0.8, 0.8), 25.0); let material2 = Material(V(0.2,0.9,0.8), V(0.3, 0.8, 0.8), 25.0);
scene.addMaterial("bluegreen2", material); scene.addMaterial("mat2", 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)); 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); light.active(true);
scene.addLight("blue", light); 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)); // 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); // light.active(false);
scene.addLight("green", light); // 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)); // 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); // light.active(false);
// scene.addLight("red", light);
let light = Ambient(V(0.5,0.5,0.5)); // let light = Ambient(V(0.5,0.5,0.5));
scene.addLight("ambient", light); // light.active(false);
// scene.addLight("ambient", light);
let sphere = Sphere(P(0.0,-10.0,0.0), 10.0 ); //let sphere = Sphere(P(0.0,-10.0,0.0), 10.0 );
let sphere_node = Node( sphere, material); //let sphere_node = Node( sphere, material);
scene.addNode("sphere",sphere_node); //scene.addNode("sphere",sphere_node);
//let mesh = Mesh("obj/cow.obj" ); //let mesh = Mesh("obj/cow.obj" );
//let mesh_node = Node(mesh); //let mesh_node = Node(mesh);
@@ -48,11 +50,11 @@ scene.addNode("sphere",sphere_node);
// child.translate(V(1.0,1.0,1.0)); // child.translate(V(1.0,1.0,1.0));
//scene.addNode(child); //scene.addNode(child);
let cube = CubeUnit(); let sphere2= SphereUnit();
let cube_node = Node( cube, material2); let sphere2_node = Node( sphere2, material2);
cube_node.rotate(0.1,0.1,45.0); // sphere2_node.rotate(0.1,0.1,0.0);
cube_node.translate(0.0,1.0,0.0); // sphere2_node.translate(0.0,1.0,0.0);
scene.addNode("cube", cube_node); scene.addNode("sphere", sphere2_node);
//let gnonom = Gnonom(); //let gnonom = Gnonom();
//let gnonom_node = Node(gnonom, material); //let gnonom_node = Node(gnonom, material);

32
rhai/space.rhai Normal file
View File

@@ -0,0 +1,32 @@
let scene = Scene();
let falloff = V(0.0,0.0,0.0);
//CAMERAS
let camera = Camera( P(100.0,100.0,100.0), P(500.0,500.0,500.0), V(0.0,1.0,0.0));
scene.addCamera("Main Camera", camera);
//Light for the sun
let light = Light(P(800.0, 800.0, 250.0), V(1.0, 1.0, 0.929), falloff);
scene.addLight("Sun", light);
//Ball for the sun
let material = Material(V(1.0, 1.0, 0.9), V(0.9, 0.9, 0.9), 10.0);
scene.addMaterial("material_sun", material);
let sphere = Sphere(P(800.0, 800.0, 200.0), 50.0);
let sphere_node = Node(sphere, material);
scene.addNode("sphere", sphere_node);
//Ball for the planet
let material = Material(V(0.2,0.8,0.2), V(0.2, 0.8, 0.8), 10.0);
scene.addMaterial("material_planet", material);
let sphere = Sphere(P(500.0, 500.0, 500.0), 50.0);
let sphere_node = Node(sphere, material);
scene.addNode("sphere", sphere_node);
// let sphere = Steiner( material);
// let sphere_node = Node(sphere);
// scene.addNode("sphere", sphere_node);
scene

View File

@@ -1,5 +1,5 @@
use crate::{node::Node, ray::*, EPSILON}; use crate::{node::Node, ray::*, EPSILON};
use nalgebra::{distance, point, Matrix4, Point3, Vector3}; use nalgebra::{distance, Matrix4, Point3, Vector3};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
@@ -386,24 +386,21 @@ impl BVH {
// Traverse the BVH, 0 will be needed to start at root node // Traverse the BVH, 0 will be needed to start at root node
pub fn traverse(&self, ray: &Ray, idx: usize) -> Option<(&Node, Intersection)> { pub fn traverse(&self, ray: &Ray, idx: usize) -> Option<(&Node, Intersection)> {
let bvh_node = &self.bvh_nodes[idx]; let bvh_node = &self.bvh_nodes[idx];
if !bvh_node.aabb.intersect_ray(ray) { if !bvh_node.aabb.intersect_ray(&ray) {
// No intersection with BVH in world coordinates // No intersection with BVH in world coordinates
return None; return None;
} }
if bvh_node.prim_count > 0 { if bvh_node.prim_count != 0 {
// Leaf node intersection // Leaf node intersection
let node_idx = bvh_node.first_prim; let node_idx = bvh_node.first_prim;
let node = &self.nodes[node_idx]; let node = &self.nodes[node_idx];
if !node.active { if !node.active {
return None; return None;
} }
let ray = ray.transform(&node.inv_model); //Transform ray to model coords if let Some(intersect) = node.intersect_ray(&ray) {
if let Some(intersect) = node.primitive.intersect_ray(&ray) {
if intersect.distance < EPSILON { if intersect.distance < EPSILON {
return None; return None;
} else { } else {
// Convert intersect back to world coords
let intersect = intersect.transform(&node.model, &node.inv_model);
return Some((node, intersect)); return Some((node, intersect));
} }
} }
@@ -438,7 +435,7 @@ impl BVH {
let mut l_count = 0; let mut l_count = 0;
let mut r_count = 0; let mut r_count = 0;
for i in 0..node.prim_count { for i in 0..node.prim_count {
let aabb = self.nodes[node.first_prim + i].primitive.get_aabb(); let aabb = self.nodes[node.first_prim + i].get_world_aabb();
if aabb.trf[axis] < pos { if aabb.trf[axis] < pos {
l_count += 1; l_count += 1;
l_aabb.grow_mut(&aabb.trf); l_aabb.grow_mut(&aabb.trf);

View File

@@ -18,18 +18,20 @@ const BUFFER_PROPORTION_MIN: f32 = 0.1;
const BUFFER_PROPORTION_MAX: f32 = 1.0; const BUFFER_PROPORTION_MAX: f32 = 1.0;
//RAY CONSTANTS //RAY CONSTANTS
const MIN_THREADS: u32 = 1;
const MAX_THREADS: u32 = 12;
const RAYS_MIN: u32 = 100; const RAYS_MIN: u32 = 100;
const RAYS_MAX: u32 = 10000; const RAYS_MAX: u32 = 10000;
const MIN_DEPTH: u8 = 5; const MIN_DEPTH: u8 = 1;
const MAX_DEPTH: u8 = 100; const MAX_DEPTH: u8 = 10;
const MIN_SAMPLES: u32 = 5; const MIN_SAMPLES: u32 = 1;
const MAX_SAMPLES: u32 = 100; const MAX_SAMPLES: u32 = 10;
const MIN_RANDOM: f64 = 100.0; const MIN_RANDOM: f64 = 100.0;
const MAX_RANDOM: f64 = 1000.0; const MAX_RANDOM: f64 = 1000.0;
//DIFFUSE CONSTANTS //DIFFUSE CONSTANTS
const MIN_DIFFUSE_RAYS: u8 = 5; const MIN_DIFFUSE_RAYS: u8 = 1;
const MAX_DIFFUSE_RAYS: u8 = 100; const MAX_DIFFUSE_RAYS: u8 = 10;
const MIN_DIFFUSE_COEFFICIENT: f32 = 0.0; const MIN_DIFFUSE_COEFFICIENT: f32 = 0.0;
const MAX_DIFFUSE_COEFFICIENT: f32 = 1.0; const MAX_DIFFUSE_COEFFICIENT: f32 = 1.0;
@@ -205,12 +207,18 @@ impl Gui {
//Raytracing options ------------------------------------------- //Raytracing options -------------------------------------------
if CollapsingHeader::new("Raytracer").build(ui) { if CollapsingHeader::new("Raytracer").build(ui) {
ui.slider(
"Threads",
MIN_THREADS,
MAX_THREADS,
&mut self.raytracing_option.threads,
);
// Numbers of rays to render per pass // Numbers of rays to render per pass
ui.slider( ui.slider(
"Rays Per Pass", "Rays Per Pass",
RAYS_MIN, RAYS_MIN,
RAYS_MAX, RAYS_MAX,
&mut self.raytracing_option.pixels_per_pass, &mut self.raytracing_option.pixels_per_thread,
); );
// Proportion of the window the buffer occupies // Proportion of the window the buffer occupies
ui.slider( ui.slider(
@@ -521,9 +529,9 @@ pub fn init_engine() -> Engine {
engine engine
.register_type::<Mesh>() .register_type::<Mesh>()
.register_fn("Mesh", Mesh::from_file); .register_fn("Mesh", Mesh::from_file);
// engine engine
// .register_type::<Rectangle>() .register_type::<RectangleXY>()
// .register_fn("Rectange", Rectangle::new) .register_fn("Rectange", RectangleXY::new)
// .register_fn("RectangleUnit", Rectangle::unit); .register_fn("RectangleUnit", RectangleXY::unit);
engine engine
} }

View File

@@ -1,12 +1,23 @@
use crate::state::run; use crate::state::run;
use bvh::BVH;
use camera::Camera;
use error_iter::ErrorIter; use error_iter::ErrorIter;
const EPSILON: f64 = 1e-8; const EPSILON: f64 = 1e-8;
const INFINITY: f64 = 1e10; const INFINITY: f64 = 1e10;
use gui::{init_engine, Gui};
use log::error; use log::error;
use nalgebra::Vector3;
use rand::random;
use ray::Ray;
use scene::Scene;
use state::RaytracingOption;
use std::env; use std::env;
use std::error::Error; use std::error::Error;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;
mod bvh; mod bvh;
mod camera; mod camera;
@@ -21,12 +32,144 @@ mod state;
fn main() { fn main() {
env_logger::init(); env_logger::init();
env::set_var("RUST_BACKTRACE", "1"); env::set_var("RUST_BACKTRACE", "1");
let args: Vec<String> = env::args().collect();
if args.len() == 6 {
let width: usize = args[1].parse().unwrap();
let height: usize = args[2].parse().unwrap();
let fovy = args[3].parse::<f64>().unwrap();
let filename = &args[4];
let savefile = &args[5];
headless(
width,
height,
fovy,
filename.to_string(),
savefile.to_string(),
);
} else {
if let Err(e) = run() { if let Err(e) = run() {
println!("Error at runtime: {}", e); println!("Error at runtime: {}", e);
}; };
} }
}
fn headless(width: usize, height: usize, fovy: f64, filename: String, savefile: String) {
let options = Arc::new(RaytracingOption {
threads: 12,
ray_samples: 1,
ray_randomness: 100.0,
clear_color: [0x22, 0x00, 0x11, 0x55],
pixel_clear: [0x55, 0x00, 0x22, 0x55],
pixels_per_thread: 200,
buffer_proportion: 1.0,
buffer_fov: 110.0,
ray_depth: 5,
diffuse_rays: 3,
diffuse_coefficient: 0.8,
bvh_active: false,
});
//Read script from file
let script = match std::fs::read_to_string(&filename) {
Ok(in_script) => in_script,
Err(e) => {
println!("{}", e);
return;
}
};
//Evaluate scene in file
let engine = init_engine();
let scene: Arc<Scene> = match engine.eval(&script) {
Ok(in_scene) => Arc::new(in_scene),
Err(e) => {
println!("{e}");
return;
}
};
//Set the camera
let mut camera = Camera::unit();
for (_, in_camera) in &scene.cameras {
camera = in_camera.clone();
}
//Cast the rays
let rays = Arc::new(Ray::cast_rays(
&camera.eye,
&camera.target,
&camera.up,
fovy,
width as u32,
height as u32,
));
//Enable bounding volume heirarchy
let bvh;
match options.bvh_active {
true => bvh = Arc::new(Some(BVH::build(&scene.nodes))),
false => bvh = Arc::new(None),
}
//Create our frame and indexer
let size = width * height;
let frame_mutex = Arc::new(Mutex::new(vec![0; size * 4]));
//Multithreading
let mut handles = vec![];
for index in 0..size {
for _ in 0..options.threads {
//Get random index from queue
//Create a nre thread for this pixel
let handle = thread::spawn({
let rays = rays.clone();
let scene = scene.clone();
let options = options.clone();
let bvh = bvh.clone();
let rays = rays.clone();
let frame_mutex = frame_mutex.clone();
move || {
//Shade colour for selected ray
let mut colour: Vector3<f32> = Vector3::zeros();
//Get the ray we want to make
let shot_ray = &rays[index];
//Send out ray_samples rays
for _ in 0..options.ray_samples {
let point = shot_ray.a;
let dir = shot_ray.b;
//Generate a random ray
let rx = (random::<f64>() - 0.5) / options.ray_randomness;
let ry = (random::<f64>() - 0.5) / options.ray_randomness;
let rz = (random::<f64>() - 0.5) / options.ray_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 / options.ray_samples as f32) * 255.0;
let rgba = [colour.x as u8, colour.y as u8, colour.z as u8, 0xff];
{
let frame = &mut frame_mutex.lock().unwrap();
frame[index * 4..(index + 1) * 4].copy_from_slice(&rgba);
}
}
});
handles.push(handle);
}
for handle in handles.drain(..) {
handle.join().unwrap();
}
}
use std::path::Path;
image::save_buffer(
Path::new(&savefile),
&frame_mutex.lock().unwrap(),
width as u32,
height as u32,
image::ColorType::Rgba8,
)
.unwrap();
}
fn log_error<E: Error + 'static>(method_name: &str, err: E) { fn log_error<E: Error + 'static>(method_name: &str, err: E) {
error!("{method_name}() failed: {err}"); error!("{method_name}() failed: {err}");

View File

@@ -1,4 +1,9 @@
use crate::{bvh::AABB, material::Material, primitive::*}; use crate::{
bvh::AABB,
material::Material,
primitive::{self, *},
ray::{Intersection, Ray},
};
use nalgebra::{Matrix4, Vector3}; use nalgebra::{Matrix4, Vector3};
use std::sync::Arc; use std::sync::Arc;
@@ -98,4 +103,17 @@ impl Node {
self.inv_model = self.model.try_inverse().unwrap(); self.inv_model = self.model.try_inverse().unwrap();
self.aabb.transform_mut(&self.model); self.aabb.transform_mut(&self.model);
} }
// Intersection of a ray, will convert to model coords and check
pub fn intersect_ray(&self, ray: &Ray) -> Option<Intersection> {
let ray = ray.transform(&self.inv_model); //Transform from world coordinates
if let Some(mut intersect) = self.primitive.intersect_ray(&ray) {
intersect.transform_mut(&self.model, &self.inv_model); //Transform to world coords
return Some(intersect);
}
return None;
}
//Gets the bounding box in world coords
pub fn get_world_aabb(&self) -> AABB {
return self.aabb.clone();
}
} }

View File

@@ -373,85 +373,51 @@ impl Primitive for Cone {
} }
// RECTANGLE ----------------------------------------------------------------- // RECTANGLE -----------------------------------------------------------------
// #[derive(Clone)] // Normal is (0.0, 0.0, 1.0) always facing towards camera at positive z axis
// pub struct Rectangle { #[derive(Clone)]
// position: Point3<f64>, pub struct RectangleXY {
// normal: Vector3<f64>, bl: Point3<f64>,
// width_direction: Vector3<f64>, tr: Point3<f64>,
// width: f64, }
// height: f64,
// }
// impl Rectangle { impl RectangleXY {
// pub fn new( pub fn new(bl: Point3<f64>, tr: Point3<f64>) -> Arc<dyn Primitive> {
// position: Point3<f64>, Arc::new(RectangleXY { bl, tr })
// normal: Vector3<f64>, }
// width_direction: Vector3<f64>, pub fn unit() -> Arc<dyn Primitive> {
// width: f64, RectangleXY::new(Point3::new(-1.0, -1.0, 0.0), Point3::new(1.0, 1.0, 0.0))
// height: f64, }
// ) -> Arc<dyn Primitive> { }
// let normal = normal.normalize();
// let width_direction = width_direction.normalize();
// let height_direction = width_direction.cross(&normal);
// Arc::new(Rectangle {
// position,
// normal: normal.normalize(),
// width_direction: width_direction.normalize(),
// width,
// height,
// })
// }
// pub fn unit() -> Arc<dyn Primitive> {
// Rectangle::new(
// Point3::new(0.0, 0.0, 0.0),
// Vector3::new(0.0, 1.0, 0.0),
// Vector3::new(1.0, 0.0, 0.0),
// 2.0,
// 2.0,
// )
// }
// }
// impl Primitive for Rectangle { impl Primitive for RectangleXY {
// fn intersect_ray(&self, ray: &Ray) -> Option<Intersection> { fn intersect_ray(&self, ray: &Ray) -> Option<Intersection> {
// let constant = self.position.coords.dot(&self.normal); let z = self.bl.z;
// let denominator = ray.b.dot(&self.normal); let az = ray.a.z;
// let t = (constant - ray.a.coords.dot(&self.normal)) / denominator; let bz = ray.b.z;
let t = (z - az) / bz;
if t > INFINITY {
return None;
}
let intersect = ray.at_t(t);
let (ix, iy) = (intersect.x, intersect.y);
// if t > INFINITY { if (ix < self.bl.x) || (ix > self.tr.x) || (iy < self.bl.y) || (iy > self.tr.y) {
// return None; return None;
// } }
// let intersect = ray.at_t(t); Some(Intersection {
// let height_direction = self.width_direction.cross(&self.normal); point: intersect,
// let (w2, h2) = (self.width / 2.0, self.height / 2.0); normal: Vector3::new(0.0, 0.0, 1.0),
// let r1 = w2 * self.width_direction; distance: t,
// let r2 = h2 * height_direction; })
// let pi = intersect - self.position; }
// let pi_dot_r1 = pi.dot(&r1);
// let pi_dot_r2 = pi.dot(&r2);
// if pi_dot_r1 >= -w2 && pi_dot_r1 <= w2 && pi_dot_r2 >= -h2 && pi_dot_r2 <= h2 { fn get_aabb(&self) -> AABB {
// return Some(Intersection { let bl = self.bl + Vector3::new(0.0, 0.0, -0.1);
// point: intersect, let tr = self.tr + Vector3::new(0.0, 0.0, 0.1);
// normal: self.normal, AABB::new(bl, tr)
// distance: t, }
// }); }
// }
// None
// }
// fn get_bounding_box(&self) -> AABB {
// let position = self.position;
// let width = self.width;
// let height = self.height;
// let width_direction = self.width_direction;
// let bln = position - width / 2.0 * width_direction - height / 2.0 * height_direction;
// let trf = position + width / 2.0 * width_direction + height / 2.0 * height_direction;
// AABB::new(bln, trf);
// todo!()
// }
// }
// Cube ----------------------------------------------------------------- // Cube -----------------------------------------------------------------
#[derive(Clone)] #[derive(Clone)]
@@ -623,7 +589,7 @@ pub struct Mesh {
impl Mesh { impl Mesh {
pub fn new(triangles: Vec<Triangle>) -> Arc<dyn Primitive> { pub fn new(triangles: Vec<Triangle>) -> Arc<dyn Primitive> {
// Calculate the bounding box for the entire mesh based on the bounding boxes of individual triangles // Calculate the bounding box for the entire mesh based on the bounding boxes of individual triangles
let bounding_box = Mesh::compute_bounding_box(&triangles); let _bounding_box = Mesh::compute_bounding_box(&triangles);
Arc::new(Mesh { triangles }) Arc::new(Mesh { triangles })
} }

View File

@@ -18,14 +18,9 @@ pub struct Intersection {
} }
//Intersection point including point and normal //Intersection point including point and normal
impl Intersection { impl Intersection {
pub fn transform(&self, trans: &Matrix4<f64>, inv_trans: &Matrix4<f64>) -> Intersection { pub fn transform_mut(&mut self, trans: &Matrix4<f64>, inv_trans: &Matrix4<f64>) {
let point = trans.transform_point(&self.point); self.point = trans.transform_point(&self.point);
let normal = inv_trans.transpose().transform_vector(&self.normal); self.normal = inv_trans.transpose().transform_vector(&self.normal);
Intersection {
point,
normal,
distance: self.distance,
}
} }
} }
@@ -62,15 +57,20 @@ impl Ray {
b: trans.transform_vector(&self.b), b: trans.transform_vector(&self.b),
} }
} }
//Transform mutably
pub fn transform_mut(&mut self, trans: &Matrix4<f64>) {
self.a = trans.transform_point(&self.a);
self.b = trans.transform_vector(&self.b);
}
//This function will determine if the ray hits an object in the scene //This function will determine if the ray hits an object in the scene
//This is not optimised as it does not include bounding boxes //This is not optimised as it does not include bounding boxes
pub fn hit_scene(&self, scene: &Scene) -> bool { pub fn hit_scene(ray: &Ray, scene: &Scene) -> bool {
for (_, node) in &scene.nodes { for (_, node) in &scene.nodes {
if !node.active { if !node.active {
continue; continue;
} }
// Transform ray into local model cordinates // Transform ray into local model cordinates
let ray = self.transform(&node.inv_model); let ray = ray.transform(&node.inv_model);
if node.primitive.intersect_ray(&ray).is_some() { if node.primitive.intersect_ray(&ray).is_some() {
return true; return true;
} }
@@ -79,26 +79,31 @@ impl Ray {
} }
//This function find the closest intersection point of a ray with an object in the scene //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 //Also not optimised, as it does not include bounding boxes
pub fn closest_intersect<'a>(&'a self, scene: &'a Scene) -> Option<(&Node, Intersection)> { pub fn closest_intersect<'a>(
ray: &'a Ray,
scene: &'a Scene,
) -> Option<(&'a Node, Intersection)> {
let mut closest_distance = f64::MAX; let mut closest_distance = f64::MAX;
let mut closest_intersect: Option<(&Node, Intersection)> = None; let mut closest_intersect: Option<(&Node, Intersection)> = None;
let ray_a = ray.a;
for (_, node) in &scene.nodes { for (_, node) in &scene.nodes {
//position of ray in world coords
if !node.active { if !node.active {
continue; continue;
} }
if node.aabb.intersect_ray(&self) { if node.aabb.intersect_ray(&ray) {
// Transform ray into local model cordinates // Transform ray into model cordinates
let ray = self.transform(&node.inv_model); let ray = ray.transform(&node.inv_model);
// Check primitive intersection // Check primitive intersection
if let Some(intersect) = node.primitive.intersect_ray(&ray) { if let Some(mut intersect) = node.primitive.intersect_ray(&ray) {
// Dont intersect with itself // Dont intersect with own primitive
if intersect.distance < EPSILON { if intersect.distance < EPSILON {
continue; continue;
} }
// Check for closest distance by converting to world coords // Check for closest distance by converting to world coords
let intersect = intersect.transform(&node.model, &node.inv_model); intersect.transform_mut(&node.model, &node.inv_model);
let distance = distance(&ray.a, &intersect.point); let distance = distance(&ray_a, &intersect.point);
if distance < closest_distance { if distance < closest_distance {
closest_distance = distance; closest_distance = distance;
closest_intersect = Some((node, intersect)); closest_intersect = Some((node, intersect));
@@ -124,7 +129,7 @@ impl Ray {
//We have a bvh so use bvh traversal //We have a bvh so use bvh traversal
Some(bvh) => { Some(bvh) => {
//Intersect the scene with the bvh //Intersect the scene with the bvh
if let Some((node, intersect)) = bvh.traverse(&self, 0) { if let Some((node, intersect)) = bvh.traverse(self, 0) {
return Some(Ray::phong_shade_point( return Some(Ray::phong_shade_point(
&scene, &self, &node, &intersect, depth, options, sbvh, &scene, &self, &node, &intersect, depth, options, sbvh,
)); ));
@@ -134,7 +139,7 @@ impl Ray {
//We dont have a bvh so use generic algorithm //We dont have a bvh so use generic algorithm
None => { None => {
//No BVH given so intersect normally //No BVH given so intersect normally
match self.closest_intersect(scene) { match Ray::closest_intersect(self, scene) {
Some((node, intersect)) => { Some((node, intersect)) => {
Some(Ray::phong_shade_point( Some(Ray::phong_shade_point(
&scene, &self, &node, &intersect, depth, options, sbvh, &scene, &self, &node, &intersect, depth, options, sbvh,
@@ -180,10 +185,10 @@ impl Ray {
let to_light = to_light.normalize(); let to_light = to_light.normalize();
//Niave Shadows //Niave Shadows
let to_light_ray = Ray::new(point, to_light); // let to_light_ray = Ray::new(point, to_light);
if to_light_ray.light_blocked(scene, node, bvh) { // if to_light_ray.light_blocked(scene, node, bvh) {
continue; // continue;
} // }
let n_dot_l = normal.dot(&to_light).max(0.0) as f32; let n_dot_l = normal.dot(&to_light).max(0.0) as f32;
@@ -235,7 +240,7 @@ impl Ray {
if !node.active { if !node.active {
continue; continue;
} }
match bvh.traverse(&self, 0) { match bvh.traverse(self, 0) {
Some(_) => return true, Some(_) => return true,
None => continue, None => continue,
} }

View File

@@ -1,6 +1,6 @@
use crate::{camera::Camera, light::Light, material::*, node::*}; use crate::{camera::Camera, light::Light, material::*, node::*};
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc;
// pub struct MultiThreadScene { // pub struct MultiThreadScene {
// pub nodes: Rc<HashMap<String, Node>>, // pub nodes: Rc<HashMap<String, Node>>,

View File

@@ -14,7 +14,6 @@ use rand::{random, thread_rng};
use std::error::Error; use std::error::Error;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex;
use anyhow::Result; use anyhow::Result;
use pixels::{Pixels, SurfaceTexture}; use pixels::{Pixels, SurfaceTexture};
@@ -36,7 +35,7 @@ pub struct RaytracingOption {
pub ray_randomness: f64, pub ray_randomness: f64,
pub clear_color: [u8; 4], pub clear_color: [u8; 4],
pub pixel_clear: [u8; 4], pub pixel_clear: [u8; 4],
pub pixels_per_pass: u32, pub pixels_per_thread: u32,
pub buffer_proportion: f32, pub buffer_proportion: f32,
pub buffer_fov: f64, pub buffer_fov: f64,
pub ray_depth: u8, pub ray_depth: u8,
@@ -48,16 +47,16 @@ impl RaytracingOption {
pub fn default() -> RaytracingOption { pub fn default() -> RaytracingOption {
RaytracingOption { RaytracingOption {
threads: 12, threads: 12,
ray_samples: 10, ray_samples: 1,
ray_randomness: 100.0, ray_randomness: 100.0,
clear_color: [0x22, 0x00, 0x11, 0x55], clear_color: [0x22, 0x00, 0x11, 0x55],
pixel_clear: [0x55, 0x00, 0x22, 0x55], pixel_clear: [0x55, 0x00, 0x22, 0x55],
pixels_per_pass: 200, pixels_per_thread: 200,
buffer_proportion: 1.0, buffer_proportion: 1.0,
buffer_fov: 110.0, buffer_fov: 110.0,
ray_depth: 5, ray_depth: 5,
diffuse_rays: 5, diffuse_rays: 3,
diffuse_coefficient: 0.5, diffuse_coefficient: 0.8,
bvh_active: false, bvh_active: false,
} }
} }
@@ -72,11 +71,11 @@ pub struct State {
buffer_width: u32, buffer_width: u32,
buffer_height: u32, buffer_height: u32,
pixels: Arc<Mutex<Pixels>>, pixels: Pixels,
gui: Gui, gui: Gui,
rays: Arc<Vec<Ray>>, rays: Arc<Vec<Ray>>,
ray_queue: Arc<Mutex<Vec<usize>>>, ray_queue: Vec<usize>,
raytracing_options: Arc<RaytracingOption>, raytracing_options: Arc<RaytracingOption>,
} }
@@ -84,7 +83,7 @@ impl State {
pub fn new(window: Window, pixels: Pixels, gui: Gui) -> Self { pub fn new(window: Window, pixels: Pixels, gui: Gui) -> Self {
let scene = Arc::new(Scene::empty()); let scene = Arc::new(Scene::empty());
let window_size = window.inner_size(); let window_size = window.inner_size();
let pixels = Arc::new(Mutex::new(pixels)); let pixels = pixels;
let camera = Camera::unit(); let camera = Camera::unit();
let rays = Arc::new(Vec::new()); let rays = Arc::new(Vec::new());
@@ -98,7 +97,7 @@ impl State {
pixels, pixels,
gui, gui,
rays, rays,
ray_queue: Arc::new(Mutex::new(Vec::new())), ray_queue: Vec::new(),
raytracing_options: Arc::new(RaytracingOption::default()), raytracing_options: Arc::new(RaytracingOption::default()),
} }
} }
@@ -133,8 +132,7 @@ impl State {
self.reset_queue(); self.reset_queue();
} }
GuiEvent::SaveImage(filename) => { GuiEvent::SaveImage(filename) => {
let pixels = &self.pixels.as_ref().lock().unwrap(); let frame = self.pixels.frame();
let frame = pixels.frame();
image::save_buffer( image::save_buffer(
Path::new(&filename), Path::new(&filename),
frame, frame,
@@ -171,16 +169,15 @@ impl State {
)); ));
// Resize buffer and surface // Resize buffer and surface
let pixels = &mut self.pixels.as_ref().lock().unwrap(); self.pixels.resize_surface(size.width, size.height)?;
pixels.resize_surface(size.width, size.height)?; self.pixels
pixels.resize_buffer(self.buffer_width, self.buffer_height)?; .resize_buffer(self.buffer_width, self.buffer_height)?;
Ok(()) Ok(())
} }
fn resize(&mut self, size: &PhysicalSize<u32>) -> Result<(), Box<dyn Error>> { fn resize(&mut self, size: &PhysicalSize<u32>) -> Result<(), Box<dyn Error>> {
let pixels = &mut self.pixels.as_ref().lock().unwrap(); self.pixels.resize_surface(size.width, size.height)?;
pixels.resize_surface(size.width, size.height)?;
Ok(()) Ok(())
} }
@@ -199,29 +196,38 @@ impl State {
let randomness = self.raytracing_options.ray_randomness; let randomness = self.raytracing_options.ray_randomness;
let samples = self.raytracing_options.ray_samples; let samples = self.raytracing_options.ray_samples;
let samples_f32 = samples as f32; let samples_f32 = samples as f32;
let num_threads = self.raytracing_options.threads;
let pixels_per_thread = self.raytracing_options.pixels_per_thread;
let mut handles = vec![]; let mut handles = vec![];
for _ in 0..self.raytracing_options.pixels_per_pass { for _ in 0..num_threads {
for _ in 0..self.raytracing_options.threads { //Get necessary variables to render
//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 rays = self.rays.clone();
let scene = self.scene.clone(); let scene = self.scene.clone();
let options = self.raytracing_options.clone(); let options = self.raytracing_options.clone();
let bvh = self.bvh.clone(); let bvh = self.bvh.clone();
let rays = rays.clone();
let pixels_mutex = self.pixels.clone(); //Get the workload for a thread
let mut load = vec![];
for _ in 0..pixels_per_thread {
match self.ray_queue.pop() {
Some(index) => load.push(index),
None => break,
}
}
//The finished queue of the thread
let mut finished = vec![];
//Create a new thread for these pixels
let handle = thread::spawn({
move || { move || {
//Shade colour for selected ray for index in &load {
//Shade colour for selected index
let mut colour: Vector3<f32> = Vector3::zeros(); let mut colour: Vector3<f32> = Vector3::zeros();
let ray = &rays[*index];
for _ in 0..samples { for _ in 0..samples {
let ray = &rays[index];
let point = ray.a; let point = ray.a;
let dir = ray.b; let dir = ray.b;
let rx = (random::<f64>() - 0.5) / randomness; let rx = (random::<f64>() - 0.5) / randomness;
@@ -240,23 +246,37 @@ impl State {
} }
colour = (colour / samples_f32) * 255.0; colour = (colour / samples_f32) * 255.0;
let rgba = [colour.x as u8, colour.y as u8, colour.z as u8, 0xff]; let rgba = [colour.x as u8, colour.y as u8, colour.z as u8, 0xff];
let pixels = &mut pixels_mutex.lock().unwrap(); finished.push(rgba);
let frame = pixels.frame_mut(); }
frame[index * 4..(index + 1) * 4].copy_from_slice(&rgba); return (load, finished);
} }
}); });
handles.push(handle); handles.push(handle);
} }
let mut all_results = vec![];
for handle in handles.drain(..) { for handle in handles.drain(..) {
handle.join().unwrap(); let (load, finished) = handle
.join()
.map_err(|e| format!("Thread panicked: {:?}", e))?;
let thread_results: Vec<_> = load.into_iter().zip(finished.into_iter()).collect();
all_results.extend(thread_results);
} }
//Now we have two vectors will all the indicies and rgba values, we can upload them to the bufer
let frame = self.pixels.frame_mut();
for result in all_results {
let index = result.0;
let rgba = result.1;
frame[index * 4..(index + 1) * 4].copy_from_slice(&rgba);
} }
Ok(()) Ok(())
} }
fn clear_buffer(&mut self) -> Result<(), Box<dyn Error>> { fn clear_buffer(&mut self) -> Result<(), Box<dyn Error>> {
let pixels = &mut self.pixels.as_ref().lock().unwrap(); let frame = self.pixels.frame_mut();
let frame = pixels.frame_mut();
for pixel in frame.chunks_exact_mut(4) { for pixel in frame.chunks_exact_mut(4) {
pixel.copy_from_slice(&self.raytracing_options.pixel_clear); pixel.copy_from_slice(&self.raytracing_options.pixel_clear);
} }
@@ -271,7 +291,7 @@ impl State {
let size = self.buffer_height as usize * self.buffer_width as usize; let size = self.buffer_height as usize * self.buffer_width as usize;
let mut ray_queue: Vec<usize> = (0..size).collect(); let mut ray_queue: Vec<usize> = (0..size).collect();
ray_queue.shuffle(&mut thread_rng()); ray_queue.shuffle(&mut thread_rng());
self.ray_queue = Arc::new(Mutex::new(ray_queue)); self.ray_queue = ray_queue;
} }
fn render(&mut self) -> Result<(), Box<dyn Error>> { fn render(&mut self) -> Result<(), Box<dyn Error>> {
@@ -289,8 +309,7 @@ impl State {
.prepare(&self.window) .prepare(&self.window)
.expect("gui.prepare() failed"); .expect("gui.prepare() failed");
// Try to render pixels // Try to render pixels
let pixels = &mut self.pixels.as_ref().lock().unwrap(); if let Err(e) = self.pixels.render_with(|encoder, render_target, context| {
if let Err(e) = pixels.render_with(|encoder, render_target, context| {
context.scaling_renderer.render(encoder, render_target); // Render pixels context.scaling_renderer.render(encoder, render_target); // Render pixels
self.gui self.gui
.render(&self.window, encoder, render_target, context)?; .render(&self.window, encoder, render_target, context)?;