100 lines
3.0 KiB
Rust
100 lines
3.0 KiB
Rust
use image::{ExtendedColorType, ImageEncoder, codecs::png::PngEncoder};
|
|
use log::{error, info, trace};
|
|
use rayon::iter::IntoParallelIterator;
|
|
use rayon::prelude::*;
|
|
use std::{fs::File, sync::Arc, time::Instant};
|
|
|
|
use crate::{
|
|
objects::{hit::Hit, traits::Hittable},
|
|
ray::Ray,
|
|
scenes::scene::Scene,
|
|
vec3::Colour,
|
|
};
|
|
|
|
pub fn write_image(filename: &str, pixels: &[u8], width: u32, height: u32) {
|
|
match File::create(filename) {
|
|
Ok(out) => {
|
|
let enc = PngEncoder::new(out);
|
|
if let Err(e) = enc.write_image(pixels, width, height, ExtendedColorType::Rgb8) {
|
|
error!("Failed writing image: {}", e);
|
|
}
|
|
}
|
|
Err(e) => error!("Failed to create image file: {} ", e),
|
|
}
|
|
}
|
|
|
|
pub fn render(scene: &mut Scene) {
|
|
scene.init();
|
|
|
|
// TODO (low priority): currently splits per vertical line, but could be more granular (per chunk)
|
|
let mut pixels = vec![0_u8; (scene.get_image_width() * scene.get_image_height() * 3) as usize];
|
|
let scanlines: Vec<(usize, &mut [u8])> = pixels
|
|
.chunks_mut(scene.get_image_width() as usize * 3)
|
|
.enumerate()
|
|
.collect();
|
|
|
|
let now = Instant::now();
|
|
scanlines.into_par_iter().for_each(|(i, chunk)| {
|
|
render_chunk(chunk, scene, i as u32);
|
|
});
|
|
info!(
|
|
"rendering took {}.{} seconds.",
|
|
now.elapsed().as_secs(),
|
|
now.elapsed().subsec_nanos()
|
|
);
|
|
|
|
info!("Writing image file...");
|
|
write_image(
|
|
&scene.get_filename(),
|
|
&pixels,
|
|
scene.get_image_width() as u32,
|
|
scene.get_image_height() as u32,
|
|
);
|
|
}
|
|
|
|
pub fn render_chunk(chunk: &mut [u8], scene: &Scene, y: u32) {
|
|
let camera = &scene.get_camera();
|
|
let aa_rate = camera.get_anti_alias_rate();
|
|
|
|
for i in 0..scene.get_image_width() {
|
|
let pixel_tl = camera.get_pixel_tl(i, y);
|
|
|
|
let mut pixel_colour = Colour::default();
|
|
for y in 1..(aa_rate + 1) {
|
|
for x in 1..(aa_rate + 1) {
|
|
let r = camera.get_ray(pixel_tl, x, y);
|
|
pixel_colour += ray_colour(&scene.get_objects(), &r, scene.get_max_depth());
|
|
}
|
|
}
|
|
|
|
let (r, g, b) = (pixel_colour / (aa_rate * aa_rate) as f32).output();
|
|
chunk[(i * 3) as usize] = r;
|
|
chunk[(i * 3) as usize + 1] = g;
|
|
chunk[(i * 3) as usize + 2] = b;
|
|
}
|
|
trace!("Line {}\tfinished.", y);
|
|
}
|
|
|
|
pub fn ray_colour(hittables: &Vec<Arc<dyn Hittable>>, r: &Ray, depth: u32) -> Colour {
|
|
if depth == 0 {
|
|
return Colour::default();
|
|
}
|
|
|
|
let closest = Hit::hit_list(hittables, r);
|
|
if let Some(hit) = closest {
|
|
if let Some((scattered, att)) = hit.mat().scatter(&hit, r) {
|
|
if let Some(ray) = scattered {
|
|
return att * ray_colour(hittables, &ray, depth - 1);
|
|
}
|
|
return att;
|
|
}
|
|
return Colour::default();
|
|
}
|
|
|
|
// background
|
|
let unit_dir = r.dir().get_unit();
|
|
let a = 0.5 * (unit_dir.y() + 1.);
|
|
|
|
(1.0 - a) * Colour::new(1., 1., 1.) + a * Colour::new(0.5, 0.7, 1.0)
|
|
}
|