raytracer now accumulates radiosities not RGB values

This commit is contained in:
CDaut 2023-11-27 20:10:10 +01:00
parent 9fa51dd71f
commit 1e752d60bf
12 changed files with 211 additions and 75 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
scenes/emissive_cube.blend Normal file

Binary file not shown.

BIN
scenes/emissive_cube.blend1 Normal file

Binary file not shown.

BIN
scenes/emissive_cube.glb Normal file

Binary file not shown.

105
src/colors.rs Normal file
View file

@ -0,0 +1,105 @@
use std::ops::{Mul, MulAssign};
use cgmath::{ElementWise, Vector4};
use image::{DynamicImage, GenericImage, Rgba};
/// normalizes the color for k rays on a single pixel
pub fn normalize_color_single_pixel(radiosity_vector: Vector4<f32>, rays_per_pixel: usize) -> Rgba<u8> {
radiosity_vector.map(|component|
f32::ceil(component / rays_per_pixel as f32) as u8
);
let radiosity_as_arr = [
radiosity_vector.x,
radiosity_vector.y,
radiosity_vector.z,
];
//linearly map luminosity values to [0..255]
let range: f32 =
radiosity_as_arr.iter().fold(f32::NEG_INFINITY, |a, b| a.max(*b)) -
radiosity_as_arr.iter().fold(f32::INFINITY, |a, b| a.min(*b));
let color_value: Vector4<u8> =
radiosity_vector.map(|lum_val|
f32::ceil((lum_val / range) * 255.0) as u8
);
//just make the pixel opaque
//TODO: consider alpha
Rgba([color_value.x, color_value.y, color_value.z, 255])
}
pub fn normalize_colors_global(radiosity_buffer: &mut Vec<Vec<Vector4<f32>>>) -> &Vec<Vec<Vector4<f32>>> {
//largest radiosity found yet
let mut maximum_colors = Vector4::new(
f32::NEG_INFINITY,
f32::NEG_INFINITY,
f32::NEG_INFINITY,
f32::NEG_INFINITY);
//smallest radiosity found yet
let mut minimum_colors = Vector4::new(
f32::INFINITY,
f32::INFINITY,
f32::INFINITY,
f32::INFINITY);
//find maximum and minimum radiosity
radiosity_buffer.iter().for_each(|col| {
col.iter().for_each(|color| {
maximum_colors.x = f32::max(maximum_colors.x, color.x);
maximum_colors.y = f32::max(maximum_colors.y, color.y);
maximum_colors.z = f32::max(maximum_colors.z, color.z);
maximum_colors.w = f32::max(maximum_colors.w, color.w);
minimum_colors.x = f32::min(minimum_colors.x, color.x);
minimum_colors.y = f32::min(minimum_colors.y, color.y);
minimum_colors.z = f32::min(minimum_colors.z, color.z);
minimum_colors.w = f32::min(minimum_colors.w, color.w);
})
});
//calculate difference between min and max pixel radiosity
let range = maximum_colors - minimum_colors;
//normalize to range
for column in &mut *radiosity_buffer {
for mut radiosity_value in column {
//normalize to range
radiosity_value.div_assign_element_wise(range);
//map to [0.0..255]
radiosity_value.mul_assign(255.0);
}
}
radiosity_buffer
}
pub fn map_radmap_to_colors(radiosity_buffer: &Vec<Vec<Vector4<f32>>>) -> Vec<Vec<Rgba<u8>>> {
//copy radiositys to a color Buffer
let mut color_buffer: Vec<Vec<Rgba<u8>>> = Vec::with_capacity(radiosity_buffer.len());
radiosity_buffer.iter().enumerate().for_each(|(x, col)| {
color_buffer.push(Vec::with_capacity(radiosity_buffer[0].len()));
col.iter().enumerate().for_each(|(y, color_value)| {
//ceil and cast individual colors
let casted = color_value.map(|comp| {
f32::ceil(comp) as u8
});
color_buffer[x].push(Rgba::from([casted.x, casted.y, casted.z, casted.w]));
});
});
color_buffer
}
pub fn store_colors_to_image(color_buffer: Vec<Vec<Rgba<u8>>>) -> DynamicImage {
//build an image
let mut output_image: DynamicImage = DynamicImage::new_rgba8(
color_buffer.len() as u32, color_buffer[0].len() as u32,
);
color_buffer.iter().enumerate().for_each(|(x, col)| {
col.iter().enumerate().for_each(|(y, color)| {
output_image.put_pixel(x as u32, y as u32, *color);
});
});
output_image
}

View file

