use std::fs::File; use std::io::{BufWriter}; use crate::hittable::HittableList; use crate::math_utils::clamp; use crate::ppm_writer::PPMWriter; use crate::types_defined::{Camera, Color, HitRecord, Point, Ray, Vec3}; use rand::rngs::ThreadRng; use rand::{Rng, thread_rng}; use crate::material::{Material, MaterialKind}; impl<'a> Camera<'a> { pub fn new( image_width: i32, aspect_ratio: f32, viewport_height: f32, camera_center: Point, focal_length: Vec3, sample_times: i8, reflect_depth: i8, ppm_file_writer: &'a mut PPMWriter> ) -> 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, ppm_file_writer } } pub fn render(&mut self, world: &HittableList) { // 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 _ 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), ); self.ppm_file_writer.write(color.to_color()); } } // 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 hit_record = &mut HitRecord{ t: 0.0, p: Vec3::new(0.0, 0.0, 0.0), normal: Vec3::new(0.0, 0.0, 0.0), front_face: false, material: None, }; let hit = world.hit(&ray, 0.001, f32::MAX, hit_record); if hit { let scatted = &mut Ray::new(Point::new(0.0, 0.0, 0.0), Vec3::random()); let attenuation = &mut Color::new(1.0, 1.0, 1.0); let hit_m = &hit_record.material; let hc = &mut HitRecord{ t: hit_record.t, p: hit_record.p, normal: hit_record.normal, front_face: hit_record.front_face, material: None, }; let hit_c = match hit_m { Some(mk) => { let mc = match mk { MaterialKind::Lambertian(l) => { if l.scatter(ray, hc, attenuation, scatted) { let r_c = self.ray_color(scatted, depth - 1, world); let sc_color = Vec3::new(attenuation.x * r_c.x, attenuation.y * r_c.y, attenuation.z * r_c.z); // *attenuation * ; Color::new(sc_color.x, sc_color.y, sc_color.z) } else { Color::new(0.0, 0.0, 0.0) } }, MaterialKind::Metal(m) => { if m.scatter(ray, hc, attenuation, scatted) { let r_c = self.ray_color(scatted, depth - 1, world); let sc_color = Vec3::new(attenuation.x * r_c.x, attenuation.y * r_c.y, attenuation.z * r_c.z); // *attenuation * ; Color::new(sc_color.x, sc_color.y, sc_color.z) } else { Color::new(0.0, 0.0, 0.0) } } }; return mc }, None => { // if hit_record.t >= 0.0 { // // diffuse normal vec // let diffuse_vec = Vec3::random_unit(); // let lambertian_vec = diffuse_vec + hit_record.normal; // // 每次反射按0.5计算颜色(反射率 ) // return 0.5 * self.ray_color(&Ray::new(ray.point, lambertian_vec), depth - 1, world); // } Color::new(0.0, 0.0, 0.0) } }; return hit_c; } // 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) } }