stocking

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

commit d099b5c11afa71975a5b9e51c12d271afcfeb9d2
parent 4f5afe74f9d1a18f02b93604bbb546a8f6e65a59
Author: Bradley Gannon <bradley@bradleygannon.com>
Date:   Tue, 16 Dec 2025 14:06:25 -0500

Add defocus blur

Complete Chapter 13

Diffstat:
Msrc/camera.rs | 34++++++++++++++++++++++++++++++----
Msrc/main.rs | 2++
Msrc/vec3.rs | 13+++++++++++++
3 files changed, 45 insertions(+), 4 deletions(-)

diff --git a/src/camera.rs b/src/camera.rs @@ -49,6 +49,9 @@ pub struct Camera { rng: RandomNumberGenerator, max_bounces: usize, center: Vec3, + defocus_angle_degrees: f64, + defocus_disk_u: Vec3, + defocus_disk_v: Vec3, } #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] @@ -61,6 +64,8 @@ pub struct CameraConfig { pub look_from: Vec3, pub look_at: Vec3, pub view_up: Vec3, + pub defocus_angle_degrees: f64, + pub focus_distance: f64, } impl Default for CameraConfig { @@ -74,6 +79,8 @@ impl Default for CameraConfig { look_from: Vec3::new(0., 0., 0.), look_at: Vec3::new(0., 0., -1.), view_up: Vec3::new(0., 1., 0.), + defocus_angle_degrees: 0., + focus_distance: 10., } } } @@ -91,14 +98,15 @@ impl Camera { look_from, look_at, view_up, + defocus_angle_degrees, + focus_distance, } = *config; let image_height = ((image_width as f64 / aspect_ratio) as usize).max(1); let theta = field_of_view_degrees.to_radians(); let h = (theta / 2.).tan(); - let focal_length = (look_from - look_at).length(); - let viewport_height = 2. * h * focal_length; + let viewport_height = 2. * h * focus_distance; let viewport_width = viewport_height * aspect_ratio; let w = (look_from - look_at).unit(); @@ -107,12 +115,17 @@ impl Camera { let viewport_u = viewport_width * u; let viewport_v = viewport_height * -v; - let viewport_upper_left = look_from - focal_length * w - viewport_u / 2. - viewport_v / 2.; + let viewport_upper_left = + look_from - (focus_distance * w) - viewport_u / 2. - viewport_v / 2.; let pixel_delta_u = viewport_u / image_width as f64; 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 defocus_radius = focus_distance * (defocus_angle_degrees / 2.).to_radians().tan(); + let defocus_disk_u = u * defocus_radius; + let defocus_disk_v = v * defocus_radius; + let rng = RandomNumberGenerator::new(Self::RNG_SEED); Camera { @@ -125,6 +138,9 @@ impl Camera { rng, max_bounces, center: look_from, + defocus_angle_degrees, + defocus_disk_u, + defocus_disk_v, } } @@ -166,6 +182,16 @@ impl Camera { 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.center, pixel_sample - self.center) + let ray_origin = if self.defocus_angle_degrees <= 0. { + self.center + } else { + self.defocus_disk_sample() + }; + Ray::new(ray_origin, pixel_sample - ray_origin) + } + + fn defocus_disk_sample(&mut self) -> Vec3 { + let p = Vec3::random_in_unit_disk(&mut self.rng); + self.center + (p.x() * self.defocus_disk_u) + (p.y() * self.defocus_disk_v) } } diff --git a/src/main.rs b/src/main.rs @@ -61,6 +61,8 @@ fn main() { look_from: Vec3::new(-2., 2., 1.), look_at: Vec3::new(0., 0., -1.), view_up: Vec3::new(0., 1., 0.), + defocus_angle_degrees: 10., + focus_distance: 3.4, }; let mut camera = Camera::new(&config); camera.render(&world); diff --git a/src/vec3.rs b/src/vec3.rs @@ -44,6 +44,19 @@ impl Vec3 { } } + pub fn random_in_unit_disk(rng: &mut RandomNumberGenerator) -> Self { + loop { + let p = Vec3::new( + rng.next_f64_in_range(-1., 1.), + rng.next_f64_in_range(-1., 1.), + 0., + ); + if p.length_squared() < 1. { + return p; + } + } + } + pub const fn x(&self) -> f64 { self.x }