diff --git a/src/main.rs b/src/main.rs index acdcb19..6c6428b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,69 +2,47 @@ mod image; mod object; mod render; mod vec3; +mod material; -use image::Image; use object::Sphere; -use render::World; -use vec3::{Point3, Ray, Vec3}; +use render::{World,Camera}; +use vec3::{Point3,Vec3}; + +use material::{Metal,Lambertian,Material}; + -use crate::vec3::Interval; fn main() { let aspect_ratio = 16.0 / 9.0; let image_width = 800; - let image_height = (image_width as f64 / aspect_ratio) as i32; - let focal_length = 1.0; let viewport_height = 2.0; - let viewport_width = viewport_height * (image_width as f64 / image_height as f64); let camera_center = Point3::new(0.0, 0.0, 0.0); - let viewport_u = Vec3::new(viewport_width as f64, 0.0, 0.0); - let viewport_v = Vec3::new(0.0, -viewport_height as f64, 0.0); - let pixel_delta_u = viewport_u / image_width as f64; - let pixel_delta_v = viewport_v / image_height as f64; + let m = Lambertian::new(Vec3::new(0.5,0.5,0.5)); + let b: Box = Box::new(m); + let world_m = std::rc::Rc::new(b); - let viewport_upper_left = (camera_center.to_vec() - - Vec3::new(0.0, 0.0, focal_length) - - viewport_u / 2.0 - - viewport_v / 2.0) - .to_point(); + let m = Lambertian::new(Vec3::new(0.1, 0.2, 0.5)); + let b: Box = Box::new(m); + let center_m = std::rc::Rc::new(b); - let pixel00_loc = viewport_upper_left + ((pixel_delta_u + pixel_delta_v) * 0.5).to_point(); + let m = Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.3); + let b: Box = Box::new(m); + let left_m = std::rc::Rc::new(b); - let mut img = Image::new(image_width, image_height); + let m = Metal::new(Vec3::new(0.8, 0.6, 0.2), 0.5); + let b: Box = Box::new(m); + let right_m = std::rc::Rc::new(b); + + let mut camera = Camera::new(aspect_ratio,image_width,focal_length,viewport_height,camera_center); let mut world = World::new(); - world.add(Sphere::new(0.5, Point3::new(0.0, 0.0, -1.0))); - world.add(Sphere::new(100.0, Point3::new(0.0, -100.5, -1.0))); + world.add(Sphere::new(0.5, Point3::new(0.0, 0.0, -1.0),center_m)); + world.add(Sphere::new(0.5, Point3::new(-1.0, 0.0, -1.0),left_m)); + world.add(Sphere::new(0.5, Point3::new(1.0, 0.0, -1.0),right_m)); + world.add(Sphere::new(100.0, Point3::new(0.0, -100.5, -1.0),world_m)); - for y in 0..img.height { - for x in 0..img.width { - if let Some(px) = img.get_pixel(x, y) { - let pixel_center = pixel00_loc - + ((pixel_delta_u * x as f64) + (pixel_delta_v * y as f64)).to_point(); - let ray_direction = (pixel_center - camera_center).to_vec(); - let ray = Ray::new(camera_center, ray_direction); - let (r, g, b) = color_ray(ray); - let i = Interval::new(0.001,f64::INFINITY); - if let Some(hit) = world.hit(ray, i) { - let (r,g,b) = hit.color(); - px.set_color(r,g,b); - } else { - px.set_color(r, g, b); - } - } else { - } - } - } - img.save("./foo.ppm").unwrap(); -} - -fn color_ray(ray: Ray) -> (f64, f64, f64) { - let unit_direction = ray.direction().unit(); - let a = 0.5 * unit_direction.y() + 1.0; - let b = 1.0 - a; - (b + 0.5 * a, b + 0.7 * a, b + 1.0 * a) + camera.render(&world); } diff --git a/src/material.rs b/src/material.rs new file mode 100644 index 0000000..c7d0efb --- /dev/null +++ b/src/material.rs @@ -0,0 +1,92 @@ +use crate::vec3::{Ray,Vec3}; +use crate::object::HitRecord; + +use rand::prelude::*; + + +pub struct Scatter { + pub attenuation: Vec3, + pub ray: Ray, +} + +pub trait Material { + fn scatter(&self, r_in: &Ray, rec: &HitRecord, rng: &mut ThreadRng) -> Option; +} + + +#[derive(Clone, Copy)] +pub struct Metal { + pub albedo: Vec3, + pub fuzz: f64, +} + +impl Metal { + pub fn new(albedo: Vec3, f: f64) -> Self { + Self { + albedo, + fuzz: if f < 1.0 { f } else { 1.0 }, + } + } +} + +impl Material for Metal { + fn scatter(&self, r_in: &Ray, rec: &HitRecord, rng: &mut ThreadRng) -> Option { + let reflected = reflect(r_in.direction().unit(), rec.normal()); + let scattered_ray = Ray::new( + rec.point(), + reflected + random_in_unit_sphere(rng) * self.fuzz + ); + + if scattered_ray.direction().dot(rec.normal()) > 0.0 { + Some(Scatter { + attenuation: self.albedo, + ray: scattered_ray, + }) + } else { + None + } + } +} + +#[derive(Clone, Copy)] +pub struct Lambertian { + pub albedo: Vec3, +} + + +impl Lambertian { + pub fn new(albedo: Vec3) -> Self { + Self { + albedo, + } + } +} + +impl Material for Lambertian { + fn scatter(&self, _r_in: &Ray, rec: &HitRecord, rng: &mut ThreadRng) -> Option { + let target = rec.point().to_vec() + rec.normal() + random_in_unit_sphere(rng); + + Some(Scatter { + attenuation: self.albedo, + ray: Ray::new(rec.point(), target - rec.point().to_vec()), + }) + } +} + + +fn reflect(v: Vec3, n: Vec3) -> Vec3 { + v - n * 2.0 * v.dot(n) +} + +fn random_in_unit_sphere(rng: &mut ThreadRng) -> Vec3 { + loop { + let p = Vec3::new( + rng.random_range(-1.0..1.0), + rng.random_range(-1.0..1.0), + rng.random_range(-1.0..1.0), + ); + if p.length_squared() < 1.0 { + return p; + } + } +} diff --git a/src/object.rs b/src/object.rs index 26cfeb4..b3c5852 100644 --- a/src/object.rs +++ b/src/object.rs @@ -1,49 +1,54 @@ use crate::vec3::{Point3, Ray, Vec3, Interval}; +use crate::material::{Material,Scatter}; + +use rand::prelude::*; + pub trait Hittable { fn hit(&self, ray: Ray, interval:Interval) -> Option; } -#[derive(Clone, Copy)] pub struct HitRecord { point: Point3, normal: Vec3, root: f64, front_face: bool, - color: (f64,f64,f64) + material: std::rc::Rc>, } impl HitRecord { - pub fn normal(self) -> Vec3 { + pub fn normal(&self) -> Vec3 { self.normal } - pub fn point(self) -> Point3 { + pub fn point(&self) -> Point3 { self.point } - pub fn root(self) -> f64 { + pub fn root(&self) -> f64 { self.root } - pub fn color(self) -> (f64,f64,f64) { - self.color - } - pub fn set_face_normal(&mut self, ray: Ray, norm: Vec3) { self.front_face = ray.direction().dot(norm) < 0.0; self.normal = if self.front_face { norm } else { norm * -1.0 }; } + + pub fn scatter(&self, r_in: &Ray, rng: &mut ThreadRng) -> Option{ + self.material.scatter(r_in,&self,rng) + } + } pub struct Sphere { radius: f64, center: Point3, + material: std::rc::Rc> } impl Sphere { - pub fn new(radius: f64, center: Point3) -> Self { - Sphere { radius, center } + pub fn new(radius: f64, center: Point3, material:std::rc::Rc>) -> Self { + Sphere { radius, center , material} } } @@ -74,7 +79,7 @@ impl Hittable for Sphere { point, normal, front_face: false, - color: (normal.x(),normal.y(),normal.z()) + material: self.material.clone() }; tmp.set_face_normal(ray, normal); Some(tmp) diff --git a/src/render.rs b/src/render.rs index 404ae30..c28d22c 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,5 +1,8 @@ use crate::object::{HitRecord, Hittable}; -use crate::vec3::{Interval, Ray}; +use crate::vec3::{Interval, Ray, Vec3, Point3}; +use crate::image::Image; + +use rand::prelude::*; pub struct World { pub objects: Vec>, @@ -7,26 +10,129 @@ pub struct World { impl World { pub fn new() -> Self { - World { - objects: Vec::new(), - } + World { + objects: Vec::new(), + } } pub fn add(&mut self, object: impl Hittable + 'static) { - self.objects.push(Box::new(object)); + self.objects.push(Box::new(object)); } pub fn hit(&self, ray: Ray, interval:Interval) -> Option { - let mut hit_anything = None; - let mut closest_so_far = interval.max(); + let mut hit_anything = None; + let mut closest_so_far = interval.max(); - for object in &self.objects { + for object in &self.objects { let i = Interval::new(interval.min(),closest_so_far); - if let Some(record) = object.hit(ray, i) { - closest_so_far = record.root(); - hit_anything = Some(record); - } - } - hit_anything + if let Some(record) = object.hit(ray, i) { + closest_so_far = record.root(); + hit_anything = Some(record); + } + } + hit_anything } } + + +pub struct Camera { + img: Image, + pixel00_loc: Point3, + camera_center: Point3, + pixel_delta_u: Vec3, + pixel_delta_v: Vec3 +} + +impl Camera { + pub fn new(aspect_ratio:f64,image_width: i32, focal_length:f64, viewport_height:f64, camera_center: Point3) -> Self{ + + let image_height = (image_width as f64 / aspect_ratio) as i32; + + let viewport_width = viewport_height * (image_width as f64 / image_height as f64); + + let viewport_u = Vec3::new(viewport_width as f64, 0.0, 0.0); + let viewport_v = Vec3::new(0.0, -viewport_height as f64, 0.0); + + let pixel_delta_u = viewport_u / image_width as f64; + let pixel_delta_v = viewport_v / image_height as f64; + + let viewport_upper_left = (camera_center.to_vec() + - Vec3::new(0.0, 0.0, focal_length) + - viewport_u / 2.0 + - viewport_v / 2.0).to_point(); + + let pixel00_loc = viewport_upper_left + ((pixel_delta_u + pixel_delta_v) * 0.5).to_point(); + + let img = Image::new(image_width, image_height); + Self{img,pixel00_loc,camera_center,pixel_delta_u,pixel_delta_v} + } + + + pub fn render(&mut self, world: &World){ + let mut rng:ThreadRng = rand::rng(); + for y in 0..self.img.height { + for x in 0..self.img.width { + let (mut r,mut g,mut b) = (0.0,0.0,0.0); + let num_samples=10.0; + let depth=10; + for _ in 0..num_samples as i32{ + let ray = self.get_ray(x,y,&mut rng); + let (tr,tg,tb)= trace(ray, &world,depth,&mut rng); + r+= tr; + g+= tg; + b+= tb; + } + r /= num_samples; + g /= num_samples; + b /= num_samples; + + r = r.clamp(0.0,1.0).sqrt(); + g = g.clamp(0.0,1.0).sqrt(); + b = b.clamp(0.0,1.0).sqrt(); + if let Some(px) = self.img.get_pixel(x,y){ + px.set_color(r,g,b); + } else{/*ignore*/} + } + } + self.img.save("./foo.ppm").unwrap(); + } + + fn get_ray(&self, x:i32,y:i32,rng:&mut ThreadRng) -> Ray{ + let offset = 0.005; + let rnd_vec = Vec3::new(rng.random_range(-offset..offset),rng.random_range(-offset..offset),0.0); + let pixel_center = self.pixel00_loc + + ((self.pixel_delta_u * (x as f64 + rnd_vec.x())) + (self.pixel_delta_v * (y as f64 + rnd_vec.y()))).to_point(); + let ray_direction = (pixel_center - self.camera_center).to_vec(); + Ray::new(self.camera_center, ray_direction)//+rnd_vec) + } + +} + +fn trace(ray: Ray, world: &World, depth: i32, rng: &mut ThreadRng) -> (f64, f64, f64) { + let i = Interval::new(0.001, f64::INFINITY); + if depth <= 0 { + return (0.0, 0.0, 0.0); + } + + if let Some(hit) = world.hit(ray, i) { + if let Some(scatter_record) = hit.scatter(&ray, rng) { + let (tr, tg, tb) = trace(scatter_record.ray, world, depth - 1, rng); + let r = scatter_record.attenuation.x() * tr; + let g = scatter_record.attenuation.y() * tg; + let b = scatter_record.attenuation.z() * tb; + + return (r, g, b); + } else { + return (0.0, 0.0, 0.0); + } + } else { + color_ray(ray) + } +} + +fn color_ray(ray: Ray) -> (f64, f64, f64) { + let unit_direction = ray.direction().unit(); + let a = 0.5 * unit_direction.y() + 1.0; + let b = 1.0 - a; + (b + 0.5 * a, b + 0.7 * a, b + 1.0 * a) +}