stocking

git clone https://git.bradleygannon.com/stocking.git
Log | Files | Refs

commit 4baaf9340cd1096b1b86a9b3fe5cbd681ba7be61
parent 8eac42fedebf335e5ff23c47ea75ab69d0c4475c
Author: Bradley Gannon <bradley@bradleygannon.com>
Date:   Mon, 15 Dec 2025 17:44:35 -0500

Add RandomNumberGenerator and multiple samples per pixel

Complete Chapter 8

Diffstat:
Msrc/camera.rs | 41++++++++++++++++++++++++++++-------------
Msrc/color.rs | 18+++++++++++++++++-
Msrc/main.rs | 3++-
Asrc/random.rs | 50++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 97 insertions(+), 15 deletions(-)

diff --git a/src/camera.rs b/src/camera.rs @@ -1,4 +1,7 @@ -use crate::{color::Color, hittable::Hittable, interval::Interval, ray::Ray, vec3::Vec3}; +use crate::{ + color::Color, hittable::Hittable, interval::Interval, random::RandomNumberGenerator, ray::Ray, + vec3::Vec3, +}; struct Image { data: Vec<Vec<Color>>, @@ -20,8 +23,7 @@ impl Image { let width = if height > 0 { self.data[0].len() } else { 0 }; output += &format!("{width} {height}\n"); output += "255\n"; - for (row_idx, row_data) in self.data.iter().enumerate() { - eprintln!("Scanlines remaining: {}", height - row_idx); + for row_data in self.data.iter() { for color in row_data.iter() { output += &format!( "{} {} {}\n", @@ -31,7 +33,6 @@ impl Image { ); } } - eprintln!("Done"); output } } @@ -42,13 +43,16 @@ pub struct Camera { pixel_delta_u: Vec3, pixel_delta_v: Vec3, pixel_origin: Vec3, + samples_per_pixel: usize, + rng: RandomNumberGenerator, } impl Camera { const FOCAL_LENGTH: f64 = 1.; const CAMERA_CENTER: Vec3 = Vec3::new(0., 0., 0.); + const RNG_SEED: u64 = 42; - pub fn new(image_width: usize, aspect_ratio: f64) -> Self { + pub fn new(image_width: usize, aspect_ratio: f64, samples_per_pixel: usize) -> Self { let image_height = ((image_width as f64 / aspect_ratio) as usize).max(1); let viewport_height = 2.; @@ -64,26 +68,29 @@ impl Camera { let pixel_delta_v = viewport_v / image_height as f64; let pixel_origin = viewport_upper_left + 0.5 * (pixel_delta_u + pixel_delta_v); + let rng = RandomNumberGenerator::new(Self::RNG_SEED); + Camera { image_width, image_height, pixel_delta_u, pixel_delta_v, pixel_origin, + samples_per_pixel, + rng, } } - pub fn render(&self, world: &[Box<dyn Hittable>]) { + pub fn render(&mut self, world: &[Box<dyn Hittable>]) { let mut image = Image::new(self.image_width, self.image_height); for i in 0..self.image_width { for j in 0..self.image_height { - let pixel_center = self.pixel_origin - + (i as f64 * self.pixel_delta_u) - + (j as f64 * self.pixel_delta_v); - let ray_direction = pixel_center - Self::CAMERA_CENTER; - let ray = Ray::new(Self::CAMERA_CENTER, ray_direction); - - image.data[j][i] = self.ray_color(&ray, world); + let mut pixel_color = Color::new(0., 0., 0.); + for _ in 0..self.samples_per_pixel { + let r = self.get_ray(i, j); + pixel_color += self.ray_color(&r, world) / self.samples_per_pixel as f64; + } + image.data[j][i] = pixel_color; } } println!("{}", image.to_ppm_string()); @@ -103,4 +110,12 @@ impl Camera { (1. - a) * Color::new(1., 1., 1.) + a * Color::new(0.5, 0.7, 1.) } } + + fn get_ray(&mut self, i: usize, j: usize) -> Ray { + let offset = Vec3::new(self.rng.next_f64() - 0.5, self.rng.next_f64() - 0.5, 0.); + let pixel_sample = self.pixel_origin + + (i as f64 + offset.x()) * self.pixel_delta_u + + (j as f64 + offset.y()) * self.pixel_delta_v; + Ray::new(Self::CAMERA_CENTER, pixel_sample - Self::CAMERA_CENTER) + } } diff --git a/src/color.rs b/src/color.rs @@ -1,4 +1,4 @@ -use std::ops::{Add, Mul}; +use std::ops::{Add, AddAssign, Div, Mul}; #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)] pub struct Color { @@ -44,6 +44,14 @@ impl Add<Color> for Color { } } +impl AddAssign<Color> for Color { + fn add_assign(&mut self, rhs: Color) { + self.red += rhs.red; + self.green += rhs.green; + self.blue += rhs.blue; + } +} + impl Mul<f64> for Color { type Output = Color; @@ -63,3 +71,11 @@ impl Mul<Color> for f64 { rhs * self } } + +impl Div<f64> for Color { + type Output = Color; + + fn div(self, rhs: f64) -> Self::Output { + self * (1. / rhs) + } +} diff --git a/src/main.rs b/src/main.rs @@ -8,6 +8,7 @@ mod camera; mod color; mod hittable; mod interval; +mod random; mod ray; mod vec3; @@ -16,6 +17,6 @@ fn main() { Box::new(Sphere::new(Vec3::new(0., 0., -1.), 0.5)), Box::new(Sphere::new(Vec3::new(0., -100.5, -1.), 100.)), ]; - let camera = Camera::new(400, 16. / 9.); + let mut camera = Camera::new(400, 16. / 9., 100); camera.render(&world); } diff --git a/src/random.rs b/src/random.rs @@ -0,0 +1,50 @@ +use std::time::SystemTime; + +pub struct RandomNumberGenerator { + state: u64, +} + +impl RandomNumberGenerator { + const MULTIPLIER: u64 = 6364136223846793005; + const INCREMENT: u64 = 1442695040888963407; + + pub fn new(seed: u64) -> Self { + let mut rng = Self { state: 0 }; + let _ = rng.next_u32(); + let _ = rng.state.wrapping_add(seed); + let _ = rng.next_u32(); + rng + } + + pub fn _new_with_time_seed() -> Self { + let nanos = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_nanos(); + Self::new(nanos as u64) + } + + fn next_u32(&mut self) -> u32 { + let old = self.state; + self.state = old + .wrapping_mul(Self::MULTIPLIER) + .wrapping_add(Self::INCREMENT); + + let xorshifted = (((old >> 18) ^ old) >> 27) as u32; + let rot = (old >> 59) as u32; + xorshifted.rotate_right(rot) + } + + pub fn next_f64(&mut self) -> f64 { + let high = self.next_u32() as u64; + let low = self.next_u32() as u64; + let bits64 = (high << 32) | low; + + let r = bits64 >> 11; + (r as f64) * (1.0 / ((1u64 << 53) as f64)) + } + + pub fn _next_f64_in_range(&mut self, min: f64, max: f64) -> f64 { + min + (max - min) * self.next_f64() + } +}