@ -3,12 +3,16 @@
mod renderer; mod renderer;
mod geometry; mod geometry;
mod ray; mod ray;
mod scene_data;
mod colors;
use std::string::String; use std::string::String;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, LockResult, Mutex};
use cgmath::Vector4;
use clap::{Parser}; use clap::{Parser};
use easy_gltf::Scene; use easy_gltf::Scene;
use image::{DynamicImage}; use image::{DynamicImage};
use crate::colors::{map_radmap_to_colors, normalize_colors_global, store_colors_to_image};
use crate::renderer::render; use crate::renderer::render;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
@ -30,6 +34,9 @@ pub struct Args {
/// rays per pixel /// rays per pixel
#[arg(long)] #[arg(long)]
multiplier: usize, multiplier: usize,
///start in debug mode (e.g. without parallelization)
#[arg(long)]
debug: bool,
} }
fn main() { fn main() {
@ -41,17 +48,21 @@ fn main() {
&args.gltf_file_path) &args.gltf_file_path)
.expect(&*format!("Failed to load glTF file {}", &args.gltf_file_path)); .expect(&*format!("Failed to load glTF file {}", &args.gltf_file_path));
//build an image let mut radiosity_buffer: Vec<Vec<Vector4<f32>>> = match render(scenes, &args).lock() {
let output_image: Arc<Mutex<DynamicImage>> = Arc::new(Mutex::new(DynamicImage::new_rgba8( Ok(buffer) => {buffer.to_vec()}
args.width as u32, args.height as u32, Err(_) => {panic!("Unable to lock radiosity buffer!")}
)));
render(scenes, &args, &output_image);
match output_image.lock() {
Ok(image) => {
image.save("result_image.png").expect("Unable to save image!");
}
Err(_) => { panic!("Error aquiring lock on image while saving!") }
}; };
//normalize radiosity values globally
let normalized =
normalize_colors_global(&mut radiosity_buffer);
//map radiositys to u8 colors
let as_colors =
map_radmap_to_colors(normalized);
//store colors to image
let output_image =
store_colors_to_image(as_colors);
output_image.save("result_image.png").expect("Unable to save image!");
} }

View file

@ -27,6 +27,7 @@ pub fn construct_primary_rays((width, height): (usize, usize),
focal_length, focal_length,
pixel_x_coord, pixel_x_coord,
pixel_y_coord, pixel_y_coord,
//just random noise
rng.gen(), rng.gen(),
rng.gen() rng.gen()
)); ));

View file

