use crate::hittable::HittableList; use crate::math_utils::clamp; use crate::types_defined::{Camera, Color, Point, Ray, Vec3}; use rand::rngs::ThreadRng; use rand::{Rng, thread_rng}; impl Camera { pub fn new( image_width: i32, aspect_ratio: f32, viewport_height: f32, camera_center: Point, focal_length: Vec3, sample_times: i8, reflect_depth: i8 ) -> Self { let image_height = ((image_width as f32 / aspect_ratio) as i32).max(1); let viewport_width = viewport_height * (image_width as f32 / image_height as f32); let viewport_u = Vec3::new(viewport_width, 0.0, 0.0); // image x--> right // | // y // space: y up , x right , z back, -z front let viewport_v = Vec3::new(0.0, -viewport_height, 0.0); // width per pix let viewport_u_delta = viewport_u / (image_width as f32); // height per pix let viewport_v_delta = viewport_v / (image_height as f32); let viewport_top_left_pixel = camera_center + focal_length - viewport_u / 2.0 - viewport_v / 2.0; // padding 0.5* delta u/v let viewport_top_left_pixel_center = viewport_top_left_pixel - viewport_u_delta / 2.0 - viewport_v_delta / 2.0; Camera { image_width, image_height, aspect_ratio, viewport_width, viewport_height, camera_center, focal_length, viewport_u, viewport_v, viewport_u_delta, viewport_v_delta, viewport_top_left_pixel_center, sample_times, reflect_depth, } } pub fn render(&self, world: &HittableList) -> String { let mut img_content = format!("P3\n{} {}\n255\n", self.image_width, self.image_height); for j in 0..self.image_height { if j % 10 == 0 { println!("scan line {}/{} ", j + 1, self.image_height); } for i in 0..self.image_width { // color let mut color = Color::new(0.0, 0.0, 0.0); let rng = &mut thread_rng(); for s in 0..self.sample_times { let bias = self.random_square(rng); let pix_sample = self.viewport_top_left_pixel_center + (i as f32 + bias.x) * self.viewport_u_delta + (j as f32 + bias.y) * self.viewport_v_delta; let r = Ray::new(self.camera_center, pix_sample - self.camera_center); let sample_color = self.ray_color(&r, self.reflect_depth, world); color = color + sample_color; } // color * each sample color color = color * (1.0 / self.sample_times as f32); // clamp color rgb let color_clamped = Color::new( clamp(color.x, 0.0, 1.0), clamp(color.y, 0.0, 1.0), clamp(color.z, 0.0, 1.0), ); // content img_content.push_str(color_clamped.to_color().as_str()); img_content.push('\n'); } } img_content } fn ray_color(&self, ray: &Ray, depth: i8, world: &HittableList) -> Vec3 { // 反射次数 if depth <= 0 { return Vec3::new(0.0, 0.0, 0.0); } // 限制出射光线的角度(0.001经验值) let hr = world.hit(&ray, 0.01, f32::MAX); if hr.t >= 0.0 { // diffuse normal vec let diffuse_vec = Vec3::random_unit(); let lambertian_vec = diffuse_vec + hr.normal; // 每次反射按0.5计算颜色(反射率 ) return 0.5 * self.ray_color(&Ray::new(ray.point, lambertian_vec), depth - 1, world); } // v / |v| let unit_direction = ray.direction / ray.direction.length(); let a = 0.5 * (unit_direction.y + 1.0); // return background color. (1.0 - a) * Color::new(1.0, 1.0, 1.0) + a * Color::new(0.5, 0.7, 1.0) } // -> [x, y, 0] fn random_square(&self, rng: &mut ThreadRng) -> Vec3 { Vec3::new(rng.gen_range(-0.5..0.1), rng.gen_range(-0.5..0.1), 0.0) } }