From 298e0a2301680f6100255db444ec0a7513bf5dab Mon Sep 17 00:00:00 2001 From: Ishan Jain Date: Sat, 6 Mar 2021 21:49:16 +0530 Subject: [PATCH] Complete simple light demo --- src/demos/mod.rs | 39 +++++----------- src/demos/simple_light.rs | 81 ++++++++++++++++++++++++++++++++++ src/hitable.rs | 19 ++++++++ src/main.rs | 13 +++--- src/materials/diffuse_light.rs | 17 +++++++ src/materials/lambertian.rs | 2 +- src/materials/metal.rs | 1 + src/materials/mod.rs | 17 ++++++- src/shapes/mod.rs | 2 + src/shapes/moving_sphere.rs | 34 +++++--------- src/shapes/sphere.rs | 35 +++++---------- src/shapes/xy_rectangle.rs | 64 +++++++++++++++++++++++++++ src/types/ray.rs | 30 ++++++++++++- 13 files changed, 270 insertions(+), 84 deletions(-) create mode 100644 src/demos/simple_light.rs create mode 100644 src/materials/diffuse_light.rs create mode 100644 src/shapes/xy_rectangle.rs diff --git a/src/demos/mod.rs b/src/demos/mod.rs index bf8517e..91cedbb 100644 --- a/src/demos/mod.rs +++ b/src/demos/mod.rs @@ -1,7 +1,4 @@ -use crate::{ - types::{Ray, Vec3}, - Camera, Hitable, HORIZONTAL_PARTITION, VERTICAL_PARTITION, -}; +use crate::{types::Vec3, Camera, Hitable, HORIZONTAL_PARTITION, VERTICAL_PARTITION}; use rand::{rngs::SmallRng, Rng, SeedableRng}; use rayon::prelude::*; use std::{ @@ -14,11 +11,13 @@ use std::{ mod checkered_motion_blur; mod image_texture; mod perlin_noise_ball; +mod simple_light; mod two_spheres; pub use checkered_motion_blur::CheckeredMotionBlur; pub use image_texture::ImageTextureDemo; pub use perlin_noise_ball::PerlinNoiseBall; +pub use simple_light::SimpleLight; pub use two_spheres::TwoSpheres; #[derive(Debug)] @@ -55,7 +54,11 @@ pub trait Demo: Send + Sync { fn camera(&self, aspect_ratio: f64) -> Camera; - fn render_chunk(&self, chunk: &mut Chunk, camera: &Camera, world: &Self::DemoT, samples: u8) { + fn get_background(&self) -> Vec3 { + Vec3::new(0.7, 0.8, 1.0) + } + + fn render_chunk(&self, chunk: &mut Chunk, camera: &Camera, world: &Self::DemoT, samples: u16) { let &mut Chunk { num: _, x, @@ -69,6 +72,7 @@ pub trait Demo: Send + Sync { let mut offset = 0; let mut rng = rand::thread_rng(); let mut rng = SmallRng::from_rng(&mut rng).unwrap(); + let background = self.get_background(); assert!(buffer.len() >= nx * ny * 4); @@ -80,7 +84,7 @@ pub trait Demo: Send + Sync { let v = (j as f64 + rng.gen::()) / y as f64; let ray = camera.get_ray(u, v, &mut rng); - color += calc_color(ray, world, 0, &mut rng); + color += ray.color(world, &mut rng, &background, 0); } color /= samples as f64; @@ -90,7 +94,7 @@ pub trait Demo: Send + Sync { }); } - fn render(&self, buf: &mut Vec, x: usize, y: usize, samples: u8) { + fn render(&self, buf: &mut Vec, x: usize, y: usize, samples: u16) { let world = self.world(); let delta_x = x / VERTICAL_PARTITION; let delta_y = y / HORIZONTAL_PARTITION; @@ -163,7 +167,7 @@ pub trait Demo: Send + Sync { } } - fn save_as_ppm(&self, buf: &[u8], width: usize, height: usize, samples: u8) { + fn save_as_ppm(&self, buf: &[u8], width: usize, height: usize, samples: u16) { let header = format!("P3\n{} {}\n255\n", width, height); let mut file = match File::create(&format!( @@ -187,22 +191,3 @@ pub trait Demo: Send + Sync { } } } - -fn calc_color(ray: Ray, world: &T, depth: u32, rng: &mut SmallRng) -> Vec3 { - if let Some(hit_rec) = world.hit(&ray, 0.001, std::f64::MAX) { - if depth >= 50 { - Vec3::new(0.0, 0.0, 0.0) - } else { - let material = hit_rec.material; - if let (attenuation, Some(scattered_ray)) = material.scatter(&ray, &hit_rec, rng) { - calc_color(scattered_ray, world, depth + 1, rng) * attenuation - } else { - Vec3::new(0.0, 0.0, 0.0) - } - } - } else { - let unit_direction = ray.direction().unit_vector(); - let t = 0.5 * (unit_direction.y() + 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/src/demos/simple_light.rs b/src/demos/simple_light.rs new file mode 100644 index 0000000..ad544dd --- /dev/null +++ b/src/demos/simple_light.rs @@ -0,0 +1,81 @@ +use std::sync::Arc; + +use rand::{prelude::SmallRng, SeedableRng}; + +use crate::{ + demos::{Demo, ParallelHit}, + materials::{DiffuseLight, Lambertian}, + shapes::{Sphere, XyRectangle}, + texture::{PerlinNoise, Solid}, + types::Vec3, + BvhNode, Camera, +}; + +pub struct SimpleLight {} + +impl Demo for SimpleLight { + type DemoT = BvhNode>; + + fn name(&self) -> &'static str { + "simple_light" + } + + fn get_background(&self) -> Vec3 { + Vec3::new(0.0, 0.0, 0.0) + } + + fn world(&self) -> Self::DemoT { + let mut world: Vec> = Vec::with_capacity(5); + let mut rng = rand::thread_rng(); + let mut rng = SmallRng::from_rng(&mut rng).unwrap(); + + world.push(Arc::new(Sphere::new( + Vec3::new(0.0, -1000.0, 0.0), + 1000.0, + Lambertian::new(PerlinNoise::with_scale(&mut rng, 4.0)), + ))); + world.push(Arc::new(Sphere::new( + Vec3::new(0.0, 2.0, 0.0), + 2.0, + Lambertian::new(PerlinNoise::with_scale(&mut rng, 4.0)), + ))); + world.push(Arc::new(XyRectangle::new( + 3.0, + 5.0, + 1.0, + 3.0, + -2.0, + DiffuseLight::new(Solid::new(Vec3::new(4.0, 4.0, 4.0))), + ))); + world.push(Arc::new(Sphere::new( + Vec3::new(0.0, 7.0, 0.0), + 2.0, + DiffuseLight::new(Solid::new(Vec3::new(4.0, 4.0, 4.0))), + ))); + world.push(Arc::new(Sphere::new( + Vec3::new(-40.0, 2.0, 5.0), + 1.0, + DiffuseLight::new(Solid::new(Vec3::new(4.0, 4.0, 4.0))), + ))); + + BvhNode::new(&mut rng, &mut world, 0.0, 1.0) + } + + fn camera(&self, aspect_ratio: f64) -> crate::Camera { + let lookfrom = Vec3::new(26.0, 3.0, 6.0); + let lookat = Vec3::new(0.0, 2.0, 0.0); + let aperture = 0.0; + let focus_distance = 10.0; + Camera::new( + lookfrom, + lookat, + Vec3::new(0.0, 1.0, 0.0), + 20.0, + aspect_ratio, + aperture, + focus_distance, + 0.0, + 1.0, + ) + } +} diff --git a/src/hitable.rs b/src/hitable.rs index 7dba258..cc1c77d 100644 --- a/src/hitable.rs +++ b/src/hitable.rs @@ -30,6 +30,25 @@ pub struct HitRecord<'a> { pub v: f64, } +impl<'a> HitRecord<'a> { + pub fn new( + t: f64, + p: Vec3, + normal: Vec3, + material: &'a dyn Material, + (u, v): (f64, f64), + ) -> Self { + Self { + t, + p, + normal, + material, + u, + v, + } + } +} + pub trait Hitable { fn hit(&self, _ray: &Ray, _t_min: f64, _t_max: f64) -> Option; diff --git a/src/main.rs b/src/main.rs index c7cd902..331673f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,11 +23,11 @@ use demos::Demo; use std::time::Instant; -const NUM_SAMPLES: u8 = 100; +const NUM_SAMPLES: u16 = 1000; const VERTICAL_PARTITION: usize = 12; const HORIZONTAL_PARTITION: usize = 12; -const WIDTH: usize = 1920; -const HEIGHT: usize = 1080; +const WIDTH: usize = 2560; +const HEIGHT: usize = 1440; fn main() -> Result<(), String> { run(WIDTH, HEIGHT) @@ -67,8 +67,7 @@ fn run(mut width: usize, mut height: usize) -> Result<(), String> { .create_texture_static(PixelFormatEnum::BGR888, width as u32, height as u32) .map_err(|e| e.to_string())?; - let mut active_demo: &dyn Demo>> = - &demos::ImageTextureDemo {}; + let mut active_demo: &dyn Demo>> = &demos::SimpleLight {}; let mut should_update = true; loop { @@ -101,6 +100,10 @@ fn run(mut width: usize, mut height: usize) -> Result<(), String> { active_demo = &demos::ImageTextureDemo {}; should_update = true; } + Some(Keycode::Num5) => { + active_demo = &demos::SimpleLight {}; + should_update = true; + } None => unreachable!(), _ => (), }; diff --git a/src/materials/diffuse_light.rs b/src/materials/diffuse_light.rs new file mode 100644 index 0000000..ff3de7c --- /dev/null +++ b/src/materials/diffuse_light.rs @@ -0,0 +1,17 @@ +use crate::{types::Vec3, Material, Texture}; + +pub struct DiffuseLight { + emit: T, +} + +impl DiffuseLight { + pub fn new(emit: T) -> Self { + Self { emit } + } +} + +impl Material for DiffuseLight { + fn emit(&self, u: f64, v: f64, p: Vec3) -> Vec3 { + self.emit.value(u, v, p) + } +} diff --git a/src/materials/lambertian.rs b/src/materials/lambertian.rs index 78966e4..9a8c245 100644 --- a/src/materials/lambertian.rs +++ b/src/materials/lambertian.rs @@ -10,7 +10,7 @@ pub struct Lambertian { albedo: T, } -impl Lambertian { +impl Lambertian { pub fn new(albedo: T) -> Self { Self { albedo } } diff --git a/src/materials/metal.rs b/src/materials/metal.rs index a056f1f..1e4ae9e 100644 --- a/src/materials/metal.rs +++ b/src/materials/metal.rs @@ -12,6 +12,7 @@ pub struct Metal { } impl Metal { + #[allow(dead_code)] pub fn new(albedo: Vec3) -> Self { Self { albedo, fuzz: 0.0 } } diff --git a/src/materials/mod.rs b/src/materials/mod.rs index 0d22bd6..996f7ff 100644 --- a/src/materials/mod.rs +++ b/src/materials/mod.rs @@ -1,8 +1,10 @@ mod dielectric; +mod diffuse_light; mod lambertian; mod metal; pub use dielectric::Dielectric; +pub use diffuse_light::DiffuseLight; pub use lambertian::Lambertian; pub use metal::Metal; use rand::{prelude::SmallRng, Rng}; @@ -13,7 +15,20 @@ use crate::{ }; pub trait Material: Send + Sync { - fn scatter(&self, ray: &Ray, hit_rec: &HitRecord, rng: &mut SmallRng) -> (Vec3, Option); + // scatter returns the attenuation and the scattered ray. + // Attenuation is ignored completely if there is no scattered ray + fn scatter( + &self, + _ray: &Ray, + _hit_rec: &HitRecord, + _rng: &mut SmallRng, + ) -> (Vec3, Option) { + (Vec3::new(0.0, 0.0, 0.0), None) + } + + fn emit(&self, _u: f64, _v: f64, _p: Vec3) -> Vec3 { + Vec3::new(0.0, 0.0, 0.0) + } } // Christophe Schlick's Polynomial approximation to figure out reflectivity as the angle changes diff --git a/src/shapes/mod.rs b/src/shapes/mod.rs index 9b309a8..9a84ea6 100644 --- a/src/shapes/mod.rs +++ b/src/shapes/mod.rs @@ -1,5 +1,7 @@ mod moving_sphere; mod sphere; +mod xy_rectangle; pub use moving_sphere::MovingSphere; pub use sphere::Sphere; +pub use xy_rectangle::XyRectangle; diff --git a/src/shapes/moving_sphere.rs b/src/shapes/moving_sphere.rs index 3bc1d40..2c6a583 100644 --- a/src/shapes/moving_sphere.rs +++ b/src/shapes/moving_sphere.rs @@ -62,35 +62,21 @@ impl Hitable for MovingSphere { let discriminant_root = discriminant.sqrt(); if discriminant > 0.0 { - let root = (-b - discriminant_root) / a; - if root < t_max && root > t_min { - let p = ray.point_at_parameter(root); - let normal = (p - self.center(ray.time())) / self.radius; - let (u, v) = Self::get_uv(normal); - - return Some(HitRecord { - t: root, - p, - u, - v, - normal, - material: &self.material, - }); + let mut root = (-b - discriminant_root) / a; + if root < t_min || root > t_max { + root = (-b + discriminant_root) / a; } - let root = (-b + discriminant_root) / a; - if root < t_max && root > t_min { + if root > t_min && root < t_max { let p = ray.point_at_parameter(root); let normal = (p - self.center(ray.time())) / self.radius; - let (u, v) = Self::get_uv(normal); - return Some(HitRecord { - t: root, + return Some(HitRecord::new( + root, p, - u, - v, - normal: (p - self.center(ray.time())) / self.radius, - material: &self.material, - }); + normal, + &self.material, + Self::get_uv(normal), + )); } } None diff --git a/src/shapes/sphere.rs b/src/shapes/sphere.rs index 5be5190..1fad9b6 100644 --- a/src/shapes/sphere.rs +++ b/src/shapes/sphere.rs @@ -49,36 +49,21 @@ impl Hitable for Sphere { let discriminant_root = discriminant.sqrt(); if discriminant > 0.0 { - let root = (-b - discriminant_root) / a; - if root < t_max && root > t_min { - let p = ray.point_at_parameter(root); - let normal = (p - self.center) / self.radius; - let (u, v) = Self::get_uv(normal); - - return Some(HitRecord { - t: root, - p, - u, - v, - normal, - material: &self.material, - }); + let mut root = (-b - discriminant_root) / a; + if root < t_min || root > t_max { + root = (-b + discriminant_root) / a; } - - let root = (-b + discriminant_root) / a; - if root < t_max && root > t_min { + if root > t_min && root < t_max { let p = ray.point_at_parameter(root); let normal = (p - self.center) / self.radius; - let (u, v) = Self::get_uv(normal); - return Some(HitRecord { - t: root, + return Some(HitRecord::new( + root, p, - u, - v, - normal: (p - self.center) / self.radius, - material: &self.material, - }); + normal, + &self.material, + Self::get_uv(normal), + )); } } None diff --git a/src/shapes/xy_rectangle.rs b/src/shapes/xy_rectangle.rs new file mode 100644 index 0000000..29bfd8f --- /dev/null +++ b/src/shapes/xy_rectangle.rs @@ -0,0 +1,64 @@ +use crate::{ + types::{Ray, Vec3}, + Aabb, HitRecord, Hitable, Material, +}; + +pub struct XyRectangle { + x0: f64, + x1: f64, + y0: f64, + y1: f64, + k: f64, + material: T, +} + +impl XyRectangle { + pub fn new(x0: f64, x1: f64, y0: f64, y1: f64, k: f64, material: T) -> Self { + Self { + x0, + x1, + y0, + y1, + k, + material, + } + } +} + +impl Hitable for XyRectangle { + fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option { + let t = (self.k - ray.origin().z()) / ray.direction().z(); + + if t < t_min || t > t_max { + None + } else { + let x = ray.origin().x() + t * ray.direction().x(); + let y = ray.origin().y() + t * ray.direction().y(); + + if x < self.x0 || x > self.x1 || y < self.y0 || y > self.y1 { + None + } else { + let u = (x - self.x0) / (self.x1 - self.x0); + let v = (y - self.y0) / (self.y1 - self.y0); + + Some(HitRecord::new( + t, + ray.point_at_parameter(t), + Vec3::new(0.0, 0.0, 1.0), + &self.material, + (u, v), + )) + } + } + } + + fn bounding_box(&self, _t0: f64, _t1: f64) -> Option { + // Since this is a axis aligned Rectangle and we are using AABB BVH, Gap between the rectangle and + // the bounding box will be infinitely small + + Some(Aabb::new( + Vec3::new(self.x0, self.y0, self.k - 0.0001), + Vec3::new(self.x1, self.y1, self.k + 0.0001), + )) + } +} diff --git a/src/types/ray.rs b/src/types/ray.rs index c94a2b4..35a1613 100644 --- a/src/types/ray.rs +++ b/src/types/ray.rs @@ -1,4 +1,6 @@ -use crate::types::Vec3; +use rand::prelude::SmallRng; + +use crate::{types::Vec3, Hitable}; pub struct Ray { a: Vec3, @@ -26,4 +28,30 @@ impl Ray { pub const fn time(&self) -> f64 { self.time } + + pub fn color( + &self, + world: &T, + rng: &mut SmallRng, + background: &Vec3, + depth: u32, + ) -> Vec3 { + if let Some(hit_rec) = world.hit(self, 0.001, std::f64::MAX) { + if depth >= 50 { + Vec3::new(0.0, 0.0, 0.0) + } else { + let material = hit_rec.material; + let emitted_color = hit_rec.material.emit(hit_rec.u, hit_rec.v, hit_rec.p); + + if let (attenuation, Some(scattered_ray)) = material.scatter(self, &hit_rec, rng) { + emitted_color + + attenuation * scattered_ray.color(world, rng, background, depth + 1) + } else { + emitted_color + } + } + } else { + *background + } + } }