Added Dielectrics, Implemented Neg for Vec3
This commit is contained in:
parent
232d3350a2
commit
95eebf21b4
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
target/
|
||||
*.ppm
|
||||
*.png
|
||||
|
|
103
src/demos/dielectric_material.rs
Normal file
103
src/demos/dielectric_material.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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)),
|
||||
)),
|
||||
],
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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!(),
|
||||
_ => (),
|
||||
};
|
||||
|
|
|
@ -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>>,
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
Loading…
Reference in New Issue
Block a user