camera.rs (7189B)
1 use std::time::Instant; 2 3 use crate::{ 4 color::LinearColor, hittable::Hittable, interval::Interval, random::RandomNumberGenerator, 5 ray::Ray, vec3::Vec3, 6 }; 7 8 #[derive(Clone, Debug, PartialEq, PartialOrd)] 9 pub struct Image { 10 data: Vec<Vec<LinearColor>>, 11 } 12 13 fn scale_f64_to_u8(value: f64) -> u8 { 14 (value * 255.) as u8 15 } 16 17 impl Image { 18 fn new(width: usize, height: usize) -> Self { 19 Image { 20 data: vec![vec![LinearColor::default(); width]; height], 21 } 22 } 23 pub fn to_ppm_string(&self) -> String { 24 let mut output = "P3\n".to_string(); 25 let height = self.data.len(); 26 let width = if height > 0 { self.data[0].len() } else { 0 }; 27 output += &format!("{width} {height}\n"); 28 output += "255\n"; 29 for row_data in self.data.iter() { 30 for color in row_data.iter() { 31 let gamma_color = color.as_gamma(); 32 output += &format!( 33 "{} {} {}\n", 34 scale_f64_to_u8(gamma_color.red()), 35 scale_f64_to_u8(gamma_color.green()), 36 scale_f64_to_u8(gamma_color.blue()) 37 ); 38 } 39 } 40 output 41 } 42 } 43 44 #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] 45 pub struct Camera { 46 image_width: usize, 47 image_height: usize, 48 pixel_delta_u: Vec3, 49 pixel_delta_v: Vec3, 50 pixel_origin: Vec3, 51 samples_per_pixel: usize, 52 rng: RandomNumberGenerator, 53 max_bounces: usize, 54 center: Vec3, 55 defocus_angle_degrees: f64, 56 defocus_disk_u: Vec3, 57 defocus_disk_v: Vec3, 58 } 59 60 #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] 61 pub struct CameraConfig { 62 pub image_width: usize, 63 pub aspect_ratio: f64, 64 pub samples_per_pixel: usize, 65 pub max_bounces: usize, 66 pub field_of_view_degrees: f64, 67 pub look_from: Vec3, 68 pub look_at: Vec3, 69 pub view_up: Vec3, 70 pub defocus_angle_degrees: f64, 71 pub focus_distance: f64, 72 pub rng: RandomNumberGenerator, 73 } 74 75 impl Default for CameraConfig { 76 fn default() -> Self { 77 CameraConfig { 78 image_width: 100, 79 aspect_ratio: 1., 80 samples_per_pixel: 10, 81 max_bounces: 10, 82 field_of_view_degrees: 90., 83 look_from: Vec3::new(0., 0., 0.), 84 look_at: Vec3::new(0., 0., -1.), 85 view_up: Vec3::new(0., 1., 0.), 86 defocus_angle_degrees: 0., 87 focus_distance: 10., 88 rng: RandomNumberGenerator::new_with_time_seed(), 89 } 90 } 91 } 92 93 impl Camera { 94 pub fn new(config: &CameraConfig) -> Self { 95 let CameraConfig { 96 image_width, 97 aspect_ratio, 98 samples_per_pixel, 99 max_bounces, 100 field_of_view_degrees, 101 look_from, 102 look_at, 103 view_up, 104 defocus_angle_degrees, 105 focus_distance, 106 rng, 107 } = *config; 108 109 let image_height = ((image_width as f64 / aspect_ratio) as usize).max(1); 110 111 let theta = field_of_view_degrees.to_radians(); 112 let h = (theta / 2.).tan(); 113 let viewport_height = 2. * h * focus_distance; 114 let viewport_width = viewport_height * aspect_ratio; 115 116 let w = (look_from - look_at).unit(); 117 let u = view_up.cross(&w).unit(); 118 let v = w.cross(&u); 119 120 let viewport_u = viewport_width * u; 121 let viewport_v = viewport_height * -v; 122 let viewport_upper_left = 123 look_from - (focus_distance * w) - viewport_u / 2. - viewport_v / 2.; 124 125 let pixel_delta_u = viewport_u / image_width as f64; 126 let pixel_delta_v = viewport_v / image_height as f64; 127 let pixel_origin = viewport_upper_left + 0.5 * (pixel_delta_u + pixel_delta_v); 128 129 let defocus_radius = focus_distance * (defocus_angle_degrees / 2.).to_radians().tan(); 130 let defocus_disk_u = u * defocus_radius; 131 let defocus_disk_v = v * defocus_radius; 132 133 Camera { 134 image_width, 135 image_height, 136 pixel_delta_u, 137 pixel_delta_v, 138 pixel_origin, 139 samples_per_pixel, 140 rng, 141 max_bounces, 142 center: look_from, 143 defocus_angle_degrees, 144 defocus_disk_u, 145 defocus_disk_v, 146 } 147 } 148 149 pub fn render(&mut self, world: &[Box<dyn Hittable>]) -> Image { 150 let mut image = Image::new(self.image_width, self.image_height); 151 let image_start = Instant::now(); 152 for j in 0..self.image_height { 153 let line_start = Instant::now(); 154 for i in 0..self.image_width { 155 let mut pixel_color = LinearColor::new(0., 0., 0.); 156 for _ in 0..self.samples_per_pixel { 157 let r = self.get_ray(i, j); 158 pixel_color += 159 self.ray_color(&r, world, self.max_bounces) / self.samples_per_pixel as f64; 160 } 161 image.data[j][i] = pixel_color; 162 } 163 let line_duration = line_start.elapsed(); 164 let pixels_per_second = self.image_width as f64 / line_duration.as_secs_f64(); 165 eprint!( 166 "\rLine {:>5}/{:>5} ({:.2} pixels / second) ", 167 j + 1, 168 self.image_height, 169 pixels_per_second 170 ); 171 } 172 let image_duration = image_start.elapsed(); 173 eprintln!( 174 "\rDone in {:.1} seconds ({:.1} pixels / second) ", 175 image_duration.as_secs_f64(), 176 self.image_width as f64 * self.image_height as f64 / image_duration.as_secs_f64() 177 ); 178 image 179 } 180 181 fn ray_color(&mut self, r: &Ray, world: &[Box<dyn Hittable>], depth: usize) -> LinearColor { 182 if depth == 0 { 183 return LinearColor::new(0., 0., 0.); 184 } 185 if let Some(hit) = world.hit(r, Interval::new(0.001, f64::INFINITY)) { 186 if let Some(scattered_ray) = hit.material().scatter(r, &hit, &mut self.rng) { 187 scattered_ray.attenuation() * self.ray_color(&scattered_ray.ray(), world, depth - 1) 188 } else { 189 LinearColor::new(0., 0., 0.) 190 } 191 } else { 192 let unit_direction = r.direction().unit(); 193 let a = 0.5 * (unit_direction.y() + 1.); 194 (1. - a) * LinearColor::new(1., 1., 1.) + a * LinearColor::new(0.5, 0.7, 1.) 195 } 196 } 197 198 fn get_ray(&mut self, i: usize, j: usize) -> Ray { 199 let offset = Vec3::new(self.rng.next_f64() - 0.5, self.rng.next_f64() - 0.5, 0.); 200 let pixel_sample = self.pixel_origin 201 + (i as f64 + offset.x()) * self.pixel_delta_u 202 + (j as f64 + offset.y()) * self.pixel_delta_v; 203 let ray_origin = if self.defocus_angle_degrees <= 0. { 204 self.center 205 } else { 206 self.defocus_disk_sample() 207 }; 208 Ray::new(ray_origin, pixel_sample - ray_origin) 209 } 210 211 fn defocus_disk_sample(&mut self) -> Vec3 { 212 let p = Vec3::random_in_unit_disk(&mut self.rng); 213 self.center + (p.x() * self.defocus_disk_u) + (p.y() * self.defocus_disk_v) 214 } 215 }