From 887d335bca342dcf68360dbdb317fc80d0a726a4 Mon Sep 17 00:00:00 2001 From: waridh Date: Sat, 17 Aug 2024 17:21:31 -0400 Subject: [PATCH] added rng antialiasing to the system --- Cargo.toml | 1 + src/camera.rs | 51 +++++++++++++++++++++++++++++++++------- src/color.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++--- src/main.rs | 7 +++--- src/vec3/mod.rs | 31 +++++++++++++++++++++++++ 5 files changed, 136 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6a2ad4b..e13b17a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,4 @@ edition = "2021" [dependencies] anyhow = "1.0.86" indicatif = "0.17.8" +rand = "0.8.5" diff --git a/src/camera.rs b/src/camera.rs index 46eddaf..c15d7a7 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,20 +1,30 @@ use crate::{color, hittable::Hittable, ray, vec3}; use indicatif::ProgressIterator; +use rand::{self, rngs::ThreadRng, Rng}; use std::{f32::INFINITY, ops::Range, rc::Rc}; const ZERO_TO_INFINITY: Range = 0f32..INFINITY; pub struct Camera { pub aspect_ratio: f32, pub image_width: usize, + pub samples_per_pixel: usize, image_height: usize, pixel00: vec3::Vec3, pixel_delta_u: vec3::Vec3, pixel_delta_v: vec3::Vec3, center: Rc, + pixel_sample_scale: f32, + rng: ThreadRng, } impl Camera { - pub fn new(aspect_ratio: f32, image_width: usize, focal_length: f32) -> Self { + pub fn new( + aspect_ratio: f32, + image_width: usize, + focal_length: f32, + samples_per_pixel: usize, + ) -> Self { + let rng = rand::thread_rng(); let image_height = match ((image_width as f32) / aspect_ratio) as usize { x if x < 1 => 1, x => x, @@ -39,36 +49,59 @@ impl Camera { - (viewport_v / 2.); let pixel00 = viewport_upper_left + (pixel_delta_u + pixel_delta_v) * 0.5; + let pixel_sample_scale = 1. / (samples_per_pixel as f32); + Camera { aspect_ratio, image_width, + samples_per_pixel, image_height, pixel00, pixel_delta_u, pixel_delta_v, center, + pixel_sample_scale, + rng, } } - /// Reders the scene with side-effects going straight to stdout + + /// Renders the scene with side-effects going straight to stdout /// A buffer writer would improve the performance of this function - pub fn render(&self, world: &impl Hittable) { + pub fn render(&mut self, world: &impl Hittable) { // render println!("P3\n{} {}\n255", self.image_width, self.image_height); for j in (0..self.image_height).progress() { for i in 0..self.image_width { - let pixel_center = - self.pixel00 + (self.pixel_delta_u * i) + (self.pixel_delta_v * j); - let raydir = pixel_center - self.center.as_ref(); - let r = ray::Ray::new(raydir, &self.center); - let pixel = Camera::ray_color(r, world); + let mut pixel = color::Color::from_args(0., 0., 0.); + for _ in 0..self.samples_per_pixel { + let r = self.get_ray(i, j); + pixel += Camera::ray_color(r, world); + } - println!("{}", pixel); + println!("{}", pixel * self.pixel_sample_scale); } } } + fn get_ray(&mut self, i: usize, j: usize) -> ray::Ray { + let offset = self.sample_square(); + let pixel_center = self.pixel00 + + (self.pixel_delta_u * ((i as f32) + offset[0])) + + (self.pixel_delta_v * ((j as f32) + offset[1])); + let raydir = pixel_center - self.center.as_ref(); + ray::Ray::new(raydir, &self.center) + } + + fn sample_square(&mut self) -> vec3::Vec3 { + vec3::Vec3( + self.rng.gen_range(-0.5..0.5), + self.rng.gen_range(-0.5..0.5), + 0., + ) + } + fn ray_color(r: ray::Ray, world: &impl Hittable) -> color::Color { match world.hit(&r, &ZERO_TO_INFINITY) { Some(t) => { diff --git a/src/color.rs b/src/color.rs index bc5cec3..7b9da1e 100644 --- a/src/color.rs +++ b/src/color.rs @@ -4,6 +4,7 @@ use std::{fmt, ops}; /// The Color newtype is a vec3 that represents a color. /// Invariant: /// The fields of this struct must be a float between 0 and 1. +#[derive(Debug, PartialEq)] pub struct Color(vec3::Vec3); impl Color { @@ -24,6 +25,15 @@ impl From<(f32, f32, f32)> for Color { } } +/// Used to clamp the color output +fn clamp(legal_range: &ops::Range, val: f32) -> f32 { + match val { + x if x > legal_range.end => legal_range.end, + x if x < legal_range.start => legal_range.start, + x => x, + } +} + impl fmt::Display for Color { /// Color has a different byte representation in ppm than vec3. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -31,9 +41,11 @@ impl fmt::Display for Color { let g = self.0[1]; let b = self.0[2]; - let ir = (255.999 * r) as u8; - let ig = (255.999 * g) as u8; - let ib = (255.999 * b) as u8; + let legal_range = 0f32..0.999; + + let ir = (255.999 * clamp(&legal_range, r)) as u8; + let ig = (255.999 * clamp(&legal_range, g)) as u8; + let ib = (255.999 * clamp(&legal_range, b)) as u8; write!(f, "{} {} {}", ir, ig, ib) } @@ -46,6 +58,12 @@ impl ops::Index for Color { } } +impl ops::IndexMut for Color { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.0[index] + } +} + impl ops::Mul for Color { type Output = Color; fn mul(self, rhs: f32) -> Self::Output { @@ -69,6 +87,26 @@ impl ops::Add for Color { } } +impl ops::AddAssign for Color { + fn add_assign(&mut self, rhs: Color) { + for i in 0..3 { + self[i] += rhs[i] + } + } +} + +impl ops::DivAssign for Color { + fn div_assign(&mut self, rhs: f32) { + self.0 /= rhs + } +} + +impl ops::DivAssign for Color { + fn div_assign(&mut self, rhs: usize) { + self.0 /= rhs + } +} + #[cfg(test)] mod test { use super::*; @@ -89,4 +127,22 @@ mod test { assert_eq!(0.25, input[1]); assert_eq!(0.125, input[2]); } + + #[test] + fn color_assign_scalar_division() { + let mut left = Color::from_args(3., 6., 9.); + left /= 2.; + let expected = Color::from_args(1.5, 3., 4.5); + + assert_eq!(left, expected); + } + + #[test] + fn color_assign_scalar_division_usize() { + let mut left = Color::from_args(3., 6., 9.); + left /= 2; + let expected = Color::from_args(1.5, 3., 4.5); + + assert_eq!(left, expected); + } } diff --git a/src/main.rs b/src/main.rs index 20876a7..43ab5d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,4 @@ -use hittable::Hittable; -use indicatif::ProgressIterator; -use std::{f32::INFINITY, ops::Range, rc::Rc}; +use std::rc::Rc; mod camera; mod color; @@ -12,8 +10,9 @@ mod vec3; fn main() { let aspect_ratio: f32 = 5. / 4.; let image_width = 1200usize; + let samples_per_pixel = 10; - let camera = camera::Camera::new(aspect_ratio, image_width, 1.0); + let mut camera = camera::Camera::new(aspect_ratio, image_width, 1.0, samples_per_pixel); // World let sphere_1 = Rc::new(sphere::Sphere::from((0., 0., -1., 0.5))); let sphere_2 = Rc::new(sphere::Sphere::from((1.0, -100.5, -1., 100.))); diff --git a/src/vec3/mod.rs b/src/vec3/mod.rs index e757443..bf13c99 100644 --- a/src/vec3/mod.rs +++ b/src/vec3/mod.rs @@ -231,6 +231,7 @@ impl ops::Div for &Vec3 { } } +/// Tested impl ops::DivAssign for Vec3 { fn div_assign(&mut self, rhs: f32) { self.0 /= rhs; @@ -239,6 +240,16 @@ impl ops::DivAssign for Vec3 { } } +/// Tested +impl ops::DivAssign for Vec3 { + fn div_assign(&mut self, rhs: usize) { + let converted = rhs as f32; + self.0 /= converted; + self.1 /= converted; + self.2 /= converted; + } +} + impl ops::Index for Vec3 { type Output = f32; fn index(&self, index: usize) -> &Self::Output { @@ -251,6 +262,17 @@ impl ops::Index for Vec3 { } } +impl ops::IndexMut for Vec3 { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + match index { + 0 => &mut self.0, + 1 => &mut self.1, + 2 => &mut self.2, + _ => panic!("Index out of bounds"), + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -430,6 +452,15 @@ mod tests { assert_eq!(left, expected); } + #[test] + fn vec3_assign_scalar_division_usize() { + let mut left = Vec3(3., 6., 9.); + left /= 2; + let expected = Vec3(1.5, 3., 4.5); + + assert_eq!(left, expected); + } + #[test] fn squared_length() { let input = Vec3(3., 6., 9.);