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:
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
}