1
0

Refactored the project and added the Texture trait

This commit is contained in:
Ishan Jain 2021-03-02 17:54:10 +05:30
parent e873ddac40
commit a278969bbf
No known key found for this signature in database
GPG Key ID: F261A0E73038D89D
14 changed files with 299 additions and 195 deletions

View File

@ -1,19 +1,17 @@
use { use crate::{
crate::{ materials::{Dielectric, Lambertian, Metal},
types::{ shapes::{MovingSphere, Sphere},
material::{Dielectric, Lambertian, Metal}, texture::Solid,
MovingSphere, Ray, Sphere, Vec3, types::{Ray, Vec3},
},
BvhNode, Camera, Hitable, HORIZONTAL_PARTITION, VERTICAL_PARTITION, BvhNode, Camera, Hitable, HORIZONTAL_PARTITION, VERTICAL_PARTITION,
}, };
rand::{rngs::SmallRng, Rng, SeedableRng}, use rand::{rngs::SmallRng, Rng, SeedableRng};
rayon::prelude::*, use rayon::prelude::*;
std::{ use std::{
fmt::{Display, Formatter, Result as FmtResult}, fmt::{Display, Formatter, Result as FmtResult},
fs::File, fs::File,
io::Write, io::Write,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
},
}; };
#[derive(Debug)] #[derive(Debug)]
@ -57,7 +55,7 @@ impl Demo {
world.push(Arc::new(Sphere::new( world.push(Arc::new(Sphere::new(
Vec3::new(0.0, -1000.0, 0.0), Vec3::new(0.0, -1000.0, 0.0),
1000.0, 1000.0,
Lambertian::new(Vec3::new(0.5, 0.5, 0.5)), Lambertian::new(Solid::new(Vec3::new(0.5, 0.5, 0.5))),
))); )));
let radius = 0.2; let radius = 0.2;
@ -79,11 +77,11 @@ impl Demo {
0.0, 0.0,
1.0, 1.0,
radius, radius,
Lambertian::new(Vec3::new( Lambertian::new(Solid::new(Vec3::new(
rng.gen::<f64>() * rng.gen::<f64>(), rng.gen::<f64>() * rng.gen::<f64>(),
rng.gen::<f64>() * rng.gen::<f64>(), rng.gen::<f64>() * rng.gen::<f64>(),
rng.gen::<f64>() * rng.gen::<f64>(), rng.gen::<f64>() * rng.gen::<f64>(),
)), ))),
))); )));
} else if choose_material_probability < 0.95 { } else if choose_material_probability < 0.95 {
// metal material // metal material
@ -115,7 +113,7 @@ impl Demo {
world.push(Arc::new(Sphere::new( world.push(Arc::new(Sphere::new(
Vec3::new(-4.0, 1.0, 0.0), Vec3::new(-4.0, 1.0, 0.0),
1.0, 1.0,
Lambertian::new(Vec3::new(0.4, 0.2, 0.1)), Lambertian::new(Solid::new(Vec3::new(0.4, 0.2, 0.1))),
))); )));
world.push(Arc::new(Sphere::new( world.push(Arc::new(Sphere::new(
Vec3::new(4.0, 1.0, 0.0), Vec3::new(4.0, 1.0, 0.0),

View File

@ -1,8 +1,8 @@
use std::sync::Arc; use std::sync::Arc;
use crate::{ use crate::{
types::{Material, Ray, Vec3}, types::{Ray, Vec3},
Aabb, Aabb, Material,
}; };
pub struct HitRecord<'a> { pub struct HitRecord<'a> {
@ -24,6 +24,10 @@ pub struct HitRecord<'a> {
/// material if any of the surface /// material if any of the surface
pub material: &'a dyn Material, pub material: &'a dyn Material,
/// texture coordinates for an object
pub u: f64,
pub v: f64,
} }
pub trait Hitable { pub trait Hitable {

View File

@ -6,6 +6,9 @@ mod camera;
mod demo; mod demo;
mod hitable; mod hitable;
mod hitable_list; mod hitable_list;
mod materials;
mod shapes;
mod texture;
mod types; mod types;
pub use camera::Camera; pub use camera::Camera;
@ -14,9 +17,11 @@ pub use aabb::Aabb;
pub use bvh::BvhNode; pub use bvh::BvhNode;
pub use hitable::{HitRecord, Hitable}; pub use hitable::{HitRecord, Hitable};
pub use hitable_list::HitableList; pub use hitable_list::HitableList;
pub use materials::Material;
use std::time::Instant; use std::time::Instant;
pub use texture::Texture;
const NUM_SAMPLES: u8 = 25; const NUM_SAMPLES: u8 = 20;
const VERTICAL_PARTITION: usize = 12; const VERTICAL_PARTITION: usize = 12;
const HORIZONTAL_PARTITION: usize = 12; const HORIZONTAL_PARTITION: usize = 12;
const WIDTH: usize = 1920; const WIDTH: usize = 1920;

View File

@ -0,0 +1,67 @@
use rand::{prelude::SmallRng, Rng};
use crate::{
materials::{reflect, refract, schlick},
types::{Ray, Vec3},
HitRecord, Material,
};
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,
rng: &mut SmallRng,
) -> (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 (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, ray_in.time())),
)
} else {
(
attenuation,
Some(Ray::new(hit_rec.p, refracted_ray, ray_in.time())),
)
}
} else {
(
attenuation,
Some(Ray::new(hit_rec.p, reflected_ray, ray_in.time())),
)
}
}
}

View File

@ -0,0 +1,26 @@
use rand::prelude::SmallRng;
use crate::{
materials::random_point_in_unit_sphere,
types::{Ray, Vec3},
HitRecord, Material, Texture,
};
pub struct Lambertian<T: Texture> {
albedo: T,
}
impl<T: Texture + Send + Sync> Lambertian<T> {
pub fn new(albedo: T) -> Self {
Self { albedo }
}
}
impl<T: Texture + Send + Sync> Material for Lambertian<T> {
fn scatter(&self, ray: &Ray, hit_rec: &HitRecord, rng: &mut SmallRng) -> (Vec3, Option<Ray>) {
let scatter_direction = hit_rec.normal + random_point_in_unit_sphere(rng);
let scattered_ray = Ray::new(hit_rec.p, scatter_direction, ray.time());
(self.albedo.value(hit_rec.u, hit_rec.v), Some(scattered_ray))
}
}

43
src/materials/metal.rs Normal file
View File

@ -0,0 +1,43 @@
use rand::prelude::SmallRng;
use crate::{
materials::{random_point_in_unit_sphere, reflect},
types::{Ray, Vec3},
HitRecord, Material,
};
pub struct Metal {
albedo: Vec3,
fuzz: f64,
}
impl Metal {
pub fn new(albedo: Vec3) -> Self {
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,
rng: &mut SmallRng,
) -> (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 + random_point_in_unit_sphere(rng) * self.fuzz,
ray_in.time(),
);
if scattered_ray.direction().dot(&hit_rec.normal) > 0.0 {
(self.albedo, Some(scattered_ray))
} else {
(self.albedo, None)
}
}
}

51
src/materials/mod.rs Normal file
View File

@ -0,0 +1,51 @@
mod dielectric;
mod lambertian;
mod metal;
pub use dielectric::Dielectric;
pub use lambertian::Lambertian;
pub use metal::Metal;
use rand::{prelude::SmallRng, Rng};
use crate::{
types::{Ray, Vec3},
HitRecord,
};
pub trait Material: Send + Sync {
fn scatter(&self, ray: &Ray, hit_rec: &HitRecord, rng: &mut SmallRng) -> (Vec3, Option<Ray>);
}
// Christophe Schlick's Polynomial approximation to figure out reflectivity as the angle changes
// See Fresnel Equations, https://en.wikipedia.org/wiki/Fresnel_equations
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<R: Rng + ?Sized>(rng: &mut R) -> 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
}

5
src/shapes/mod.rs Normal file
View File

@ -0,0 +1,5 @@
mod moving_sphere;
mod sphere;
pub use moving_sphere::MovingSphere;
pub use sphere::Sphere;

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
types::{Material, Ray, Vec3}, types::{Ray, Vec3},
Aabb, HitRecord, Hitable, Aabb, HitRecord, Hitable, Material,
}; };
pub struct MovingSphere<T: Material + Sized> { pub struct MovingSphere<T: Material + Sized> {
@ -36,6 +36,19 @@ impl<T: Material + Sized> MovingSphere<T> {
+ (self.center_end - self.center_start) + (self.center_end - self.center_start)
* ((time - self.time_start) / (self.time_end - self.time_start)) * ((time - self.time_start) / (self.time_end - self.time_start))
} }
/// p is a point on the sphere of radius 1 & center at origin
/// u is between [0,1]. Angle around Y axis from -X axis
/// v is between [0,1]. Angle from -Y to +Y axis
pub fn get_uv(p: Vec3) -> (f64, f64) {
let theta = (-p.y()).acos();
let phi = f64::atan2(-p.z(), p.x()) + std::f64::consts::PI;
let u = phi / (2.0 * std::f64::consts::PI);
let v = theta / std::f64::consts::PI;
(u, v)
}
} }
impl<T: Material + Sized> Hitable for MovingSphere<T> { impl<T: Material + Sized> Hitable for MovingSphere<T> {
@ -52,19 +65,29 @@ impl<T: Material + Sized> Hitable for MovingSphere<T> {
let root = (-b - discriminant_root) / a; let root = (-b - discriminant_root) / a;
if root < t_max && root > t_min { if root < t_max && root > t_min {
let p = ray.point_at_parameter(root); let p = ray.point_at_parameter(root);
let normal = (p - self.center(ray.time())) / self.radius;
let (u, v) = Self::get_uv(normal);
return Some(HitRecord { return Some(HitRecord {
t: root, t: root,
p, p,
normal: (p - self.center(ray.time())) / self.radius, u,
v,
normal,
material: &self.material, material: &self.material,
}); });
} }
let root = (-b + discriminant_root) / a; let root = (-b + discriminant_root) / a;
if root < t_max && root > t_min { if root < t_max && root > t_min {
let p = ray.point_at_parameter(root); let p = ray.point_at_parameter(root);
let normal = (p - self.center(ray.time())) / self.radius;
let (u, v) = Self::get_uv(normal);
return Some(HitRecord { return Some(HitRecord {
t: root, t: root,
p, p,
u,
v,
normal: (p - self.center(ray.time())) / self.radius, normal: (p - self.center(ray.time())) / self.radius,
material: &self.material, material: &self.material,
}); });

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
types::{Material, Ray, Vec3}, types::{Ray, Vec3},
Aabb, HitRecord, Hitable, Aabb, HitRecord, Hitable, Material,
}; };
pub struct Sphere<T: Material + Sized> { pub struct Sphere<T: Material + Sized> {
@ -17,6 +17,19 @@ impl<T: Material + Sized> Sphere<T> {
material, material,
} }
} }
/// p is a point on the sphere of radius 1 & center at origin
/// u is between [0,1]. Angle around Y axis from -X axis
/// v is between [0,1]. Angle from -Y to +Y axis
pub fn get_uv(p: Vec3) -> (f64, f64) {
let theta = (-p.y()).acos();
let phi = f64::atan2(-p.z(), p.x()) + std::f64::consts::PI;
let u = phi / (2.0 * std::f64::consts::PI);
let v = theta / std::f64::consts::PI;
(u, v)
}
} }
impl<T: Material + Sized> Hitable for Sphere<T> { impl<T: Material + Sized> Hitable for Sphere<T> {
@ -39,10 +52,15 @@ impl<T: Material + Sized> Hitable for Sphere<T> {
let root = (-b - discriminant_root) / a; let root = (-b - discriminant_root) / a;
if root < t_max && root > t_min { if root < t_max && root > t_min {
let p = ray.point_at_parameter(root); let p = ray.point_at_parameter(root);
let normal = (p - self.center) / self.radius;
let (u, v) = Self::get_uv(normal);
return Some(HitRecord { return Some(HitRecord {
t: root, t: root,
p, p,
normal: (p - self.center) / self.radius, u,
v,
normal,
material: &self.material, material: &self.material,
}); });
} }
@ -50,10 +68,14 @@ impl<T: Material + Sized> Hitable for Sphere<T> {
let root = (-b + discriminant_root) / a; let root = (-b + discriminant_root) / a;
if root < t_max && root > t_min { if root < t_max && root > t_min {
let p = ray.point_at_parameter(root); let p = ray.point_at_parameter(root);
let normal = (p - self.center) / self.radius;
let (u, v) = Self::get_uv(normal);
return Some(HitRecord { return Some(HitRecord {
t: root, t: root,
p, p,
u,
v,
normal: (p - self.center) / self.radius, normal: (p - self.center) / self.radius,
material: &self.material, material: &self.material,
}); });

9
src/texture/mod.rs Normal file
View File

@ -0,0 +1,9 @@
mod solid;
pub use solid::Solid;
use crate::types::Vec3;
pub trait Texture {
fn value(&self, u: f64, v: f64) -> Vec3;
}

17
src/texture/solid.rs Normal file
View File

@ -0,0 +1,17 @@
use crate::{types::Vec3, Texture};
pub struct Solid {
color: Vec3,
}
impl Solid {
pub const fn new(color: Vec3) -> Self {
Self { color }
}
}
impl Texture for Solid {
fn value(&self, _u: f64, _v: f64) -> Vec3 {
self.color
}
}

View File

@ -1,160 +0,0 @@
use {
crate::{
types::{Ray, Vec3},
HitRecord,
},
rand::{rngs::SmallRng, Rng},
};
pub trait Material: Send + Sync {
fn scatter(&self, ray: &Ray, hit_rec: &HitRecord, rng: &mut SmallRng) -> (Vec3, Option<Ray>);
}
pub struct Lambertian {
albedo: Vec3,
}
impl Lambertian {
pub fn new(a: Vec3) -> Self {
Self { albedo: a }
}
}
impl Material for Lambertian {
fn scatter(&self, ray: &Ray, hit_rec: &HitRecord, rng: &mut SmallRng) -> (Vec3, Option<Ray>) {
let target = hit_rec.p + hit_rec.normal + random_point_in_unit_sphere(rng);
let scattered_ray = Ray::new(hit_rec.p, target - hit_rec.p, ray.time());
(self.albedo, Some(scattered_ray))
}
}
pub struct Metal {
albedo: Vec3,
fuzz: f64,
}
impl Metal {
pub fn new(albedo: Vec3) -> Self {
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,
rng: &mut SmallRng,
) -> (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 + random_point_in_unit_sphere(rng) * self.fuzz,
ray_in.time(),
);
if scattered_ray.direction().dot(&hit_rec.normal) > 0.0 {
(self.albedo, Some(scattered_ray))
} else {
(self.albedo, None)
}
}
}
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,
rng: &mut SmallRng,
) -> (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 (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, ray_in.time())),
)
} else {
(
attenuation,
Some(Ray::new(hit_rec.p, refracted_ray, ray_in.time())),
)
}
} else {
(
attenuation,
Some(Ray::new(hit_rec.p, reflected_ray, ray_in.time())),
)
}
}
}
// Christophe Schlick's Polynomial approximation to figure out reflectivity as the angle changes
// See Fresnel Equations, https://en.wikipedia.org/wiki/Fresnel_equations
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<R: Rng + ?Sized>(rng: &mut R) -> 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,11 +1,5 @@
pub mod material;
mod moving_sphere;
mod ray; mod ray;
mod sphere;
mod vec3; mod vec3;
pub use material::Material;
pub use moving_sphere::MovingSphere;
pub use ray::Ray; pub use ray::Ray;
pub use sphere::Sphere;
pub use vec3::Vec3; pub use vec3::Vec3;