diff --git a/output_image.png b/output_image.png index 637585d..3bfc4a1 100644 Binary files a/output_image.png and b/output_image.png differ diff --git a/scenes/emissive_cube.blend b/scenes/emissive_cube.blend index c3b341a..d183243 100644 Binary files a/scenes/emissive_cube.blend and b/scenes/emissive_cube.blend differ diff --git a/scenes/emissive_cube.blend1 b/scenes/emissive_cube.blend1 index 7909fd1..3454a1e 100644 Binary files a/scenes/emissive_cube.blend1 and b/scenes/emissive_cube.blend1 differ diff --git a/scenes/emissive_cube.glb b/scenes/emissive_cube.glb index 6d2baca..5f0b7a6 100644 Binary files a/scenes/emissive_cube.glb and b/scenes/emissive_cube.glb differ diff --git a/scenes/mirror.blend b/scenes/mirror.blend index 43691a1..427a3f2 100644 Binary files a/scenes/mirror.blend and b/scenes/mirror.blend differ diff --git a/scenes/mirror.blend1 b/scenes/mirror.blend1 index 214b3cb..d955a8c 100644 Binary files a/scenes/mirror.blend1 and b/scenes/mirror.blend1 differ diff --git a/scenes/mirror.glb b/scenes/mirror.glb index 9e7dc58..6332efb 100644 Binary files a/scenes/mirror.glb and b/scenes/mirror.glb differ diff --git a/src/colors.rs b/src/colors.rs index f4a9ff8..d53fba0 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -25,13 +25,15 @@ pub fn normalize_colors_global(radiosity_buffer: &mut Vec>>) -> //calculate difference between min and max pixel radiosity let range = maximum_radiosity - minimum_radiosity; + //normalize to range for column in &mut *radiosity_buffer { for radiosity_value in column { //Gamma correct radiosity_value.map(|single_channel_radiosity| - f32::powf(single_channel_radiosity, 1.0/GAMMA) + f32::powf(single_channel_radiosity, 1.0 / GAMMA) ); + //normalize to range radiosity_value.div_assign_element_wise(range); //map to [0.0..255] diff --git a/src/main.rs b/src/main.rs index 1c79a46..b6e1d9e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,7 +38,7 @@ pub struct Args { debug: bool, ///path tracing recursion depth #[arg(long)] - recurse: usize + recurse: usize, } fn main() { @@ -52,8 +52,8 @@ fn main() { println!("Path tracing image. Columns finished:"); let mut radiosity_buffer: Vec>> = match render(scenes, &args).lock() { - Ok(buffer) => {buffer.to_vec()} - Err(_) => {panic!("Unable to lock radiosity buffer!")} + Ok(buffer) => { buffer.to_vec() } + Err(_) => { panic!("Unable to lock radiosity buffer!") } }; //normalize radiosity values globally @@ -66,6 +66,8 @@ fn main() { let output_image = store_colors_to_image(as_colors); - output_image.save("/home/clemens/repositorys/raytrace-rs/output_image.png").expect("Unable to save image!"); + output_image + .save("/home/clemens/repositorys/raytrace-rs/output_image.png") + .expect("Unable to save image!"); } diff --git a/src/math_utils.rs b/src/math_utils.rs index 28b1360..8c27d0a 100644 --- a/src/math_utils.rs +++ b/src/math_utils.rs @@ -29,9 +29,28 @@ pub fn uniform_random_angle_triangle_hemisphere(intersected_triangle: Triangle, } /// Calculate a triangle from the three Vertices -pub fn face_normal_for_triangle(triangle: &Triangle) -> Vector3 { +pub fn face_normal_for_triangle(triangle: &Triangle, raydir: Vector3) -> Vector3 { let tangent = (triangle[0].position - triangle[1].position).normalize(); - tangent.cross(triangle[0].position - - triangle[2].position).normalize() + let mut normal = tangent.cross(triangle[0].position - + triangle[2].position).normalize(); + + if normal.dot(-raydir) < 0.0 { + normal *= -1.0; + } + + normal +} + +/// mirrors the given ray direction vector +/// (assuming to be pointing towards the camera) along the normal +pub fn mirror_ray(ray_direction: Vector3, normal: Vector3) -> Vector3 { + let mut new_normal = normal; + if normal.dot(-ray_direction) < 0.0 { + new_normal = normal * -1.0; + } + + //mirror vector (specular sampling) + ray_direction - + 2.0 * (ray_direction.dot(new_normal)) * new_normal } \ No newline at end of file diff --git a/src/renderer.rs b/src/renderer.rs index f2012a6..4cd4cde 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -8,11 +8,12 @@ use easy_gltf::{Camera, Projection, Scene}; use indicatif::ParallelProgressIterator; use crate::Args; use crate::geometry::{Intersectable}; -use crate::math_utils::{face_normal_for_triangle, uniform_random_angle_triangle_hemisphere}; +use crate::math_utils::{face_normal_for_triangle, mirror_ray, uniform_random_angle_triangle_hemisphere}; use crate::ray::{construct_primary_rays, Ray}; use crate::scene_data::IntersectionData; const RAY_EPSILON: f32 = 0.0007; +const RAY_DECAY: f32 = 0.1; pub fn render(scenes: &Vec, cl_args: &Args) -> Arc>>>> { @@ -67,12 +68,13 @@ pub fn render(scenes: &Vec, //let the initial pixel color be black and opaque let mut pixel_radiosity: Vector4 = Vector4::new(0.0, 0.0, 0.0, 1.0); - //cast each ray and get the output luminosity and sum them up rays.iter().for_each(|ray| { pixel_radiosity = pixel_radiosity.add( - raytrace(&ray, render_scene, cl_args.recurse) - ); + raytrace(&ray, + render_scene, + cl_args.recurse, + )); }); //store radiosity into the buffer @@ -87,12 +89,18 @@ pub fn render(scenes: &Vec, radiosity_buffer } - -fn raytrace(ray: &Ray, scene: &Scene, recursion_depth_left: usize) -> Vector4 { +/// raytrace recursively +/// returns a Color Vector +fn raytrace(ray: &Ray, scene: &Scene, + recursion_depth_left: usize, +) + -> Vector4 { let mut pixel_radiosity: Vector4 = Vector4::new(0.0, 0.0, 0.0, 1.0); //abort if no recursion steps are left - if recursion_depth_left == 0 { return pixel_radiosity; } + if recursion_depth_left == 0 { + return pixel_radiosity; + } let mut smallest_t: f32 = f32::MAX; let mut intersection_data: Option = None; @@ -148,7 +156,8 @@ fn raytrace(ray: &Ray, scene: &Scene, recursion_depth_left: usize) -> Vector4 Vector4 { + recursion_depth_left: usize, +) -> Vector4 { let mut pixel_radiosity: Vector4 = Vector4::new(0.0, 0.0, 0.0, 1.0); @@ -162,6 +171,10 @@ fn accumulate_colors(intersection_data: &IntersectionData, .get_base_color_alpha(Vector2::new(0.0, 0.0)) ); + //make shorter rays contribute more towards the total radiosity + //TODO: (probably) broken + let ray_decay_factor = f32::powf(recursion_depth_left as f32 + 1.0, RAY_DECAY); + pixel_radiosity *= ray_decay_factor; //get the intersected triangle and calculate the face normals let intersected_triangle = intersection_data.intersected_triangle(); @@ -169,8 +182,10 @@ fn accumulate_colors(intersection_data: &IntersectionData, let decision_factor = rand::random::(); let mut direction: Vector3; - let face_normal = - face_normal_for_triangle(&intersection_data.intersected_triangle()); + let face_normal = face_normal_for_triangle( + &intersection_data.intersected_triangle(), + intersection_data.ray().direction, + ); let rough_sampling: bool = decision_factor <= intersection_data.material().pbr.roughness_factor; @@ -182,9 +197,7 @@ fn accumulate_colors(intersection_data: &IntersectionData, intersection_data.ray(), ); } else { - //mirror vector (specular sampling) - direction = intersection_data.ray().direction - - 2.0 * (intersection_data.ray().direction.dot(face_normal)) * face_normal; + direction = mirror_ray(intersection_data.ray().direction, face_normal); } direction = direction.normalize(); @@ -196,10 +209,12 @@ fn accumulate_colors(intersection_data: &IntersectionData, }; //path trace recursively - let incoming_radiosity: Vector4 = raytrace( - &secondary_ray, - global_scene, - recursion_depth_left - 1); + let incoming_radiosity: Vector4 = + raytrace( + &secondary_ray, + global_scene, + recursion_depth_left - 1, + ); //weigh by cosine term depending on view angel let cos_weighting = direction.dot( @@ -212,12 +227,18 @@ fn accumulate_colors(intersection_data: &IntersectionData, brdf = intersection_data.material() //TODO: texture sampling! .get_base_color_alpha(Vector2::new(0.0, 0.0)); - pixel_radiosity += brdf.mul_element_wise(incoming_radiosity) * cos_weighting * 2.0 * PI; + + //weigh incoming radiosity more than own color so object won't look emissive + pixel_radiosity += 1.5 * incoming_radiosity; + pixel_radiosity += 1.2 * brdf.mul_element_wise(incoming_radiosity); + pixel_radiosity *= cos_weighting * 2.0 * PI * ray_decay_factor; } else { + //specular sampling pixel_radiosity += incoming_radiosity * cos_weighting * 2.0 * PI + intersection_data.material() //TODO: texture sampling! - .get_base_color_alpha(Vector2::new(0.0, 0.0)); + .get_base_color_alpha(Vector2::new(0.0, 0.0)) + * ray_decay_factor; } pixel_radiosity