Refactored to improve some performance.

1. Removed duplication to increase performance a little bit
2. Fixed some minor rendering related issues
3. Added a Defocus Blur Demo
This commit is contained in:
Ishan Jain 2020-03-11 16:09:39 +05:30
parent 52c6c76c0d
commit f75d0d2b7d
16 changed files with 399 additions and 128 deletions

View File

@ -1,10 +1,19 @@
use crate::types::{Ray, Vec3};
use {
crate::types::{Ray, Vec3},
rand::Rng,
};
pub struct Camera {
pub origin: Vec3,
pub horizontal: Vec3,
pub vertical: Vec3,
pub lower_left_corner: Vec3,
origin: Vec3,
horizontal: Vec3,
vertical: Vec3,
lower_left_corner: Vec3,
lens_radius: f64,
// position vectors
u: Vec3,
v: Vec3,
w: Vec3,
}
impl Camera {
@ -13,7 +22,15 @@ impl Camera {
// look_at is the point where camera is looking
// v_up is camera's up vector. i.e. it points upwards from the camera
// orthogonal to look_from - look_at vector
pub fn new(look_from: Vec3, look_at: Vec3, v_up: Vec3, vertical_fov: f64, aspect: f64) -> Self {
pub fn new(
look_from: Vec3,
look_at: Vec3,
v_up: Vec3,
vertical_fov: f64,
aspect: f64,
aperture: f64,
focus_distance: f64,
) -> Self {
// convert degree to radian
let angle = vertical_fov * std::f64::consts::PI / 180.0;
let half_height = (angle / 2.0).tan();
@ -24,35 +41,42 @@ impl Camera {
let u = v_up.cross(&w).unit_vector();
let v = w.cross(&u);
let lower_left_corner = origin - u * half_width - v * half_height - w;
let lower_left_corner = origin
- u * focus_distance * half_width
- v * focus_distance * half_height
- w * focus_distance;
let horizontal = u * half_width * 2.0;
let vertical = v * half_height * 2.0;
let lens_radius = aperture / 2.0;
Self {
lens_radius,
lower_left_corner,
horizontal,
vertical,
origin,
u,
v,
w,
}
}
pub fn get_ray(&self, u: f64, v: f64) -> Ray {
let mut rng = rand::thread_rng();
let rd = random_in_unit_disk(&mut rng) * self.lens_radius;
let offset = self.u * rd.x() + self.v * rd.y();
Ray::new(
self.origin,
self.lower_left_corner + self.horizontal * u + self.vertical * v - self.origin,
self.origin + offset,
self.lower_left_corner + self.horizontal * u + self.vertical * v - self.origin - offset,
)
}
}
impl std::default::Default for Camera {
fn default() -> Self {
Camera::new(
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(0.0, 0.0, -1.0),
Vec3::new(0.0, 1.0, 0.0),
90.0,
// 2:1 aspect ratio width:height
2.0,
)
fn random_in_unit_disk(rng: &mut rand::rngs::ThreadRng) -> Vec3 {
let mut p = Vec3::new(rng.gen::<f64>(), rng.gen::<f64>(), 0.0) * 2.0 - Vec3::new(1.0, 0.0, 0.0);
while p.dot(&p) >= 1.0 {
p = Vec3::new(rng.gen::<f64>(), rng.gen::<f64>(), 0.0) * 2.0 - Vec3::new(1.0, 0.0, 0.0);
}
p
}

125
src/demos/defocus_blur.rs Normal file
View File

@ -0,0 +1,125 @@
use {
crate::{
demos::{Chunk, Demo},
types::{
material::{Dielectric, Lambertian, Metal},
Hitable, HitableList, Ray, Sphere, Vec3,
},
Camera,
},
rand::Rng,
};
pub struct DefocusBlur;
impl Demo for DefocusBlur {
fn name(&self) -> &'static str {
"defocus-blur"
}
fn world(&self) -> Option<HitableList> {
let radius = (std::f64::consts::PI / 4.0).cos();
Some(HitableList {
list: vec![
Box::new(Sphere::with_material(
Vec3::new(-radius, 0.0, -1.0),
radius,
Box::new(Lambertian::new(Vec3::new(0.0, 0.0, 1.0))),
)),
Box::new(Sphere::with_material(
Vec3::new(radius, 0.0, -1.0),
radius,
Box::new(Metal::with_fuzz(Vec3::new(0.5, 0.5, 0.0), 0.2)),
)),
Box::new(Sphere::with_material(
Vec3::new(-3.0 * radius, 0.0, -1.0),
radius,
Box::new(Dielectric::new(1.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))),
)),
],
})
}
fn camera(&self, aspect_ratio: f64) -> Option<Camera> {
let lookfrom = Vec3::new(3.0, 3.0, 2.0);
let lookat = Vec3::new(0.0, 0.0, -1.0);
let aperture = 2.0;
let distance_to_focus = (lookfrom - lookat).length();
let camera = Camera::new(
lookfrom,
lookat,
Vec3::new(0.0, 1.0, 0.0),
20.0,
aspect_ratio,
aperture,
distance_to_focus,
);
Some(camera)
}
fn render_chunk(
&self,
chunk: &mut Chunk,
camera: Option<&Camera>,
world: Option<&HitableList>,
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 camera = camera.unwrap();
let world = world.unwrap();
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
}
}

View File

@ -17,16 +17,8 @@ impl Demo for DielectricMaterial {
"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 {
fn world(&self) -> Option<HitableList> {
Some(HitableList {
list: vec![
Box::new(Sphere::with_material(
Vec3::new(0.0, 0.0, -1.0),
@ -54,9 +46,26 @@ impl Demo for DielectricMaterial {
Box::new(Dielectric::new(1.5)),
)),
],
};
})
}
fn render_chunk(
&self,
chunk: &mut Chunk,
camera: Option<&Camera>,
world: Option<&HitableList>,
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 camera = camera.unwrap();
let world = world.unwrap();
let camera: Camera = Default::default();
let mut rng = rand::thread_rng();
let mut offset = 0;

View File

@ -14,7 +14,22 @@ impl Demo for DiffuseMaterials {
"diffuse-materials"
}
fn render_chunk(&self, chunk: &mut Chunk, samples: u8) {
fn world(&self) -> Option<HitableList> {
Some(HitableList {
list: vec![
Box::new(Sphere::new(Vec3::new(0.0, 0.0, -1.0), 0.5)),
Box::new(Sphere::new(Vec3::new(0.0, -100.5, -1.0), 100.0)),
],
})
}
fn render_chunk(
&self,
chunk: &mut Chunk,
camera: Option<&Camera>,
world: Option<&HitableList>,
samples: u8,
) {
let x = chunk.x;
let y = chunk.y;
let nx = chunk.nx;
@ -22,15 +37,9 @@ impl Demo for DiffuseMaterials {
let start_x = chunk.start_x;
let start_y = chunk.start_y;
let buffer = &mut chunk.buffer;
let camera = camera.unwrap();
let world = world.unwrap();
let world = HitableList {
list: vec![
Box::new(Sphere::new(Vec3::new(0.0, 0.0, -1.0), 0.5)),
Box::new(Sphere::new(Vec3::new(0.0, -100.5, -1.0), 100.0)),
],
};
let camera: Camera = Default::default();
let mut rng = rand::thread_rng();
let mut offset = 0;
for j in start_y..start_y + ny {

View File

@ -1,6 +1,7 @@
use crate::{
demos::{Chunk, Demo},
types::{Hitable, HitableList, Ray, Sphere, Vec3},
Camera,
};
pub struct HitableSphere;
@ -9,7 +10,22 @@ impl Demo for HitableSphere {
"sphere-using-hit-table"
}
fn render_chunk(&self, chunk: &mut Chunk, _samples: u8) {
fn world(&self) -> Option<HitableList> {
Some(HitableList {
list: vec![
Box::new(Sphere::new(Vec3::new(0.0, 0.0, -1.0), 0.5)),
Box::new(Sphere::new(Vec3::new(0.0, -100.5, -1.0), 100.0)),
],
})
}
fn render_chunk(
&self,
chunk: &mut Chunk,
_camera: Option<&Camera>,
world: Option<&HitableList>,
_samples: u8,
) {
let x = chunk.x;
let y = chunk.y;
let nx = chunk.nx;
@ -17,18 +33,13 @@ impl Demo for HitableSphere {
let start_x = chunk.start_x;
let start_y = chunk.start_y;
let buffer = &mut chunk.buffer;
let world = world.unwrap();
let lower_left_corner = Vec3::new(-2.0, -1.0, -1.0);
let horizontal = Vec3::new(4.0, 0.0, 0.0);
let vertical = Vec3::new(0.0, 2.0, 0.0);
let origin = Vec3::new(0.0, 0.0, 0.0);
let world = HitableList {
list: vec![
Box::new(Sphere::new(Vec3::new(0.0, 0.0, -1.0), 0.5)),
Box::new(Sphere::new(Vec3::new(0.0, -100.5, -1.0), 100.0)),
],
};
let mut offset = 0;
for j in start_y..start_y + ny {
for i in start_x..start_x + nx {

View File

@ -1,7 +1,8 @@
pub struct LinearGradientRectangle;
use crate::{
types::{Ray, Vec3},
types::{HitableList, Ray, Vec3},
Camera,
{demos::Chunk, Demo},
};
@ -10,7 +11,13 @@ impl Demo for LinearGradientRectangle {
"linear-gradient-rectangle"
}
fn render_chunk(&self, chunk: &mut Chunk, _samples: u8) {
fn render_chunk(
&self,
chunk: &mut Chunk,
_camera: Option<&Camera>,
_world: Option<&HitableList>,
_samples: u8,
) {
let x = chunk.x;
let y = chunk.y;
let nx = chunk.nx;

View File

@ -17,16 +17,8 @@ impl Demo for Materials {
"metal-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 {
fn world(&self) -> Option<HitableList> {
Some(HitableList {
list: vec![
Box::new(Sphere::with_material(
Vec3::new(0.0, 0.0, -1.0),
@ -49,9 +41,25 @@ impl Demo for Materials {
Box::new(Metal::with_fuzz(Vec3::new(0.8, 0.8, 0.8), 0.5)),
)),
],
};
})
}
fn render_chunk(
&self,
chunk: &mut Chunk,
camera: Option<&Camera>,
world: Option<&HitableList>,
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 camera = camera.unwrap();
let camera: Camera = Default::default();
let mut rng = rand::thread_rng();
let mut offset = 0;
@ -63,7 +71,7 @@ impl Demo for Materials {
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 += calc_color(ray, world.unwrap(), 0);
}
color /= samples as f64;

View File

@ -1,3 +1,4 @@
mod defocus_blur;
mod dielectric_material;
mod diffuse_materials;
mod hitable_sphere;
@ -9,6 +10,7 @@ mod simple_rectangle;
mod simple_sphere;
mod surface_normal_sphere;
pub use defocus_blur::DefocusBlur;
pub use dielectric_material::DielectricMaterial;
pub use diffuse_materials::DiffuseMaterials;
pub use hitable_sphere::HitableSphere;
@ -21,7 +23,10 @@ pub use simple_sphere::SimpleSphere;
pub use surface_normal_sphere::SurfaceNormalSphere;
use {
crate::{HORIZONTAL_PARTITION, VERTICAL_PARTITION},
crate::{
types::{HitableList, Vec3},
Camera, HORIZONTAL_PARTITION, VERTICAL_PARTITION,
},
rayon::prelude::*,
std::{
fs::File,
@ -45,45 +50,69 @@ pub trait Demo: std::marker::Sync {
fn render(&self, buf: &mut Vec<u8>, width: usize, height: usize, samples: u8) {
let nx = width / VERTICAL_PARTITION;
let ny = height / HORIZONTAL_PARTITION;
let world = self.world();
let camera = self.camera(nx as f64 / ny as f64);
let buf = Arc::new(Mutex::new(buf));
(0..VERTICAL_PARTITION).into_par_iter().for_each(move |j| {
(0..VERTICAL_PARTITION).into_par_iter().for_each(|j| {
let buf = buf.clone();
(0..HORIZONTAL_PARTITION)
.into_par_iter()
.for_each(move |i| {
let start_y = j * ny;
let start_x = i * nx;
let x = width;
let y = height;
let mut chunk = Chunk {
x,
y,
nx,
ny,
start_x,
start_y,
buffer: vec![0; nx * ny * 4],
};
self.render_chunk(&mut chunk, samples);
(0..HORIZONTAL_PARTITION).into_par_iter().for_each(|i| {
let start_y = j * ny;
let start_x = i * nx;
let x = width;
let y = height;
let mut chunk = Chunk {
x,
y,
nx,
ny,
start_x,
start_y,
buffer: vec![0; nx * ny * 4],
};
self.render_chunk(&mut chunk, camera.as_ref(), world.as_ref(), samples);
let mut buf = buf.lock().unwrap();
let mut buf = buf.lock().unwrap();
let mut temp_offset = 0;
for j in start_y..start_y + ny {
let real_offset = ((y - j - 1) * x + start_x) * 4;
let mut temp_offset = 0;
for j in start_y..start_y + ny {
let real_offset = ((y - j - 1) * x + start_x) * 4;
buf[real_offset..real_offset + nx * 4]
.copy_from_slice(&chunk.buffer[temp_offset..temp_offset + nx * 4]);
buf[real_offset..real_offset + nx * 4]
.copy_from_slice(&chunk.buffer[temp_offset..temp_offset + nx * 4]);
temp_offset += nx * 4;
}
})
temp_offset += nx * 4;
}
})
});
}
fn render_chunk(&self, chunk: &mut Chunk, samples: u8);
fn world(&self) -> Option<HitableList> {
None
}
fn camera(&self, aspect_ratio: f64) -> Option<Camera> {
let lookfrom = Vec3::new(0.0, 0.0, 0.0);
let lookat = Vec3::new(0.0, 0.0, -1.0);
Some(Camera::new(
lookfrom,
lookat,
Vec3::new(0.0, 1.0, 0.0),
90.0,
aspect_ratio,
0.0,
1.0,
))
}
fn render_chunk(
&self,
chunk: &mut Chunk,
camera: Option<&Camera>,
world: Option<&HitableList>,
samples: u8,
);
fn name(&self) -> &'static str;

View File

@ -17,17 +17,9 @@ impl Demo for PositionableCamera {
"positionable-camera"
}
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;
fn world(&self) -> Option<HitableList> {
let radius = (std::f64::consts::PI / 4.0).cos();
let world = HitableList {
Some(HitableList {
list: vec![
Box::new(Sphere::with_material(
Vec3::new(-radius, 0.0, -1.0),
@ -50,15 +42,39 @@ impl Demo for PositionableCamera {
Box::new(Lambertian::new(Vec3::new(0.8, 0.8, 0.0))),
)),
],
};
})
}
let camera = Camera::new(
Vec3::new(-2.0, 2.0, 1.0),
Vec3::new(0.0, 0.0, -1.0),
fn camera(&self, aspect_ratio: f64) -> Option<Camera> {
let lookfrom = Vec3::new(-2.0, 2.0, 1.0);
let lookat = Vec3::new(0.0, 0.0, -1.0);
Some(Camera::new(
lookfrom,
lookat,
Vec3::new(0.0, 1.0, 0.0),
68.0,
nx as f64 / ny as f64,
);
aspect_ratio,
0.0,
1.0,
))
}
fn render_chunk(
&self,
chunk: &mut Chunk,
camera: Option<&Camera>,
world: Option<&HitableList>,
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 camera = camera.unwrap();
let world = world.unwrap();
let mut rng = rand::thread_rng();
let mut offset = 0;
@ -72,9 +88,7 @@ impl Demo for PositionableCamera {
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;

View File

@ -12,7 +12,23 @@ impl Demo for SimpleAntialiasing {
fn name(&self) -> &'static str {
"simple-antialiasing"
}
fn render_chunk(&self, chunk: &mut Chunk, samples: u8) {
fn world(&self) -> Option<HitableList> {
Some(HitableList {
list: vec![
Box::new(Sphere::new(Vec3::new(0.0, 0.0, -1.0), 0.5)),
Box::new(Sphere::new(Vec3::new(0.0, -100.5, -1.0), 100.0)),
],
})
}
fn render_chunk(
&self,
chunk: &mut Chunk,
camera: Option<&Camera>,
world: Option<&HitableList>,
samples: u8,
) {
let x = chunk.x;
let y = chunk.y;
let nx = chunk.nx;
@ -20,15 +36,8 @@ impl Demo for SimpleAntialiasing {
let start_x = chunk.start_x;
let start_y = chunk.start_y;
let buffer = &mut chunk.buffer;
let camera = camera.unwrap();
let world = HitableList {
list: vec![
Box::new(Sphere::new(Vec3::new(0.0, 0.0, -1.0), 0.5)),
Box::new(Sphere::new(Vec3::new(0.0, -100.5, -1.0), 100.0)),
],
};
let camera: Camera = Default::default();
let mut rng = rand::thread_rng();
let mut offset = 0;
@ -40,7 +49,7 @@ impl Demo for SimpleAntialiasing {
let v = (j as f64 + rng.gen::<f64>()) / y as f64;
let r = camera.get_ray(u, v);
color += calc_color(r, &world);
color += calc_color(r, world.unwrap());
}
color /= samples as f64;
buffer[offset] = (255.99 * color.r()) as u8;

View File

@ -1,4 +1,8 @@
use crate::demos::{Chunk, Demo};
use crate::{
demos::{Chunk, Demo},
types::HitableList,
Camera,
};
pub struct SimpleRectangle;
@ -7,7 +11,13 @@ impl Demo for SimpleRectangle {
"simple_rectangle"
}
fn render_chunk(&self, chunk: &mut Chunk, _samples: u8) {
fn render_chunk(
&self,
chunk: &mut Chunk,
_camera: Option<&Camera>,
_world: Option<&HitableList>,
_samples: u8,
) {
let x = chunk.x;
let y = chunk.y;
let nx = chunk.nx;

View File

@ -1,6 +1,7 @@
use crate::{
demos::{Chunk, Demo},
types::{Ray, Vec3},
types::{HitableList, Ray, Vec3},
Camera,
};
const RADIUS: f64 = 0.5;
@ -12,7 +13,13 @@ impl Demo for SimpleSphere {
"simple_sphere"
}
fn render_chunk(&self, chunk: &mut Chunk, _samples: u8) {
fn render_chunk(
&self,
chunk: &mut Chunk,
_camera: Option<&Camera>,
_world: Option<&HitableList>,
_samples: u8,
) {
let x = chunk.x;
let y = chunk.y;
let nx = chunk.nx;

View File

@ -1,6 +1,7 @@
use crate::{
demos::{Chunk, Demo},
types::{Ray, Vec3},
types::{HitableList, Ray, Vec3},
Camera,
};
const RADIUS: f64 = 0.5;
@ -11,7 +12,13 @@ impl Demo for SurfaceNormalSphere {
"surface_normal_sphere"
}
fn render_chunk(&self, chunk: &mut Chunk, _samples: u8) {
fn render_chunk(
&self,
chunk: &mut Chunk,
_camera: Option<&Camera>,
_world: Option<&HitableList>,
_samples: u8,
) {
let x = chunk.x;
let y = chunk.y;
let nx = chunk.nx;

View File

@ -80,6 +80,7 @@ fn main() -> Result<(), String> {
Some(Keycode::Num8) => active_demo = Box::new(demos::Materials),
Some(Keycode::Num9) => active_demo = Box::new(demos::DielectricMaterial),
Some(Keycode::Num0) => active_demo = Box::new(demos::PositionableCamera),
Some(Keycode::Minus) => active_demo = Box::new(demos::DefocusBlur),
None => unreachable!(),
_ => (),
};

View File

@ -21,7 +21,7 @@ pub struct HitRecord<'a> {
pub material: Option<&'a Box<dyn Material>>,
}
pub trait Hitable {
pub trait Hitable: Send + Sync {
fn hit(&self, _ray: &Ray, _t_min: f64, _t_max: f64) -> Option<HitRecord> {
None
}

View File

@ -3,7 +3,7 @@ use {
rand::Rng,
};
pub trait Material {
pub trait Material: Send + Sync {
fn scatter(&self, ray: &Ray, hit_rec: &HitRecord) -> (Vec3, Option<Ray>);
}
@ -106,7 +106,8 @@ impl Material for Dielectric {
}
}
// Polynomial approximation to figure out reflectivity as the angle changes
// 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;