1
0

Completed Motion Blur Image

Moving to a different format for the project. Instead of having
a generic `Demo` trait implemented by all the demos amongst which
one can switch by pressing 1-0 keys, We'll just have one demo only.
And, To switch between different images, They can checkout to
respective branch
This commit is contained in:
Ishan Jain 2020-04-17 21:48:09 +05:30
parent 35259ad16d
commit 1a7b674ac4
No known key found for this signature in database
GPG Key ID: 1E9A32B8969AF420
15 changed files with 449 additions and 446 deletions

44
Cargo.lock generated
View File

@ -99,9 +99,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.67"
version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0"
[[package]]
name = "maybe-uninit"
@ -111,11 +111,11 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
name = "memoffset"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9"
checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8"
dependencies = [
"rustc_version",
"autocfg",
]
[[package]]
@ -145,6 +145,7 @@ dependencies = [
"rand_chacha",
"rand_core",
"rand_hc",
"rand_pcg",
]
[[package]]
@ -175,6 +176,15 @@ dependencies = [
"rand_core",
]
[[package]]
name = "rand_pcg"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
dependencies = [
"rand_core",
]
[[package]]
name = "rayon"
version = "1.3.0"
@ -208,15 +218,6 @@ dependencies = [
"sdl2",
]
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
@ -245,21 +246,6 @@ dependencies = [
"libc",
]
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"

View File

@ -4,7 +4,11 @@ version = "0.1.0"
authors = ["ishanjain28 <ishanjain28@gmail.com>"]
edition = "2018"
[features]
gui = ["sdl2"]
[dependencies]
sdl2 = "0.33.0"
rand = "0.7.3"
sdl2 = { version = "0.33.0", optional = true }
rand = { version = "0.7.3", features = ["small_rng"] }
rayon = "1.3.0"

View File