@ -1,17 +1,16 @@
use std::ops::Mul; use std::ops::{Add, Mul};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, LockResult, Mutex};
use cgmath::{Angle, Matrix4, Vector2, Vector3}; use cgmath::{Angle, ElementWise, Matrix4, Vector2, Vector3, Vector4};
use rayon::iter::{IntoParallelIterator, ParallelIterator}; use rayon::iter::{IntoParallelIterator, ParallelIterator};
use easy_gltf::model::{Mode}; use easy_gltf::model::{Mode};
use easy_gltf::{Camera, Projection, Scene}; use easy_gltf::{Camera, Material, Projection, Scene};
use image::{DynamicImage, GenericImage, Rgba};
use crate::Args; use crate::Args;
use crate::geometry::{Intersectable}; use crate::geometry::{Intersectable};
use crate::ray::{construct_primary_rays, Ray}; use crate::ray::{construct_primary_rays, Ray};
use crate::scene_data::IntersectionData;
pub fn render(scenes: &Vec<Scene>, pub fn render(scenes: &Vec<Scene>,
cl_args: &Args, cl_args: &Args) -> Arc<Mutex<Vec<Vec<Vector4<f32>>>>> {
output_image: &Arc<Mutex<DynamicImage>>) {
let render_scene: &Scene = &scenes[cl_args.scene_index]; let render_scene: &Scene = &scenes[cl_args.scene_index];
let render_camera: &Camera = &scenes[cl_args.scene_index].cameras[cl_args.camera_index]; let render_camera: &Camera = &scenes[cl_args.scene_index].cameras[cl_args.camera_index];
@ -32,13 +31,20 @@ pub fn render(scenes: &Vec<Scene>,
Projection::Orthographic { .. } => { panic!("Orthographic rendering not supported.") } Projection::Orthographic { .. } => { panic!("Orthographic rendering not supported.") }
}; };
// the distance from the camera origin to the view plane // the distance from the camera origin to the view plane
let z: f32 = cl_args.height as f32 / (2.0 * fovy.mul(0.5).tan()); let z: f32 = cl_args.height as f32 / (2.0 * fovy.mul(0.5).tan());
let mut radiosity_buffer: Arc<Mutex<Vec<Vec<Vector4<f32>>>>> =
Arc::new(Mutex::new(Vec::with_capacity(cl_args.width)));
//prepare the radiosity buffer
(0..cl_args.height).into_iter().for_each(|py| {
radiosity_buffer.lock().unwrap().push(Vec::with_capacity(cl_args.height))
});
//iterate over all pixels in the image //iterate over all pixels in the image
(0..cl_args.width).into_par_iter().for_each(|px| { (0..cl_args.width).into_iter().for_each(|px| {
(0..cl_args.height).into_par_iter().for_each(|py| { (0..cl_args.height).into_iter().for_each(|py| {
//construct all rays //construct all rays
let rays: Vec<Ray> = construct_primary_rays( let rays: Vec<Ray> = construct_primary_rays(
(cl_args.width, cl_args.height), (cl_args.width, cl_args.height),
@ -49,42 +55,35 @@ pub fn render(scenes: &Vec<Scene>,
); );
//let the initial pixel color be black and opaque //let the initial pixel color be black and opaque
let mut pixel_luminosity: [f32; 4] = [0.0, 0.0, 0.0, 255.0]; let mut pixel_radiosity: Vector4<f32> = Vector4::new(0.0, 0.0, 0.0, 1.0);
//cast each ray and get the output color //cast each ray and get the output luminosity and sum them up
rays.iter().for_each(|ray| { rays.iter().for_each(|ray| {
pixel_luminosity = raytrace(ray, render_scene, 4) pixel_radiosity = pixel_radiosity.add(
.zip(pixel_luminosity) raytrace(ray, render_scene, 4)
.map(|(a, b)| a as f32 + b);
});
//save pixel to output image
match output_image.lock() {
Ok(mut image) => {
//save pixel
image.put_pixel(
px as u32,
py as u32,
colormap(
pixel_luminosity,
cl_args.multiplier),
); );
});
//store radiosity into the buffer
match radiosity_buffer.clone().lock() {
Ok(mut buffer) => {
buffer[px].push(pixel_radiosity);
} }
Err(_) => { panic!("Unable to obtain lock on image!") } Err(_) => {panic!("Unable to lock pixel buffer!")}
} }
}); });
}); });
radiosity_buffer
} }
fn raytrace(ray: &Ray, scene: &Scene, recursion_depth: u32) -> [u8; 4] { fn raytrace(ray: &Ray, scene: &Scene, recursion_depth: u32) -> Vector4<f32> {
let mut pixel_color: [u8; 4] = [0, 0, 0, 255]; let mut pixel_radiosity: Vector4<f32> = Vector4::new(0.0, 0.0, 0.0, 255.0);
let mut smallest_t: f32 = f32::MAX; let mut smallest_t: f32 = f32::MAX;
let mut clostest_intersection_point: Option<Vector3<f32>> = None; let mut intersection_data: Option<IntersectionData> = None;
let mut color_at_isec: [u8; 4] = [0, 0, 0, 255];
//test intersection with all models in the scene //test intersection with all models in the scene
//TODO: Improve, to avoid iterating all models //TODO: Improve, to avoid iterating all models
@ -107,49 +106,44 @@ fn raytrace(ray: &Ray, scene: &Scene, recursion_depth: u32) -> [u8; 4] {
// a new closer Point is found // a new closer Point is found
if t < smallest_t { if t < smallest_t {
smallest_t = t; smallest_t = t;
//TODO: lighting model is not at all considered. Just a debug hack. intersection_data =
clostest_intersection_point = Option::from(isec); Option::from(
let color_vec = model.material().get_base_color_alpha( IntersectionData::new(
Vector2::new(0.0, 0.0) isec,
).map(|comp| comp * 255.0); model.material())
);
color_at_isec = [
color_vec[0] as u8,
color_vec[1] as u8,
color_vec[2] as u8,
color_vec[3] as u8
]
} }
} }
}; };
}); });
}); });
//make pixel opaque if intersection is found match intersection_data {
match clostest_intersection_point { Some(isec_data) => {
Some(_) => { pixel_radiosity = accumulate_colors(isec_data.intersection_point(),
pixel_color = color_at_isec; isec_data.material());
} }
None {} => {} None {} => {}
} }
pixel_color pixel_radiosity
} }
fn colormap(luminosity_array: [f32; 4], rays_per_pixel: usize) -> Rgba<u8> { /// called iff an intersection is detected to (recursively) accumulate radiosity at intersection
luminosity_array.map(|component| fn accumulate_colors(_intersection_point: Vector3<f32>, isec_material: &Arc<Material>) -> Vector4<f32> {
f32::ceil(component / rays_per_pixel as f32) as u8 let mut pixel_radiosity: Vector4<f32>
= Vector4::new(0.0, 0.0, 0.0, 1.0);
//accumulate colors at point
//emmisive Component
pixel_radiosity +=
isec_material.emissive.factor.extend(1.0)
.mul_element_wise(
isec_material.get_base_color_alpha(Vector2::new(0.0, 0.0))
); );
//linearly map luminosity values to [0..255]
let range: f32 =
luminosity_array.iter().fold(f32::NEG_INFINITY, |a, b| a.max(*b)) -
luminosity_array.iter().fold(f32::INFINITY, |a, b| a.min(*b));
let color_value: [u8; 4] = //TODO: Path tracing. Scatter ray in one random direction
luminosity_array.map(|lum_val|
f32::ceil((lum_val / range) * 255.0) as u8
);
Rgba::from(color_value) pixel_radiosity
} }

25
src/scene_data.rs Normal file
View file

@ -0,0 +1,25 @@
use std::sync::Arc;
use cgmath::Vector3;
use easy_gltf::Material;
pub (crate) struct IntersectionData{
intersection_point: Vector3<f32>,
material: Arc<Material>
}
impl IntersectionData{
pub fn new(intersection_point: Vector3<f32>, material: Arc<Material>) -> IntersectionData{
IntersectionData{
intersection_point,
material,
}
}
pub fn intersection_point(&self) -> Vector3<f32> {
self.intersection_point
}
pub fn material(&self) -> &Arc<Material> {
&self.material
}
}