Compare commits

...

5 Commits

Author SHA1 Message Date
4ce43e12af Gathered up the scene components
After nearly a month of not touching the project, I've finally finished
collecting the scene parts. :l

With that, the rearrange is complete. On to the next thing!
2023-09-17 12:16:33 -05:00
76233f82a4 Condensing the rendering components
All the rendering bits together... except for the ones I missed. Never
mind those. This one has section headers so I can try to stay organized.
I'm gonna need to actually *do* things in this file going forward.
2023-08-19 20:37:51 -05:00
9badea407d Rect in primitives, collect the tests
There's the rectangle!

Also the tests. Cargo doesn't complain, but my YcmCompleter (so
rust-analyze, I think) does. The tests are all one big slab again.
2023-08-19 20:01:33 -05:00
f5eae46f17 Condensing the primitives
Group the informational types together. `vec3.rs` was renamed, and
the Ray implementation was copied into it. The Rect (and possibly a
Point) struct will be moved in, next. It's bad to have a `misc` or
`util` section, but "primitives" doesn't really do it for me, either.

My thought is that the sections will be:
- Primitives
- Renderer
- Scene Components

The renderer section would cover the image description and generation.
Image size and pixel sample count, but also things like the tile size,
render command dispatching, and file write-out. Anything to do with
producing the render.

The scene components section covers anything that goes in the render.
Obvious parts are the spheres and their materials, but this will also
include the camera. After all, it exists "in the world", and there could
be multiple.
2023-08-19 19:28:43 -05:00
809d7b678b Clean up some leftovers
Quickly sweeping up a few bits and pieces that got left around. This
seems like an okay way to load the codebase into my brain before doing
the big rearranging.
2023-08-19 18:44:57 -05:00
8 changed files with 430 additions and 475 deletions

View File

@@ -1,76 +0,0 @@
/*
* let viewport = (aspect_ratio * 2.0, 2.0);
let focal_length = 1.0;
let origin = Vec3::new(0.0, 0.0, 0.0);
let horizontal = Vec3::new(viewport.0, 0.0, 0.0);
let vertical = Vec3::new(0.0, viewport.1, 0.0);
let lower_left_corner = origin - horizontal/2.0 - vertical/2.0 - Vec3::new(0.0, 0.0, focal_length);
*/
use crate::vec3::Vec3;
use crate::ray::Ray;
use crate::degrees_to_radians;
use rand::rngs::SmallRng;
#[derive (Clone, Copy)]
pub struct Camera {
origin: Vec3,
lower_left_corner: Vec3,
horizontal: Vec3,
vertical: Vec3,
u: Vec3, v: Vec3, /*w: Vec3,*/
lens_radius: f32,
}
impl Camera {
pub fn new(
lookfrom: Vec3,
lookat: Vec3,
vup: Vec3,
vfov: f32,
aspect_ratio: f32,
aperture: f32,
focus_dist: f32
) -> Camera {
let theta = degrees_to_radians(vfov);
let h = (theta / 2.0).tan();
let vp_height = 2.0 * h;
let vp_width = aspect_ratio * vp_height;
let w = Vec3::as_unit(lookfrom - lookat);
let u = Vec3::as_unit(Vec3::cross(vup, w));
let v = Vec3::cross(w, u);
let orig = lookfrom;
let horiz = u * vp_width * focus_dist;
let verti = v * vp_height * focus_dist;
let lower_left_corner = orig - horiz / 2.0 - verti / 2.0 - w * focus_dist;
Camera{
origin: orig,
lower_left_corner,
horizontal: horiz,
vertical: verti,
u, v, /* w,*/
lens_radius: aperture / 2.0,
}
}
pub fn get_ray(&self, s: f32, t: f32, srng: &mut SmallRng) -> Ray {
let rd = Vec3::rand_in_unit_disk(srng) * self.lens_radius;
let offset = self.u * rd.x + self.v * rd.y;
let dir = self.lower_left_corner
+ self.horizontal * s
+ self.vertical * t
- self.origin - offset;
Ray{
orig: self.origin + offset,
dir,
}
}
}

View File