@ -8,15 +8,15 @@ pub struct Camera {
horizontal: Vec3,
vertical: Vec3,
lower_left_corner: Vec3,
lens_radius: f32,
lens_radius: f64,
// position vectors
u: Vec3,
v: Vec3,
w: Vec3,
shutter_open: f32,
shutter_close: f32,
shutter_open: f64,
shutter_close: f64,
}
impl Camera {
@ -29,15 +29,15 @@ impl Camera {
look_from: Vec3,
look_at: Vec3,
v_up: Vec3,
vertical_fov: f32,
aspect: f32,
aperture: f32,
focus_distance: f32,
shutter_open: f32,
shutter_close: f32,
vertical_fov: f64,
aspect: f64,
aperture: f64,
focus_distance: f64,
shutter_open: f64,
shutter_close: f64,
) -> Self {
// convert degree to radian
let angle = vertical_fov * std::f32::consts::PI / 180.0;
let angle = vertical_fov * std::f64::consts::PI / 180.0;
let half_height = (angle / 2.0).tan();
let half_width = aspect * half_height;
@ -68,11 +68,10 @@ impl Camera {
}
}
pub fn get_ray(&self, u: f32, v: f32) -> Ray {
let mut rng = rand::thread_rng();
let rd = random_in_unit_disk(&mut rng) * self.lens_radius;
pub fn get_ray<R: Rng + ?Sized>(&self, u: f64, v: f64, rng: &mut R) -> Ray {
let rd = random_in_unit_disk(rng) * self.lens_radius;
let offset = self.u * rd.x() + self.v * rd.y();
let time = self.shutter_open + rng.gen::<f32>() * (self.shutter_close - self.shutter_open);
let time = self.shutter_open + rng.gen::<f64>() * (self.shutter_close - self.shutter_open);
Ray::new(
self.origin + offset,
self.lower_left_corner + self.horizontal * u + self.vertical * v - self.origin - offset,
@ -81,11 +80,11 @@ impl Camera {
}
}
fn random_in_unit_disk(rng: &mut rand::rngs::ThreadRng) -> Vec3 {
let mut p = Vec3::new(rng.gen::<f32>(), rng.gen::<f32>(), 0.0) * 2.0 - Vec3::new(1.0, 1.0, 0.0);
fn random_in_unit_disk<R: Rng + ?Sized>(rng: &mut R) -> Vec3 {
let mut p = Vec3::new(rng.gen::<f64>(), rng.gen::<f64>(), 0.0) * 2.0 - Vec3::new(1.0, 1.0, 0.0);
while p.dot(&p) >= 1.0 {
p = Vec3::new(rng.gen::<f32>(), rng.gen::<f32>(), 0.0) * 2.0 - Vec3::new(1.0, 0.0, 0.0);
p = Vec3::new(rng.gen::<f64>(), rng.gen::<f64>(), 0.0) * 2.0 - Vec3::new(1.0, 0.0, 0.0);
}
p
}

290
src/demo.rs Normal file
View File

@ -0,0 +1,290 @@
use {
crate::{
types::{
material::{Dielectric, Lambertian, Metal},
Hitable, HitableList, MovingSphere, Ray, Sphere, Vec3,
},
Camera, HORIZONTAL_PARTITION, VERTICAL_PARTITION,
},
rand::{rngs::SmallRng, Rng, SeedableRng},
rayon::prelude::*,
std::{
fmt::{Display, Formatter, Result as FmtResult},
fs::File,
io::Write,
sync::{Arc, Mutex},
},
};
#[derive(Debug)]
pub struct Chunk {
num: usize,
x: usize,
y: usize,
nx: usize,
ny: usize,
start_x: usize,
start_y: usize,
buffer: Vec<u8>,
}
impl Display for Chunk {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(
f,
"Chunk #{}: Start X = {} Start Y = {} Size X = {} Size = {}",
self.num, self.start_x, self.start_y, self.nx, self.ny
)
}
}
pub struct Demo;
impl Demo {
pub fn name(&self) -> &'static str {
"motion_blur"
}
fn world(&self) -> HitableList {
let mut world = HitableList {
list: Vec::with_capacity(500),
};
let mut rng = rand::thread_rng();
let mut rng = SmallRng::from_rng(&mut rng).unwrap();
world.push(Box::new(Sphere::new(
Vec3::new(0.0, -1000.0, 0.0),
1000.0,
Lambertian::new(Vec3::new(0.5, 0.5, 0.5)),
)));
let radius = 0.2;
let l = Vec3::new(4.0, 0.2, 0.0);
for a in -10..10 {
let a = a as f64;
for b in -10..10 {
let b = b as f64;
let choose_material_probability = rng.gen::<f64>();
let center = Vec3::new(a + 0.9 * rng.gen::<f64>(), 0.2, b + 0.9 * rng.gen::<f64>());
if (center - l).length() > 0.9 {
if choose_material_probability < 0.8 {
// diffuse material
world.push(Box::new(MovingSphere::new(
center,
center + Vec3::new(0.0, 0.5 * rng.gen::<f64>(), 0.0),
0.0,
1.0,
radius,
Lambertian::new(Vec3::new(
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 {
// metal material
world.push(Box::new(Sphere::new(
center,
radius,
Metal::with_fuzz(
Vec3::new(
(1.0 + rng.gen::<f64>()) * 0.5,
(1.0 + rng.gen::<f64>()) * 0.5,
(1.0 + rng.gen::<f64>()) * 0.5,
),
0.5 * rng.gen::<f64>(),
),
)));
} else {
// glass material
world.push(Box::new(Sphere::new(center, radius, Dielectric::new(1.5))));
}
}
}
}
world.push(Box::new(Sphere::new(
Vec3::new(0.0, 1.0, 0.0),
1.0,
Dielectric::new(1.5),
)));
world.push(Box::new(Sphere::new(
Vec3::new(-4.0, 1.0, 0.0),
1.0,
Lambertian::new(Vec3::new(0.4, 0.2, 0.1)),
)));
world.push(Box::new(Sphere::new(
Vec3::new(4.0, 1.0, 0.0),
1.0,
Metal::with_fuzz(Vec3::new(0.7, 0.6, 0.5), 0.0),
)));
world
}
fn camera(&self, aspect_ratio: f64) -> Camera {
let lookfrom = Vec3::new(13.0, 2.0, 3.0);
let lookat = Vec3::new(0.0, 0.0, 0.0);
let aperture = 0.1;
let focus_distance = 10.0;
Camera::new(
lookfrom,
lookat,
Vec3::new(0.0, 1.0, 0.0),
20.0,
aspect_ratio,
aperture,
focus_distance,
0.0,
1.0,
)
}
fn render_chunk(&self, chunk: &mut Chunk, camera: &Camera, world: &HitableList, samples: u8) {
let &mut Chunk {
num: _,
x,
y,
nx,
ny,
start_x,
start_y,
ref mut buffer,
} = chunk;
let mut offset = 0;
let mut rng = rand::thread_rng();
let mut rng = SmallRng::from_rng(&mut rng).unwrap();
assert!(buffer.len() >= nx * ny * 4);
(start_y..start_y + ny).for_each(|j| {
(start_x..start_x + nx).for_each(|i| {
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, &mut rng);
color += calc_color(ray, &world, 0, &mut rng);
}
color /= samples as f64;
self.update_rgb(buffer, color, offset);
offset += 4;
});
});
}
pub fn render(&self, buf: &mut Vec<u8>, x: usize, y: usize, samples: u8) {
let world = self.world();
let delta_x = x / VERTICAL_PARTITION;
let delta_y = y / HORIZONTAL_PARTITION;
let remx = x % VERTICAL_PARTITION;
let remy = y % HORIZONTAL_PARTITION;
// There can be tiny error here if the canvas height/width is not perfectly divisible
// by vertical/horizontal partitions in the chunks around the edges
// but umm, i'll just ignore those for now.
let camera = self.camera(delta_x as f64 / delta_y as f64);
let buf = Arc::new(Mutex::new(buf));
(0..VERTICAL_PARTITION).into_par_iter().for_each(|j| {
let buf = buf.clone();
(0..HORIZONTAL_PARTITION).into_par_iter().for_each(|i| {
let mut nx = delta_x;
let mut ny = delta_y;
let start_y = j * ny;
let start_x = i * nx;
match (i + 1, j + 1) {
(HORIZONTAL_PARTITION, VERTICAL_PARTITION) => {
nx += remx;
ny += remy;
}
(HORIZONTAL_PARTITION, _) => nx += remx,
(_, VERTICAL_PARTITION) => ny += remy,
_ => (),
};
let mut chunk = Chunk {
num: j * HORIZONTAL_PARTITION + i,
x,
y,
nx,
ny,
start_x,
start_y,
buffer: vec![0; nx * ny * 4],
};
println!("{}", chunk);
self.render_chunk(&mut chunk, &camera, &world, samples);
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;
buf[real_offset..real_offset + nx * 4]
.copy_from_slice(&chunk.buffer[temp_offset..temp_offset + nx * 4]);
temp_offset += nx * 4;
}
println!("Rendered {}", chunk);
});
});
}
#[inline]
fn update_rgb(&self, buffer: &mut [u8], color: Vec3, offset: usize) {
if let Some(pos) = buffer.get_mut(offset) {
*pos = (255.99 * color.r().sqrt()) as u8;
}
if let Some(pos) = buffer.get_mut(offset + 1) {
*pos = (255.99 * color.g().sqrt()) as u8;
}
if let Some(pos) = buffer.get_mut(offset + 2) {
*pos = (255.99 * color.b().sqrt()) as u8;
}
}
pub fn save_as_ppm(&self, buf: &[u8], width: usize, height: usize) {
let header = format!("P3\n{} {}\n255\n", width, height);
let mut file = match File::create(&format!("{}-{}x{}.ppm", self.name(), width, height)) {
Ok(file) => file,
Err(e) => panic!("couldn't create {}: {}", self.name(), e),
};
file.write_all(header.as_bytes())
.expect("error in writing file header");
for i in buf.chunks(4) {
match file.write_all(format!("{} {} {}\n", i[0], i[1], i[2]).as_bytes()) {
Ok(_) => (),
Err(e) => panic!("couldn't write to {}: {}", self.name(), e),
}
}
}
}
fn calc_color(ray: Ray, world: &HitableList, depth: u32, rng: &mut SmallRng) -> 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;
if let (attenuation, Some(scattered_ray)) = material.scatter(&ray, &hit_rec, rng) {
calc_color(scattered_ray, &world, depth + 1, rng) * 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,117 +0,0 @@
use {
crate::{
types::{HitableList, Vec3},
Camera, HORIZONTAL_PARTITION, VERTICAL_PARTITION,
},
rayon::prelude::*,
std::{
fs::File,
io::Write,
sync::{Arc, Mutex},
},
};
mod motion_blur;
pub use motion_blur::MotionBlur;
pub struct Chunk {
x: usize,
y: usize,
nx: usize,
ny: usize,
start_x: usize,
start_y: usize,
buffer: Vec<u8>,
}
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 f32 / ny as f32);
let buf = Arc::new(Mutex::new(buf));
(0..VERTICAL_PARTITION).into_par_iter().for_each(|j| {
let buf = buf.clone();
(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 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]);
temp_offset += nx * 4;
}
})
});
}
fn world(&self) -> Option<HitableList> {
None
}
fn camera(&self, aspect_ratio: f32) -> 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, //aperture
1.0, // focus_distance
0.0, // shutter open time
1.0, // shutter close time
))
}
fn render_chunk(
&self,
chunk: &mut Chunk,
camera: Option<&Camera>,
world: Option<&HitableList>,
samples: u8,
);
fn name(&self) -> &'static str;
fn save_as_ppm(&self, buf: &[u8], width: usize, height: usize) {
let header = format!("P3\n{} {}\n255\n", width, height);
let mut file = match File::create(&format!("{}-{}x{}.ppm", self.name(), width, height)) {
Ok(file) => file,
Err(e) => panic!("couldn't create {}: {}", self.name(), e),
};
file.write_all(header.as_bytes())
.expect("error in writing file header");
for i in buf.chunks(4) {
match file.write_all(format!("{} {} {}\n", i[0], i[1], i[2]).as_bytes()) {
Ok(_) => (),
Err(e) => panic!("couldn't write to {}: {}", self.name(), e),
}
}
}
}

