From 86ad7845219e8db06fe47b62794180e1b40f90a5 Mon Sep 17 00:00:00 2001 From: Julian T Date: Sun, 1 Aug 2021 22:55:07 +0200 Subject: Implement dielectric material from RTIAW --- src/core/mod.rs | 4 ++-- src/main.rs | 15 +++++++----- src/material/dielectric.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++ src/material/mod.rs | 4 ++-- src/world/shapes/sphere.rs | 15 ++++++++---- 5 files changed, 83 insertions(+), 14 deletions(-) create mode 100644 src/material/dielectric.rs (limited to 'src') diff --git a/src/core/mod.rs b/src/core/mod.rs index 07793ec..95d450c 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -18,14 +18,14 @@ pub use ray::Ray; use crate::Number; -fn min (a: T, b: T) -> T { +pub fn min (a: T, b: T) -> T { if b < a { return b; } a } -fn max (a: T, b: T) -> T { +pub fn max (a: T, b: T) -> T { if b > a { return b; } diff --git a/src/main.rs b/src/main.rs index 5f336c1..a05c1c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,23 +13,26 @@ fn main() { let cam = Camera::new(&CameraSettings { target: Vector3f::new_xyz(0.0, 0.0, -1.0), - origin: Vector3f::new_xyz(1.7, 0.0, 0.3), + origin: Vector3f::new_xyz(0.0, 0.0, 1.0), up: Vector3f::new_xyz(0.0, 1.0, 0.0), - fov: 60.0, + fov: 45.0, filmsize: res, focus: None, - aperture: Some(20.0), + aperture: Some(10.0), + //aperture: None, }); let brown = Arc::new(Lambertian::new(Spectrum::new_rgb(0.5, 0.3, 0.0))); let blue = Arc::new(Lambertian::new(Spectrum::new_rgb(0.0, 0.3, 0.7))); - let metal = Arc::new(Reflectant::new(Spectrum::new_rgb(0.8, 0.8, 0.9), Some(1.0))); + let green = Arc::new(Lambertian::new(Spectrum::new_rgb(0.0, 0.7, 0.3))); + let metal = Arc::new(Dielectric::new(1.5)); let sun = Arc::new(DiffuseLight::new_white(13.0)); let mut scn = Scene::new(); scn.add_objects(vec![ Object::new(metal, Sphere::new(0.2, Vector3f::new_xyz(0.0, 0.0, -1.0))), - Object::new(blue, Sphere::new(0.5, Vector3f::new_xyz(1.0, 0.0, -1.0))), + Object::new(blue.clone(), Sphere::new(0.5, Vector3f::new_xyz(1.0, 0.0, -1.5))), + Object::new(green, Sphere::new(0.3, Vector3f::new_xyz(0.5, 0.0, -2.5))), Object::new(brown, Sphere::new(100.0, Vector3f::new_xyz(0.0, -100.5, -1.0))), Object::new(sun, Sphere::new(0.4, Vector3f::new_xyz(-1.0, 0.7, 0.0))), ]); @@ -45,7 +48,7 @@ fn main() { let mut film = Film::new(res); { - let coord = RenderCoord::new(&mut film, Vector2i::new_xy(64, 64), 400); + let coord = RenderCoord::new(&mut film, Vector2i::new_xy(64, 64), 50); coord.run_threaded(&ctx, &mut sampler, 8); } diff --git a/src/material/dielectric.rs b/src/material/dielectric.rs new file mode 100644 index 0000000..41f47c2 --- /dev/null +++ b/src/material/dielectric.rs @@ -0,0 +1,59 @@ +use super::Material; +use crate::core::{min, Vector3f, Spectrum, Ray}; +use crate::world::Intersection; +use crate::sample::Sampler; +use crate::Float; + +pub struct Dielectric { + ratio: Float, +} + +fn reflect(v: Vector3f, n: Vector3f) -> Vector3f { + v - n * (2.0 * v.dot(&n)) +} + +// Implementation from RTIOW +fn refract(v: Vector3f, n: Vector3f, r_ratio: Float, cos_theta: Float) -> Vector3f { + let r_perp = (v + n * cos_theta) * r_ratio; + let r_parallel = n * (-(1.0 - r_perp.len_squared()).abs().sqrt()); + + r_perp + r_parallel +} + +// Schlick Approximation, explained in RTIOW +fn fresnel(cos: Float, ratio: Float) -> Float { + let mut r0 = (1.0-ratio) / (1.0+ratio); + r0 = r0 * r0; + + r0 + (1.0-r0)*(1.0-cos).powi(5) +} + +impl Dielectric { + pub fn new(ratio: Float) -> Self { + Self { ratio } + } +} + +impl Material for Dielectric { + // Implementation from RTIOW + fn scatter(&self, ray: &Ray, i: &Intersection, sampler: &mut dyn Sampler) -> Option<(Spectrum, Ray)> { + let ratio = if i.front {1.0/self.ratio} else {self.ratio}; + + let ray_dir = ray.direction.norm(); + let cos_theta = min((-ray_dir).dot(&i.n), 1.0); + let sin_theta = (1.0 - cos_theta*cos_theta).sqrt(); + + // Test if it is possible for the ray the retract or if it must reflect. + let cannot_refract = (ratio * sin_theta) > 1.0; + let direction = if cannot_refract || (fresnel(cos_theta, ratio) > sampler.get_sample()) { + reflect(ray_dir, i.n) + } else { + refract(ray_dir, i.n, ratio, cos_theta) + }; + + Some(( + Spectrum::WHITE, + Ray::new(i.p, direction), + )) + } +} diff --git a/src/material/mod.rs b/src/material/mod.rs index 3e92bf6..d3c3154 100644 --- a/src/material/mod.rs +++ b/src/material/mod.rs @@ -3,14 +3,14 @@ use crate::world::Intersection; use crate::sample::Sampler; mod lambertian; -mod reflectant; mod diffuse_light; mod sky_light; +mod dielectric; pub use lambertian::Lambertian; -pub use reflectant::Reflectant; pub use diffuse_light::DiffuseLight; pub use sky_light::SkyLight; +pub use dielectric::Dielectric; pub trait Material: Sync + Send { fn scatter(&self, _: &Ray, _: &Intersection, _: &mut dyn Sampler) -> Option<(Spectrum, Ray)> { diff --git a/src/world/shapes/sphere.rs b/src/world/shapes/sphere.rs index 9ecedd6..1df9c35 100644 --- a/src/world/shapes/sphere.rs +++ b/src/world/shapes/sphere.rs @@ -1,7 +1,7 @@ //! Implements sphere //! //! Spheres are relatively easy to calculate intersections between -use crate::Float; +use crate::{Float, NEAR_ZERO}; use crate::core::{Ray, Vector3f, Bound3f}; use crate::world::{Hittable, Intersection}; @@ -37,10 +37,17 @@ impl Hittable for Sphere { if disc < 0.0 { None } else { - let distance = (-half_b - disc.sqrt()) / a; - if distance < 0.0 { - return None + let disc_sqrt = disc.sqrt(); + + let mut distance = -half_b - disc_sqrt; + if distance <= NEAR_ZERO { + distance = -half_b + disc_sqrt; + } + if distance <= NEAR_ZERO { + return None; } + + distance /= a; let w = ray.at(distance); Some(Intersection::new( self.norm_at(&w), -- cgit v1.2.3