From 30c8b6054ff04776a37991dee9592525094596a6 Mon Sep 17 00:00:00 2001 From: ishanjain28 Date: Thu, 13 Feb 2020 16:12:52 +0530 Subject: [PATCH] Added upto hit table based sphere, Working on anti-aliasing module --- ria-weekend/src/camera.rs | 43 +++++++++++++ ria-weekend/src/demos/hitable_sphere.rs | 58 ++++++++++++++++++ ria-weekend/src/demos/mod.rs | 8 ++- ria-weekend/src/demos/simple_antialiasing.rs | 63 ++++++++++++++++++++ ria-weekend/src/main.rs | 11 +++- ria-weekend/src/types/hitable.rs | 13 ++++ ria-weekend/src/types/hitable_list.rs | 19 ++++++ ria-weekend/src/types/mod.rs | 6 ++ ria-weekend/src/types/sphere.rs | 58 ++++++++++++++++++ 9 files changed, 275 insertions(+), 4 deletions(-) create mode 100644 ria-weekend/src/camera.rs create mode 100644 ria-weekend/src/demos/hitable_sphere.rs create mode 100644 ria-weekend/src/demos/simple_antialiasing.rs create mode 100644 ria-weekend/src/types/hitable.rs create mode 100644 ria-weekend/src/types/hitable_list.rs create mode 100644 ria-weekend/src/types/sphere.rs diff --git a/ria-weekend/src/camera.rs b/ria-weekend/src/camera.rs new file mode 100644 index 0000000..6d17e34 --- /dev/null +++ b/ria-weekend/src/camera.rs @@ -0,0 +1,43 @@ +use crate::types::{Ray, Vec3}; + +pub struct Camera { + pub origin: Vec3, + pub horizontal: Vec3, + pub vertical: Vec3, + pub lower_left_corner: Vec3, +} + +impl Camera { + pub const fn new( + origin: Vec3, + horizontal: Vec3, + vertical: Vec3, + lower_left_corner: Vec3, + ) -> Self { + Self { + origin, + horizontal, + vertical, + lower_left_corner, + } + } + + pub fn get_ray(&self, u: f32, v: f32) -> Ray { + Ray::new( + self.origin, + self.lower_left_corner + self.horizontal * u + self.vertical * v - self.origin, + ) + } +} + +impl std::default::Default for Camera { + fn default() -> Self { + Camera { + origin: Vec3::new(0.0, 0.0, 0.0), + // Because canvas is in 2:1 ratio + horizontal: Vec3::new(4.0, 0.0, 0.0), + vertical: Vec3::new(0.0, 2.0, 0.0), + lower_left_corner: Vec3::new(-2.0, -1.0, -1.0), + } + } +} diff --git a/ria-weekend/src/demos/hitable_sphere.rs b/ria-weekend/src/demos/hitable_sphere.rs new file mode 100644 index 0000000..a176793 --- /dev/null +++ b/ria-weekend/src/demos/hitable_sphere.rs @@ -0,0 +1,58 @@ +use crate::{ + demos::Demo, + types::{Hitable, HitableList, Ray, Sphere, Vec3}, +}; +pub struct HitableSphere; + +impl Demo for HitableSphere { + fn name(&self) -> &'static str { + "Sphere using Hit table" + } + + fn render(&self, buf: &mut [u8], width: usize, height: usize, _samples: u8) { + let lower_left_corner = Vec3::new(-2.0, -1.0, -1.0); + let horizontal = Vec3::new(4.0, 0.0, 0.0); + let vertical = Vec3::new(0.0, 2.0, 0.0); + let origin = Vec3::new(0.0, 0.0, 0.0); + + let world = HitableList { + list: vec![ + Box::new(Sphere::new(Vec3::new(0.0, 0.0, -1.0), 0.5)), + Box::new(Sphere::new(Vec3::new(0.0, -100.5, -1.0), 100.0)), + ], + }; + + let mut offset = 0; + for j in (0..height).rev() { + for i in 0..width { + let u = i as f32 / width as f32; + let v = j as f32 / height as f32; + let ray = Ray::new(origin, lower_left_corner + horizontal * u + vertical * v); + + let color = calc_color(ray, &world); + buf[offset] = (255.99 * color.r()) as u8; + buf[offset + 1] = (255.99 * color.g()) as u8; + buf[offset + 2] = (255.99 * color.b()) as u8; + offset += 4; + } + } + } +} + +fn calc_color(ray: Ray, world: &HitableList) -> Vec3 { + if let Some(hit_rec) = world.hit(&ray, 0.0, std::f32::MAX) { + // It's easier to visualise normals as unit vectors + // So, This trick of adding 1 to each dimension and then halving + // the resulting value shifts the normals from -1<->1 range to + // 0<->1 range + Vec3::new( + hit_rec.normal.x() + 1.0, + hit_rec.normal.y() + 1.0, + hit_rec.normal.z() + 1.0, + ) * 0.5 + } else { + let unit_direction = ray.direction().unit_vector(); + let t = unit_direction.y() * 0.5 + 1.0; + Vec3::new(1.0, 1.0, 1.0) * (1.0 - t) + Vec3::new(0.5, 0.7, 1.0) * t + } +} diff --git a/ria-weekend/src/demos/mod.rs b/ria-weekend/src/demos/mod.rs index 6d0782d..5c1fa0d 100644 --- a/ria-weekend/src/demos/mod.rs +++ b/ria-weekend/src/demos/mod.rs @@ -1,9 +1,13 @@ +mod hitable_sphere; mod linear_gradient_rectangle; +mod simple_antialiasing; mod simple_rectangle; mod simple_sphere; mod surface_normal_sphere; +pub use hitable_sphere::HitableSphere; pub use linear_gradient_rectangle::LinearGradientRectangle; +pub use simple_antialiasing::SimpleAntialiasing; pub use simple_rectangle::SimpleRectangle; pub use simple_sphere::SimpleSphere; pub use surface_normal_sphere::SurfaceNormalSphere; @@ -21,11 +25,11 @@ pub trait Demo { Ok(file) => file, Err(e) => panic!("couldn't create {}: {}", self.name(), e), }; - file.write(header.as_bytes()) + file.write_all(header.as_bytes()) .expect("error in writing file header"); for i in buf.chunks(4) { - match file.write(format!("{} {} {}\n", i[0], i[1], i[2]).as_bytes()) { + match file.write_all(format!("{} {} {}\n", i[0], i[1], i[2]).as_bytes()) { Ok(_) => (), Err(e) => panic!("couldn't write to {}: {}", self.name(), e), } diff --git a/ria-weekend/src/demos/simple_antialiasing.rs b/ria-weekend/src/demos/simple_antialiasing.rs new file mode 100644 index 0000000..a4b1393 --- /dev/null +++ b/ria-weekend/src/demos/simple_antialiasing.rs @@ -0,0 +1,63 @@ +use { + crate::{ + demos::Demo, + types::{Hitable, HitableList, Ray, Sphere, Vec3}, + Camera, + }, + rand::Rng, +}; +pub struct SimpleAntialiasing; + +impl Demo for SimpleAntialiasing { + fn name(&self) -> &'static str { + "A simple antialiasing implementation" + } + + fn render(&self, buf: &mut [u8], width: usize, height: usize, samples: u8) { + let world = HitableList { + list: vec![ + Box::new(Sphere::new(Vec3::new(0.0, 0.0, -1.0), 0.5)), + Box::new(Sphere::new(Vec3::new(0.0, -100.5, -1.0), 100.0)), + ], + }; + + let camera: Camera = Default::default(); + let mut rng = rand::thread_rng(); + let mut offset = 0; + for j in (0..height).rev() { + for i in 0..width { + let mut color = Vec3::new(0.0, 0.0, 0.0); + for _s in 0..samples { + let u = (i as f32 + rng.gen::()) / width as f32; + let v = (j as f32 + rng.gen::()) / height as f32; + + let r = camera.get_ray(u, v); + color += calc_color(r, &world); + } + color /= samples as f32; + buf[offset] = (255.99 * color.r()) as u8; + buf[offset + 1] = (255.99 * color.g()) as u8; + buf[offset + 2] = (255.99 * color.b()) as u8; + offset += 4; + } + } + } +} + +fn calc_color(ray: Ray, world: &HitableList) -> Vec3 { + if let Some(hit_rec) = world.hit(&ray, 0.0, std::f32::MAX) { + // It's easier to visualise normals as unit vectors + // So, This trick of adding 1 to each dimension and then halving + // the resulting value shifts the normals from -1<->1 range to + // 0<->1 range + Vec3::new( + hit_rec.normal.x() + 1.0, + hit_rec.normal.y() + 1.0, + hit_rec.normal.z() + 1.0, + ) * 0.5 + } else { + let unit_direction = ray.direction().unit_vector(); + let t = unit_direction.y() * 0.5 + 1.0; + Vec3::new(1.0, 1.0, 1.0) * (1.0 - t) + Vec3::new(0.5, 0.7, 1.0) * t + } +} diff --git a/ria-weekend/src/main.rs b/ria-weekend/src/main.rs index 95d1b98..c57f62b 100644 --- a/ria-weekend/src/main.rs +++ b/ria-weekend/src/main.rs @@ -1,6 +1,11 @@ +#![allow(clippy::suspicious_arithmetic_impl)] + +mod camera; mod demos; mod types; +pub use camera::Camera; + use { demos::Demo, sdl2::{ @@ -10,13 +15,13 @@ use { }, }; -const NUM_SAMPLES: u8 = 10; +const NUM_SAMPLES: u8 = 100; fn main() -> Result<(), String> { let sdl_ctx = sdl2::init()?; let video_subsys = sdl_ctx.video()?; - let (mut width, mut height) = (1280usize, 640usize); + let (mut width, mut height) = (1280, 640); let window = video_subsys .window("Ray tracing in a weekend", width as u32, height as u32) @@ -65,6 +70,8 @@ fn main() -> Result<(), String> { } Some(Keycode::Num3) => active_demo = Box::new(demos::SimpleSphere), Some(Keycode::Num4) => active_demo = Box::new(demos::SurfaceNormalSphere), + Some(Keycode::Num5) => active_demo = Box::new(demos::HitableSphere), + Some(Keycode::Num6) => active_demo = Box::new(demos::SimpleAntialiasing), None => unreachable!(), _ => (), }; diff --git a/ria-weekend/src/types/hitable.rs b/ria-weekend/src/types/hitable.rs new file mode 100644 index 0000000..6db86fb --- /dev/null +++ b/ria-weekend/src/types/hitable.rs @@ -0,0 +1,13 @@ +use crate::types::{Ray, Vec3}; + +pub struct HitRecord { + pub t: f32, + pub p: Vec3, + pub normal: Vec3, +} + +pub trait Hitable { + fn hit(&self, _ray: &Ray, _t_min: f32, _t_max: f32) -> Option { + None + } +} diff --git a/ria-weekend/src/types/hitable_list.rs b/ria-weekend/src/types/hitable_list.rs new file mode 100644 index 0000000..41f2849 --- /dev/null +++ b/ria-weekend/src/types/hitable_list.rs @@ -0,0 +1,19 @@ +use crate::types::{HitRecord, Hitable, Ray}; + +pub struct HitableList { + pub list: Vec>, +} + +impl Hitable for HitableList { + fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> Option { + let mut closest_so_far = t_max; + let mut hit_rec: Option = None; + for obj in &self.list { + if let Some(l_hit_rec) = obj.hit(ray, t_min, closest_so_far) { + closest_so_far = l_hit_rec.t; + hit_rec = Some(l_hit_rec); + } + } + hit_rec + } +} diff --git a/ria-weekend/src/types/mod.rs b/ria-weekend/src/types/mod.rs index d5dd4ac..b0582db 100644 --- a/ria-weekend/src/types/mod.rs +++ b/ria-weekend/src/types/mod.rs @@ -1,5 +1,11 @@ +mod hitable; +mod hitable_list; mod ray; +mod sphere; mod vec3; +pub use hitable::{HitRecord, Hitable}; +pub use hitable_list::HitableList; pub use ray::Ray; +pub use sphere::Sphere; pub use vec3::Vec3; diff --git a/ria-weekend/src/types/sphere.rs b/ria-weekend/src/types/sphere.rs new file mode 100644 index 0000000..36ebf78 --- /dev/null +++ b/ria-weekend/src/types/sphere.rs @@ -0,0 +1,58 @@ +use crate::types::{HitRecord, Hitable, Ray, Vec3}; +pub struct Sphere { + center: Vec3, + radius: f32, +} + +impl Sphere { + pub fn new(center: Vec3, radius: f32) -> Self { + Self { center, radius } + } + const fn radius(&self) -> f32 { + self.radius + } + const fn center(&self) -> Vec3 { + self.center + } +} + +impl Hitable for Sphere { + fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> Option { + let oc = ray.origin() - self.center; + let a = ray.direction().dot(&ray.direction()); + let b = oc.dot(&ray.direction()); + let c = oc.dot(&oc) - self.radius * self.radius; + + // The discriminant is calculated using b^2 - 4 * a * c + // but in this specific case, If we put the equation in the + // formula to find quadratic roots, We can get this shorter + // formula to find the discriminant. + // Check this for detailed proof + // https://vchizhov.github.io/resources/ray%20tracing/ray%20tracing%20tutorial%20series%20vchizhov/ray_casting/part1/intersecting_a_sphere.md.html#appendix + let discriminant = b * b - a * c; + + if discriminant > 0.0 { + let root = (-b - discriminant.sqrt()) / a; + if root < t_max && root > t_min { + let p = ray.point_at_parameter(root); + return Some(HitRecord { + t: root, + p, + normal: (p - self.center) / self.radius, + }); + } + + let root = (-b + discriminant.sqrt()) / a; + if root < t_max && root > t_min { + let p = ray.point_at_parameter(root); + + return Some(HitRecord { + t: root, + p, + normal: (p - self.center) / self.radius, + }); + } + } + None + } +}