M src/hittable.rs => src/hittable.rs +3 -1
@@ 1,10 1,12 @@
use crate::ray::*;
use crate::vec3::*;
+use crate::MaterialPtr;
-#[derive(Debug, Clone, Copy, Default)]
+#[derive(Debug, Clone)]
pub struct HitRecord {
pub p: Point3,
pub normal: Vec3,
+ pub mat_ptr: MaterialPtr,
pub t: f64,
pub front_face: bool,
}
M src/main.rs => src/main.rs +33 -4
@@ 21,6 21,9 @@ use rtweekend::*;
mod camera;
use camera::*;
+mod material;
+use material::*;
+
fn hit_sphere(center: &Point3, radius: f64, r: &Ray) -> f64 {
let oc = r.origin() - *center;
let a = r.direction().length_squared();
@@ 41,8 44,10 @@ fn ray_color(r: &Ray, world: &dyn Hittable, depth: isize) -> Color {
return Color::default();
}
if let Some(rec) = world.hit(r, 0.001, INFINITY) {
- let target = rec.p + rec.normal + random_unit_vector();
- return 0.5 * ray_color(&Ray::new(&rec.p, &(target - rec.p)), world, depth - 1);
+ if let Some((attenuation, scattered)) = rec.mat_ptr.scatter(&r, &rec) {
+ return attenuation * ray_color(&scattered, world, depth - 1);
+ }
+ return Color::default();
}
let unit_direction = unit_vector(r.direction());
let t = 0.5 * (unit_direction.y() + 1.0);
@@ 56,6 61,7 @@ fn main() {
// Image
let aspect_ratio = 16.0 / 9.0;
let image_width = 400;
+ // let image_width = 1000;
let image_width = 1600;
let image_height = (image_width as f64 / aspect_ratio) as i32;
let samples_per_pixel = 100;
@@ 63,8 69,31 @@ fn main() {
// World
let mut world = HittableList::empty();
- world.add(Arc::new(Sphere::new(Point3::new(0.0, 0.0, -1.0), 0.5)));
- world.add(Arc::new(Sphere::new(Point3::new(0.0, -100.5, -1.0), 100.0)));
+ let material_ground: MaterialPtr = Arc::new(Lambertian::new(Color::new(0.8, 0.8, 0.0)));
+ let material_center: MaterialPtr = Arc::new(Lambertian::new(Color::new(0.7, 0.3, 0.3)));
+ let material_left: MaterialPtr = Arc::new(Metal::new(Color::new(0.8, 0.8, 0.8), 0.3));
+ let material_right: MaterialPtr = Arc::new(Metal::new(Color::new(0.8, 0.6, 0.2), 1.0));
+
+ world.add(Arc::new(Sphere::new(
+ Point3::new(0.0, -100.5, -1.0),
+ 100.0,
+ material_ground,
+ )));
+ world.add(Arc::new(Sphere::new(
+ Point3::new(0.0, 0.0, -1.0),
+ 0.5,
+ material_center,
+ )));
+ world.add(Arc::new(Sphere::new(
+ Point3::new(-1.0, 0.0, -1.0),
+ 0.5,
+ material_left,
+ )));
+ world.add(Arc::new(Sphere::new(
+ Point3::new(1.0, 0.0, -1.0),
+ 0.5,
+ material_right,
+ )));
// Camera
let cam = Camera::new();
A src/material.rs => src/material.rs +62 -0
@@ 0,0 1,62 @@
+use crate::{
+ dot, random_in_unit_sphere, random_unit_vector, reflect, unit_vector, Color, HitRecord, Ray,
+};
+use std::fmt::Debug;
+
+pub trait Material: Debug {
+ fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> Option<(Color, Ray)>;
+}
+
+pub type MaterialPtr = std::sync::Arc<dyn Material>;
+
+#[derive(Debug)]
+pub struct Lambertian {
+ albedo: Color,
+}
+
+impl Lambertian {
+ pub fn new(albedo: Color) -> Self {
+ Self { albedo }
+ }
+}
+
+impl Material for Lambertian {
+ fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> Option<(Color, Ray)> {
+ let mut scatter_direction = rec.normal + random_unit_vector();
+ // Catch degenerate scatter direction
+ if scatter_direction.near_zero() {
+ scatter_direction = rec.normal;
+ }
+ let scattered = Ray::new(&rec.p, &scatter_direction);
+ let attenuation = self.albedo;
+ Some((attenuation, scattered))
+ }
+}
+
+#[derive(Debug)]
+pub struct Metal {
+ albedo: Color,
+ fuzz: f64,
+}
+
+impl Metal {
+ pub fn new(albedo: Color, fuzz: f64) -> Self {
+ Self {
+ albedo,
+ fuzz: if fuzz < 1.0 { fuzz } else { 1.0 },
+ }
+ }
+}
+
+impl Material for Metal {
+ fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> Option<(Color, Ray)> {
+ let reflected = reflect(&unit_vector(r_in.direction()), &rec.normal);
+ let scattered = Ray::new(&rec.p, &(reflected + self.fuzz * random_in_unit_sphere()));
+ let attenuation = self.albedo;
+ if dot(&scattered.direction(), &rec.normal) > 0.0 {
+ Some((attenuation, scattered))
+ } else {
+ None
+ }
+ }
+}
M src/sphere.rs => src/sphere.rs +11 -3
@@ 1,16 1,22 @@
use crate::hittable::*;
use crate::ray::*;
use crate::vec3::*;
+use crate::MaterialPtr;
-#[derive(Debug, Clone, Copy, Default)]
+#[derive(Debug, Clone)]
pub struct Sphere {
center: Point3,
radius: f64,
+ mat_ptr: MaterialPtr,
}
impl Sphere {
- pub fn new(center: Point3, radius: f64) -> Self {
- Self { center, radius }
+ pub fn new(center: Point3, radius: f64, mat_ptr: MaterialPtr) -> Self {
+ Self {
+ center,
+ radius,
+ mat_ptr,
+ }
}
}
@@ 34,6 40,7 @@ impl Hittable for Sphere {
p,
normal: (p - self.center) / self.radius,
front_face: false,
+ mat_ptr: self.mat_ptr.clone(),
};
let outward_normal = (rec.p - self.center) / self.radius;
rec.set_face_normal(r, &outward_normal);
@@ 49,6 56,7 @@ impl Hittable for Sphere {
p,
normal: (p - self.center) / self.radius,
front_face: false,
+ mat_ptr: self.mat_ptr.clone(),
};
let outward_normal = (rec.p - self.center) / self.radius;
rec.set_face_normal(r, &outward_normal);
M src/vec3.rs => src/vec3.rs +16 -0
@@ 43,6 43,11 @@ impl Vec3 {
random_double_range(r.clone()),
)
}
+
+ pub fn near_zero(&self) -> bool {
+ const S: f64 = 1e-8;
+ ((self.e[0]).abs() < S) && ((self.e[1]).abs() < S) && ((self.e[2]).abs() < S)
+ }
}
impl std::ops::Neg for &Vec3 {
@@ 130,6 135,13 @@ impl std::ops::Mul<Vec3> for f64 {
}
}
+impl std::ops::Mul<&Vec3> for f64 {
+ type Output = Vec3;
+ fn mul(self, rhs: &Vec3) -> Vec3 {
+ *rhs * self
+ }
+}
+
impl std::ops::MulAssign<f64> for Vec3 {
fn mul_assign(&mut self, rhs: f64) {
self.e[0] *= rhs;
@@ 212,3 224,7 @@ pub fn random_in_unit_sphere() -> Vec3 {
pub fn random_unit_vector() -> Vec3 {
unit_vector(random_in_unit_sphere())
}
+
+pub fn reflect(v: &Vec3, n: &Vec3) -> Vec3 {
+ *v - 2.0 * dot(v, n) * n
+}