Refactored the project and added the Texture trait
This commit is contained in:
parent
e873ddac40
commit
a278969bbf
28
src/demo.rs
28
src/demo.rs
|
@ -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),
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
67
src/materials/dielectric.rs
Normal file
67
src/materials/dielectric.rs
Normal 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())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
src/materials/lambertian.rs
Normal file
26
src/materials/lambertian.rs
Normal 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
43
src/materials/metal.rs
Normal 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
51
src/materials/mod.rs
Normal 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
5
src/shapes/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
mod moving_sphere;
|
||||||
|
mod sphere;
|
||||||
|
|
||||||
|
pub use moving_sphere::MovingSphere;
|
||||||
|
pub use sphere::Sphere;
|
|
@ -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,
|
||||||
});
|
});
|
|
@ -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
9
src/texture/mod.rs
Normal 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
17
src/texture/solid.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user