From 03e5a88f9b7da79749370685e8f5afaf03a25b4a Mon Sep 17 00:00:00 2001 From: Sakarias Johansson Date: Mon, 16 Jan 2023 20:58:55 +0100 Subject: =?UTF-8?q?=F0=9F=8E=A8=20Add=20dialectric=20material?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- racer-tracer/src/main.rs | 17 +++++++---- racer-tracer/src/material.rs | 1 + racer-tracer/src/material/dialectric.rs | 53 +++++++++++++++++++++++++++++++++ racer-tracer/src/render.rs | 6 +++- racer-tracer/src/vec3.rs | 28 +++++++++++++++++ 5 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 racer-tracer/src/material/dialectric.rs (limited to 'racer-tracer/src') diff --git a/racer-tracer/src/main.rs b/racer-tracer/src/main.rs index d95717d..5331fd1 100644 --- a/racer-tracer/src/main.rs +++ b/racer-tracer/src/main.rs @@ -16,7 +16,7 @@ use std::{ vec::Vec, }; -use material::{lambertian::Lambertian, metal::Metal, Material}; +use material::{dialectric::Dialectric, lambertian::Lambertian, metal::Metal, Material}; use minifb::{Key, Window, WindowOptions}; use synchronoise::SignalEvent; @@ -39,11 +39,11 @@ fn create_scene() -> Scene { let material_ground: SharedMaterial = Arc::new(Box::new(Lambertian::new(Color::new(0.8, 0.8, 0.0)))); let material_center: SharedMaterial = - Arc::new(Box::new(Lambertian::new(Color::new(0.7, 0.3, 0.3)))); - let material_left: SharedMaterial = - Arc::new(Box::new(Metal::new(Color::new(0.8, 0.8, 0.8), 0.3))); + Arc::new(Box::new(Lambertian::new(Color::new(0.1, 0.2, 0.5)))); + let material_left: SharedMaterial = Arc::new(Box::new(Dialectric::new(1.5))); + let material_right: SharedMaterial = - Arc::new(Box::new(Metal::new(Color::new(0.8, 0.6, 0.2), 0.1))); + Arc::new(Box::new(Metal::new(Color::new(0.8, 0.6, 0.2), 0.0))); scene.add(Box::new(Sphere::new( Vec3::new(0.0, -100.5, -1.0), @@ -60,6 +60,13 @@ fn create_scene() -> Scene { 0.5, Arc::clone(&material_left), ))); + + scene.add(Box::new(Sphere::new( + Vec3::new(-1.0, 0.0, -1.0), + -0.4, + Arc::clone(&material_left), + ))); + scene.add(Box::new(Sphere::new( Vec3::new(1.0, 0.0, -1.0), 0.5, diff --git a/racer-tracer/src/material.rs b/racer-tracer/src/material.rs index b6ce418..a43b29f 100644 --- a/racer-tracer/src/material.rs +++ b/racer-tracer/src/material.rs @@ -1,3 +1,4 @@ +pub mod dialectric; pub mod lambertian; pub mod metal; diff --git a/racer-tracer/src/material/dialectric.rs b/racer-tracer/src/material/dialectric.rs new file mode 100644 index 0000000..5f3aa02 --- /dev/null +++ b/racer-tracer/src/material/dialectric.rs @@ -0,0 +1,53 @@ +use crate::{ + material::Material, + ray::Ray, + util::random_double, + vec3::{dot, reflect, refract, Color}, +}; + +pub struct Dialectric { + refraction_index: f64, +} + +impl Dialectric { + pub fn new(refraction_index: f64) -> Self { + Self { refraction_index } + } + + fn reflectance(cosine: f64, refraction_index: f64) -> f64 { + // Schlick approximation + let mut r0 = (1.0 - refraction_index) / (1.0 + refraction_index); + r0 = r0 * r0; + r0 + (1.0 - r0) * (1.0 - cosine).powf(5.0) + } +} + +impl Material for Dialectric { + fn scatter( + &self, + ray: &crate::ray::Ray, + rec: &crate::geometry::HitRecord, + ) -> Option<(Ray, Color)> { + let refraction_ratio = if rec.front_face { + 1.0 / self.refraction_index + } else { + self.refraction_index + }; + + let unit_direction = &ray.direction().unit_vector(); + let cos_theta = f64::min(dot(&-unit_direction, &rec.normal), 1.0); + let sin_theta = f64::sqrt(1.0 - cos_theta * cos_theta); + + let cannot_refract = refraction_ratio * sin_theta > 1.0; + + let direction = if cannot_refract + || Dialectric::reflectance(cos_theta, refraction_ratio) > random_double() + { + reflect(unit_direction, &rec.normal) + } else { + refract(unit_direction, &rec.normal, refraction_ratio) + }; + + Some((Ray::new(rec.point, direction), Color::new(1.0, 1.0, 1.0))) + } +} diff --git a/racer-tracer/src/render.rs b/racer-tracer/src/render.rs index 9d029e3..c5013cf 100644 --- a/racer-tracer/src/render.rs +++ b/racer-tracer/src/render.rs @@ -50,6 +50,10 @@ pub fn raytrace( scale: usize, max_depth: usize, ) { + // TODO: This scale shit doesn't work. + // Just force power of two or other solutions to avoid this. + // Can be ok for preview but the actual render could use a different function. + let mut scaled_width = image.width / scale; let mut scaled_height = image.height / scale; // In the case where we get an odd one out we patch the widht and @@ -66,7 +70,7 @@ pub fn raytrace( scaled_width += 1; } - if scaled_width * scale != image.height + if scaled_height * scale != image.height && (image.y + scaled_height * scale + 1 < image.screen_height) { scaled_height += 1; diff --git a/racer-tracer/src/vec3.rs b/racer-tracer/src/vec3.rs index 659d3ed..9061eba 100644 --- a/racer-tracer/src/vec3.rs +++ b/racer-tracer/src/vec3.rs @@ -146,6 +146,18 @@ impl ops::Add for Vec3 { } } +impl ops::Add for &Vec3 { + type Output = Vec3; + + fn add(self, rhs: Vec3) -> Self::Output { + Vec3::new( + self.data[0] + rhs.data[0], + self.data[1] + rhs.data[1], + self.data[2] + rhs.data[2], + ) + } +} + impl ops::AddAssign for Vec3 { fn add_assign(&mut self, rhs: Vec3) { self.data[0] += rhs.data[0]; @@ -275,6 +287,14 @@ impl ops::Neg for Vec3 { } } +impl ops::Neg for &Vec3 { + type Output = Vec3; + + fn neg(self) -> Self::Output { + Vec3::new(-self.data[0], -self.data[1], -self.data[2]) + } +} + impl PartialEq for Vec3 { fn eq(&self, other: &Self) -> bool { self.data[0] == other.data[0] @@ -305,6 +325,14 @@ pub fn reflect(v1: &Vec3, v2: &Vec3) -> Vec3 { v1 - 2.0 * v1.dot(v2) * v2 } +pub fn refract(uv: &Vec3, n: &Vec3, etai_over_etat: f64) -> Vec3 { + let cos_theta = f64::min(dot(&-uv, n), 1.0); + + let r_out_perp = etai_over_etat * (uv + (cos_theta * n)); + let r_out_parallel = -f64::sqrt(f64::abs(1.0 - r_out_perp.length_squared())) * n; + r_out_perp + r_out_parallel +} + pub fn random_in_unit_sphere() -> Vec3 { let mut v = Vec3::random_range(-1.0, 1.0); while v.length_squared() >= 1.0 { -- cgit v1.2.3