Added Materials, Lambertian and Metal along with Materials Demo

1. Added Materials Demo and Lambertian/Metal Materials.
2. Added a Multiplication implementation for Vec3 * Vec3
This commit is contained in:
Ishan Jain 2020-02-13 23:59:03 +05:30
parent d4bee42b78
commit edb0b64282
9 changed files with 197 additions and 10 deletions

View File

@ -39,9 +39,15 @@ impl Demo for DiffuseMaterials {
}
color /= samples as f64;
buf[offset] = (255.99 * color.r()) as u8;
buf[offset + 1] = (255.99 * color.g()) as u8;
buf[offset + 2] = (255.99 * color.b()) as u8;
// Without taking square root of each color, we get a picture that
// is quite dark
// Spheres in this case are absorbing 50% of the light casted on them
// So, IRL, It *should* look a bit lighter in color
// To do that, We apply gamma correction by a factor of 2
// which means multiple rgb values by 1/gamma aka 1/2
buf[offset] = (255.99 * color.r().sqrt()) as u8;
buf[offset + 1] = (255.99 * color.g().sqrt()) as u8;
buf[offset + 2] = (255.99 * color.b().sqrt()) as u8;
offset += 4;
}
}
@ -49,8 +55,12 @@ impl Demo for DiffuseMaterials {
}
fn calc_color(ray: Ray, world: &HitableList, rng: &mut rand::rngs::ThreadRng) -> Vec3 {
if let Some(hit_rec) = world.hit(&ray, 0.0, std::f64::MAX) {
let target = hit_rec.p + hit_rec.normal + random_point_in_unit_sphere(rng);
// The value of t_min here could've been 0.0 but since f32/f64 can only be
// partially compared, It may cause shadow acne effect.
// 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);
calc_color(Ray::new(hit_rec.p, target - hit_rec.p), &world, rng) * 0.5
} else {
let unit_direction = ray.direction().unit_vector();
@ -59,7 +69,7 @@ fn calc_color(ray: Ray, world: &HitableList, rng: &mut rand::rngs::ThreadRng) ->
}
}
fn random_point_in_unit_sphere(rng: &mut rand::rngs::ThreadRng) -> Vec3 {
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 {

86
src/demos/materials.rs Normal file
View File

@ -0,0 +1,86 @@
use {
crate::{
demos::Demo,
types::{material, Hitable, HitableList, Ray, Sphere, Vec3},
Camera,
},
rand::Rng,
};
pub struct Materials;
impl Demo for Materials {
fn name(&self) -> &'static str {
"Metal Material"
}
fn render(&self, buf: &mut [u8], width: usize, height: usize, samples: u8) {
let world = HitableList {
list: vec![
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(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(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(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))),
)),
],
};
let camera: Camera = Default::default();
let mut rng = rand::thread_rng();
let mut offset = 0;
for j in (0..height).rev() {
for i in 0..width {
let mut color = Vec3::new(0.0, 0.0, 0.0);
for _s in 0..samples {
let u = (i as f64 + rng.gen::<f64>()) / width as f64;
let v = (j as f64 + rng.gen::<f64>()) / height as f64;
let ray = camera.get_ray(u, v);
color += calc_color(ray, &world, 0);
}
color /= samples as f64;
// gamma 2 corrected
buf[offset] = (255.99 * color.r().sqrt()) as u8;
buf[offset + 1] = (255.99 * color.g().sqrt()) as u8;
buf[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

@ -1,6 +1,7 @@
mod diffuse_materials;
mod hitable_sphere;
mod linear_gradient_rectangle;
mod materials;
mod simple_antialiasing;
mod simple_rectangle;
mod simple_sphere;
@ -9,6 +10,7 @@ mod surface_normal_sphere;
pub use diffuse_materials::DiffuseMaterials;
pub use hitable_sphere::HitableSphere;
pub use linear_gradient_rectangle::LinearGradientRectangle;
pub use materials::Materials;
pub use simple_antialiasing::SimpleAntialiasing;
pub use simple_rectangle::SimpleRectangle;
pub use simple_sphere::SimpleSphere;

View File

@ -73,6 +73,7 @@ fn main() -> Result<(), String> {
Some(Keycode::Num5) => active_demo = Box::new(demos::HitableSphere),
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),
None => unreachable!(),
_ => (),
};

View File

@ -1,9 +1,10 @@
use crate::types::{Ray, Vec3};
use crate::types::{Material, Ray, Vec3};
pub struct HitRecord {
pub struct HitRecord<'a> {
pub t: f64,
pub p: Vec3,
pub normal: Vec3,
pub material: Option<&'a Box<dyn Material>>,
}
pub trait Hitable {

64
src/types/material.rs Normal file
View File

@ -0,0 +1,64 @@
use {
crate::types::{HitRecord, Ray, Vec3},
rand::Rng,
};
pub trait Material {
fn scatter(&self, ray: &Ray, hit_rec: &HitRecord) -> (Vec3, Option<Ray>);
}
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);
}
point
}
pub struct Metal {
albedo: Vec3,
}
impl Metal {
pub fn new(albedo: Vec3) -> Self {
Self { albedo }
}
}
impl Material for Metal {
fn scatter(&self, ray_in: &Ray, hit_rec: &HitRecord) -> (Vec3, Option<Ray>) {
let reflected_ray = reflect(ray_in.direction().unit_vector(), hit_rec.normal);
let scattered_ray = Ray::new(hit_rec.p, reflected_ray);
if scattered_ray.direction().dot(&hit_rec.normal) > 0.0 {
(self.albedo, Some(scattered_ray))
} else {
(self.albedo, None)
}
}
}
fn reflect(incident: Vec3, normal: Vec3) -> Vec3 {
incident - normal * incident.dot(&normal) * 2.0
}

View File

@ -1,11 +1,13 @@
mod hitable;
mod hitable_list;
pub mod material;
mod ray;
mod sphere;
mod vec3;
pub use hitable::{HitRecord, Hitable};
pub use hitable_list::HitableList;
pub use material::Material;
pub use ray::Ray;
pub use sphere::Sphere;
pub use vec3::Vec3;

View File

@ -1,12 +1,24 @@
use crate::types::{HitRecord, Hitable, Ray, Vec3};
use crate::types::{HitRecord, Hitable, Material, Ray, Vec3};
pub struct Sphere {
center: Vec3,
radius: f64,
material: Option<Box<dyn Material>>,
}
impl Sphere {
pub fn new(center: Vec3, radius: f64) -> Self {
Self { center, radius }
Self {
center,
radius,
material: None,
}
}
pub fn with_material(center: Vec3, radius: f64, material: Box<dyn Material>) -> Self {
Self {
center,
radius,
material: Some(material),
}
}
}
@ -33,6 +45,7 @@ impl Hitable for Sphere {
t: root,
p,
normal: (p - self.center) / self.radius,
material: self.material.as_ref(),
});
}
@ -44,6 +57,7 @@ impl Hitable for Sphere {
t: root,
p,
normal: (p - self.center) / self.radius,
material: self.material.as_ref(),
});
}
}

View File

@ -130,6 +130,13 @@ impl Mul<f64> for Vec3 {
}
}
impl Mul<Vec3> for Vec3 {
type Output = Vec3;
fn mul(self, o: Vec3) -> Vec3 {
Vec3([self[0] * o[0], self[1] * o[1], self[2] * o[2]])
}
}
impl Div<Vec3> for Vec3 {
type Output = Vec3;