raytracer now accumulates radiosities not RGB values
This commit is contained in:
parent
9fa51dd71f
commit
1e752d60bf
12 changed files with 211 additions and 75 deletions
105
src/colors.rs
Normal file
105
src/colors.rs
Normal 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
|
||||
}
|
||||
35
src/main.rs
35
src/main.rs
|
|
@ -3,12 +3,16 @@
|
|||
mod renderer;
|
||||
mod geometry;
|
||||
mod ray;
|
||||
mod scene_data;
|
||||
mod colors;
|
||||
|
||||
use std::string::String;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{Arc, LockResult, Mutex};
|
||||
use cgmath::Vector4;
|
||||
use clap::{Parser};
|
||||
use easy_gltf::Scene;
|
||||
use image::{DynamicImage};
|
||||
use crate::colors::{map_radmap_to_colors, normalize_colors_global, store_colors_to_image};
|
||||
use crate::renderer::render;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
|
|
@ -30,6 +34,9 @@ pub struct Args {
|
|||
/// rays per pixel
|
||||
#[arg(long)]
|
||||
multiplier: usize,
|
||||
///start in debug mode (e.g. without parallelization)
|
||||
#[arg(long)]
|
||||
debug: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
@ -41,17 +48,21 @@ fn main() {
|
|||
&args.gltf_file_path)
|
||||
.expect(&*format!("Failed to load glTF file {}", &args.gltf_file_path));
|
||||
|
||||
//build an image
|
||||
let output_image: Arc<Mutex<DynamicImage>> = Arc::new(Mutex::new(DynamicImage::new_rgba8(
|
||||
args.width as u32, args.height as u32,
|
||||
)));
|
||||
|
||||
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!") }
|
||||
let mut radiosity_buffer: Vec<Vec<Vector4<f32>>> = match render(scenes, &args).lock() {
|
||||
Ok(buffer) => {buffer.to_vec()}
|
||||
Err(_) => {panic!("Unable to lock radiosity buffer!")}
|
||||
};
|
||||
|
||||
//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!");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ pub fn construct_primary_rays((width, height): (usize, usize),
|
|||
focal_length,
|
||||
pixel_x_coord,
|
||||
pixel_y_coord,
|
||||
//just random noise
|
||||
rng.gen(),
|
||||
rng.gen()
|
||||
));
|
||||
|
|
|
|||
120
src/renderer.rs
120
src/renderer.rs
|
|
@ -1,17 +1,16 @@
|
|||
use std::ops::Mul;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use cgmath::{Angle, Matrix4, Vector2, Vector3};
|
||||
use std::ops::{Add, Mul};
|
||||
use std::sync::{Arc, LockResult, Mutex};
|
||||
use cgmath::{Angle, ElementWise, Matrix4, Vector2, Vector3, Vector4};
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use easy_gltf::model::{Mode};
|
||||
use easy_gltf::{Camera, Projection, Scene};
|
||||
use image::{DynamicImage, GenericImage, Rgba};
|
||||
use easy_gltf::{Camera, Material, Projection, Scene};
|
||||
use crate::Args;
|
||||
use crate::geometry::{Intersectable};
|
||||
use crate::ray::{construct_primary_rays, Ray};
|
||||
use crate::scene_data::IntersectionData;
|
||||
|
||||
pub fn render(scenes: &Vec<Scene>,
|
||||
cl_args: &Args,
|
||||
output_image: &Arc<Mutex<DynamicImage>>) {
|
||||
cl_args: &Args) -> Arc<Mutex<Vec<Vec<Vector4<f32>>>>> {
|
||||
let render_scene: &Scene = &scenes[cl_args.scene_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.") }
|
||||
};
|
||||
|
||||
|
||||
// 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 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
|
||||
(0..cl_args.width).into_par_iter().for_each(|px| {
|
||||
(0..cl_args.height).into_par_iter().for_each(|py| {
|
||||
(0..cl_args.width).into_iter().for_each(|px| {
|
||||
(0..cl_args.height).into_iter().for_each(|py| {
|
||||
//construct all rays
|
||||
let rays: Vec<Ray> = construct_primary_rays(
|
||||
(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 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| {
|
||||
pixel_luminosity = raytrace(ray, render_scene, 4)
|
||||
.zip(pixel_luminosity)
|
||||
.map(|(a, b)| a as f32 + b);
|
||||
pixel_radiosity = pixel_radiosity.add(
|
||||
raytrace(ray, render_scene, 4)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
//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] {
|
||||
let mut pixel_color: [u8; 4] = [0, 0, 0, 255];
|
||||
fn raytrace(ray: &Ray, scene: &Scene, recursion_depth: u32) -> Vector4<f32> {
|
||||
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 clostest_intersection_point: Option<Vector3<f32>> = None;
|
||||
let mut color_at_isec: [u8; 4] = [0, 0, 0, 255];
|
||||
let mut intersection_data: Option<IntersectionData> = None;
|
||||
|
||||
//test intersection with all models in the scene
|
||||
//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
|
||||
if t < smallest_t {
|
||||
smallest_t = t;
|
||||
//TODO: lighting model is not at all considered. Just a debug hack.
|
||||
clostest_intersection_point = Option::from(isec);
|
||||
let color_vec = model.material().get_base_color_alpha(
|
||||
Vector2::new(0.0, 0.0)
|
||||
).map(|comp| comp * 255.0);
|
||||
|
||||
color_at_isec = [
|
||||
color_vec[0] as u8,
|
||||
color_vec[1] as u8,
|
||||
color_vec[2] as u8,
|
||||
color_vec[3] as u8
|
||||
]
|
||||
intersection_data =
|
||||
Option::from(
|
||||
IntersectionData::new(
|
||||
isec,
|
||||
model.material())
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
//make pixel opaque if intersection is found
|
||||
match clostest_intersection_point {
|
||||
Some(_) => {
|
||||
pixel_color = color_at_isec;
|
||||
match intersection_data {
|
||||
Some(isec_data) => {
|
||||
pixel_radiosity = accumulate_colors(isec_data.intersection_point(),
|
||||
isec_data.material());
|
||||
}
|
||||
None {} => {}
|
||||
}
|
||||
|
||||
pixel_color
|
||||
pixel_radiosity
|
||||
}
|
||||
|
||||
fn colormap(luminosity_array: [f32; 4], rays_per_pixel: usize) -> Rgba<u8> {
|
||||
luminosity_array.map(|component|
|
||||
f32::ceil(component / rays_per_pixel as f32) as u8
|
||||
);
|
||||
/// called iff an intersection is detected to (recursively) accumulate radiosity at intersection
|
||||
fn accumulate_colors(_intersection_point: Vector3<f32>, isec_material: &Arc<Material>) -> Vector4<f32> {
|
||||
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] =
|
||||
luminosity_array.map(|lum_val|
|
||||
f32::ceil((lum_val / range) * 255.0) as u8
|
||||
);
|
||||
//TODO: Path tracing. Scatter ray in one random direction
|
||||
|
||||
pixel_radiosity
|
||||
}
|
||||
|
||||
Rgba::from(color_value)
|
||||
}
|
||||
25
src/scene_data.rs
Normal file
25
src/scene_data.rs
Normal 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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue