Added Dielectrics, Implemented Neg for Vec3

This commit is contained in:
Ishan Jain 2020-03-08 21:39:06 +05:30
parent 232d3350a2
commit 95eebf21b4
10 changed files with 240 additions and 30 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
target/
*.ppm
*.png

View File

@ -0,0 +1,103 @@
use {
crate::{
demos::{Chunk, Demo},
types::{
material::{Dielectric, Lambertian, Metal},
Hitable, HitableList, Ray, Sphere, Vec3,
},
Camera,
},
rand::Rng,
};
pub struct DielectricMaterial;
impl Demo for DielectricMaterial {
fn name(&self) -> &'static str {
"dielectric-material"
}
fn render_chunk(&self, chunk: &mut Chunk, samples: u8) {
let x = chunk.x;
let y = chunk.y;
let nx = chunk.nx;
let ny = chunk.ny;
let start_x = chunk.start_x;
let start_y = chunk.start_y;
let buffer = &mut chunk.buffer;
let world = HitableList {
list: vec![
Box::new(Sphere::with_material(
Vec3::new(0.0, 0.0, -1.0),
0.5,
Box::new(Lambertian::new(Vec3::new(0.1, 0.2, 0.5))),
)),
Box::new(Sphere::with_material(
Vec3::new(0.0, -100.5, -1.0),
100.0,
Box::new(Lambertian::new(Vec3::new(0.8, 0.8, 0.0))),
)),
Box::new(Sphere::with_material(
Vec3::new(1.0, 0.0, -1.0),
0.5,
Box::new(Metal::new(Vec3::new(0.8, 0.6, 0.2))),
)),
Box::new(Sphere::with_material(
Vec3::new(-1.0, 0.0, -1.0),
0.5,
Box::new(Dielectric::new(1.5)),
)),
Box::new(Sphere::with_material(
Vec3::new(-1.0, 0.0, -1.0),
-0.45,
Box::new(Dielectric::new(1.5)),
)),
],
};
let camera: Camera = Default::default();
let mut rng = rand::thread_rng();
let mut offset = 0;
for j in start_y..start_y + ny {
for i in start_x..start_x + nx {
let mut color = Vec3::new(0.0, 0.0, 0.0);
for _s in 0..samples {
let u = (i as f64 + rng.gen::<f64>()) / x as f64;
let v = (j as f64 + rng.gen::<f64>()) / y as f64;
let ray = camera.get_ray(u, v);
color += calc_color(ray, &world, 0);
}
color /= samples as f64;
// gamma 2 corrected
buffer[offset] = (255.99 * color.r().sqrt()) as u8;
buffer[offset + 1] = (255.99 * color.g().sqrt()) as u8;
buffer[offset + 2] = (255.99 * color.b().sqrt()) as u8;
offset += 4;
}
}
}
}
fn calc_color(ray: Ray, world: &HitableList, depth: u32) -> Vec3 {
if let Some(hit_rec) = world.hit(&ray, 0.001, std::f64::MAX) {
if depth >= 50 {
Vec3::new(0.0, 0.0, 0.0)
} else {
let material = hit_rec.material.as_ref();
if let (attenuation, Some(scattered_ray)) = material.unwrap().scatter(&ray, &hit_rec) {
calc_color(scattered_ray, &world, depth + 1) * attenuation
} else {
Vec3::new(0.0, 0.0, 0.0)
}
}
} else {
let unit_direction = ray.direction().unit_vector();
let t = 0.5 * (unit_direction.y() + 1.0);
Vec3::new(1.0, 1.0, 1.0) * (1.0 - t) + Vec3::new(0.5, 0.7, 1.0) * t
}
}

View File

@ -67,7 +67,7 @@ fn calc_color(ray: Ray, world: &HitableList, rng: &mut rand::rngs::ThreadRng) ->
// To combat this problem, We set a bias
// More information here, https://www.opengl-tutorial.org/intermediate-tutorials/tutorial-16-shadow-mapping/#shadow-acne
if let Some(hit_rec) = world.hit(&ray, 0.001, std::f64::MAX) {
let target = hit_rec.p + hit_rec.normal + random_point_in_unit_space(rng);
let target = hit_rec.p + hit_rec.normal + random_point_in_unit_sphere(rng);
calc_color(Ray::new(hit_rec.p, target - hit_rec.p), &world, rng) * 0.5
} else {
let unit_direction = ray.direction().unit_vector();
@ -76,7 +76,7 @@ fn calc_color(ray: Ray, world: &HitableList, rng: &mut rand::rngs::ThreadRng) ->
}
}
fn random_point_in_unit_space(rng: &mut rand::rngs::ThreadRng) -> Vec3 {
fn random_point_in_unit_sphere(rng: &mut rand::rngs::ThreadRng) -> Vec3 {
let mut point = Vec3::new(rng.gen::<f64>(), rng.gen::<f64>(), rng.gen::<f64>()) * 2.0
- Vec3::new(1.0, 1.0, 1.0);
while point.sq_len() >= 1.0 {

View File

@ -1,7 +1,10 @@
use {
crate::{
demos::{Chunk, Demo},
types::{material, Hitable, HitableList, Ray, Sphere, Vec3},
types::{
material::{Lambertian, Metal},
Hitable, HitableList, Ray, Sphere, Vec3,
},
Camera,
},
rand::Rng,
@ -11,7 +14,7 @@ pub struct Materials;
impl Demo for Materials {
fn name(&self) -> &'static str {
"Metal Material"
"metal-material"
}
fn render_chunk(&self, chunk: &mut Chunk, samples: u8) {
@ -28,22 +31,22 @@ impl Demo for Materials {
Box::new(Sphere::with_material(
Vec3::new(0.0, 0.0, -1.0),
0.5,
Box::new(material::Lambertian::new(Vec3::new(0.8, 0.3, 0.3))),
Box::new(Lambertian::new(Vec3::new(0.8, 0.3, 0.3))),
)),
Box::new(Sphere::with_material(
Vec3::new(0.0, -100.5, -1.0),
100.0,
Box::new(material::Lambertian::new(Vec3::new(0.8, 0.8, 0.0))),
Box::new(Lambertian::new(Vec3::new(0.8, 0.8, 0.0))),
)),
Box::new(Sphere::with_material(
Vec3::new(1.0, 0.0, -1.0),
0.5,
Box::new(material::Metal::new(Vec3::new(0.8, 0.6, 0.2))),
Box::new(Metal::with_fuzz(Vec3::new(0.8, 0.6, 0.2), 0.3)),
)),
Box::new(Sphere::with_material(
Vec3::new(-1.0, 0.0, -1.0),
0.5,
Box::new(material::Metal::new(Vec3::new(0.8, 0.8, 0.8))),
Box::new(Metal::with_fuzz(Vec3::new(0.8, 0.8, 0.8), 0.5)),
)),
],
};

View File

@ -1,3 +1,4 @@
mod dielectric_material;
mod diffuse_materials;
mod hitable_sphere;
mod linear_gradient_rectangle;
@ -7,6 +8,7 @@ mod simple_rectangle;
mod simple_sphere;
mod surface_normal_sphere;
pub use dielectric_material::DielectricMaterial;
pub use diffuse_materials::DiffuseMaterials;
pub use hitable_sphere::HitableSphere;
pub use linear_gradient_rectangle::LinearGradientRectangle;

View File

@ -78,6 +78,7 @@ fn main() -> Result<(), String> {
Some(Keycode::Num6) => active_demo = Box::new(demos::SimpleAntialiasing),
Some(Keycode::Num7) => active_demo = Box::new(demos::DiffuseMaterials),
Some(Keycode::Num8) => active_demo = Box::new(demos::Materials),
Some(Keycode::Num9) => active_demo = Box::new(demos::DielectricMaterial),
None => unreachable!(),
_ => (),
};

View File

@ -1,9 +1,23 @@
use crate::types::{Material, Ray, Vec3};
pub struct HitRecord<'a> {
/// Rays are represented by A + t * B
/// where A is the source point and B destination point
/// by adjusting t we can move forward/back on the ray
///
/// t is the point at which a ray intersected another object.
/// As in, If we put this value of t in A + t * B equation, We'll get the exact
/// point at which a ray intersects some other object
pub t: f64,
/// Ray object otherwise is represented by the Source/Destination points
/// p is what we get when we perform the operation, A + t * B
/// i.e. A vector from Ray source to the point t
pub p: Vec3,
/// unit outward facing normal
pub normal: Vec3,
/// material if any of the surface
pub material: Option<&'a Box<dyn Material>>,
}

View File

@ -2,6 +2,7 @@ use {
crate::types::{HitRecord, Ray, Vec3},
rand::Rng,
};
pub trait Material {
fn scatter(&self, ray: &Ray, hit_rec: &HitRecord) -> (Vec3, Option<Ray>);
}
@ -10,46 +11,45 @@ pub struct Lambertian {
albedo: Vec3,
}
impl Material for Lambertian {
fn scatter(&self, _ray: &Ray, hit_rec: &HitRecord) -> (Vec3, Option<Ray>) {
let mut rng = rand::thread_rng();
let target = hit_rec.p + hit_rec.normal + random_point_in_unit_space(&mut rng);
let scattered_ray = Ray::new(hit_rec.p, target - hit_rec.p);
(self.albedo, Some(scattered_ray))
}
}
impl Lambertian {
pub fn new(a: Vec3) -> Self {
Self { albedo: a }
}
}
fn random_point_in_unit_space(rng: &mut rand::rngs::ThreadRng) -> Vec3 {
let mut point = Vec3::new(rng.gen::<f64>(), rng.gen::<f64>(), rng.gen::<f64>()) * 2.0
- Vec3::new(1.0, 1.0, 1.0);
while point.sq_len() >= 1.0 {
point = Vec3::new(rng.gen::<f64>(), rng.gen::<f64>(), rng.gen::<f64>()) * 2.0
- Vec3::new(1.0, 1.0, 1.0);
impl Material for Lambertian {
fn scatter(&self, _ray: &Ray, hit_rec: &HitRecord) -> (Vec3, Option<Ray>) {
let mut rng = rand::thread_rng();
let target = hit_rec.p + hit_rec.normal + random_point_in_unit_sphere(&mut rng);
let scattered_ray = Ray::new(hit_rec.p, target - hit_rec.p);
(self.albedo, Some(scattered_ray))
}
point
}
pub struct Metal {
albedo: Vec3,
fuzz: f64,
}
impl Metal {
pub fn new(albedo: Vec3) -> Self {
Self { albedo }
Self { albedo, fuzz: 0.0 }
}
pub fn with_fuzz(albedo: Vec3, fuzz: f64) -> Self {
Self { albedo, fuzz }
}
}
impl Material for Metal {
fn scatter(&self, ray_in: &Ray, hit_rec: &HitRecord) -> (Vec3, Option<Ray>) {
let mut rng = rand::thread_rng();
let reflected_ray = reflect(ray_in.direction().unit_vector(), hit_rec.normal);
let scattered_ray = Ray::new(hit_rec.p, reflected_ray);
let scattered_ray = Ray::new(
hit_rec.p,
reflected_ray + random_point_in_unit_sphere(&mut rng) * self.fuzz,
);
if scattered_ray.direction().dot(&hit_rec.normal) > 0.0 {
(self.albedo, Some(scattered_ray))
@ -59,6 +59,82 @@ impl Material for Metal {
}
}
pub struct Dielectric {
reflection_index: f64,
}
impl Dielectric {
pub fn new(reflection_index: f64) -> Self {
Self { reflection_index }
}
}
impl Material for Dielectric {
fn scatter(&self, ray_in: &Ray, hit_rec: &HitRecord) -> (Vec3, Option<Ray>) {
let reflected_ray = reflect(ray_in.direction(), hit_rec.normal);
// Glass absorbs nothing! So, Attenuation is always going to be 1.0 for this
let attenuation = Vec3::new(1.0, 1.0, 1.0);
let mut rng = rand::thread_rng();
let (outward_normal, ni_over_nt, cosine) = if ray_in.direction().dot(&hit_rec.normal) > 0.0
{
(
-hit_rec.normal,
self.reflection_index,
(ray_in.direction().dot(&hit_rec.normal) * self.reflection_index)
/ ray_in.direction().length(),
)
} else {
(
hit_rec.normal,
1.0 / self.reflection_index,
(-ray_in.direction().dot(&hit_rec.normal)) / ray_in.direction().length(),
)
};
if let Some(refracted_ray) = refract(ray_in.direction(), outward_normal, ni_over_nt) {
let reflect_prob = schlick(cosine, self.reflection_index);
if rng.gen::<f64>() < reflect_prob {
(attenuation, Some(Ray::new(hit_rec.p, reflected_ray)))
} else {
(attenuation, Some(Ray::new(hit_rec.p, refracted_ray)))
}
} else {
(attenuation, Some(Ray::new(hit_rec.p, reflected_ray)))
}
}
}
// Polynomial approximation to figure out reflectivity as the angle changes
fn schlick(cosine: f64, reflection_index: f64) -> f64 {
let mut r0 = (1.0 - reflection_index) / (1.0 + reflection_index);
r0 = r0 * r0;
r0 + (1.0 - r0) * (1.0 - cosine).powf(5.0)
}
fn reflect(incident: Vec3, normal: Vec3) -> Vec3 {
incident - normal * incident.dot(&normal) * 2.0
}
// Snell's Law
fn refract(incident: Vec3, normal: Vec3, ni_over_nt: f64) -> Option<Vec3> {
let uv = incident.unit_vector();
let dt = uv.dot(&normal);
let discriminant = 1.0 - ni_over_nt * ni_over_nt * (1.0 - dt * dt);
if discriminant > 0.0 {
Some((uv - normal * dt) * ni_over_nt - normal * discriminant.sqrt())
} else {
None
}
}
fn random_point_in_unit_sphere(rng: &mut rand::rngs::ThreadRng) -> Vec3 {
let mut point = Vec3::new(rng.gen::<f64>(), rng.gen::<f64>(), rng.gen::<f64>()) * 2.0
- Vec3::new(1.0, 1.0, 1.0);
while point.sq_len() >= 1.0 {
point = Vec3::new(rng.gen::<f64>(), rng.gen::<f64>(), rng.gen::<f64>()) * 2.0
- Vec3::new(1.0, 1.0, 1.0);
}
point
}

View File

@ -1,4 +1,5 @@
use crate::types::{HitRecord, Hitable, Material, Ray, Vec3};
pub struct Sphere {
center: Vec3,
radius: f64,
@ -36,9 +37,10 @@ impl Hitable for Sphere {
// Check this for detailed proof
// https://vchizhov.github.io/resources/ray%20tracing/ray%20tracing%20tutorial%20series%20vchizhov/ray_casting/part1/intersecting_a_sphere.md.html#appendix
let discriminant = b * b - a * c;
let discriminant_root = discriminant.sqrt();
if discriminant > 0.0 {
let root = (-b - discriminant.sqrt()) / a;
let root = (-b - discriminant_root) / a;
if root < t_max && root > t_min {
let p = ray.point_at_parameter(root);
return Some(HitRecord {
@ -49,7 +51,7 @@ impl Hitable for Sphere {
});
}
let root = (-b + discriminant.sqrt()) / a;
let root = (-b + discriminant_root) / a;
if root < t_max && root > t_min {
let p = ray.point_at_parameter(root);

View File

@ -1,6 +1,6 @@
use std::{
fmt::{Display, Formatter, Result as FmtResult},
ops::{Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Sub, SubAssign},
ops::{Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign},
};
#[derive(Copy, Clone)]
@ -107,6 +107,14 @@ impl SubAssign for Vec3 {
}
}
impl Neg for Vec3 {
type Output = Vec3;
fn neg(self) -> Vec3 {
Vec3([-self[0], -self[1], -self[2]])
}
}
impl MulAssign<Vec3> for Vec3 {
fn mul_assign(&mut self, o: Vec3) {
self[0] *= o[0];