View File

@ -1,185 +0,0 @@
use {
crate::{
demos::{Chunk, Demo},
types::{
material::{Dielectric, Lambertian, Metal},
Hitable, HitableList, MovingSphere, Ray, Sphere, Vec3,
},
Camera,
},
rand::Rng,
};
const RANGE: i32 = 10;
pub struct MotionBlur;
impl Demo for MotionBlur {
fn name(&self) -> &'static str {
"motion_blur"
}
fn world(&self) -> Option<HitableList> {
let mut world = HitableList {
list: Vec::with_capacity(500),
};
world.push(Box::new(Sphere::new(
Vec3::new(0.0, -1000.0, 0.0),
1000.0,
Box::new(Lambertian::new(Vec3::new(0.5, 0.5, 0.5))),
)));
let mut rng = rand::thread_rng();
let radius = 0.2;
let l = Vec3::new(4.0, 0.2, 0.0);
for a in -RANGE..RANGE {
let a = a as f32;
for b in -RANGE..RANGE {
let b = b as f32;
let choose_material_probability = rng.gen::<f32>();
let center = Vec3::new(a + 0.9 * rng.gen::<f32>(), 0.2, b + 0.9 * rng.gen::<f32>());
if (center - l).length() > 0.9 {
if choose_material_probability < 0.8 {
// diffuse material
world.push(Box::new(MovingSphere::new(
center,
center + Vec3::new(0.0, 0.5 * rng.gen::<f32>(), 0.0),
0.0,
1.0,
radius,
Box::new(Lambertian::new(Vec3::new(
rng.gen::<f32>() * rng.gen::<f32>(),
rng.gen::<f32>() * rng.gen::<f32>(),
rng.gen::<f32>() * rng.gen::<f32>(),
))),
)));
} else if choose_material_probability < 0.95 {
// metal material
world.push(Box::new(Sphere::new(
center,
radius,
Box::new(Metal::with_fuzz(
Vec3::new(
(1.0 + rng.gen::<f32>()) * 0.5,
(1.0 + rng.gen::<f32>()) * 0.5,
(1.0 + rng.gen::<f32>()) * 0.5,
),
0.5 * rng.gen::<f32>(),
)),
)));
} else {
// glass material
world.push(Box::new(Sphere::new(
center,
radius,
Box::new(Dielectric::new(1.5)),
)));
}
}
}
}
world.push(Box::new(Sphere::new(
Vec3::new(0.0, 1.0, 0.0),
1.0,
Box::new(Dielectric::new(1.5)),
)));
world.push(Box::new(Sphere::new(
Vec3::new(-4.0, 1.0, 0.0),
1.0,
Box::new(Lambertian::new(Vec3::new(0.4, 0.2, 0.1))),
)));
world.push(Box::new(Sphere::new(
Vec3::new(4.0, 1.0, 0.0),
1.0,
Box::new(Metal::with_fuzz(Vec3::new(0.7, 0.6, 0.5), 0.0)),
)));
Some(world)
}
fn camera(&self, aspect_ratio: f32) -> Option<Camera> {
let lookfrom = Vec3::new(13.0, 2.0, 3.0);
let lookat = Vec3::new(0.0, 0.0, 0.0);
let aperture = 0.1;
let focus_distance = 10.0;
let camera = Camera::new(
lookfrom,
lookat,
Vec3::new(0.0, 1.0, 0.0),
20.0,
aspect_ratio,
aperture,
focus_distance,
0.0,
1.0,
);
Some(camera)
}
fn render_chunk(
&self,
chunk: &mut Chunk,
camera: Option<&Camera>,
world: Option<&HitableList>,
samples: u8,
) {
let &mut Chunk {
x,
y,
nx,
ny,
start_x,
start_y,
ref mut buffer,
} = chunk;
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 f32 + rng.gen::<f32>()) / x as f32;
let v = (j as f32 + rng.gen::<f32>()) / y as f32;
let ray = camera.get_ray(u, v);
color += calc_color(ray, &world, 0);
}
color /= samples as f32;
// 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::f32::MAX) {
if depth >= 50 {
Vec3::new(0.0, 0.0, 0.0)
} else {
let material = hit_rec.material;
if let (attenuation, Some(scattered_ray)) = material.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

@ -3,30 +3,33 @@
extern crate test;
mod camera;
mod demos;
mod demo;
mod types;
pub use camera::Camera;
use {
demos::Demo,
sdl2::{
event::{Event, WindowEvent},
keyboard::Keycode,
pixels::PixelFormatEnum,
},
std::time::Instant,
};
use std::time::Instant;
const NUM_SAMPLES: u8 = 100;
const VERTICAL_PARTITION: usize = 8;
const HORIZONTAL_PARTITION: usize = 8;
const WIDTH: usize = 800;
const HEIGHT: usize = 800;
fn main() -> Result<(), String> {
run(WIDTH, HEIGHT)
}
#[cfg(feature = "gui")]
fn run(mut width: usize, mut height: usize) -> Result<(), String> {
use sdl2::{
event::{Event, WindowEvent},
keyboard::Keycode,
pixels::PixelFormatEnum,
};
let sdl_ctx = sdl2::init()?;
let video_subsys = sdl_ctx.video()?;
let (mut width, mut height): (usize, usize) = (2000, 1000);
let window = video_subsys
.window("Ray tracing in a weekend", width as u32, height as u32)
.position_centered()
@ -49,15 +52,9 @@ fn main() -> Result<(), String> {
.create_texture_static(PixelFormatEnum::BGR888, width as u32, height as u32)
.map_err(|e| e.to_string())?;
//println!("{:?} {:?} {:?}", texture.query(), texture.color_mod(), texture.alpha_mod());
let active_demo = demo::Demo;
let mut active_demo: &dyn Demo = &demos::MotionBlur;
// TODO: Should update when window is unfocus since the project window retains
// data from overlapped window
// TODO: Maybe consider using condition variable to make loop {} not run at full
// speed at all times pinning a core at 100%
let mut should_update = true;
loop {
for event in event_pump.poll_iter() {
match event {
@ -97,7 +94,7 @@ fn main() -> Result<(), String> {
println!(
"Demo {} Time Taken(s) = {}",
active_demo.name(),
now.elapsed().as_secs_f32()
now.elapsed().as_secs_f64()
);
texture.update(None, &buffer, width * 4).unwrap();
@ -107,3 +104,29 @@ fn main() -> Result<(), String> {
}
}
}
#[cfg(not(feature = "gui"))]
fn run(width: usize, height: usize) -> Result<(), String> {
let mut buffer = vec![0; width * height * 4];
let demo = demo::Demo;
println!(
"Starting {} at {}x{} with {} samples",
demo.name(),
width,
height,
NUM_SAMPLES
);
let now = Instant::now();
demo.render(&mut buffer, width, height, NUM_SAMPLES);
println!(
"Rendered Demo {}. Time Taken(s) = {}",
demo.name(),
now.elapsed().as_secs_f64()
);
demo.save_as_ppm(&buffer, width, height);
Ok(())
}

View File

@ -1,7 +1,4 @@
use {
crate::types::Vec3,
crate::types::{Material, Ray},
};
use crate::types::{Material, Ray, Vec3};
pub struct HitRecord<'a> {
/// Rays are represented by A + t * B
@ -11,7 +8,7 @@ pub struct HitRecord<'a> {
/// t is the point at which a ray intersected another object.
/// As in, If we put this value of t in A + t * B equation, We'll get the exact
/// point at which a ray intersects some other object
pub t: f32,
pub t: f64,
/// Ray object otherwise is represented by the Source/Destination points
/// p is what we get when we perform the operation, A + t * B
/// i.e. A vector from Ray source to the point t
@ -21,11 +18,11 @@ pub struct HitRecord<'a> {
pub normal: Vec3,
/// material if any of the surface
pub material: &'a Box<dyn Material>,
pub material: &'a dyn Material,
}
pub trait Hitable: Send + Sync {
fn hit(&self, _ray: &Ray, _t_min: f32, _t_max: f32) -> Option<HitRecord> {
fn hit(&self, _ray: &Ray, _t_min: f64, _t_max: f64) -> Option<HitRecord> {
None
}
}

View File

@ -5,7 +5,7 @@ pub struct HitableList {
}
impl Hitable for HitableList {
fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let mut closest_so_far = t_max;
let mut hit_rec: Option<HitRecord> = None;
for obj in &self.list {

View File

@ -1,10 +1,10 @@
use {
crate::types::{HitRecord, Ray, Vec3},
rand::Rng,
rand::{rngs::SmallRng, Rng},
};
pub trait Material: Send + Sync {
fn scatter(&self, ray: &Ray, hit_rec: &HitRecord) -> (Vec3, Option<Ray>);
fn scatter(&self, ray: &Ray, hit_rec: &HitRecord, rng: &mut SmallRng) -> (Vec3, Option<Ray>);
}
pub struct Lambertian {
@ -18,9 +18,8 @@ impl Lambertian {
}
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_sphere(&mut rng);
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))
@ -29,26 +28,29 @@ impl Material for Lambertian {
pub struct Metal {
albedo: Vec3,
fuzz: f32,
fuzz: f64,
}
impl Metal {
pub fn new(albedo: Vec3) -> Self {
Self { albedo, fuzz: 0.0 }
}
pub fn with_fuzz(albedo: Vec3, fuzz: f32) -> Self {
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) -> (Vec3, Option<Ray>) {
let mut rng = rand::thread_rng();
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(&mut rng) * self.fuzz,
reflected_ray + random_point_in_unit_sphere(rng) * self.fuzz,
ray_in.time(),
);
@ -61,21 +63,25 @@ impl Material for Metal {
}
pub struct Dielectric {
reflection_index: f32,
reflection_index: f64,
}
impl Dielectric {
pub fn new(reflection_index: f32) -> Self {
pub fn new(reflection_index: f64) -> Self {
Self { reflection_index }
}
}
impl Material for Dielectric {
fn scatter(&self, ray_in: &Ray, hit_rec: &HitRecord) -> (Vec3, Option<Ray>) {
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 mut rng = rand::thread_rng();
let (outward_normal, ni_over_nt, cosine) = if ray_in.direction().dot(&hit_rec.normal) > 0.0
{
@ -96,7 +102,7 @@ impl Material for Dielectric {
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::<f32>() < reflect_prob {
if rng.gen::<f64>() < reflect_prob {
(
attenuation,
Some(Ray::new(hit_rec.p, reflected_ray, ray_in.time())),
@ -118,7 +124,7 @@ impl Material for Dielectric {
// 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: f32, reflection_index: f32) -> f32 {
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)
@ -129,7 +135,7 @@ fn reflect(incident: Vec3, normal: Vec3) -> Vec3 {
}
// Snell's Law
fn refract(incident: Vec3, normal: Vec3, ni_over_nt: f32) -> Option<Vec3> {
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);
@ -140,11 +146,11 @@ fn refract(incident: Vec3, normal: Vec3, ni_over_nt: f32) -> Option<Vec3> {
}
}
fn random_point_in_unit_sphere(rng: &mut rand::rngs::ThreadRng) -> Vec3 {
let mut point = Vec3::new(rng.gen::<f32>(), rng.gen::<f32>(), rng.gen::<f32>()) * 2.0
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::<f32>(), rng.gen::<f32>(), rng.gen::<f32>()) * 2.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,22 +1,22 @@
use crate::types::{HitRecord, Hitable, Material, Ray, Vec3};
pub struct MovingSphere {
radius: f32,
pub struct MovingSphere<T: Material + Sized> {
radius: f64,
center_start: Vec3,
center_end: Vec3,
time_start: f32,
time_end: f32,
material: Box<dyn Material>,
time_start: f64,
time_end: f64,
material: T,
}
impl MovingSphere {
impl<T: Material + Sized> MovingSphere<T> {
pub fn new(
center_start: Vec3,
center_end: Vec3,
time_start: f32,
time_end: f32,
radius: f32,
material: Box<dyn Material>,
time_start: f64,
time_end: f64,
radius: f64,
material: T,
) -> Self {
Self {
center_start,
@ -28,15 +28,15 @@ impl MovingSphere {
}
}
fn center(&self, time: f32) -> Vec3 {
fn center(&self, time: f64) -> Vec3 {
self.center_start
+ (self.center_end - self.center_start)
* ((time - self.time_start) / (self.time_end - self.time_start))
}
}
impl Hitable for MovingSphere {
fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
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());

View File

@ -3,11 +3,11 @@ use crate::types::Vec3;
pub struct Ray {
a: Vec3,
b: Vec3,
time: f32,
time: f64,
}
impl Ray {
pub fn new(a: Vec3, b: Vec3, time: f32) -> Ray {
pub fn new(a: Vec3, b: Vec3, time: f64) -> Ray {
Ray { a, b, time }
}
#[inline]
@ -19,11 +19,11 @@ impl Ray {
self.b
}
#[inline]
pub fn point_at_parameter(&self, t: f32) -> Vec3 {
pub fn point_at_parameter(&self, t: f64) -> Vec3 {
self.a + self.b * t
}
#[inline]
pub const fn time(&self) -> f32 {
pub const fn time(&self) -> f64 {
self.time
}
}

View File

@ -1,13 +1,13 @@
use crate::types::{HitRecord, Hitable, Material, Ray, Vec3};
pub struct Sphere {
pub struct Sphere<T: Material + Sized> {
center: Vec3,
radius: f32,
material: Box<dyn Material>,
radius: f64,
material: T,
}
impl Sphere {
pub fn new(center: Vec3, radius: f32, material: Box<dyn Material>) -> Self {
impl<T: Material + Sized> Sphere<T> {
pub fn new(center: Vec3, radius: f64, material: T) -> Self {
Self {
center,
radius,
@ -16,8 +16,8 @@ impl Sphere {
}
}
impl Hitable for Sphere {
fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
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());

View File

@ -4,50 +4,50 @@ use std::{
};
#[derive(Copy, Clone)]
pub struct Vec3([f32; 3]);
pub struct Vec3([f64; 3]);
impl Vec3 {
#[inline]
pub const fn new(a: f32, b: f32, c: f32) -> Vec3 {
pub const fn new(a: f64, b: f64, c: f64) -> Vec3 {
Vec3([a, b, c])
}
#[inline]
pub fn x(&self) -> f32 {
pub fn x(&self) -> f64 {
self[0]
}
#[inline]
pub fn y(&self) -> f32 {
pub fn y(&self) -> f64 {
self[1]
}
#[inline]
pub fn z(&self) -> f32 {
pub fn z(&self) -> f64 {
self[2]
}
#[inline]
pub fn r(&self) -> f32 {
pub fn r(&self) -> f64 {
self[0]
}
#[inline]
pub fn g(&self) -> f32 {
pub fn g(&self) -> f64 {
self[1]
}
#[inline]
pub fn b(&self) -> f32 {
pub fn b(&self) -> f64 {
self[2]
}
#[inline]
pub fn length(&self) -> f32 {
pub fn length(&self) -> f64 {
self.sq_len().sqrt()
}
#[inline]
pub fn sq_len(&self) -> f32 {
pub fn sq_len(&self) -> f64 {
self[0] * self[0] + self[1] * self[1] + self[2] * self[2]
}
#[inline]
pub fn dot(&self, v: &Vec3) -> f32 {
pub fn dot(&self, v: &Vec3) -> f64 {
self[0] * v[0] + self[1] * v[1] + self[2] * v[2]
}
@ -62,7 +62,7 @@ impl Vec3 {
#[inline]
pub fn make_unit_vector(&mut self) {
let k = 1.0f32 / (self[0] * self[0] + self[1] * self[1] + self[2] * self[2]);
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;
@ -123,17 +123,17 @@ impl MulAssign<Vec3> for Vec3 {
}
}
impl MulAssign<f32> for Vec3 {
fn mul_assign(&mut self, o: f32) {
impl MulAssign<f64> for Vec3 {
fn mul_assign(&mut self, o: f64) {
self[0] *= o;
self[1] *= o;
self[2] *= o;
}
}
impl Mul<f32> for Vec3 {
impl Mul<f64> for Vec3 {
type Output = Vec3;
fn mul(self, o: f32) -> Vec3 {
fn mul(self, o: f64) -> Vec3 {
Vec3([self[0] * o, self[1] * o, self[2] * o])
}
}
@ -153,17 +153,17 @@ impl Div<Vec3> for Vec3 {
}
}
impl Div<f32> for Vec3 {
impl Div<f64> for Vec3 {
type Output = Vec3;
fn div(self, o: f32) -> Vec3 {
fn div(self, o: f64) -> Vec3 {
let o = 1.0 / o;
Vec3([self[0] * o, self[1] * o, self[2] * o])
}
}
impl DivAssign<f32> for Vec3 {
fn div_assign(&mut self, o: f32) {
impl DivAssign<f64> for Vec3 {
fn div_assign(&mut self, o: f64) {
let o = 1.0 / o;
self.0[0] *= o;
self.0[1] *= o;
@ -172,15 +172,15 @@ impl DivAssign<f32> for Vec3 {
}
impl Index<usize> for Vec3 {
type Output = f32;
type Output = f64;
fn index(&self, q: usize) -> &f32 {
fn index(&self, q: usize) -> &f64 {
&self.0[q]
}
}
impl IndexMut<usize> for Vec3 {
fn index_mut(&mut self, q: usize) -> &mut f32 {
fn index_mut(&mut self, q: usize) -> &mut f64 {
&mut self.0[q]
}
}