175 lines
4.5 KiB
Rust
175 lines
4.5 KiB
Rust
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
|
|
|
use crate::{
|
|
builder::RaytracerBuilder,
|
|
hittable::{Hittable, HittableList, Object},
|
|
interval::WORLD,
|
|
material::Material,
|
|
ray::Ray,
|
|
vec3::{Color, Point3, Vec3},
|
|
};
|
|
|
|
pub struct Raytracer {
|
|
world: HittableList,
|
|
|
|
samples_per_pixel: usize,
|
|
max_depth: usize,
|
|
pixel_samples_scale: f64,
|
|
|
|
image_width: usize,
|
|
image_height: usize,
|
|
|
|
center: Point3,
|
|
pixel00_loc: Point3,
|
|
pixel_delta_u: Vec3,
|
|
pixel_delta_v: Vec3,
|
|
|
|
defocus_angle: f64,
|
|
defocus_disk_u: Vec3,
|
|
defocus_disk_v: Vec3,
|
|
}
|
|
|
|
impl From<RaytracerBuilder> for Raytracer {
|
|
fn from(config: RaytracerBuilder) -> Self {
|
|
let aspect_ratio: f64 = config.image_width as f64 / config.image_height as f64;
|
|
let theta = config.vertical_fov.to_radians();
|
|
let h = (theta / 2.0).tan();
|
|
let viewport_height = 2.0 * h * config.focus_dist;
|
|
let viewport_width = viewport_height as f64 * aspect_ratio;
|
|
|
|
let center = config.look_from;
|
|
|
|
let w = (config.look_from - config.look_at).unit_vector();
|
|
let u = config.vup.cross(&w).unit_vector();
|
|
let v = w.cross(&u);
|
|
|
|
let viewport_u = viewport_width * u;
|
|
let viewport_v = viewport_height * -v;
|
|
|
|
let pixel_delta_u = viewport_u / config.image_width as f64;
|
|
let pixel_delta_v = viewport_v / config.image_height as f64;
|
|
|
|
let viewport_upper_left =
|
|
center - (config.focus_dist * w) - (viewport_u / 2.0) - (viewport_v / 2.0);
|
|
let pixel00_loc = viewport_upper_left + 0.5 * (pixel_delta_u + pixel_delta_v);
|
|
|
|
let defocus_radius = config.focus_dist * (config.defocus_angle / 2.0).to_radians().tan();
|
|
let defocus_disk_u = u * defocus_radius;
|
|
let defocus_disk_v = v * defocus_radius;
|
|
|
|
Self {
|
|
world: config.world,
|
|
samples_per_pixel: config.samples_per_pixel,
|
|
max_depth: config.max_depth,
|
|
pixel_samples_scale: 1.0 / config.samples_per_pixel as f64,
|
|
image_width: config.image_width,
|
|
image_height: config.image_height,
|
|
center,
|
|
pixel00_loc,
|
|
pixel_delta_u,
|
|
pixel_delta_v,
|
|
defocus_angle: config.defocus_angle,
|
|
defocus_disk_u,
|
|
defocus_disk_v,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Raytracer {
|
|
pub fn builder() -> RaytracerBuilder {
|
|
RaytracerBuilder::default()
|
|
}
|
|
|
|
pub fn add(&mut self, object: Object) {
|
|
self.world.add(object);
|
|
}
|
|
|
|
fn sample_square() -> Vec3 {
|
|
Vec3::new(random_f64() - 0.5, random_f64() - 0.5, 0.0)
|
|
}
|
|
|
|
fn defocus_disk_sample(&self) -> Point3 {
|
|
let p = Point3::random_in_unit_disk();
|
|
self.center + (p[0] * self.defocus_disk_u) + (p[1] * self.defocus_disk_v)
|
|
}
|
|
|
|
fn get_ray(&self, i: usize, j: usize) -> Ray {
|
|
let offset = Self::sample_square();
|
|
let pixel_sample = self.pixel00_loc
|
|
+ ((i as f64 + offset.x()) * self.pixel_delta_u)
|
|
+ ((j as f64 + offset.y()) * self.pixel_delta_v);
|
|
|
|
let origin = match self.defocus_angle <= 0.0 {
|
|
true => self.center,
|
|
false => self.defocus_disk_sample(),
|
|
};
|
|
let direction = pixel_sample - origin;
|
|
|
|
Ray::new(origin, direction)
|
|
}
|
|
|
|
fn ray_color(&self, depth: usize, ray: &Ray) -> Color {
|
|
if depth == 0 {
|
|
return Color::default();
|
|
}
|
|
|
|
if let Some(hit_record) = self.world.hit(ray, &WORLD) {
|
|
if let Some((attenuation, scattered)) = hit_record.mat.scatter(ray, &hit_record) {
|
|
return attenuation * self.ray_color(depth - 1, &scattered);
|
|
}
|
|
return Color::default();
|
|
}
|
|
|
|
let unit_direction = ray.direction().unit_vector();
|
|
let a = 0.5 * (unit_direction.y() + 1.0);
|
|
((1.0 - a) * Color::new(1.0, 1.0, 1.0)) + (a * Color::new(0.5, 0.7, 1.0))
|
|
}
|
|
|
|
pub fn render_multithread_rayon(&self) -> Vec<Color> {
|
|
(0..self.image_height)
|
|
.into_par_iter()
|
|
.flat_map(|y| (0..self.image_width).into_par_iter().map(move |x| (x, y)))
|
|
.map(|(j, i)| {
|
|
let mut pixel_color = Vec3::default();
|
|
|
|
for _ in 0..self.samples_per_pixel {
|
|
let ray = self.get_ray(i, j);
|
|
pixel_color += self.ray_color(self.max_depth, &ray);
|
|
}
|
|
|
|
pixel_color * self.pixel_samples_scale
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn render(&self) -> Vec<Color> {
|
|
let mut out = Vec::with_capacity(self.image_height * self.image_width);
|
|
|
|
for j in 0..self.image_height {
|
|
tracing::debug!("Scanlines remaining: {}", self.image_height - j);
|
|
for i in 0..self.image_width {
|
|
let mut pixel_color = Vec3::default();
|
|
|
|
for _ in 0..self.samples_per_pixel {
|
|
let ray = self.get_ray(i, j);
|
|
pixel_color += self.ray_color(self.max_depth, &ray);
|
|
}
|
|
|
|
out.push(pixel_color * self.pixel_samples_scale);
|
|
}
|
|
}
|
|
|
|
out
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn random_f64() -> f64 {
|
|
fastrand::f64()
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn random_f64_range(min: f64, max: f64) -> f64 {
|
|
min + (max - min) * random_f64()
|
|
}
|