diff --git a/rtiow/src/bin/tracer.rs b/rtiow/src/bin/tracer.rs index 7affaf2..fd4de3d 100644 --- a/rtiow/src/bin/tracer.rs +++ b/rtiow/src/bin/tracer.rs @@ -6,6 +6,7 @@ use rand::Rng; use rtiow::camera::Camera; use rtiow::hitable::Hit; use rtiow::hitable_list::HitableList; +use rtiow::material::Dielectric; use rtiow::material::Lambertian; use rtiow::material::Metal; use rtiow::ray::Ray; @@ -38,7 +39,7 @@ fn main() -> Result<(), std::io::Error> { Sphere::new( Vec3::new(0., 0., -1.), 0.5, - Box::new(Lambertian::new(Vec3::new(0.8, 0.3, 0.3))), + Box::new(Lambertian::new(Vec3::new(0.1, 0.2, 0.5))), ), Sphere::new( Vec3::new(0., -100.5, -1.), @@ -50,10 +51,11 @@ fn main() -> Result<(), std::io::Error> { 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(Dielectric::new(1.5))), Sphere::new( Vec3::new(-1., 0., -1.), - 0.5, - Box::new(Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.8)), + -0.45, + Box::new(Dielectric::new(1.5)), ), ]; let cam = Camera::new2x1(); diff --git a/rtiow/src/bin/tracer_dielectric.rs b/rtiow/src/bin/tracer_dielectric.rs new file mode 100644 index 0000000..fd4de3d --- /dev/null +++ b/rtiow/src/bin/tracer_dielectric.rs @@ -0,0 +1,83 @@ +extern crate rand; +extern crate rtiow; + +use rand::Rng; + +use rtiow::camera::Camera; +use rtiow::hitable::Hit; +use rtiow::hitable_list::HitableList; +use rtiow::material::Dielectric; +use rtiow::material::Lambertian; +use rtiow::material::Metal; +use rtiow::ray::Ray; +use rtiow::sphere::Sphere; +use rtiow::vec3::Vec3; + +fn color(r: Ray, world: &Hit, depth: usize) -> Vec3 { + if let Some(rec) = world.hit(r, 0.001, std::f32::MAX) { + 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. + 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, + Box::new(Lambertian::new(Vec3::new(0.1, 0.2, 0.5))), + ), + 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(Dielectric::new(1.5))), + Sphere::new( + Vec3::new(-1., 0., -1.), + -0.45, + Box::new(Dielectric::new(1.5)), + ), + ]; + 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, 0); + } + col = col / ns as f32; + // Gamma correct, use gamma 2 correction, which is 1/gamma where gamma=2 which is 1/2 + // or sqrt. + col = Vec3::new(col[0].sqrt(), col[1].sqrt(), col[2].sqrt()); + 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/material.rs b/rtiow/src/material.rs index f65b3ce..3cee78e 100644 --- a/rtiow/src/material.rs +++ b/rtiow/src/material.rs @@ -21,6 +21,7 @@ fn random_in_unit_sphere() -> Vec3 { } } +#[derive(Default)] pub struct ScatterResponse { pub scattered: Ray, pub attenutation: Vec3, @@ -79,3 +80,65 @@ impl Material for Metal { } } } + +fn refract(v: Vec3, n: Vec3, ni_over_nt: f32) -> Option { + let uv = v.unit_vector(); + let dt = dot(uv, n); + let discriminant = 1. - ni_over_nt * ni_over_nt * (1. - dt * dt); + if discriminant > 0. { + return Some(ni_over_nt * (uv - n * dt) - n * discriminant.sqrt()); + } + None +} + +fn schlick(cosine: f32, ref_idx: f32) -> f32 { + let mut r0 = (1. - ref_idx) / (1. + ref_idx); + r0 = r0 * r0; + r0 + (1. - r0) * (1. - cosine).powf(5.) +} + +pub struct Dielectric { + ref_idx: f32, +} + +impl Dielectric { + pub fn new(ref_idx: f32) -> Dielectric { + Dielectric { ref_idx } + } +} + +impl Material for Dielectric { + fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> ScatterResponse { + let reflected = reflect(r_in.direction(), rec.normal); + let (outward_normal, ni_over_nt, cosine) = if dot(r_in.direction(), rec.normal) > 0. { + ( + -rec.normal, + self.ref_idx, + self.ref_idx * dot(r_in.direction(), rec.normal) / r_in.direction().length(), + ) + } else { + ( + rec.normal, + 1. / self.ref_idx, + -dot(r_in.direction(), rec.normal) / r_in.direction().length(), + ) + }; + let scattered = + if let Some(refracted) = refract(r_in.direction(), outward_normal, ni_over_nt) { + let mut rng = rand::thread_rng(); + if rng.gen_range::(0., 1.) < schlick(cosine, self.ref_idx) { + Ray::new(rec.p, reflected) + } else { + Ray::new(rec.p, refracted) + } + } else { + Ray::new(rec.p, reflected) + }; + + ScatterResponse { + attenutation: Vec3::new(1., 1., 1.), + scattered, + reflected: true, + } + } +} diff --git a/rtiow/src/vec3.rs b/rtiow/src/vec3.rs index 47197c9..981df5c 100644 --- a/rtiow/src/vec3.rs +++ b/rtiow/src/vec3.rs @@ -4,6 +4,7 @@ use std::ops::Add; use std::ops::Div; use std::ops::Index; use std::ops::Mul; +use std::ops::Neg; use std::ops::Sub; use std::str; @@ -150,6 +151,14 @@ impl Mul for Vec3 { } } +impl Neg for Vec3 { + type Output = Vec3; + + fn neg(self) -> Vec3 { + -1. * self + } +} + impl Sub for Vec3 { type Output = Vec3;