@@ -1,90 +0,0 @@
use crate::vec3::Vec3;
use crate::ray::Ray;
use crate::material::Material;
pub struct HitRecord{
pub p: Vec3,
pub normal: Vec3,
pub material: Option<Material>,
pub t: f32,
pub front_face: bool,
}
impl HitRecord{
pub fn set_face_normal(&mut self, r: Ray, outward_normal: Vec3) -> (){
self.front_face = Vec3::dot(r.dir, outward_normal) < 0.0;
self.normal = if self.front_face { outward_normal } else { -outward_normal };
}
}
#[derive (Clone)]
pub enum Hittable {
Sphere { center: Vec3, radius: f32, material: Option<Material> },
HittableList { hittables: Vec<Hittable> }
}
impl Hittable {
pub fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
match self {
Hittable::HittableList { hittables } => {
let mut might_return = HitRecord {
p: Vec3::zero(),
normal: Vec3::zero(),
material: None,
t: t_max,
front_face: false,
};
let mut hit_anything = false;
for item in hittables {
if let Some(record) = item.hit(r, t_min, might_return.t){
hit_anything = true;
might_return = record;
}
}
if hit_anything{
return Some(might_return);
} else { return None; }
}
Hittable::Sphere { center, radius, material } => {
let oc = r.orig - *center;
let a = r.dir.length_squared();
let half_b = Vec3::dot(oc, r.dir);
let c = oc.length_squared() - radius * radius;
let discriminant = half_b*half_b - a*c;
if discriminant < 0.0 {
return None;
}
let sqrtd = discriminant.sqrt();
// nearest root that lies within tolerance
let mut root = (-half_b - sqrtd) / a;
if root < t_min || root > t_max {
root = (-half_b + sqrtd) / a;
if root < t_min || root > t_max {
return None;
}
}
let mut record = HitRecord{
p: r.at(root),
normal: (r.at(root) - *center) / *radius,
material: *material,
t: root,
front_face: false,
};
let outward_normal = (record.p - *center) / *radius;
record.set_face_normal(r, outward_normal);
Some(record)
}
}
}
pub fn push(&mut self, item: Hittable) {
if let Hittable::HittableList { hittables } = self {
hittables.push(item);
}
}
}

View File

@@ -1,27 +1,20 @@
mod primitives;
mod renderer;
mod scene;
mod vec3; use crate::primitives::Vec3;
mod ray; use crate::scene::{
mod camera; Camera,
mod material; Hittable,
mod hittable; Material,
mod thread_utils; };
use crate::renderer::RenderCommand;
use crate::vec3::Vec3;
use crate::ray::Ray;
use crate::hittable::Hittable;
use crate::material::Material;
use crate::camera::Camera;
use crate::thread_utils::RenderCommand;
use rand::{Rng, SeedableRng}; use rand::{Rng, SeedableRng};
use rand::rngs::SmallRng; use rand::rngs::SmallRng;
use rand::distributions::Uniform; use rand::distributions::Uniform;
use itertools;
use itertools::Itertools;
use std::ops;
use std::thread; use std::thread;
fn main() { fn main() {
@@ -42,26 +35,21 @@ fn main() {
// camera // camera
let lookfrom = Vec3::new(13.0, 2.0, 3.0);
let lookat = Vec3::zero();
let vup = Vec3::new(0.0, 1.0, 0.0);
let dist_to_focus = 10.0;
let aperture = 0.1;
let cam = Camera::new( let cam = Camera::new(
lookfrom, Vec3::new(13.0, 2.0, 3.0), // lookfrom
lookat, Vec3::zero(), // lookat
vup, Vec3::new(0.0, 1.0, 0.0), // vup
20.0, 20.0,
aspect_ratio, aspect_ratio,
aperture, 0.1, // aperture
dist_to_focus 10.0, // dist_to_focus
); );
// render // render
// The render loop should now be a job submission mechanism // The render loop should now be a job submission mechanism
// Iterate lines, submitting them as tasks to the thread. // Iterate lines, submitting them as tasks to the thread.
println!("P3\n{} {}\n255", image.0, image.1); println!("P3\n{} {}\n255", image.0, image.1);
let context = RenderContext { let context = renderer::RenderContext {
camera: cam, camera: cam,
image, image,
max_depth, max_depth,
@@ -70,7 +58,7 @@ fn main() {
}; };
thread::scope(|s| { thread::scope(|s| {
let (mut dispatcher, scanline_receiver) = thread_utils::Dispatcher::new(&small_rng, 12); let (mut dispatcher, scanline_receiver) = renderer::Dispatcher::new(&small_rng, 12);
s.spawn(move || { s.spawn(move || {
for y in (0..image.1).rev() { for y in (0..image.1).rev() {
@@ -103,7 +91,7 @@ fn main() {
* received item *is* the next-to-write and skip the buffering step. * received item *is* the next-to-write and skip the buffering step.
* But I need to make the concept work at all, first. * But I need to make the concept work at all, first.
*/ */
let mut raster_segments = Vec::<thread_utils::RenderResult>::new(); let mut raster_segments = Vec::<renderer::RenderResult>::new();
let mut sl_output_index = image.1-1; // scanlines count down, start at image height. let mut sl_output_index = image.1-1; // scanlines count down, start at image height.
while let Ok(scanline) = scanline_receiver.recv() { while let Ok(scanline) = scanline_receiver.recv() {
eprintln!("Received scanline: {}", scanline.line_num); eprintln!("Received scanline: {}", scanline.line_num);
@@ -135,149 +123,13 @@ fn main() {
eprintln!("Done!"); eprintln!("Done!");
} }
fn print_scanline(scanline: thread_utils::RenderResult, samples_per_pixel: u32){ fn print_scanline(scanline: renderer::RenderResult, samples_per_pixel: u32){
eprintln!("Printing scanline num: {}", scanline.line_num); eprintln!("Printing scanline num: {}", scanline.line_num);
for color in &scanline.line { for color in &scanline.line {
println!("{}", color.print_ppm(samples_per_pixel)); println!("{}", color.print_ppm(samples_per_pixel));
} }
} }
#[derive (Clone)]
pub struct RenderContext{
image: (i32, i32),
samples_per_pixel: u32,
max_depth: u32,
world: Hittable,
camera: Camera,
}
pub struct DistributionContianer {
distrib_zero_one: Uniform<f32>,
distrib_plusminus_one: Uniform<f32>,
}
impl DistributionContianer {
fn new() -> Self {
DistributionContianer {
distrib_zero_one: Uniform::new(0.0, 1.0),
distrib_plusminus_one: Uniform::new(-1.0, 1.0),
}
}
}
fn render_line(y: i32, small_rng: &mut SmallRng, context: RenderContext, distr: &DistributionContianer) -> Vec<Vec3> {
//TODO: Ensure that the compiler hoists the distribution's out as constants
// else, do so manually
(0..context.image.0).map(|x| {
sample_pixel(x, y, small_rng, &context, distr)
}).collect()
}
fn sample_pixel(x: i32, y: i32, small_rng: &mut SmallRng, context: &RenderContext, distr: &DistributionContianer) -> Vec3{
(0..context.samples_per_pixel).into_iter().fold(
Vec3::zero(),
|color, _sample| {
let u = ((x as f32) + small_rng.sample(distr.distrib_zero_one)) / ((context.image.0 - 1) as f32);
let v = ((y as f32) + small_rng.sample(distr.distrib_zero_one)) / ((context.image.1 - 1) as f32);
let ray = context.camera.get_ray(u, v, small_rng);
color + ray_color(ray, &context.world, context.max_depth, small_rng, distr.distrib_plusminus_one)
}
)
}
fn range2d(bounds: (i32, i32, i32, i32)) -> impl Iterator<Item = (i32, i32)> {
let rheight = bounds.1..(bounds.1+bounds.3);
rheight.flat_map(move |y| {
let rwidth = bounds.0..(bounds.0+bounds.2);
rwidth.map( move |x| {
(x, y)
})
})
}
#[derive (Copy, Clone)]
struct Rect {
x: i32,
y: i32,
w: i32,
h: i32,
}
/* Iterable that produces pixels left-to-right, top-to-bottom.
* `Tile`s represent the render space, not the finished image.
* There is no internal pixel buffer
*/
type TileCursorIter = itertools::Product<ops::Range<i32>, ops::Range<i32>>;
struct Tile {
bounds: Rect,
context: RenderContext,
small_rng: SmallRng,
rand_distr: DistributionContianer,
cursor: TileCursorIter,
}
impl Tile{
fn new(
bounds: Rect,
context: RenderContext,
small_rng: SmallRng,
rand_distr: DistributionContianer
) -> Self
{
Tile { bounds, context, small_rng, rand_distr,
cursor: (bounds.x..(bounds.x + bounds.w))
.cartesian_product(bounds.y..(bounds.y + bounds.h)
)
}
}
}
impl Iterator for Tile {
type Item = Vec3;
fn next(&mut self) -> Option<Self::Item> {
if let Some((x, y)) = self.cursor.next(){
Some(sample_pixel(
x, y,
&mut self.small_rng,
&self.context,
&self.rand_distr,
))
} else {
None
}
}
}
fn ray_color(r: Ray, world: &Hittable, depth: u32, srng: &mut SmallRng, distrib: Uniform<f32> ) -> Vec3 {
// recursion depth guard
if depth == 0 {
return Vec3::zero();
}
if let Some(rec) = world.hit(r, 0.001, f32::INFINITY){
let mut scattered = Ray {
orig: Vec3::zero(),
dir: Vec3::zero()
};
let mut attenuation = Vec3::zero();
match rec.material {
Some(mat) => {
if mat.scatter(r, rec, &mut attenuation, &mut scattered, srng) {
return attenuation * ray_color(scattered, world, depth-1, srng, distrib);
};
},
None => return Vec3::zero(),
}
}
let unitdir = Vec3::as_unit(r.dir);
let t = 0.5 * (unitdir.y + 1.0);
return Vec3::ones() * (1.0 - t) + Vec3::new(0.5, 0.7, 1.0) * t
}
fn random_scene(srng: &mut SmallRng) -> Hittable { fn random_scene(srng: &mut SmallRng) -> Hittable {
let mat_ground = Material::Lambertian { albedo: Vec3::new(0.5, 0.5, 0.5) }; let mat_ground = Material::Lambertian { albedo: Vec3::new(0.5, 0.5, 0.5) };
let mut world = Hittable::HittableList { hittables : Vec::<Hittable>::new() }; let mut world = Hittable::HittableList { hittables : Vec::<Hittable>::new() };
@@ -361,7 +213,4 @@ fn random_scene(srng: &mut SmallRng) -> Hittable {
return world; return world;
} }
pub fn degrees_to_radians(degrees: f32) -> f32 {
degrees * std::f32::consts::PI / 180.0
}

View File

@@ -1,92 +0,0 @@
use crate::ray::Ray;
use crate::hittable::HitRecord;
use crate::vec3;
use crate::vec3::Vec3;
use rand::Rng;
use rand::rngs::SmallRng;
use rand::distributions::Uniform;
#[derive(Copy, Clone, Debug)]
pub enum Material{
Lambertian { albedo: Vec3 },
Metal { albedo:Vec3, fuzz: f32 },
Dielectric { index_refraction: f32 },
}
impl Material {
pub fn scatter(
&self,
ray_in: Ray,
rec: HitRecord,
attenuation: &mut Vec3,
scattered: &mut Ray,
srng: &mut SmallRng,
) -> bool {
match self {
Material::Lambertian { albedo } => {
let scatter_dir = rec.normal + Vec3::rand_unit_vector(srng);
// The compiler might be smart enough to compute this ^^^ just once. In which case,
// I don't need to do this weird dance. Oh well. It'll work.
let scatter_dir = if scatter_dir.near_zero() { // if near zero,
rec.normal // replace with normal
} else {
scatter_dir // else preserve current
};
//TODO: Revisit this out-parameter pattern
// It's a side effect of C++'s obtuse move semantics (and the RTIOW author not
// using them at all)
*scattered = Ray{
orig: rec.p,
dir: scatter_dir
};
*attenuation = *albedo; // deref on both sides? Wacky
return true;
},
Material::Metal { albedo, fuzz } => {
let reflected = Vec3::reflect(
Vec3::as_unit(ray_in.dir),
rec.normal
);
*scattered = Ray{
orig: rec.p,
dir: reflected + Vec3::rand_in_unit_sphere(srng) * *fuzz,
};
*attenuation = *albedo;
return Vec3::dot(scattered.dir, rec.normal) > 0.0;
},
Material::Dielectric { index_refraction } => {
*attenuation = Vec3::ones();
let refraction_ratio = if rec.front_face { 1.0 / index_refraction } else { *index_refraction };
let unit_direction = Vec3::as_unit(ray_in.dir);
let cos_theta = vec3::min(Vec3::dot(-unit_direction, rec.normal), 1.0);
let sin_theta = (1.0 - cos_theta * cos_theta).sqrt();
let cannot_refract = refraction_ratio * sin_theta > 1.0;
let distrib_zero_one = Uniform::new(0.0, 1.0);
let direction = if cannot_refract || Material::reflectance(cos_theta, refraction_ratio) > srng.sample(distrib_zero_one) {
Vec3::reflect(unit_direction, rec.normal)
} else {
Vec3::refract(unit_direction, rec.normal, refraction_ratio)
};
*scattered = Ray {
orig: rec.p,
dir: direction
};
return true;
},
}
}
fn reflectance(cosine: f32, ref_idx: f32) -> f32 {
// Schlick's approximation for reflectance.
let r0 = (1.0 - ref_idx) / (1.0 + ref_idx);
let r0 = r0 * r0;
return r0 + (1.0 - r0) * (1.0 - cosine).powf(5.0);
}
}

View File

@@ -97,9 +97,9 @@ impl Vec3{
let g = (self.y * scale).sqrt(); let g = (self.y * scale).sqrt();
let b = (self.z * scale).sqrt(); let b = (self.z * scale).sqrt();
let ir = (clamp(r, 0.0, 0.999) * 256.0) as i32; let ir = (r.clamp( 0.0, 0.999) * 256.0) as i32;
let ig = (clamp(g, 0.0, 0.999) * 256.0) as i32; let ig = (g.clamp( 0.0, 0.999) * 256.0) as i32;
let ib = (clamp(b, 0.0, 0.999) * 256.0) as i32; let ib = (b.clamp( 0.0, 0.999) * 256.0) as i32;
format!("{} {} {}", ir, ig, ib) format!("{} {} {}", ir, ig, ib)
} }
@@ -116,7 +116,7 @@ impl Vec3{
} }
pub fn refract(uv: Vec3, n: Vec3, etai_over_etat: f32) -> Vec3 { pub fn refract(uv: Vec3, n: Vec3, etai_over_etat: f32) -> Vec3 {
let cos_theta = min(Vec3::dot(-uv, n), 1.0); let cos_theta = Vec3::dot(-uv, n).min(1.0);
let r_out_perp = (uv + n * cos_theta) * etai_over_etat; let r_out_perp = (uv + n * cos_theta) * etai_over_etat;
let r_out_parallel = n * -(1.0 - r_out_perp.length_squared()).abs().sqrt(); let r_out_parallel = n * -(1.0 - r_out_perp.length_squared()).abs().sqrt();
r_out_perp + r_out_parallel r_out_perp + r_out_parallel
@@ -288,16 +288,24 @@ impl Display for Vec3 {
} }
} }
pub fn clamp(input: f32, lower: f32, upper: f32) -> f32 { #[derive(Copy, Clone)]
min(max(input, lower), upper) pub struct Ray{
pub orig: Vec3,
pub dir: Vec3,
} }
pub fn min(a: f32, b: f32) -> f32 { impl Ray{
if a < b { a } else { b } pub fn at(&self, t: f32) -> Vec3 {
self.orig + self.dir*t
}
} }
pub fn max(a: f32, b: f32) -> f32 { #[derive (Copy, Clone)]
if a > b { a } else { b } pub struct Rect {
pub x: i32,
pub y: i32,
pub w: i32,
pub h: i32,
} }
#[cfg(test)] #[cfg(test)]
@@ -559,5 +567,17 @@ mod test{
eprintln!("Diff: {}", diff); eprintln!("Diff: {}", diff);
assert!(Vec3::near_zero(&diff)); assert!(Vec3::near_zero(&diff));
} }
}
#[test]
fn check_lerp(){
let ray = Ray{
orig: Vec3::new(0.0, 0.0, 0.0),
dir: Vec3::new(1.0, 1.0, 0.0)
};
let half = ray.at(0.5);
assert_eq!(
half,
Vec3::new(0.5, 0.5, 0.0)
);
}
}

View File

@@ -1,32 +0,0 @@
use crate::vec3::Vec3;
#[derive(Copy, Clone)]
pub struct Ray{
pub orig: Vec3,
pub dir: Vec3,
}
impl Ray{
pub fn at(&self, t: f32) -> Vec3 {
self.orig + self.dir*t
}
}
#[cfg(test)]
mod test{
use super::*;
#[test]
fn check_lerp(){
let ray = Ray{
orig: Vec3::new(0.0, 0.0, 0.0),
dir: Vec3::new(1.0, 1.0, 0.0)
};
let half = ray.at(0.5);
assert_eq!(
half,
Vec3::new(0.5, 0.5, 0.0)
);
}
}

View File

@@ -1,12 +1,149 @@
use crate::RenderContext; use crate::primitives::{Vec3, Ray, Rect};
use crate::Vec3; use crate::scene::{
use crate::{render_line, DistributionContianer}; Camera,
Hittable,
};
use core::cmp::Ordering; use core::cmp::Ordering;
use std::thread; use std::thread;
use std::sync::mpsc; use std::sync::mpsc;
use std::ops;
use rand::Rng;
use rand::rngs::SmallRng; use rand::rngs::SmallRng;
use rand::distributions::Uniform;
use itertools::Itertools;
// =================
// Description parts
// =================
#[derive (Clone)]
pub struct RenderContext{
pub image: (i32, i32),
pub samples_per_pixel: u32,
pub max_depth: u32,
pub world: Hittable,
pub camera: Camera,
}
pub struct DistributionContianer {
pub distrib_zero_one: Uniform<f32>,
pub distrib_plusminus_one: Uniform<f32>,
}
impl DistributionContianer {
fn new() -> Self {
DistributionContianer {
distrib_zero_one: Uniform::new(0.0, 1.0),
distrib_plusminus_one: Uniform::new(-1.0, 1.0),
}
}
}
// =============
// Drawing Parts
// =============
fn render_line(y: i32, small_rng: &mut SmallRng, context: RenderContext, distr: &DistributionContianer) -> Vec<Vec3> {
//TODO: Ensure that the compiler hoists the distribution's out as constants
// else, do so manually
(0..context.image.0).map(|x| {
sample_pixel(x, y, small_rng, &context, distr)
}).collect()
}
fn ray_color(r: Ray, world: &Hittable, depth: u32, srng: &mut SmallRng, distrib: Uniform<f32> ) -> Vec3 {
// recursion depth guard
if depth == 0 {
return Vec3::zero();
}
if let Some(rec) = world.hit(r, 0.001, f32::INFINITY){
let mut scattered = Ray {
orig: Vec3::zero(),
dir: Vec3::zero()
};
let mut attenuation = Vec3::zero();
match rec.material {
Some(mat) => {
if mat.scatter(r, rec, &mut attenuation, &mut scattered, srng) {
return attenuation * ray_color(scattered, world, depth-1, srng, distrib);
};
},
None => return Vec3::zero(),
}
}
let unitdir = Vec3::as_unit(r.dir);
let t = 0.5 * (unitdir.y + 1.0);
return Vec3::ones() * (1.0 - t) + Vec3::new(0.5, 0.7, 1.0) * t
}
fn sample_pixel(x: i32, y: i32, small_rng: &mut SmallRng, context: &RenderContext, distr: &DistributionContianer) -> Vec3{
(0..context.samples_per_pixel).into_iter().fold(
Vec3::zero(),
|color, _sample| {
let u = ((x as f32) + small_rng.sample(distr.distrib_zero_one)) / ((context.image.0 - 1) as f32);
let v = ((y as f32) + small_rng.sample(distr.distrib_zero_one)) / ((context.image.1 - 1) as f32);
let ray = context.camera.get_ray(u, v, small_rng);
color + ray_color(ray, &context.world, context.max_depth, small_rng, distr.distrib_plusminus_one)
}
)
}
// ===============
// Execution parts
// ===============
/* Iterable that produces pixels left-to-right, top-to-bottom.
* `Tile`s represent the render space, not the finished image.
* There is no internal pixel buffer
*/
type TileCursorIter = itertools::Product<ops::Range<i32>, ops::Range<i32>>;
struct Tile {
bounds: Rect,
context: RenderContext,
small_rng: SmallRng,
rand_distr: DistributionContianer,
cursor: TileCursorIter,
}
impl Tile{
fn new(
bounds: Rect,
context: RenderContext,
small_rng: SmallRng,
rand_distr: DistributionContianer
) -> Self
{
Tile { bounds, context, small_rng, rand_distr,
cursor: (bounds.x..(bounds.x + bounds.w))
.cartesian_product(bounds.y..(bounds.y + bounds.h)
)
}
}
}
impl Iterator for Tile {
type Item = Vec3;
fn next(&mut self) -> Option<Self::Item> {
if let Some((x, y)) = self.cursor.next(){
Some(sample_pixel(
x, y,
&mut self.small_rng,
&self.context,
&self.rand_distr,
))
} else {
None
}
}
}
#[derive (Clone)] #[derive (Clone)]

239
src/scene.rs Normal file
View File

@@ -0,0 +1,239 @@
use crate::primitives::{Vec3, Ray};
use rand::Rng;
use rand::rngs::SmallRng;
use rand::distributions::Uniform;
pub struct HitRecord{
pub p: Vec3,
pub normal: Vec3,
pub material: Option<Material>,
pub t: f32,
pub front_face: bool,
}
impl HitRecord{
pub fn set_face_normal(&mut self, r: Ray, outward_normal: Vec3) -> (){
self.front_face = Vec3::dot(r.dir, outward_normal) < 0.0;
self.normal = if self.front_face { outward_normal } else { -outward_normal };
}
}
#[derive (Clone)]
pub enum Hittable {
Sphere { center: Vec3, radius: f32, material: Option<Material> },
HittableList { hittables: Vec<Hittable> }
}
impl Hittable {
pub fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
match self {
Hittable::HittableList { hittables } => {
let mut might_return = HitRecord {
p: Vec3::zero(),
normal: Vec3::zero(),
material: None,
t: t_max,
front_face: false,
};
let mut hit_anything = false;
for item in hittables {
if let Some(record) = item.hit(r, t_min, might_return.t){
hit_anything = true;
might_return = record;
}
}
if hit_anything{
return Some(might_return);
} else { return None; }
}
Hittable::Sphere { center, radius, material } => {
let oc = r.orig - *center;
let a = r.dir.length_squared();
let half_b = Vec3::dot(oc, r.dir);
let c = oc.length_squared() - radius * radius;
let discriminant = half_b*half_b - a*c;
if discriminant < 0.0 {
return None;
}
let sqrtd = discriminant.sqrt();
// nearest root that lies within tolerance
let mut root = (-half_b - sqrtd) / a;
if root < t_min || root > t_max {
root = (-half_b + sqrtd) / a;
if root < t_min || root > t_max {
return None;
}
}
let mut record = HitRecord{
p: r.at(root),
normal: (r.at(root) - *center) / *radius,
material: *material,
t: root,
front_face: false,
};
let outward_normal = (record.p - *center) / *radius;
record.set_face_normal(r, outward_normal);
Some(record)
}
}
}
pub fn push(&mut self, item: Hittable) {
if let Hittable::HittableList { hittables } = self {
hittables.push(item);
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum Material{
Lambertian { albedo: Vec3 },
Metal { albedo:Vec3, fuzz: f32 },
Dielectric { index_refraction: f32 },
}
impl Material {
pub fn scatter(
&self,
ray_in: Ray,
rec: HitRecord,
attenuation: &mut Vec3,
scattered: &mut Ray,
srng: &mut SmallRng,
) -> bool {
match self {
Material::Lambertian { albedo } => {
let scatter_dir = rec.normal + Vec3::rand_unit_vector(srng);
// The compiler might be smart enough to compute this ^^^ just once. In which case,
// I don't need to do this weird dance. Oh well. It'll work.
let scatter_dir = if scatter_dir.near_zero() { // if near zero,
rec.normal // replace with normal
} else {
scatter_dir // else preserve current
};
//TODO: Revisit this out-parameter pattern
// It's a side effect of C++'s obtuse move semantics (and the RTIOW author not
// using them at all)
*scattered = Ray{
orig: rec.p,
dir: scatter_dir
};
*attenuation = *albedo; // deref on both sides? Wacky
return true;
},
Material::Metal { albedo, fuzz } => {
let reflected = Vec3::reflect(
Vec3::as_unit(ray_in.dir),
rec.normal
);
*scattered = Ray{
orig: rec.p,
dir: reflected + Vec3::rand_in_unit_sphere(srng) * *fuzz,
};
*attenuation = *albedo;
return Vec3::dot(scattered.dir, rec.normal) > 0.0;
},
Material::Dielectric { index_refraction } => {
*attenuation = Vec3::ones();
let refraction_ratio = if rec.front_face { 1.0 / index_refraction } else { *index_refraction };
let unit_direction = Vec3::as_unit(ray_in.dir);
let cos_theta = Vec3::dot(-unit_direction, rec.normal).min(1.0);
let sin_theta = (1.0 - cos_theta * cos_theta).sqrt();
let cannot_refract = refraction_ratio * sin_theta > 1.0;
let distrib_zero_one = Uniform::new(0.0, 1.0);
let direction = if cannot_refract || Material::reflectance(cos_theta, refraction_ratio) > srng.sample(distrib_zero_one) {
Vec3::reflect(unit_direction, rec.normal)
} else {
Vec3::refract(unit_direction, rec.normal, refraction_ratio)
};
*scattered = Ray {
orig: rec.p,
dir: direction
};
return true;
},
}
}
fn reflectance(cosine: f32, ref_idx: f32) -> f32 {
// Schlick's approximation for reflectance.
let r0 = (1.0 - ref_idx) / (1.0 + ref_idx);
let r0 = r0 * r0;
return r0 + (1.0 - r0) * (1.0 - cosine).powf(5.0);
}
}
// Camera
pub fn degrees_to_radians(degrees: f32) -> f32 {
degrees * std::f32::consts::PI / 180.0
}
#[derive (Clone, Copy)]
pub struct Camera {
origin: Vec3,
lower_left_corner: Vec3,
horizontal: Vec3,
vertical: Vec3,
u: Vec3, v: Vec3, /*w: Vec3,*/
lens_radius: f32,
}
impl Camera {
pub fn new(
lookfrom: Vec3,
lookat: Vec3,
vup: Vec3,
vfov: f32,
aspect_ratio: f32,
aperture: f32,
focus_dist: f32
) -> Camera {
let theta = degrees_to_radians(vfov);
let h = (theta / 2.0).tan();
let vp_height = 2.0 * h;
let vp_width = aspect_ratio * vp_height;
let w = Vec3::as_unit(lookfrom - lookat);
let u = Vec3::as_unit(Vec3::cross(vup, w));
let v = Vec3::cross(w, u);
let orig = lookfrom;
let horiz = u * vp_width * focus_dist;
let verti = v * vp_height * focus_dist;
let lower_left_corner = orig - horiz / 2.0 - verti / 2.0 - w * focus_dist;
Camera{
origin: orig,
lower_left_corner,
horizontal: horiz,
vertical: verti,
u, v, /* w,*/
lens_radius: aperture / 2.0,
}
}
pub fn get_ray(&self, s: f32, t: f32, srng: &mut SmallRng) -> Ray {
let rd = Vec3::rand_in_unit_disk(srng) * self.lens_radius;
let offset = self.u * rd.x + self.v * rd.y;
let dir = self.lower_left_corner
+ self.horizontal * s
+ self.vertical * t
- self.origin - offset;
Ray{
orig: self.origin + offset,
dir,
}
}
}