diff --git a/src/demos/cornell_box.rs b/src/demos/cornell_box.rs index dd3c232..e400c98 100644 --- a/src/demos/cornell_box.rs +++ b/src/demos/cornell_box.rs @@ -1,19 +1,16 @@ use std::sync::Arc; -use rand::{prelude::SmallRng, Rng, SeedableRng}; - use crate::{ demos::Demo, hitable::{ hitable_list::HitableList, - shapes::{Cuboid, MovingSphere, RectBuilder, Sphere}, - volume::ConstantMedium, + shapes::{Cuboid, RectBuilder, Sphere}, Hitable, }, - materials::{Dielectric, DiffuseLight, Isotropic, Lambertian, MaterialBuilder, Metal}, - texture::{ImageTexture, PerlinNoise, Solid}, + materials::{DiffuseLight, Lambertian, MaterialBuilder, Metal}, + texture::Solid, types::Vec3, - BvhNode, Camera, + Camera, }; pub struct CornellBox {} @@ -26,125 +23,79 @@ impl Demo for CornellBox { } fn world(&self) -> Self::DemoT { - let mut rng = rand::thread_rng(); - let mut rng = SmallRng::from_rng(&mut rng).unwrap(); - - let mut ground_boxes = HitableList { list: Vec::new() }; - let ground = Lambertian::new(Solid::new(Vec3::new(0.48, 0.83, 0.53))); - - for i in 0..20 { - let i = i as f64; - for j in 0..20 { - let j = j as f64; - - let w = 100.0; - let x0 = -1000.0 + i * w; - let z0 = -1000.0 + j * w; - let y0 = 0.0; - - let x1 = x0 + w; - let y1 = rng.gen_range(1.0..=101.0); - let z1 = z0 + w; - - ground_boxes.push(Arc::new(Cuboid::new( - Vec3::new(x0, y0, z0), - Vec3::new(x1, y1, z1), - ground.clone(), - ))); - } - } + let red = Lambertian::new(Solid::new(Vec3::new(0.65, 0.05, 0.05))); + let white = Lambertian::new(Solid::new(Vec3::new(0.73, 0.73, 0.73))); + let green = Lambertian::new(Solid::new(Vec3::new(0.12, 0.45, 0.15))); + let light = DiffuseLight::new(Solid::new(Vec3::splat(15))); let mut objects = HitableList { list: Vec::new() }; - objects.push(Arc::new(BvhNode::new( - &mut rng, - &mut ground_boxes.list, - 0.0, - 1.0, - ))); - let light = DiffuseLight::new(Solid::new(Vec3::splat(7.0))); objects.push(Arc::new( RectBuilder - .x(123.0..=423.0) - .z(147.0..=412.0) + .y(0.0..=555.0) + .z(0.0..=555.0) + .x(555.0) + .material(green), + )); + objects.push(Arc::new( + RectBuilder + .y(0.0..=555.0) + .z(0.0..=555.0) + .x(0.0) + .material(red), + )); + objects.push(Arc::new( + RectBuilder + .x(213.0..=343.0) + .z(227.0..=332.0) .y(554.0) .material(light), )); - - let center1 = Vec3::new(400.0, 400.0, 200.0); - let center2 = center1 + Vec3::new(30.0, 0.0, 0.0); - objects.push(Arc::new(MovingSphere::new( - center1, - center2, - 0.0, - 1.0, - 50.0, - Lambertian::new(Solid::new(Vec3::new(0.7, 0.3, 0.1))), - ))); - - objects.push(Arc::new(Sphere::new( - Vec3::new(260.0, 150.0, 45.0), - 50.0, - Dielectric::new(1.5), - ))); - - objects.push(Arc::new(Sphere::new( - Vec3::new(0.0, 150.0, 145.0), - 50.0, - Metal::with_fuzz(Vec3::new(0.8, 0.8, 0.9), 1.0), - ))); - - let boundary = Sphere::new(Vec3::new(360.0, 150.0, 145.0), 70.0, Dielectric::new(1.5)); - objects.push(Arc::new(boundary.clone())); - objects.push(Arc::new(ConstantMedium::new( - boundary, - Isotropic::new(Solid::new(Vec3::new(0.2, 0.4, 0.9))), - 0.2, - ))); - - objects.push(Arc::new(ConstantMedium::new( - Sphere::new(Vec3::splat(0.0), 5000.0, Dielectric::new(1.5)), - Isotropic::new(Solid::new(Vec3::splat(1.0))), - 0.0001, - ))); - - let earthmap = ImageTexture::from_filename("assets/earthmap.jpg") - .expect("error in reading assets/earthmap.jpg"); - objects.push(Arc::new(Sphere::new( - Vec3::new(400.0, 200.0, 400.0), - 100.0, - Lambertian::new(earthmap), - ))); - - objects.push(Arc::new(Sphere::new( - Vec3::new(220.0, 280.0, 300.0), - 80.0, - Lambertian::new(PerlinNoise::with_scale(&mut rng, 0.1)), - ))); - - let mut boxes2 = HitableList { list: Vec::new() }; - let white = Lambertian::new(Solid::new(Vec3::splat(0.73))); - for _ in 0..1000 { - boxes2.push(Arc::new(Sphere::new( - Vec3::random_in_range(&mut rng, 0.0..=165.0), - 10.0, - white.clone(), - ))); - } + objects.push(Arc::new( + RectBuilder + .x(0.0..=555.0) + .z(0.0..=555.0) + .y(555.0) + .material(white.clone()), + )); + objects.push(Arc::new( + RectBuilder + .x(0.0..=555.0) + .z(0.0..=555.0) + .y(0.0) + .material(white.clone()), + )); + objects.push(Arc::new( + RectBuilder + .x(0.0..=555.0) + .y(0.0..=555.0) + .z(555.0) + .material(white.clone()), + )); objects.push(Arc::new( - BvhNode::new(&mut rng, &mut boxes2.list, 0.0, 1.0) - .rotate_y(15.0) - .translate(Vec3::new(-100.0, 270.0, 395.0)), + Cuboid::new( + Vec3::splat(0.0), + Vec3::new(165.0, 330.0, 165.0), + white.clone(), + ) + .rotate_y(15.0) + .translate(Vec3::new(265.0, 0.0, 295.0)), + )); + + objects.push(Arc::new( + Cuboid::new(Vec3::splat(0.0), Vec3::splat(165.0), white) + .rotate_y(-18.0) + .translate(Vec3::new(130.0, 0.0, 65.0)), )); objects } fn camera(&self, aspect_ratio: f64) -> Camera { - let lookfrom = Vec3::new(478.0, 278.0, -600.0); + let lookfrom = Vec3::new(278.0, 278.0, -800.0); let lookat = Vec3::new(278.0, 278.0, 0.0); - let aperture = 0.1; + let aperture = 0.0; let focus_distance = 40.0; Camera::new( lookfrom, diff --git a/src/main.rs b/src/main.rs index 9929a80..7e47d93 100644 --- a/src/main.rs +++ b/src/main.rs @@ -137,6 +137,8 @@ fn run(mut width: usize, mut height: usize) -> Result<(), String> { } if should_update { let now = Instant::now(); + // TODO(ishan): Update it to only re-render if height/width has changed + // this block should run if the app was sent to background active_demo.render(&mut buffer, width, height, NUM_SAMPLES); println!( "Demo {} Time Taken(s) = {}", diff --git a/src/materials/dielectric.rs b/src/materials/dielectric.rs index f99bd5e..e619bf0 100644 --- a/src/materials/dielectric.rs +++ b/src/materials/dielectric.rs @@ -24,7 +24,7 @@ impl Material for Dielectric { ray_in: &Ray, hit_rec: &HitRecord, rng: &mut SmallRng, - ) -> (Vec3, Option) { + ) -> (Vec3, f64, Option) { // Glass absorbs nothing! So, Attenuation is always going to be 1.0 for this let attenuation = Vec3::splat(1.0); @@ -44,17 +44,20 @@ impl Material for Dielectric { let direction = reflect(unit_direction, hit_rec.normal); ( attenuation, + 0.0, Some(Ray::new(hit_rec.p, direction, ray_in.time())), ) } else if let Some(direction) = refract(unit_direction, hit_rec.normal, refraction_ratio) { ( attenuation, + 0.0, Some(Ray::new(hit_rec.p, direction, ray_in.time())), ) } else { let direction = reflect(unit_direction, hit_rec.normal); ( attenuation, + 0.0, Some(Ray::new(hit_rec.p, direction, ray_in.time())), ) } diff --git a/src/materials/isotropic.rs b/src/materials/isotropic.rs index 8440c75..1998c3f 100644 --- a/src/materials/isotropic.rs +++ b/src/materials/isotropic.rs @@ -18,9 +18,15 @@ impl Isotropic { } impl Material for Isotropic { - fn scatter(&self, ray: &Ray, hit_rec: &HitRecord, rng: &mut SmallRng) -> (Vec3, Option) { + fn scatter( + &self, + ray: &Ray, + hit_rec: &HitRecord, + rng: &mut SmallRng, + ) -> (Vec3, f64, Option) { ( self.texture.value(hit_rec.u, hit_rec.v, hit_rec.p), + 0.0, Some(Ray::new( hit_rec.p, random_point_in_unit_sphere(rng), diff --git a/src/materials/lambertian.rs b/src/materials/lambertian.rs index ff46239..118b2bd 100644 --- a/src/materials/lambertian.rs +++ b/src/materials/lambertian.rs @@ -2,7 +2,7 @@ use rand::prelude::SmallRng; use crate::{ hitable::HitRecord, - materials::random_point_in_unit_sphere, + materials::{random_point_in_unit_hemisphere, random_point_in_unit_sphere}, types::{Ray, Vec3}, Material, Texture, }; @@ -19,13 +19,29 @@ impl Lambertian { } impl Material for Lambertian { - fn scatter(&self, ray: &Ray, hit_rec: &HitRecord, rng: &mut SmallRng) -> (Vec3, Option) { - let scatter_direction = hit_rec.normal + random_point_in_unit_sphere(rng); - let scattered_ray = Ray::new(hit_rec.p, scatter_direction, ray.time()); + fn scatter( + &self, + ray: &Ray, + hit_rec: &HitRecord, + rng: &mut SmallRng, + ) -> (Vec3, f64, Option) { + let direction = random_point_in_unit_hemisphere(rng, &hit_rec.normal); + let scattered_ray = Ray::new(hit_rec.p, direction.unit_vector(), ray.time()); ( self.albedo.value(hit_rec.u, hit_rec.v, hit_rec.p), + 0.5 / std::f64::consts::PI, Some(scattered_ray), ) } + + fn scatter_pdf(&self, _ray: &Ray, hit_rec: &HitRecord, scatterd: &Ray) -> f64 { + let cosine: f64 = hit_rec.normal.dot(&scatterd.direction.unit_vector()); + + if cosine < 0.0 { + 0.0 + } else { + cosine / std::f64::consts::PI + } + } } diff --git a/src/materials/metal.rs b/src/materials/metal.rs index 0023a63..6a695f7 100644 --- a/src/materials/metal.rs +++ b/src/materials/metal.rs @@ -29,7 +29,7 @@ impl Material for Metal { ray_in: &Ray, hit_rec: &HitRecord, rng: &mut SmallRng, - ) -> (Vec3, Option) { + ) -> (Vec3, f64, Option) { let reflected_ray = reflect(ray_in.direction.unit_vector(), hit_rec.normal); let scattered_ray = Ray::new( hit_rec.p, @@ -38,9 +38,9 @@ impl Material for Metal { ); if scattered_ray.direction.dot(&hit_rec.normal) > 0.0 { - (self.albedo, Some(scattered_ray)) + (self.albedo, 0.0, Some(scattered_ray)) } else { - (self.albedo, None) + (self.albedo, 0.0, None) } } } diff --git a/src/materials/mod.rs b/src/materials/mod.rs index 9904cd0..6ca2e72 100644 --- a/src/materials/mod.rs +++ b/src/materials/mod.rs @@ -24,8 +24,12 @@ pub trait Material: Send + Sync { _ray: &Ray, _hit_rec: &HitRecord, _rng: &mut SmallRng, - ) -> (Vec3, Option) { - (Vec3::splat(0.0), None) + ) -> (Vec3, f64, Option) { + (Vec3::splat(0.0), 0.0, None) + } + + fn scatter_pdf(&self, _ray: &Ray, _hit_rec: &HitRecord, _scattered: &Ray) -> f64 { + 0.0 } fn emit(&self, _u: f64, _v: f64, _p: Vec3) -> Vec3 { @@ -58,11 +62,41 @@ fn refract(incident: Vec3, normal: Vec3, ni_over_nt: f64) -> Option { } fn random_point_in_unit_sphere(rng: &mut R) -> Vec3 { - let mut point = Vec3::random(rng) * 2.0 - Vec3::splat(1.0); - while point.sq_len() >= 1.0 { - point = Vec3::random(rng) * 2.0 - Vec3::splat(1.0); + let u: f64 = rng.gen(); + let v: f64 = rng.gen(); + + let theta = u * 2.0 * std::f64::consts::PI; + let phi = (2.0 * v - 1.0).acos(); + + let radius = rng.gen::().cbrt(); + + let x = radius * phi.sin() * theta.cos(); + let y = radius * phi.sin() * theta.sin(); + let z = radius * phi.cos(); + + Vec3::new(x, y, z) +} + +fn random_point_in_unit_hemisphere(rng: &mut R, normal: &Vec3) -> Vec3 { + let u: f64 = rng.gen(); + let v: f64 = rng.gen(); + + let theta = u * 2.0 * std::f64::consts::PI; + let phi = (v.sqrt()).acos(); + + let radius = rng.gen::().cbrt(); + + let x = radius * phi.sin() * theta.cos(); + let y = radius * phi.sin() * theta.sin(); + let z = radius * phi.cos(); + + let point = Vec3::new(x, y, z); + + if point.dot(normal) >= 0.0 { + point + } else { + -point } - point } pub trait MaterialBuilder { diff --git a/src/types/ray.rs b/src/types/ray.rs index d38d153..b2f4826 100644 --- a/src/types/ray.rs +++ b/src/types/ray.rs @@ -40,9 +40,14 @@ impl Ray { 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) { + if let (attenuation, pdf, Some(scattered_ray)) = + material.scatter(self, &hit_rec, rng) + { emitted_color - + attenuation * scattered_ray.color(world, rng, background, depth + 1) + + attenuation + * material.scatter_pdf(self, &hit_rec, &scattered_ray) + * scattered_ray.color(world, rng, background, depth + 1) + / pdf } else { emitted_color } diff --git a/src/types/simd_vec3.rs b/src/types/simd_vec3.rs index 51ba16a..aa6ada3 100644 --- a/src/types/simd_vec3.rs +++ b/src/types/simd_vec3.rs @@ -47,6 +47,11 @@ impl Vec3 { self.get::() } + #[inline] + pub fn abs(&self) -> Self { + Vec3::new(self.x().abs(), self.y().abs(), self.z().abs()) + } + pub fn get(&self) -> f64 { unsafe { self.0.extract_unchecked(D::INDEX) } }