1
0

can create a stripped/basic version of cornell box

1. Refactored the project
2. Changed src/shapes/rectangle.rs to make it easy to create rectangles
   aligned with any(X/Y/Z) Axis.
This commit is contained in:
Ishan Jain 2021-04-07 22:15:04 +05:30
parent 298e0a2301
commit 9d82129891
31 changed files with 705 additions and 239 deletions

1
Cargo.lock generated
View File

@ -331,6 +331,7 @@ name = "rtnw"
version = "0.1.0"
dependencies = [
"image",
"num-traits",
"rand",
"rayon",
"sdl2",

View File

@ -3,14 +3,14 @@ authors = ["ishanjain28 <ishanjain28@gmail.com>"]
edition = "2018"
name = "rtnw"
version = "0.1.0"
[dependencies]
num-traits = "*"
rayon = "1.5.0"
[dependencies.image]
default-features = false
features = ["jpeg", "png"]
version = "0.23.14"
default-features = false
[dependencies.rand]
features = ["small_rng"]

View File

@ -13,9 +13,9 @@ impl Aabb {
pub fn hit(&self, ray: &Ray, mut t_min: f64, mut t_max: f64) -> bool {
for i in 0..=2 {
let inverse_dir = 1.0 / ray.direction()[i];
let mut t0 = (self.min[i] - ray.origin()[i]) * inverse_dir;
let mut t1 = (self.max[i] - ray.origin()[i]) * inverse_dir;
let inverse_dir = 1.0 / ray.direction[i];
let mut t0 = (self.min[i] - ray.origin[i]) * inverse_dir;
let mut t1 = (self.max[i] - ray.origin[i]) * inverse_dir;
if inverse_dir < 0.0 {
std::mem::swap(&mut t0, &mut t1);
}
@ -31,17 +31,8 @@ impl Aabb {
}
pub fn surrounding_box(box0: Aabb, box1: Aabb) -> Self {
let smol_box = Vec3::new(
box0.min.x().min(box1.min.x()),
box0.min.y().min(box1.min.y()),
box0.min.z().min(box1.min.z()),
);
let big_box = Vec3::new(
box0.max.x().max(box1.max.x()),
box0.max.y().max(box1.max.y()),
box0.max.z().max(box1.max.z()),
);
let smol_box = Vec3::min(box0.min, box1.min);
let big_box = Vec3::max(box0.max, box1.max);
Self {
min: smol_box,

View File

@ -1,10 +1,13 @@
use crate::{
demos::{Demo, ParallelHit},
materials::{Dielectric, Lambertian, Metal},
hitable::{
shapes::{MovingSphere, Sphere},
BvhNode,
},
materials::{Dielectric, Lambertian, Metal},
texture::{Checker, Solid},
types::Vec3,
BvhNode, Camera,
Camera,
};
use rand::{rngs::SmallRng, Rng, SeedableRng};
use std::sync::Arc;
@ -18,6 +21,10 @@ impl Demo for CheckeredMotionBlur {
"checkered_motion_blur"
}
fn get_background(&self) -> Vec3 {
Vec3::new(0.7, 0.8, 1.0)
}
fn world(&self) -> Self::DemoT {
let mut world: Vec<Arc<dyn ParallelHit>> = Vec::with_capacity(500);

110
src/demos/cornell_box.rs Normal file
View File

@ -0,0 +1,110 @@
use std::sync::Arc;
use rand::{prelude::SmallRng, SeedableRng};
use crate::{
demos::{Demo, ParallelHit},
hitable::shapes::{Cuboid, RectBuilder},
materials::{DiffuseLight, Lambertian, MaterialBuilder},
texture::Solid,
types::Vec3,
BvhNode, Camera,
};
pub struct CornellBox {}
impl Demo for CornellBox {
type DemoT = BvhNode<Arc<dyn ParallelHit>>;
fn name(&self) -> &'static str {
"cornell_box"
}
fn world(&self) -> Self::DemoT {
let mut world: Vec<Arc<dyn ParallelHit>> = Vec::with_capacity(8);
let mut rng = rand::thread_rng();
let mut rng = SmallRng::from_rng(&mut rng).unwrap();
let red = Lambertian::new(Solid::new(Vec3::new(0.65, 0.05, 0.05)));
let white = Lambertian::new(Solid::new(Vec3::splat(0.73)));
let green = Lambertian::new(Solid::new(Vec3::new(0.12, 0.45, 0.15)));
let light = DiffuseLight::new(Solid::new(Vec3::splat(15.0)));
world.push(Arc::new(
RectBuilder
.y(0.0..=555.0)
.z(0.0..=555.0)
.x(555.0)
.material(green),
));
world.push(Arc::new(
RectBuilder
.y(0.0..=555.0)
.z(0.0..=555.0)
.x(0.0)
.material(red),
));
world.push(Arc::new(
RectBuilder
.x(213.0..=343.0)
.z(227.0..=332.0)
.y(554.0)
.material(light),
));
world.push(Arc::new(
RectBuilder
.x(0.0..=555.0)
.z(0.0..=555.0)
.y(0.0)
.material(white),
));
world.push(Arc::new(
RectBuilder
.x(0.0..=555.0)
.z(0.0..=555.0)
.y(555.0)
.material(white),
));
world.push(Arc::new(
RectBuilder
.x(0.0..=555.0)
.y(0.0..=555.0)
.z(555.0)
.material(white),
));
// Add the two boxes
world.push(Arc::new(Cuboid::new(
Vec3::new(136.0, 0.0, 65.0),
Vec3::new(295.0, 165.0, 230.0),
white,
)));
world.push(Arc::new(Cuboid::new(
Vec3::new(265.0, 0.0, 295.0),
Vec3::new(430.0, 330.0, 460.0),
white,
)));
BvhNode::new(&mut rng, &mut world, 0.0, 1.0)
}
fn camera(&self, aspect_ratio: f64) -> Camera {
let lookfrom = Vec3::new(278.0, 278.0, -800.0);
let lookat = Vec3::new(278.0, 278.0, 0.0);
let aperture = 0.1;
let focus_distance = 40.0;
Camera::new(
lookfrom,
lookat,
Vec3::new(0.0, 1.0, 0.0),
40.0,
aspect_ratio,
aperture,
focus_distance,
0.0,
1.0,
)
}
}

View File

@ -4,8 +4,8 @@ use rand::{prelude::SmallRng, SeedableRng};
use crate::{
demos::{Demo, ParallelHit},
hitable::shapes::Sphere,
materials::Lambertian,
shapes::Sphere,
texture::ImageTexture,
types::Vec3,
BvhNode, Camera,
@ -20,6 +20,10 @@ impl Demo for ImageTextureDemo {
"image_texture"
}
fn get_background(&self) -> Vec3 {
Vec3::new(0.7, 0.8, 1.0)
}
fn world(&self) -> Self::DemoT {
let mut world: Vec<Arc<dyn ParallelHit>> = Vec::with_capacity(1);

View File

@ -1,4 +1,8 @@
use crate::{types::Vec3, Camera, Hitable, HORIZONTAL_PARTITION, VERTICAL_PARTITION};
use crate::{
hitable::Hitable,
types::{Color, Vec3},
Camera, HORIZONTAL_PARTITION, VERTICAL_PARTITION,
};
use rand::{rngs::SmallRng, Rng, SeedableRng};
use rayon::prelude::*;
use std::{
@ -9,12 +13,14 @@ use std::{
};
mod checkered_motion_blur;
mod cornell_box;
mod image_texture;
mod perlin_noise_ball;
mod simple_light;
mod two_spheres;
pub use checkered_motion_blur::CheckeredMotionBlur;
pub use cornell_box::CornellBox;
pub use image_texture::ImageTextureDemo;
pub use perlin_noise_ball::PerlinNoiseBall;
pub use simple_light::SimpleLight;
@ -55,7 +61,7 @@ pub trait Demo: Send + Sync {
fn camera(&self, aspect_ratio: f64) -> Camera;
fn get_background(&self) -> Vec3 {
Vec3::new(0.7, 0.8, 1.0)
Vec3::new(0.0, 0.0, 0.0)
}
fn render_chunk(&self, chunk: &mut Chunk, camera: &Camera, world: &Self::DemoT, samples: u16) {
@ -156,14 +162,16 @@ pub trait Demo: Send + Sync {
#[inline]
fn update_rgb(&self, buffer: &mut [u8], color: Vec3, offset: usize) {
let color: Color = color.into();
if let Some(pos) = buffer.get_mut(offset) {
*pos = (255.99 * color.r().sqrt()) as u8;
*pos = color.0;
}
if let Some(pos) = buffer.get_mut(offset + 1) {
*pos = (255.99 * color.g().sqrt()) as u8;
*pos = color.1
}
if let Some(pos) = buffer.get_mut(offset + 2) {
*pos = (255.99 * color.b().sqrt()) as u8;
*pos = color.2;
}
}

View File

@ -4,11 +4,11 @@ use rand::{prelude::SmallRng, SeedableRng};
use crate::{
demos::{Demo, ParallelHit},
hitable::{shapes::Sphere, BvhNode},
materials::Lambertian,
shapes::Sphere,
texture::PerlinNoise,
types::Vec3,
BvhNode, Camera,
Camera,
};
pub struct PerlinNoiseBall {}
@ -20,6 +20,10 @@ impl Demo for PerlinNoiseBall {
"perlin_noise"
}
fn get_background(&self) -> Vec3 {
Vec3::new(0.7, 0.8, 1.0)
}
fn world(&self) -> Self::DemoT {
let mut world: Vec<Arc<dyn ParallelHit>> = Vec::with_capacity(2);

View File

@ -4,11 +4,14 @@ use rand::{prelude::SmallRng, SeedableRng};
use crate::{
demos::{Demo, ParallelHit},
materials::{DiffuseLight, Lambertian},
shapes::{Sphere, XyRectangle},
hitable::{
shapes::{RectBuilder, Sphere},
BvhNode,
},
materials::{DiffuseLight, Lambertian, MaterialBuilder},
texture::{PerlinNoise, Solid},
types::Vec3,
BvhNode, Camera,
Camera,
};
pub struct SimpleLight {}
@ -20,10 +23,6 @@ impl Demo for SimpleLight {
"simple_light"
}
fn get_background(&self) -> Vec3 {
Vec3::new(0.0, 0.0, 0.0)
}
fn world(&self) -> Self::DemoT {
let mut world: Vec<Arc<dyn ParallelHit>> = Vec::with_capacity(5);
let mut rng = rand::thread_rng();
@ -39,14 +38,14 @@ impl Demo for SimpleLight {
2.0,
Lambertian::new(PerlinNoise::with_scale(&mut rng, 4.0)),
)));
world.push(Arc::new(XyRectangle::new(
3.0,
5.0,
1.0,
3.0,
-2.0,
DiffuseLight::new(Solid::new(Vec3::new(4.0, 4.0, 4.0))),
)));
world.push(Arc::new(
RectBuilder
.x(3.0..=5.0)
.y(1.0..=3.0)
.z(-2.0)
.material(DiffuseLight::new(Solid::new(Vec3::new(4.0, 4.0, 4.0)))),
));
world.push(Arc::new(Sphere::new(
Vec3::new(0.0, 7.0, 0.0),
2.0,

View File

@ -4,11 +4,11 @@ use rand::{prelude::SmallRng, SeedableRng};
use crate::{
demos::{Demo, ParallelHit},
hitable::{shapes::Sphere, BvhNode},
materials::Lambertian,
shapes::Sphere,
texture::{Checker, Solid},
types::Vec3,
BvhNode, Camera,
Camera,
};
pub struct TwoSpheres {}
@ -20,6 +20,10 @@ impl Demo for TwoSpheres {
"two_checkered_sphere"
}
fn get_background(&self) -> Vec3 {
Vec3::new(0.7, 0.8, 1.0)
}
fn world(&self) -> Self::DemoT {
let mut world: Vec<Arc<dyn ParallelHit>> = Vec::with_capacity(2);

View File

@ -2,7 +2,11 @@ use std::cmp::Ordering;
use rand::{prelude::SliceRandom, Rng};
use crate::{types::Ray, Aabb, HitRecord, Hitable};
use crate::{
hitable::{HitRecord, Hitable},
types::Ray,
Aabb,
};
pub struct BvhNode<T: Hitable> {
bounding_box: Aabb,

View File

@ -28,6 +28,8 @@ pub struct HitRecord<'a> {
/// texture coordinates for an object
pub u: f64,
pub v: f64,
pub front_face: bool,
}
impl<'a> HitRecord<'a> {
@ -45,6 +47,17 @@ impl<'a> HitRecord<'a> {
material,
u,
v,
front_face: false,
}
}
pub fn set_face_normal(&mut self, ray: &Ray) {
self.front_face = ray.direction.dot(&self.normal) < 0.0;
self.normal = if self.front_face {
self.normal
} else {
-self.normal
}
}
}
@ -63,3 +76,37 @@ impl<T: Hitable + ?Sized> Hitable for Arc<T> {
self.as_ref().bounding_box(t0, t1)
}
}
pub struct Translate<T> {
object: T,
offset: Vec3,
}
impl<T> Translate<T> {
pub fn new(object: T, offset: Vec3) -> Self {
Self { object, offset }
}
}
impl<T: Hitable> Hitable for Translate<T> {
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let moved_ray = Ray::new(ray.origin - self.offset, ray.direction, ray.time());
if let Some(mut hit) = self.object.hit(&moved_ray, t_min, t_max) {
hit.p += self.offset;
hit.set_face_normal(&moved_ray);
Some(hit)
} else {
None
}
}
fn bounding_box(&self, t0: f64, t1: f64) -> Option<Aabb> {
if let Some(bbox) = self.object.bounding_box(t0, t1) {
Some(Aabb::new(bbox.min + self.offset, bbox.max + self.offset))
} else {
None
}
}
}

View File

@ -1,6 +1,11 @@
use std::sync::Arc;
use crate::{demos::ParallelHit, types::Ray, Aabb, HitRecord, Hitable};
use crate::{
demos::ParallelHit,
hitable::{HitRecord, Hitable},
types::Ray,
Aabb,
};
pub struct HitableList {
pub list: Vec<Arc<dyn ParallelHit>>,

7
src/hitable/mod.rs Normal file
View File

@ -0,0 +1,7 @@
pub mod bvh;
pub mod hitable;
pub mod hitable_list;
pub mod shapes;
pub use bvh::*;
pub use hitable::*;

View File

@ -0,0 +1,87 @@
use std::sync::Arc;
use crate::{
hitable::{hitable_list::HitableList, shapes::RectBuilder, HitRecord, Hitable},
materials::{Material, MaterialBuilder},
types::{Ray, Vec3},
Aabb,
};
pub struct Cuboid {
min: Vec3,
max: Vec3,
sides: HitableList,
}
impl Cuboid {
pub fn new(p0: Vec3, p1: Vec3, mat: impl Material + Clone + 'static) -> Self {
Self {
min: p0,
max: p1,
sides: Self::build_cuboid(p0, p1, mat),
}
}
fn build_cuboid(p0: Vec3, p1: Vec3, mat: impl Material + Clone + 'static) -> HitableList {
let mut sides = HitableList {
list: Vec::with_capacity(6),
};
sides.push(Arc::new(
RectBuilder
.x(p0.x()..=p1.x())
.y(p0.y()..=p1.y())
.z(p1.z())
.material(mat.clone()),
));
sides.push(Arc::new(
RectBuilder
.x(p0.x()..=p1.x())
.y(p0.y()..=p1.y())
.z(p0.z())
.material(mat.clone()),
));
sides.push(Arc::new(
RectBuilder
.x(p0.x()..=p1.x())
.z(p0.z()..=p1.z())
.y(p1.y())
.material(mat.clone()),
));
sides.push(Arc::new(
RectBuilder
.x(p0.x()..=p1.x())
.z(p0.z()..=p1.z())
.y(p0.y())
.material(mat.clone()),
));
sides.push(Arc::new(
RectBuilder
.y(p0.y()..=p1.y())
.z(p0.z()..=p1.z())
.x(p1.x())
.material(mat.clone()),
));
sides.push(Arc::new(
RectBuilder
.y(p0.y()..=p1.y())
.z(p0.z()..=p1.z())
.x(p0.x())
.material(mat),
));
sides
}
}
impl Hitable for Cuboid {
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
self.sides.hit(ray, t_min, t_max)
}
fn bounding_box(&self, _t0: f64, _t1: f64) -> Option<Aabb> {
Some(Aabb::new(self.min, self.max))
}
}

View File

@ -1,7 +1,9 @@
mod cuboid;
mod moving_sphere;
mod rectangle;
mod sphere;
mod xy_rectangle;
pub use cuboid::Cuboid;
pub use moving_sphere::MovingSphere;
pub use rectangle::RectBuilder;
pub use sphere::Sphere;
pub use xy_rectangle::XyRectangle;

View File

@ -1,6 +1,7 @@
use crate::{
hitable::{HitRecord, Hitable},
types::{Ray, Vec3},
Aabb, HitRecord, Hitable, Material,
Aabb, Material,
};
pub struct MovingSphere<T: Material + Sized> {
@ -53,9 +54,9 @@ impl<T: Material + Sized> MovingSphere<T> {
impl<T: Material + Sized> Hitable for MovingSphere<T> {
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let oc = ray.origin() - self.center(ray.time());
let a = ray.direction().dot(&ray.direction());
let b = oc.dot(&ray.direction());
let oc = ray.origin - self.center(ray.time());
let a = ray.direction.dot(&ray.direction);
let b = oc.dot(&ray.direction);
let c = oc.dot(&oc) - self.radius * self.radius;
let discriminant = b * b - a * c;

View File

@ -0,0 +1,175 @@
use std::{marker::PhantomData, ops::RangeInclusive};
use crate::{
hitable::{HitRecord, Hitable},
materials::MaterialBuilder,
types::{Ray, Vec3},
Aabb, Dimension, Material, X, Y, Z,
};
type DimRange = RangeInclusive<f64>;
pub struct Rectangle<D1, D2, D3, T> {
d1_range: DimRange,
d2_range: DimRange,
d3: f64,
material: T,
tag: PhantomData<(D1, D2, D3)>,
}
impl<D1, D2, D3, T> Hitable for Rectangle<D1, D2, D3, T>
where
T: Material,
D1: Dimension,
D2: Dimension,
D3: Dimension,
{
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let t = (self.d3 - ray.origin.get::<D3>()) / ray.direction.get::<D3>();
if t < t_min || t > t_max {
return None;
}
let d1 = ray.origin.get::<D1>() + t * ray.direction.get::<D1>();
let d2 = ray.origin.get::<D2>() + t * ray.direction.get::<D2>();
if !self.d1_range.contains(&d1) || !self.d2_range.contains(&d2) {
return None;
}
let u = (d1 - self.d1_range.start()) / (self.d1_range.end() - self.d1_range.start());
let v = (d2 - self.d2_range.start()) / (self.d2_range.end() - self.d2_range.start());
let mut hit_rec = HitRecord::new(
t,
ray.point_at_parameter(t),
Vec3::splat(0.0).set::<D3>(1.0),
&self.material,
(u, v),
);
hit_rec.set_face_normal(ray);
Some(hit_rec)
}
fn bounding_box(&self, _t0: f64, _t1: f64) -> Option<Aabb> {
// Since this is a axis aligned Rectangle and we are using AABB BVH, Gap between the rectangle and
// the bounding box will be infinitely small
let (&d1_0, &d1_1) = (self.d1_range.start(), self.d1_range.end());
let (&d2_0, &d2_1) = (self.d2_range.start(), self.d2_range.end());
let min = Vec3::splat(self.d3 - 0.0001)
.set::<D1>(d1_0)
.set::<D2>(d2_0);
let max = Vec3::splat(self.d3 + 0.0001)
.set::<D1>(d1_1)
.set::<D2>(d2_1);
Some(Aabb::new(min, max))
}
}
// taken from, https://github.com/Globidev/toy-rt/blob/master/trt-core/src/hit/rect.rs#L74
// because it's amazing!
pub struct RectBuilder;
macro_rules! builder {
($name:ident, $dim:ty) => {
pub fn $name(self, range: RangeInclusive<f64>) -> OneBoundedRectBuilder<$dim> {
OneBoundedRectBuilder {
range,
tag: PhantomData,
}
}
};
}
pub struct OneBoundedRectBuilder<D> {
range: DimRange,
tag: PhantomData<D>,
}
impl RectBuilder {
builder!(x, X);
builder!(y, Y);
builder!(z, Z);
}
macro_rules! one_bounded_rect_builder {
($name:ident, $dim1: ty, $dim2: ty) => {
pub fn $name(self, d2_range: DimRange) -> TwoBoundedRectBuilder<$dim1, $dim2> {
TwoBoundedRectBuilder {
d1_range: self.range,
d2_range,
tag: PhantomData,
}
}
};
}
impl OneBoundedRectBuilder<X> {
one_bounded_rect_builder!(y, X, Y);
one_bounded_rect_builder!(z, X, Z);
}
impl OneBoundedRectBuilder<Y> {
one_bounded_rect_builder!(x, Y, X);
one_bounded_rect_builder!(z, Y, Z);
}
impl OneBoundedRectBuilder<Z> {
one_bounded_rect_builder!(x, Z, X);
one_bounded_rect_builder!(y, Z, Y);
}
pub struct TwoBoundedRectBuilder<D1, D2> {
d1_range: DimRange,
d2_range: DimRange,
tag: PhantomData<(D1, D2)>,
}
macro_rules! two_bounded_rect_builder {
($name:ident, $dim1: ty, $dim2: ty, $dim3: ty) => {
pub fn $name(self, $name: f64) -> ThreeBoundedRectBuilder<$dim1, $dim2, $dim3> {
ThreeBoundedRectBuilder {
d1_range: self.d1_range,
d2_range: self.d2_range,
d3: $name,
tag: PhantomData,
}
}
};
}
impl TwoBoundedRectBuilder<X, Y> {
two_bounded_rect_builder!(z, X, Y, Z);
}
impl TwoBoundedRectBuilder<X, Z> {
two_bounded_rect_builder!(y, X, Z, Y);
}
impl TwoBoundedRectBuilder<Y, Z> {
two_bounded_rect_builder!(x, Y, Z, X);
}
pub struct ThreeBoundedRectBuilder<D1, D2, D3> {
d1_range: DimRange,
d2_range: DimRange,
d3: f64,
tag: PhantomData<(D1, D2, D3)>,
}
impl<D1, D2, D3, T> MaterialBuilder<T> for ThreeBoundedRectBuilder<D1, D2, D3> {
type Finished = Rectangle<D1, D2, D3, T>;
fn material(self, material: T) -> Self::Finished {
Rectangle {
d1_range: self.d1_range,
d2_range: self.d2_range,
d3: self.d3,
material,
tag: PhantomData,
}
}
}

View File

@ -1,6 +1,7 @@
use crate::{
hitable::{HitRecord, Hitable},
types::{Ray, Vec3},
Aabb, HitRecord, Hitable, Material,
Aabb, Material,
};
pub struct Sphere<T: Material + Sized> {
@ -34,9 +35,9 @@ impl<T: Material + Sized> Sphere<T> {
impl<T: Material + Sized> Hitable for Sphere<T> {
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let oc = ray.origin() - self.center;
let a = ray.direction().dot(&ray.direction());
let b = oc.dot(&ray.direction());
let oc = ray.origin - self.center;
let a = ray.direction.dot(&ray.direction);
let b = oc.dot(&ray.direction);
let c = oc.dot(&oc) - self.radius * self.radius;
// The discriminant is calculated using b^2 - 4 * a * c

View File

@ -1,33 +1,32 @@
#![allow(clippy::suspicious_arithmetic_impl)]
mod aabb;
mod bvh;
mod camera;
mod demos;
mod hitable;
mod hitable_list;
mod materials;
mod shapes;
mod texture;
mod types;
pub use aabb::Aabb;
pub use bvh::BvhNode;
pub use camera::Camera;
pub use hitable::{HitRecord, Hitable};
pub use hitable_list::HitableList;
pub use materials::Material;
pub use texture::Texture;
pub use types::{Dimension, X, Y, Z};
use crate::hitable::BvhNode;
use demos::Demo;
use std::time::Instant;
const NUM_SAMPLES: u16 = 1000;
pub trait Asf64: num_traits::AsPrimitive<f64> {}
impl<T: num_traits::AsPrimitive<f64>> Asf64 for T {}
const NUM_SAMPLES: u16 = 100;
const VERTICAL_PARTITION: usize = 12;
const HORIZONTAL_PARTITION: usize = 12;
const WIDTH: usize = 2560;
const HEIGHT: usize = 1440;
const WIDTH: usize = 1500;
const HEIGHT: usize = 1500;
fn main() -> Result<(), String> {
run(WIDTH, HEIGHT)
@ -67,7 +66,7 @@ fn run(mut width: usize, mut height: usize) -> Result<(), String> {
.create_texture_static(PixelFormatEnum::BGR888, width as u32, height as u32)
.map_err(|e| e.to_string())?;
let mut active_demo: &dyn Demo<DemoT = BvhNode<Arc<dyn ParallelHit>>> = &demos::SimpleLight {};
let mut active_demo: &dyn Demo<DemoT = BvhNode<Arc<dyn ParallelHit>>> = &demos::CornellBox {};
let mut should_update = true;
loop {
@ -104,6 +103,10 @@ fn run(mut width: usize, mut height: usize) -> Result<(), String> {
active_demo = &demos::SimpleLight {};
should_update = true;
}
Some(Keycode::Num6) => {
active_demo = &demos::CornellBox {};
should_update = true;
}
None => unreachable!(),
_ => (),
};

View File

@ -1,18 +1,19 @@
use rand::{prelude::SmallRng, Rng};
use crate::{
hitable::HitRecord,
materials::{reflect, refract, schlick},
types::{Ray, Vec3},
HitRecord, Material,
Material,
};
pub struct Dielectric {
reflection_index: f64,
refraction_index: f64,
}
impl Dielectric {
pub fn new(reflection_index: f64) -> Self {
Self { reflection_index }
pub fn new(refraction_index: f64) -> Self {
Self { refraction_index }
}
}
@ -23,44 +24,37 @@ impl Material for Dielectric {
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(),
)
let refraction_ratio = if hit_rec.front_face {
1.0 / self.refraction_index
} else {
(
hit_rec.normal,
1.0 / self.reflection_index,
(-ray_in.direction().dot(&hit_rec.normal)) / ray_in.direction().length(),
)
self.refraction_index
};
if let Some(refracted_ray) = refract(ray_in.direction(), outward_normal, ni_over_nt) {
let reflect_prob = schlick(cosine, self.reflection_index);
let unit_direction = ray_in.direction.unit_vector();
let cosine = (-unit_direction).dot(&hit_rec.normal).min(1.0);
let sin_theta = (1.0 - cosine * cosine).sqrt();
if rng.gen::<f64>() < reflect_prob {
let cannot_refract = refraction_ratio * sin_theta > 1.0;
if cannot_refract || schlick(cosine, refraction_ratio) > rng.gen::<f64>() {
let direction = reflect(unit_direction, hit_rec.normal);
(
attenuation,
Some(Ray::new(hit_rec.p, reflected_ray, ray_in.time())),
Some(Ray::new(hit_rec.p, direction, ray_in.time())),
)
} else if let Some(direction) = refract(unit_direction, hit_rec.normal, refraction_ratio) {
(
attenuation,
Some(Ray::new(hit_rec.p, direction, ray_in.time())),
)
} else {
let direction = reflect(unit_direction, hit_rec.normal);
(
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())),
Some(Ray::new(hit_rec.p, direction, ray_in.time())),
)
}
}

View File

@ -1,11 +1,13 @@
use rand::prelude::SmallRng;
use crate::{
hitable::HitRecord,
materials::random_point_in_unit_sphere,
types::{Ray, Vec3},
HitRecord, Material, Texture,
Material, Texture,
};
#[derive(Copy, Clone)]
pub struct Lambertian<T: Texture> {
albedo: T,
}

View File

@ -1,9 +1,10 @@
use rand::prelude::SmallRng;
use crate::{
hitable::HitRecord,
materials::{random_point_in_unit_sphere, reflect},
types::{Ray, Vec3},
HitRecord, Material,
Material,
};
pub struct Metal {
@ -28,14 +29,14 @@ impl Material for Metal {
hit_rec: &HitRecord,
rng: &mut SmallRng,
) -> (Vec3, Option<Ray>) {
let reflected_ray = reflect(ray_in.direction().unit_vector(), hit_rec.normal);
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 {
if scattered_ray.direction.dot(&hit_rec.normal) > 0.0 {
(self.albedo, Some(scattered_ray))
} else {
(self.albedo, None)

View File

@ -10,8 +10,8 @@ pub use metal::Metal;
use rand::{prelude::SmallRng, Rng};
use crate::{
hitable::HitRecord,
types::{Ray, Vec3},
HitRecord,
};
pub trait Material: Send + Sync {
@ -23,11 +23,11 @@ pub trait Material: Send + Sync {
_hit_rec: &HitRecord,
_rng: &mut SmallRng,
) -> (Vec3, Option<Ray>) {
(Vec3::new(0.0, 0.0, 0.0), None)
(Vec3::splat(0.0), None)
}
fn emit(&self, _u: f64, _v: f64, _p: Vec3) -> Vec3 {
Vec3::new(0.0, 0.0, 0.0)
Vec3::splat(0.0)
}
}
@ -56,11 +56,15 @@ fn refract(incident: Vec3, normal: Vec3, ni_over_nt: f64) -> Option<Vec3> {
}
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);
let mut point = Vec3::random(rng) * 2.0 - Vec3::splat(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 = Vec3::random(rng) * 2.0 - Vec3::splat(1.0);
}
point
}
pub trait MaterialBuilder<T> {
type Finished;
fn material(self, material: T) -> Self::Finished;
}

View File

@ -1,64 +0,0 @@
use crate::{
types::{Ray, Vec3},
Aabb, HitRecord, Hitable, Material,
};
pub struct XyRectangle<T: Material> {
x0: f64,
x1: f64,
y0: f64,
y1: f64,
k: f64,
material: T,
}
impl<T: Material> XyRectangle<T> {
pub fn new(x0: f64, x1: f64, y0: f64, y1: f64, k: f64, material: T) -> Self {
Self {
x0,
x1,
y0,
y1,
k,
material,
}
}
}
impl<T: Material> Hitable for XyRectangle<T> {
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let t = (self.k - ray.origin().z()) / ray.direction().z();
if t < t_min || t > t_max {
None
} else {
let x = ray.origin().x() + t * ray.direction().x();
let y = ray.origin().y() + t * ray.direction().y();
if x < self.x0 || x > self.x1 || y < self.y0 || y > self.y1 {
None
} else {
let u = (x - self.x0) / (self.x1 - self.x0);
let v = (y - self.y0) / (self.y1 - self.y0);
Some(HitRecord::new(
t,
ray.point_at_parameter(t),
Vec3::new(0.0, 0.0, 1.0),
&self.material,
(u, v),
))
}
}
}
fn bounding_box(&self, _t0: f64, _t1: f64) -> Option<Aabb> {
// Since this is a axis aligned Rectangle and we are using AABB BVH, Gap between the rectangle and
// the bounding box will be infinitely small
Some(Aabb::new(
Vec3::new(self.x0, self.y0, self.k - 0.0001),
Vec3::new(self.x1, self.y1, self.k + 0.0001),
))
}
}

View File

@ -1,5 +1,6 @@
use crate::{types::Vec3, Texture};
#[derive(Copy, Clone)]
pub struct Solid {
color: Vec3,
}

12
src/types/color.rs Normal file
View File

@ -0,0 +1,12 @@
use crate::types::Vec3;
pub struct Color(pub u8, pub u8, pub u8);
impl From<Vec3> for Color {
fn from(v: Vec3) -> Self {
let v = v.sqrt() * 255.99;
let (r, g, b) = (v.x(), v.y(), v.z());
Self(r as u8, g as u8, b as u8)
}
}

21
src/types/dimension.rs Normal file
View File

@ -0,0 +1,21 @@
// Taken from, https://github.com/Globidev/toy-rt/tree/master/trt-core/src/dimension.rs
pub trait Dimension {
const INDEX: usize;
}
pub struct X;
pub struct Y;
pub struct Z;
impl Dimension for X {
const INDEX: usize = 0;
}
impl Dimension for Y {
const INDEX: usize = 1;
}
impl Dimension for Z {
const INDEX: usize = 2;
}

View File

@ -1,5 +1,9 @@
mod color;
mod dimension;
mod ray;
mod vec3;
pub use color::Color;
pub use dimension::{Dimension, X, Y, Z};
pub use ray::Ray;
pub use vec3::Vec3;

View File

@ -1,28 +1,25 @@
use rand::prelude::SmallRng;
use crate::{types::Vec3, Hitable};
use crate::{hitable::Hitable, types::Vec3};
pub struct Ray {
a: Vec3,
b: Vec3,
pub origin: Vec3,
pub direction: Vec3,
time: f64,
}
impl Ray {
pub fn new(a: Vec3, b: Vec3, time: f64) -> Ray {
Ray { a, b, time }
pub fn new(origin: Vec3, direction: Vec3, time: f64) -> Ray {
Ray {
origin,
direction,
time,
}
#[inline]
pub const fn origin(&self) -> Vec3 {
self.a
}
#[inline]
pub const fn direction(&self) -> Vec3 {
self.b
}
#[inline]
pub fn point_at_parameter(&self, t: f64) -> Vec3 {
self.a + self.b * t
self.origin + self.direction * t
}
#[inline]
pub const fn time(&self) -> f64 {
@ -38,7 +35,7 @@ impl Ray {
) -> Vec3 {
if let Some(hit_rec) = world.hit(self, 0.001, std::f64::MAX) {
if depth >= 50 {
Vec3::new(0.0, 0.0, 0.0)
Vec3::splat(0.0f64)
} else {
let material = hit_rec.material;
let emitted_color = hit_rec.material.emit(hit_rec.u, hit_rec.v, hit_rec.p);

View File

@ -5,37 +5,45 @@ use std::{
use rand::Rng;
#[derive(Debug, Copy, Clone)]
use crate::{Asf64, Dimension, X, Y, Z};
#[derive(Default, Debug, Copy, Clone)]
pub struct Vec3([f64; 3]);
impl Vec3 {
#[inline]
pub const fn new(a: f64, b: f64, c: f64) -> Vec3 {
Vec3([a, b, c])
pub fn new(a: impl Asf64, b: impl Asf64, c: impl Asf64) -> Vec3 {
Vec3([a.as_(), b.as_(), c.as_()])
}
pub fn splat(xyz: impl Asf64) -> Self {
Self::new(xyz, xyz, xyz)
}
pub fn random<R: Rng + ?Sized>(rng: &mut R) -> Self {
Self(rng.gen())
}
#[inline]
pub fn x(&self) -> f64 {
self[0]
self.get::<X>()
}
#[inline]
pub fn y(&self) -> f64 {
self[1]
self.get::<Y>()
}
#[inline]
pub fn z(&self) -> f64 {
self[2]
self.get::<Z>()
}
#[inline]
pub fn r(&self) -> f64 {
self[0]
pub fn get<D: Dimension>(&self) -> f64 {
self.0[D::INDEX]
}
#[inline]
pub fn g(&self) -> f64 {
self[1]
}
#[inline]
pub fn b(&self) -> f64 {
self[2]
pub fn set<D: Dimension>(mut self, value: f64) -> Self {
self.0[D::INDEX] = value;
self
}
#[inline]
@ -45,40 +53,55 @@ impl Vec3 {
#[inline]
pub fn sq_len(&self) -> f64 {
self[0] * self[0] + self[1] * self[1] + self[2] * self[2]
self.x() * self.x() + self.y() * self.y() + self.z() * self.z()
}
#[inline]
pub fn dot(&self, v: &Vec3) -> f64 {
self[0] * v[0] + self[1] * v[1] + self[2] * v[2]
self.x() * v.x() + self.y() * v.y() + self.z() * v.z()
}
#[inline]
pub fn cross(&self, v: &Vec3) -> Vec3 {
Vec3([
self[1] * v[2] - self[2] * v[1],
self[2] * v[0] - self[0] * v[2],
self[0] * v[1] - self[1] * v[0],
self.y() * v.z() - self.z() * v.y(),
self.z() * v.x() - self.x() * v.z(),
self.x() * v.y() - self.y() * v.x(),
])
}
#[inline]
pub fn make_unit_vector(&mut self) {
let k = 1.0f64 / (self[0] * self[0] + self[1] * self[1] + self[2] * self[2]);
self[0] *= k;
self[1] *= k;
self[2] *= k;
pub fn unit_vector(self) -> Vec3 {
self / self.length()
}
pub fn min(self, other: Self) -> Vec3 {
Self([
self.x().min(other.x()),
self.y().min(other.y()),
self.z().min(other.z()),
])
}
pub fn max(self, other: Self) -> Vec3 {
Self([
self.x().max(other.x()),
self.y().max(other.y()),
self.z().max(other.z()),
])
}
pub fn min_element(self) -> f64 {
self.x().min(self.y()).min(self.z())
}
pub fn max_element(self) -> f64 {
self.x().max(self.y()).max(self.z())
}
#[inline]
pub fn unit_vector(&self) -> Vec3 {
let length = self.length();
Vec3([self[0] / length, self[1] / length, self[2] / length])
}
#[inline]
pub fn random<R: Rng + ?Sized>(rng: &mut R) -> Vec3 {
Vec3([rng.gen(), rng.gen(), rng.gen()])
pub fn sqrt(self) -> Self {
Vec3::new(self.x().sqrt(), self.y().sqrt(), self.z().sqrt())
}
}
@ -86,7 +109,7 @@ impl Add for Vec3 {
type Output = Vec3;
fn add(self, o: Vec3) -> Vec3 {
Vec3([self[0] + o[0], self[1] + o[1], self[2] + o[2]])
Vec3([self.x() + o.x(), self.y() + o.y(), self.z() + o.z()])
}
}
@ -102,15 +125,15 @@ impl Sub for Vec3 {
type Output = Vec3;
fn sub(self, o: Vec3) -> Vec3 {
Vec3([self[0] - o[0], self[1] - o[1], self[2] - o[2]])
Vec3([self.x() - o.x(), self.y() - o.y(), self.z() - o.z()])
}
}
impl SubAssign for Vec3 {
fn sub_assign(&mut self, o: Vec3) {
self[0] -= o[0];
self[1] -= o[1];
self[2] -= o[2];
self.0[0] -= o.0[0];
self.0[1] -= o.0[1];
self.0[2] -= o.0[2];
}
}
@ -118,37 +141,37 @@ impl Neg for Vec3 {
type Output = Vec3;
fn neg(self) -> Vec3 {
Vec3([-self[0], -self[1], -self[2]])
Vec3([-self.x(), -self.y(), -self.z()])
}
}
impl MulAssign<Vec3> for Vec3 {
fn mul_assign(&mut self, o: Vec3) {
self[0] *= o[0];
self[1] *= o[1];
self[2] *= o[2];
self.0[0] *= o.0[0];
self.0[1] *= o.0[1];
self.0[2] *= o.0[2];
}
}
impl MulAssign<f64> for Vec3 {
fn mul_assign(&mut self, o: f64) {
self[0] *= o;
self[1] *= o;
self[2] *= o;
self.0[0] *= o;
self.0[1] *= o;
self.0[2] *= o;
}
}
impl Mul<f64> for Vec3 {
type Output = Vec3;
fn mul(self, o: f64) -> Vec3 {
Vec3([self[0] * o, self[1] * o, self[2] * o])
Vec3([self.x() * o, self.y() * o, self.z() * o])
}
}
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]])
Vec3([self.x() * o.x(), self.y() * o.y(), self.z() * o.z()])
}
}
@ -156,7 +179,7 @@ impl Div<Vec3> for Vec3 {
type Output = Vec3;
fn div(self, o: Vec3) -> Vec3 {
Vec3([self[0] / o[0], self[1] / o[1], self[2] / o[2]])
Vec3([self.x() / o.x(), self.y() / o.y(), self.z() / o.z()])
}
}
@ -165,7 +188,7 @@ impl Div<f64> for Vec3 {
fn div(self, o: f64) -> Vec3 {
let o = 1.0 / o;
Vec3([self[0] * o, self[1] * o, self[2] * o])
Vec3([self.x() * o, self.y() * o, self.z() * o])
}
}
@ -194,6 +217,17 @@ impl IndexMut<usize> for Vec3 {
impl Display for Vec3 {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
f.write_fmt(format_args!("{} {} {}", self[0], self[1], self[2]))
f.write_fmt(format_args!(
"{} {} {}",
self.get::<X>(),
self.get::<Y>(),
self.get::<Z>()
))
}
}
impl<A: Asf64, B: Asf64, C: Asf64> From<(A, B, C)> for Vec3 {
fn from((x, y, z): (A, B, C)) -> Self {
Self::new(x, y, z)
}
}