diff --git a/.gitignore b/.gitignore index 80942b8..bb580b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /target *.ppm +*.jpg +*.png flamegraph.svg -perf.data* \ No newline at end of file +perf.data* diff --git a/src/hittable.rs b/src/hittable.rs index 07712ea..e0215ce 100644 --- a/src/hittable.rs +++ b/src/hittable.rs @@ -1,17 +1,25 @@ -use super::Point3; -use super::Ray; -use super::Vec3; +use super::{ + Point3, + Vec3, + Color, + Ray, + Material, + Arc, + Metal +}; pub struct HitRecord { pub p: Point3, pub normal: Vec3, + pub mat_ptr: Arc, pub t: f64, - front_face: bool, + pub front_face: bool, } pub struct Sphere { center: Point3, radius: f64, + pub mat_ptr: Arc, } pub trait Hittable: Sync + Send { @@ -28,10 +36,11 @@ impl HitRecord { } } - pub fn new(p: Point3, normal: Vec3, t: f64, front_face: bool) -> Self { + pub fn new(p: Point3, normal: Vec3, m: Arc, t: f64, front_face: bool) -> Self { HitRecord { p: p, normal: normal, + mat_ptr: m, t: t, front_face: front_face, } @@ -39,8 +48,9 @@ impl HitRecord { pub fn empty() -> Self { Self::new( - Point3::new(0.0, 0.0, 0.0), - Vec3::new(0.0, 0.0, 0.0), + Point3::null(), + Vec3::null(), + Arc::new(Metal::new(&Color::null())), 0.0, false, ) @@ -48,10 +58,11 @@ impl HitRecord { } impl Sphere { - pub fn new(cen: Point3, r: f64) -> Self { + pub fn new(cen: Point3, r: f64, m: Arc) -> Self { Sphere { center: cen, radius: r, + mat_ptr: m, } } } @@ -70,12 +81,12 @@ impl Hittable for Sphere { let sqrtd = discriminant.sqrt(); - let root = (-half_b - sqrtd) / a; + let mut root = (-half_b - sqrtd) / a; let normal = (r.at(root) - self.center) / self.radius; if root < t_min || t_max < root || Vec3::dot(normal, r.direction()) > 0.0 { - let root2 = (-half_b + sqrtd) / a; - let normal = (r.at(root2) - self.center) / self.radius; - if root2 < t_min || t_max < root || Vec3::dot(normal, r.direction()) > 0.0 { + root = (-half_b + sqrtd) / a; + let normal = (r.at(root) - self.center) / self.radius; + if root < t_min || t_max < root || Vec3::dot(normal, r.direction()) > 0.0 { return false; } } @@ -84,6 +95,7 @@ impl Hittable for Sphere { rec.p = r.at(rec.t); let outward_normal = (rec.p - self.center) / self.radius; rec.set_face_normal(r, outward_normal); + rec.mat_ptr = self.mat_ptr.clone(); return true; } diff --git a/src/main.rs b/src/main.rs index 54d96b4..9e92e40 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ mod hittable_list; mod ray; mod utility; mod vec3; +mod material; use camera::Camera; use hittable::{HitRecord, Hittable, Sphere}; @@ -18,17 +19,25 @@ use std::env; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; use vec3::{Color, Point3, Vec3}; +use material::{Material, Lambertian, Metal}; fn ray_color(r: &Ray, world: &HittableList, depth: u32) -> Color { let mut rec = HitRecord::empty(); if depth <= 0 { - return Color::new(0.0, 0.0, 0.0); + return Color::null(); } - if world.hit(r, 0.0, f64::INFINITY, &mut rec) { - let target = rec.p + rec.normal + Vec3::random_unit_vector(); // rec.p + rec.normal.random_in_hemisphere(); - return 0.5 * ray_color(&Ray::new(rec.p, target - rec.p), world, depth - 1); + if world.hit(r, 0.001, f64::INFINITY, &mut rec) { + let mut scattered = Ray::new(Point3::null(), Vec3::null()); + let mut attenuation = Color::null(); + if rec.mat_ptr.scatter(r, &rec, &mut attenuation, &mut scattered) { + return attenuation * ray_color(&scattered, world, depth-1); + } + return Color::null(); + + //let target = rec.p + rec.normal + Vec3::random_unit_vector(); // rec.p + rec.normal.random_in_hemisphere(); + //return 0.5 * ray_color(&Ray::new(rec.p, target - rec.p), world, depth - 1); } let unit_direction = r.direction(); let t = 0.5 * (unit_direction.y() + 1.0); @@ -41,32 +50,46 @@ fn main() { // Image let aspect_ratio = 16.0 / 9.0; - let image_width = 1000; + let image_width = 1920; let image_height = (image_width as f64 / aspect_ratio) as u32; let samples_per_pixel = 100_u32; let max_depth = 50; // World let mut world = HittableList::new(); - world.add(Box::::new(Sphere::new( - Point3::new(0.0, 0.0, -1.0), - 0.5, - ))); + + let material_ground = Arc::new(Lambertian::new(&Color::new(0.8, 0.8, 0.0))); + let material_center = Arc::new(Lambertian::new(&Color::new(0.7, 0.1, 0.2))); + let material_left = Arc::new(Metal::new(&Color::new(0.8, 0.8, 0.8))); + let material_right = Arc::new(Metal::new(&Color::new(0.8, 0.6, 0.2))); + let material_mirror = Arc::new(Metal::new(&Color::new(0.9, 0.9, 0.9))); + + + world.add(Box::::new(Sphere::new( Point3::new(0.0, -100.5, -1.0), 100.0, + material_ground.clone(), ))); world.add(Box::::new(Sphere::new( - Point3::new(1.0, 0.0, -1.5), - 0.3, + Point3::new(0.0, 0.0, -1.0), + 0.5, + material_center.clone(), ))); world.add(Box::::new(Sphere::new( - Point3::new(1.0, 1.1, -1.5), - 0.3, + Point3::new(-1.0, 0.0, -1.0), + 0.5, + material_mirror.clone(), ))); world.add(Box::::new(Sphere::new( - Point3::new(-1.0, 1.1, -1.5), - 0.3, + Point3::new(1.0, 0.0, -1.0), + 0.5, + material_mirror.clone(), + ))); + world.add(Box::::new(Sphere::new( + Point3::new(-1.0, 1.1, -1.2), + 0.4, + material_left.clone(), ))); /* diff --git a/src/material.rs b/src/material.rs new file mode 100644 index 0000000..0f003ea --- /dev/null +++ b/src/material.rs @@ -0,0 +1,63 @@ +use super::{ + Ray, + HitRecord, + Color, + Vec3, +}; + +pub struct Lambertian { + albedo: Color, +} + +pub struct Metal { + albedo: Color, +} + +pub trait Material: Sync + Send { + fn scatter(&self,r_in: &Ray, rec: &HitRecord, attenuation: &mut Color, scattered: &mut Ray) -> bool; +} + +impl Lambertian { + pub fn new(a: &Color) -> Self { + Lambertian { + albedo: a.clone(), + } + } +} + +impl Material for Lambertian { + fn scatter(&self, _r_in: &Ray, rec: &HitRecord, attenuation: &mut Color, scattered: &mut Ray) -> bool { + let mut scatter_direction = rec.normal + Vec3::random_unit_vector(); + + if scatter_direction.near_zero() { + scatter_direction = rec.normal; + } + + *scattered = Ray::new(rec.p, scatter_direction); + *attenuation = self.albedo.clone(); + return true; + } +} + +impl Metal { + pub fn new(a: &Color) -> Self { + Metal { + albedo: *a, + } + } + + pub fn clone(&self) -> Self { + Self::new(&self.albedo.clone()) + } +} + +impl Material for Metal { + fn scatter(&self, r_in: &Ray, rec: &HitRecord, attenuation: &mut Color, scattered: &mut Ray) -> bool { + let reflected = Vec3::reflect(&Vec3::unit_vector(r_in.direction()), &rec.normal); + + *scattered = Ray::new(rec.p, reflected); + *attenuation = self.albedo.clone(); + + return Vec3::dot(scattered.direction(), rec.normal) > 0.0; + } +} \ No newline at end of file diff --git a/src/vec3.rs b/src/vec3.rs index dde0d13..2e0bbed 100644 --- a/src/vec3.rs +++ b/src/vec3.rs @@ -14,6 +14,14 @@ impl Vec3 { Vec3 { e: [x, y, z] } } + pub fn null() -> Self { + Self::new(0.0, 0.0, 0.0) + } + + pub fn clone(&self) -> Self { + Self::new(self.x(), self.y(), self.z()) + } + pub fn length(&self) -> f64 { self.length_squared().sqrt() } @@ -38,6 +46,15 @@ impl Vec3 { a / a.length() } + pub fn reflect(v: &Self, n: &Self) -> Self { + *v - 2.0*Self::dot(*v, *n) * *(n) + } + + pub fn near_zero(&self) -> bool { + let s = 1e-8; + return (self.e[0].abs() < s) && (self.e[1].abs() < s) && (self.e[2].abs() < s); + } + pub fn random_f64() -> Vec3 { Vec3::new( utility::random_f64(), @@ -128,6 +145,14 @@ impl Mul for f64 { } } +impl Mul for Vec3 { + type Output = Self; + + fn mul(self, rhs: Self) -> Self { + Self::new(self.x()*rhs.x(), self.y()*rhs.y(), self.z()*rhs.z()) + } +} + impl MulAssign for Vec3 { fn mul_assign(&mut self, rhs: f64) { self.e[0] *= rhs;