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:
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()
+ }
+}