180 lines
6.8 KiB
Rust
180 lines
6.8 KiB
Rust
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<BufWriter<File>>
|
||
) -> 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)
|
||
}
|
||
}
|