From 899f81eed6c221dce22333ad03704b12d7634a54 Mon Sep 17 00:00:00 2001 From: Sakarias Johansson Date: Sun, 8 Jan 2023 17:51:44 +0100 Subject: =?UTF-8?q?=F0=9F=8C=8D=20Add=20Geometry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created a trait for all geometry that has to implement a hit function. Depending on if the ray hits or not it returns an option with the color. - Add support for multiple samples per pixel Current issues: - Using cooperative multitasking which isn't that helpful in this situation since it's like running without async but without overhead. Should switch to rayon. - All data gets copied once per job. Will decide later what to do (copy or put locks and share data between jobs). --- .gitignore | 1 + default.nix | 1 - racer-tracer/Cargo.toml | 1 + racer-tracer/src/camera.rs | 13 +++-- racer-tracer/src/geometry.rs | 54 +++++++++++++++++++ racer-tracer/src/geometry/sphere.rs | 68 +++++++++++++++++++++++ racer-tracer/src/image.rs | 4 +- racer-tracer/src/main.rs | 76 +++++++++++++++++++------- racer-tracer/src/ray.rs | 2 +- racer-tracer/src/scene.rs | 58 ++++++++++++++++++++ racer-tracer/src/util.rs | 57 ++++++-------------- racer-tracer/src/vec3.rs | 104 +++++++++++++++++++++++++++++++++--- 12 files changed, 367 insertions(+), 72 deletions(-) create mode 100644 racer-tracer/src/geometry.rs create mode 100644 racer-tracer/src/geometry/sphere.rs create mode 100644 racer-tracer/src/scene.rs diff --git a/.gitignore b/.gitignore index b428354..47cf520 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /release* +/result* diff --git a/default.nix b/default.nix index fe76061..326d701 100644 --- a/default.nix +++ b/default.nix @@ -60,7 +60,6 @@ nixpkgs.stdenv.mkDerivation { shellHook = '' export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${nixpkgs.lib.makeLibraryPath [ nixpkgs.xorg.libX11 nixpkgs.xorg.libXcursor ] } - #export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib64/ export RUST_SRC_PATH=${rustSrcNoSymlinks} ''; } diff --git a/racer-tracer/Cargo.toml b/racer-tracer/Cargo.toml index a147cfe..2083dcf 100644 --- a/racer-tracer/Cargo.toml +++ b/racer-tracer/Cargo.toml @@ -9,3 +9,4 @@ minifb = "0.23" tokio = { version = "1.14.0", features = ["macros", "rt", "rt-multi-thread"] } thiserror = "1" futures = "0.3" +rand = "0.8.5" diff --git a/racer-tracer/src/camera.rs b/racer-tracer/src/camera.rs index 5f0abbb..2c7be12 100644 --- a/racer-tracer/src/camera.rs +++ b/racer-tracer/src/camera.rs @@ -1,4 +1,5 @@ use crate::image::Image; +use crate::ray::Ray; use crate::vec3::Vec3; #[derive(Clone)] @@ -9,7 +10,7 @@ pub struct Camera { pub origin: Vec3, pub horizontal: Vec3, pub vertical: Vec3, - pub lower_left_corner: Vec3, + pub upper_left_corner: Vec3, } impl Camera { @@ -25,10 +26,16 @@ impl Camera { origin, horizontal, vertical, - lower_left_corner: origin + upper_left_corner: origin + vertical / 2.0 - horizontal / 2.0 - - vertical / 2.0 - Vec3::new(0.0, 0.0, focal_length), } } + + pub fn get_ray(&self, u: f64, v: f64) -> Ray { + Ray::new( + self.origin, + self.upper_left_corner + u * self.horizontal - v * self.vertical - self.origin, + ) + } } diff --git a/racer-tracer/src/geometry.rs b/racer-tracer/src/geometry.rs new file mode 100644 index 0000000..aa1c18c --- /dev/null +++ b/racer-tracer/src/geometry.rs @@ -0,0 +1,54 @@ +pub mod sphere; + +use crate::ray::Ray; +use crate::vec3::Vec3; + +pub struct HitRecord { + pub point: Vec3, + pub normal: Vec3, + pub t: f64, + pub front_face: bool, + pub color: Vec3, +} + +impl HitRecord { + fn new(point: Vec3, t: f64, color: Vec3) -> Self { + Self { + point, + normal: Vec3::default(), + t, + front_face: true, + color, + } + } + + fn set_face_normal(&mut self, ray: &Ray, outward_normal: Vec3) { + self.front_face = ray.direction().dot(&outward_normal) < 0.0; + self.normal = if self.front_face { + outward_normal + } else { + -outward_normal + }; + } +} + +impl Default for HitRecord { + fn default() -> Self { + HitRecord { + point: Vec3::default(), + normal: Vec3::default(), + t: 0.0, + front_face: true, + color: Vec3::default(), + } + } +} + +pub trait Hittable { + //pub trait Hittable { + fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option; + + // Manual ugly clone since forcing on Clone would make it a super trait. + // Clone requires Sized and super traits cannot be Sized. + fn clone_box(&self) -> Box; +} diff --git a/racer-tracer/src/geometry/sphere.rs b/racer-tracer/src/geometry/sphere.rs new file mode 100644 index 0000000..6a0ab9c --- /dev/null +++ b/racer-tracer/src/geometry/sphere.rs @@ -0,0 +1,68 @@ +use std::option::Option; + +use crate::geometry::{HitRecord, Hittable}; +use crate::ray::Ray; +use crate::vec3::Vec3; + +pub struct Sphere { + pos: Vec3, + radius: f64, + material: Vec3, // Just a color for now. +} + +impl Sphere { + pub fn new(pos: Vec3, radius: f64, material: Vec3) -> Self { + Self { + pos, + radius, + material, + } + } +} + +impl Clone for Sphere { + fn clone(&self) -> Self { + Self { + pos: self.pos, + radius: self.radius, + material: self.material, + } + } +} + +impl Hittable for Sphere { + fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option { + let oc = ray.origin() - self.pos; + let a = ray.direction().length_squared(); + let half_b = oc.dot(ray.direction()); + let c = oc.length_squared() - self.radius * self.radius; + let discriminant = half_b * half_b - a * c; + + if discriminant < 0.0 { + return None; + } + + let sqrtd = discriminant.sqrt(); + + // Find the nearest root that lies in acceptable range. + let mut root = (-half_b - sqrtd) / a; + if root < t_min || t_max < root { + root = (-half_b + sqrtd) / a; + + if root < t_min || t_max < root { + return None; + } + } + + let point = ray.at(root); + let outward_normal = (point - self.pos) / self.radius; + + let mut hit_record = HitRecord::new(point, root, self.material); + hit_record.set_face_normal(ray, outward_normal); + Some(hit_record) + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/racer-tracer/src/image.rs b/racer-tracer/src/image.rs index 1b5f61e..2df11dc 100644 --- a/racer-tracer/src/image.rs +++ b/racer-tracer/src/image.rs @@ -3,14 +3,16 @@ pub struct Image { pub aspect_ratio: f64, pub width: usize, pub height: usize, + pub samples_per_pixel: usize, } impl Image { - pub fn new(aspect_ratio: f64, width: usize) -> Image { + pub fn new(aspect_ratio: f64, width: usize, samples_per_pixel: usize) -> Image { Image { aspect_ratio, width, height: (width as f64 / aspect_ratio) as usize, + samples_per_pixel, } } } diff --git a/racer-tracer/src/main.rs b/racer-tracer/src/main.rs index 1951da5..9d45762 100644 --- a/racer-tracer/src/main.rs +++ b/racer-tracer/src/main.rs @@ -1,51 +1,86 @@ #[macro_use] mod error; mod camera; +mod geometry; mod image; mod ray; +mod scene; mod util; mod vec3; -use crate::vec3::Vec3; use std::vec::Vec; use futures::{select, stream::FuturesUnordered, stream::StreamExt}; +use geometry::Hittable; use minifb::{Key, Window, WindowOptions}; -fn ray_color(ray: &ray::Ray) -> Vec3 { - let unit_direction = vec3::unit_vector(ray.direction()); +use crate::camera::Camera; +use crate::geometry::sphere::Sphere; +use crate::image::Image; +use crate::ray::Ray; +use crate::scene::Scene; +use crate::util::random_double; +use crate::vec3::Vec3; + +fn ray_color(scene: &Scene, ray: &Ray) -> Vec3 { + if let Some(hit_record) = scene.hit(ray, 0.0, std::f64::INFINITY) { + //return hit_record.color; + return 0.5 * (hit_record.normal + Vec3::new(1.0, 1.0, 1.0)); + } + + // Sky + let unit_direction = ray.direction().unit_vector(); let t = 0.5 * (unit_direction.y() + 1.0); (1.0 - t) * Vec3::new(1.0, 1.0, 1.0) + t * Vec3::new(0.5, 0.7, 1.0) } +// TODO: Rustify async fn raytrace( - camera: camera::Camera, - image: image::Image, + scene: Scene, + camera: Camera, + image: Image, row: usize, ) -> Result<(usize, Vec), error::TracerError> { let mut buffer: Vec = vec![0; image.width as usize]; + let mut colors: Vec = vec![Vec3::default(); image.width as usize]; for i in 0..buffer.len() { - let u: f64 = i as f64 / (image.width - 1) as f64; - let v: f64 = row as f64 / (image.height - 1) as f64; - let ray = ray::Ray::new( - camera.origin, - camera.lower_left_corner + u * camera.horizontal + v * camera.vertical - camera.origin, - ); - let col = ray_color(&ray); - buffer[i] = col.as_color(); + for _ in 0..image.samples_per_pixel { + let u: f64 = (i as f64 + random_double()) / (image.width - 1) as f64; + let v: f64 = (row as f64 + random_double()) / (image.height - 1) as f64; + colors[i] += ray_color(&scene, &camera.get_ray(u, v)); + } + } + + for i in 0..image.width { + buffer[i] = (colors[i] / image.samples_per_pixel as f64).as_color(); } Ok((row, buffer)) } +fn create_scene() -> Scene { + let mut scene = Scene::new(); + let sphere1 = Sphere::new(Vec3::new(0.0, 0.0, -1.0), 0.5, Vec3::new(0.0, 1.0, 0.0)); + let sphere2 = Sphere::new( + Vec3::new(0.0, -100.5, -1.0), + 100.0, + Vec3::new(0.0, 1.0, 0.0), + ); + scene.add(Box::new(sphere1)); + scene.add(Box::new(sphere2)); + scene +} + async fn run( rows_per_update: u32, aspect_ratio: f64, - screen_height: usize, + screen_width: usize, + samples: usize, ) -> Result<(), error::TracerError> { - let image = image::Image::new(aspect_ratio, screen_height); + let image = image::Image::new(aspect_ratio, screen_width, samples); let camera = camera::Camera::new(&image, 2.0, 1.0); + let scene = create_scene(); let mut screen_buffer: Vec = vec![0; image.width * image.height]; let mut window = Window::new( @@ -61,13 +96,18 @@ async fn run( // One future per row is a bit high. // Could do something less spammy. for h in 0..image.height { - futs.push(raytrace(camera.clone(), image.clone(), h)); + // TODO: Either clone all or lock em all. + futs.push(raytrace(scene.clone(), camera.clone(), image.clone(), h)); } + // TODO: use rayon + // Since it's cooperative multitasking this is not really helpful at the moment. + // You will get pretty much get the same result without the tokio asyncness. + // using rayon with threads is a different matter. let mut complete = false; while window.is_open() && !window.is_key_down(Key::Escape) { if !complete { - for _ in 1..rows_per_update { + for _ in 0..rows_per_update { select! { res = futs.select_next_some() => { let row_buffer = res.expect("Expected to get data"); @@ -95,7 +135,7 @@ async fn run( #[tokio::main] async fn main() { - if let Err(e) = run(50, 16.0 / 9.0, 1200).await { + if let Err(e) = run(100, 16.0 / 9.0, 1200, 100).await { eprintln!("{}", e); std::process::exit(e.into()) } diff --git a/racer-tracer/src/ray.rs b/racer-tracer/src/ray.rs index 1325cc2..33e62f2 100644 --- a/racer-tracer/src/ray.rs +++ b/racer-tracer/src/ray.rs @@ -19,6 +19,6 @@ impl Ray { } pub fn at(&self, go_length: f64) -> Vec3 { - self.origin.clone() + go_length * self.direction.clone() + self.origin + go_length * self.direction } } diff --git a/racer-tracer/src/scene.rs b/racer-tracer/src/scene.rs new file mode 100644 index 0000000..2d530af --- /dev/null +++ b/racer-tracer/src/scene.rs @@ -0,0 +1,58 @@ +use crate::geometry::Hittable; + +pub struct Scene { + objects: Vec>, +} + +impl Scene { + pub fn new() -> Self { + Self { + objects: Vec::new(), + } + } + + pub fn add(&mut self, hittable: Box) { + self.objects.push(hittable); + } +} + +// TODO: What to do? +// Cloning everything is nice since then every task can do whatever they like. +// Cloning everything is bad becuse you copy everything which takes time. +// Could also put locks on the Scene but then it becomes this global object that everyone +// wants to access at the same time. +// Will do some laborations later and decide on a solution. +impl Clone for Scene { + fn clone(&self) -> Self { + let mut objects = Vec::with_capacity(self.objects.capacity()); + for i in self.objects.iter() { + objects.push(i.clone_box()); + } + Self { objects } + } +} + +impl Hittable for Scene { + fn hit( + &self, + ray: &crate::ray::Ray, + t_min: f64, + t_max: f64, + ) -> Option { + let mut rec = None; + let mut closes_so_far = t_max; + + for obj in self.objects.iter() { + if let Some(hit_rec) = obj.hit(ray, t_min, closes_so_far) { + closes_so_far = hit_rec.t; + rec = Some(hit_rec); + } + } + + rec + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/racer-tracer/src/util.rs b/racer-tracer/src/util.rs index e223c67..196b82d 100644 --- a/racer-tracer/src/util.rs +++ b/racer-tracer/src/util.rs @@ -1,43 +1,18 @@ -pub fn hsv_to_rgb(h: f64, s: f64, v: f64) -> u32 { - let s: f64 = s / 100.0; - let v: f64 = v / 100.0; - let c: f64 = s * v; - let mut a: f64 = h / 60.0; - a %= 2.0f64; - let x: f64 = c * (1f64 - (a - 1f64).abs()); - let m: f64 = v - c; +use rand::Rng; +/* +// For later use +fn degrees_to_radians(degrees: f64) -> f64 { + degrees * std::f64::consts::PI / 180.0 +}*/ - let r: f64; - let g: f64; - let b: f64; - if (0.0..60.0).contains(&h) { - r = c; - g = x; - b = 0.0; - } else if (60.0..120.0).contains(&h) { - r = x; - g = c; - b = 0.0; - } else if (120.0..180.0).contains(&h) { - r = 0.0; - g = c; - b = x; - } else if (180.0..240.0).contains(&h) { - r = 0.0; - g = x; - b = c; - } else if (240.0..300.0).contains(&h) { - r = x; - g = 0.0; - b = c; - } else { - r = c; - g = 0.0; - b = x; - } - - let red: u32 = ((r + m) * 255.0) as u32; - let green: u32 = ((g + m) * 255.0) as u32; - let blue: u32 = ((b + m) * 255.0) as u32; - ((red as u32) << 16) | ((green as u32) << 8) | blue as u32 +pub fn random_double() -> f64 { + let mut rng = rand::thread_rng(); + rng.gen::() } + +/* +// For later use +pub fn random_double_range(min: f64, max: f64) -> f64 { + let mut rng = rand::thread_rng(); + rng.gen_range(min..max) +}*/ diff --git a/racer-tracer/src/vec3.rs b/racer-tracer/src/vec3.rs index 47df292..10f3bfa 100644 --- a/racer-tracer/src/vec3.rs +++ b/racer-tracer/src/vec3.rs @@ -86,15 +86,31 @@ impl ops::AddAssign for Vec3 { } } +fn vec_sub(v1: &[f64; 3], v2: &[f64; 3]) -> Vec3 { + Vec3::new(v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2]) +} + impl ops::Sub for Vec3 { type Output = Vec3; fn sub(self, rhs: Vec3) -> Self::Output { - Vec3::new( - rhs.data[0] - self.data[0], - rhs.data[1] - self.data[1], - rhs.data[2] - self.data[2], - ) + vec_sub(&self.data, &rhs.data) + } +} + +impl ops::Sub<&Vec3> for Vec3 { + type Output = Vec3; + + fn sub(self, rhs: &Vec3) -> Self::Output { + vec_sub(&self.data, &rhs.data) + } +} + +impl ops::Sub for &Vec3 { + type Output = Vec3; + + fn sub(self, rhs: Vec3) -> Self::Output { + vec_sub(&self.data, &rhs.data) } } @@ -151,7 +167,7 @@ impl ops::Mul<&Vec3> for f64 { type Output = Vec3; fn mul(self, rhs: &Vec3) -> Self::Output { - Vec3::new(rhs.data[0] * self, rhs.data[1] * self, rhs.data[2] * self) + *rhs * self } } @@ -171,7 +187,7 @@ impl ops::Mul for f64 { type Output = Vec3; fn mul(self, rhs: Vec3) -> Self::Output { - rhs * self + Vec3::new(rhs.data[0] * self, rhs.data[1] * self, rhs.data[2] * self) } } @@ -191,6 +207,14 @@ impl ops::Neg for Vec3 { } } +impl PartialEq for Vec3 { + fn eq(&self, other: &Self) -> bool { + self.data[0] == other.data[0] + && self.data[1] == other.data[1] + && self.data[2] == other.data[2] + } +} + impl fmt::Display for Vec3 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str( @@ -202,3 +226,69 @@ impl fmt::Display for Vec3 { ) } } + +impl std::fmt::Debug for Vec3 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Vec3").field("data", &self.data).finish() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn add() { + let v1 = Vec3::new(1.0, 2.0, 3.0); + let v2 = Vec3::new(2.0, 4.0, 6.0); + let v3 = v1 + v2; + let v4 = v2 + v1; + + assert_eq!(*v3.x(), 3.0); + assert_eq!(*v3.y(), 6.0); + assert_eq!(*v3.z(), 9.0); + assert_eq!(v3, v4); + } + + #[test] + fn sub() { + let v1 = Vec3::new(1.0, 2.0, 3.0); + let v2 = Vec3::new(2.0, 4.0, 6.0); + let v3 = v1 - v2; + assert_eq!(v3.x(), &-1.0); + assert_eq!(v3.y(), &-2.0); + assert_eq!(v3.z(), &-3.0); + + let v4 = v2 - v1; + assert_eq!(v4.x(), &1.0); + assert_eq!(v4.y(), &2.0); + assert_eq!(v4.z(), &3.0); + } + + #[test] + fn mul() { + let v1 = Vec3::new(1.0, -2.0, 3.0); + let v2 = v1 * 5.0; + + assert_eq!(v2.x(), &5.0); + assert_eq!(v2.y(), &-10.0); + assert_eq!(v2.z(), &15.0); + + let v3 = Vec3::new(4.0, 8.0, 16.0); + let v4 = v1 * v3; + + assert_eq!(v4.x(), &4.0); + assert_eq!(v4.y(), &-16.0); + assert_eq!(v4.z(), &48.0); + } + + #[test] + fn div() { + let v1 = Vec3::new(1.0, -2.0, 3.0); + let v2 = v1 / 2.0; + + assert_eq!(v2.x(), &0.5); + assert_eq!(v2.y(), &-1.0); + assert_eq!(v2.z(), &1.5); + } +} -- cgit v1.2.3