diff --git a/rtiow/src/bin/tracer.rs b/rtiow/src/bin/tracer.rs index acb5500..7affaf2 100644 --- a/rtiow/src/bin/tracer.rs +++ b/rtiow/src/bin/tracer.rs @@ -6,30 +6,20 @@ use rand::Rng; use rtiow::camera::Camera; use rtiow::hitable::Hit; use rtiow::hitable_list::HitableList; +use rtiow::material::Lambertian; +use rtiow::material::Metal; use rtiow::ray::Ray; use rtiow::sphere::Sphere; use rtiow::vec3::Vec3; -fn random_in_unit_sphere() -> Vec3 { - let mut rng = rand::thread_rng(); - let v = Vec3::new(1., 1., 1.); - loop { - let p = 2. * Vec3::new( - rng.gen_range::(0., 1.), - rng.gen_range::(0., 1.), - rng.gen_range::(0., 1.), - ) - v; - if p.squared_length() < 1. { - return p; - } - } -} - -fn color(r: Ray, world: &Hit) -> Vec3 { +fn color(r: Ray, world: &Hit, depth: usize) -> Vec3 { if let Some(rec) = world.hit(r, 0.001, std::f32::MAX) { - let target = rec.p + rec.normal + random_in_unit_sphere(); - // 50% grey material - return 0.5 * color(Ray::new(rec.p, target - rec.p), world); + let scatter_response = rec.material.scatter(&r, &rec); + if depth < 50 && scatter_response.reflected { + return scatter_response.attenutation + * color(scatter_response.scattered, world, depth + 1); + } + return Default::default(); } // No hit, choose color from background. @@ -45,8 +35,26 @@ fn main() -> Result<(), std::io::Error> { let ns = 100; println!("P3\n{} {}\n255", nx, ny); let objects = vec![ - Sphere::new(Vec3::new(0., 0., -1.), 0.5), - Sphere::new(Vec3::new(0., -100.5, -1.), 100.), + Sphere::new( + Vec3::new(0., 0., -1.), + 0.5, + Box::new(Lambertian::new(Vec3::new(0.8, 0.3, 0.3))), + ), + Sphere::new( + Vec3::new(0., -100.5, -1.), + 100., + Box::new(Lambertian::new(Vec3::new(0.8, 0.8, 0.))), + ), + Sphere::new( + Vec3::new(1., 0., -1.), + 0.5, + Box::new(Metal::new(Vec3::new(0.8, 0.6, 0.2), 0.2)), + ), + Sphere::new( + Vec3::new(-1., 0., -1.), + 0.5, + Box::new(Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.8)), + ), ]; let cam = Camera::new2x1(); let world = HitableList::new(objects.iter().map(|o| o).collect()); @@ -57,7 +65,7 @@ fn main() -> Result<(), std::io::Error> { let u = (rng.gen_range::(0., 1.) + i as f32) / nx as f32; let v = (rng.gen_range::(0., 1.) + j as f32) / ny as f32; let r = cam.get_ray(u, v); - col = col + color(r, &world); + col = col + color(r, &world, 0); } col = col / ns as f32; // Gamma correct, use gamma 2 correction, which is 1/gamma where gamma=2 which is 1/2 diff --git a/rtiow/src/bin/tracer_anti_alias.rs b/rtiow/src/bin/tracer_anti_alias.rs deleted file mode 100644 index 50c790c..0000000 --- a/rtiow/src/bin/tracer_anti_alias.rs +++ /dev/null @@ -1,53 +0,0 @@ -extern crate rand; -extern crate rtiow; - -use rand::Rng; - -use rtiow::camera::Camera; -use rtiow::hitable::Hit; -use rtiow::hitable_list::HitableList; -use rtiow::ray::Ray; -use rtiow::sphere::Sphere; -use rtiow::vec3::Vec3; - -fn color(r: Ray, world: &Hit) -> Vec3 { - if let Some(rec) = world.hit(r, 0., std::f32::MAX) { - return (rec.normal + 1.) * 0.5; - } - - // No hit, choose color from background. - let unit_direction = r.direction().unit_vector(); - let t = 0.5 * (unit_direction.y + 1.); - Vec3::new(1., 1., 1.) * (1. - t) + Vec3::new(0.5, 0.7, 1.) * t -} - -fn main() -> Result<(), std::io::Error> { - let mut rng = rand::thread_rng(); - let nx = 200; - let ny = 100; - let ns = 100; - println!("P3\n{} {}\n255", nx, ny); - let objects = vec![ - Sphere::new(Vec3::new(0., 0., -1.), 0.5), - Sphere::new(Vec3::new(0., -100.5, -1.), 100.), - ]; - let cam = Camera::new2x1(); - let world = HitableList::new(objects.iter().map(|o| o).collect()); - for j in (0..ny).rev() { - for i in 0..nx { - let mut col: Vec3 = Default::default(); - for _ in 0..ns { - let u = (rng.gen_range::(0., 1.) + i as f32) / nx as f32; - let v = (rng.gen_range::(0., 1.) + j as f32) / ny as f32; - let r = cam.get_ray(u, v); - col = col + color(r, &world); - } - col = col / ns as f32; - let ir = (255.99 * col[0]) as u32; - let ig = (255.99 * col[1]) as u32; - let ib = (255.99 * col[2]) as u32; - println!("{} {} {}", ir, ig, ib); - } - } - Ok(()) -} diff --git a/rtiow/src/bin/tracer_diffuse.rs b/rtiow/src/bin/tracer_material.rs similarity index 60% rename from rtiow/src/bin/tracer_diffuse.rs rename to rtiow/src/bin/tracer_material.rs index acb5500..7affaf2 100644 --- a/rtiow/src/bin/tracer_diffuse.rs +++ b/rtiow/src/bin/tracer_material.rs @@ -6,30 +6,20 @@ use rand::Rng; use rtiow::camera::Camera; use rtiow::hitable::Hit; use rtiow::hitable_list::HitableList; +use rtiow::material::Lambertian; +use rtiow::material::Metal; use rtiow::ray::Ray; use rtiow::sphere::Sphere; use rtiow::vec3::Vec3; -fn random_in_unit_sphere() -> Vec3 { - let mut rng = rand::thread_rng(); - let v = Vec3::new(1., 1., 1.); - loop { - let p = 2. * Vec3::new( - rng.gen_range::(0., 1.), - rng.gen_range::(0., 1.), - rng.gen_range::(0., 1.), - ) - v; - if p.squared_length() < 1. { - return p; - } - } -} - -fn color(r: Ray, world: &Hit) -> Vec3 { +fn color(r: Ray, world: &Hit, depth: usize) -> Vec3 { if let Some(rec) = world.hit(r, 0.001, std::f32::MAX) { - let target = rec.p + rec.normal + random_in_unit_sphere(); - // 50% grey material - return 0.5 * color(Ray::new(rec.p, target - rec.p), world); + let scatter_response = rec.material.scatter(&r, &rec); + if depth < 50 && scatter_response.reflected { + return scatter_response.attenutation + * color(scatter_response.scattered, world, depth + 1); + } + return Default::default(); } // No hit, choose color from background. @@ -45,8 +35,26 @@ fn main() -> Result<(), std::io::Error> { let ns = 100; println!("P3\n{} {}\n255", nx, ny); let objects = vec![ - Sphere::new(Vec3::new(0., 0., -1.), 0.5), - Sphere::new(Vec3::new(0., -100.5, -1.), 100.), + Sphere::new( + Vec3::new(0., 0., -1.), + 0.5, + Box::new(Lambertian::new(Vec3::new(0.8, 0.3, 0.3))), + ), + Sphere::new( + Vec3::new(0., -100.5, -1.), + 100., + Box::new(Lambertian::new(Vec3::new(0.8, 0.8, 0.))), + ), + Sphere::new( + Vec3::new(1., 0., -1.), + 0.5, + Box::new(Metal::new(Vec3::new(0.8, 0.6, 0.2), 0.2)), + ), + Sphere::new( + Vec3::new(-1., 0., -1.), + 0.5, + Box::new(Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.8)), + ), ]; let cam = Camera::new2x1(); let world = HitableList::new(objects.iter().map(|o| o).collect()); @@ -57,7 +65,7 @@ fn main() -> Result<(), std::io::Error> { let u = (rng.gen_range::(0., 1.) + i as f32) / nx as f32; let v = (rng.gen_range::(0., 1.) + j as f32) / ny as f32; let r = cam.get_ray(u, v); - col = col + color(r, &world); + col = col + color(r, &world, 0); } col = col / ns as f32; // Gamma correct, use gamma 2 correction, which is 1/gamma where gamma=2 which is 1/2 diff --git a/rtiow/src/bin/tracer_multiple_spheres.rs b/rtiow/src/bin/tracer_multiple_spheres.rs deleted file mode 100644 index 132fcce..0000000 --- a/rtiow/src/bin/tracer_multiple_spheres.rs +++ /dev/null @@ -1,46 +0,0 @@ -extern crate rtiow; - -use rtiow::hitable::Hit; -use rtiow::hitable_list::HitableList; -use rtiow::ray::Ray; -use rtiow::sphere::Sphere; -use rtiow::vec3::Vec3; - -fn color(r: Ray, world: &Hit) -> Vec3 { - if let Some(rec) = world.hit(r, 0., std::f32::MAX) { - return (rec.normal + 1.) * 0.5; - } - - // No hit, choose color from background. - let unit_direction = r.direction().unit_vector(); - let t = 0.5 * (unit_direction.y + 1.); - Vec3::new(1., 1., 1.) * (1. - t) + Vec3::new(0.5, 0.7, 1.) * t -} - -fn main() -> Result<(), std::io::Error> { - let nx = 200; - let ny = 100; - println!("P3\n{} {}\n255", nx, ny); - let lower_left_corner = Vec3::new(-2., -1., -1.); - let horizontal = Vec3::new(4., 0., 0.); - let vertical = Vec3::new(0., 2., 0.); - let origin: Vec3 = Default::default(); - let objects = vec![ - Sphere::new(Vec3::new(0., 0., -1.), 0.5), - Sphere::new(Vec3::new(0., -100.5, -1.), 100.), - ]; - let world = HitableList::new(objects.iter().map(|o| o).collect()); - for j in (0..ny).rev() { - for i in 0..nx { - let u = i as f32 / nx as f32; - let v = j as f32 / ny as f32; - let r = Ray::new(origin, lower_left_corner + horizontal * u + vertical * v); - let col = color(r, &world); - let ir = (255.99 * col[0]) as u32; - let ig = (255.99 * col[1]) as u32; - let ib = (255.99 * col[2]) as u32; - println!("{} {} {}", ir, ig, ib); - } - } - Ok(()) -} diff --git a/rtiow/src/hitable.rs b/rtiow/src/hitable.rs index 86295ac..eeec7ea 100644 --- a/rtiow/src/hitable.rs +++ b/rtiow/src/hitable.rs @@ -1,11 +1,12 @@ +use material::Material; use ray::Ray; use vec3::Vec3; -#[derive(Copy, Clone)] -pub struct HitRecord { +pub struct HitRecord<'m> { pub t: f32, pub p: Vec3, pub normal: Vec3, + pub material: &'m Material, } pub trait Hit { diff --git a/rtiow/src/lib.rs b/rtiow/src/lib.rs index a9d5303..01ecb3e 100644 --- a/rtiow/src/lib.rs +++ b/rtiow/src/lib.rs @@ -1,6 +1,7 @@ pub mod camera; pub mod hitable; pub mod hitable_list; +pub mod material; pub mod ray; pub mod sphere; pub mod vec3; diff --git a/rtiow/src/material.rs b/rtiow/src/material.rs new file mode 100644 index 0000000..f65b3ce --- /dev/null +++ b/rtiow/src/material.rs @@ -0,0 +1,81 @@ +extern crate rand; +use self::rand::Rng; + +use hitable::HitRecord; +use ray::Ray; +use vec3::dot; +use vec3::Vec3; + +fn random_in_unit_sphere() -> Vec3 { + let mut rng = rand::thread_rng(); + let v = Vec3::new(1., 1., 1.); + loop { + let p = 2. * Vec3::new( + rng.gen_range::(0., 1.), + rng.gen_range::(0., 1.), + rng.gen_range::(0., 1.), + ) - v; + if p.squared_length() < 1. { + return p; + } + } +} + +pub struct ScatterResponse { + pub scattered: Ray, + pub attenutation: Vec3, + pub reflected: bool, +} + +pub trait Material { + fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> ScatterResponse; +} + +pub struct Lambertian { + albedo: Vec3, +} + +impl Lambertian { + pub fn new(albedo: Vec3) -> Lambertian { + Lambertian { albedo } + } +} + +impl Material for Lambertian { + fn scatter(&self, _r_in: &Ray, rec: &HitRecord) -> ScatterResponse { + let target = rec.p + rec.normal + random_in_unit_sphere(); + ScatterResponse { + attenutation: self.albedo, + scattered: Ray::new(rec.p, target - rec.p), + reflected: true, + } + } +} + +pub struct Metal { + albedo: Vec3, + fuzzy: f32, +} + +fn reflect(v: Vec3, n: Vec3) -> Vec3 { + v - 2. * dot(v, n) * n +} + +impl Metal { + pub fn new(albedo: Vec3, fuzzy: f32) -> Metal { + let fuzzy = fuzzy.min(1.); + Metal { albedo, fuzzy } + } +} + +impl Material for Metal { + fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> ScatterResponse { + let reflected = reflect(r_in.direction().unit_vector(), rec.normal); + let scattered = Ray::new(rec.p, reflected + self.fuzzy * random_in_unit_sphere()); + ScatterResponse { + scattered, + attenutation: self.albedo, + reflected: dot(scattered.direction(), rec.normal) > 0., + } + } +} diff --git a/rtiow/src/sphere.rs b/rtiow/src/sphere.rs index d0a900c..c118012 100644 --- a/rtiow/src/sphere.rs +++ b/rtiow/src/sphere.rs @@ -1,18 +1,23 @@ use hitable::Hit; use hitable::HitRecord; +use material::Material; use ray::Ray; use vec3::dot; use vec3::Vec3; -#[derive(Copy, Clone, Default)] pub struct Sphere { center: Vec3, radius: f32, + material: Box, } impl Sphere { - pub fn new(center: Vec3, radius: f32) -> Sphere { - Sphere { center, radius } + pub fn new(center: Vec3, radius: f32, material: Box) -> Sphere { + Sphere { + center, + radius, + material, + } } } @@ -31,6 +36,7 @@ impl Hit for Sphere { t: temp, p, normal: (p - self.center) / self.radius, + material: &*self.material, }); } let temp = (-b + (b * b - a * c).sqrt()) / a; @@ -40,6 +46,7 @@ impl Hit for Sphere { t: temp, p, normal: (p - self.center) / self.radius, + material: &*self.material, }); } } diff --git a/rtiow/src/vec3.rs b/rtiow/src/vec3.rs index fd70a7e..47197c9 100644 --- a/rtiow/src/vec3.rs +++ b/rtiow/src/vec3.rs @@ -40,12 +40,7 @@ impl Vec3 { } pub fn unit_vector(self) -> Vec3 { - let k = 1. / self.length(); - Vec3 { - x: self.x / k, - y: self.y / k, - z: self.z / k, - } + self / self.length() } pub fn make_unit_vector(&mut self) {