122 lines
4.3 KiB
Rust
122 lines
4.3 KiB
Rust
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)
|
||
}
|
||
}
|