From b29e6d1613ee3a0d9ccb4a3e2a979e5028826f58 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Fri, 20 Mar 2020 20:36:32 +0100 Subject: [PATCH 1/7] library: render: scene: fix reflection handling The reflection will be calculated even if it ends up not contributing to the final color of an object. This allows for a more systematic use of coefficients without applying them twice, like it was done for the refraction transparency handling... --- src/render/scene.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/render/scene.rs b/src/render/scene.rs index d1a755f..d3a4894 100644 --- a/src/render/scene.rs +++ b/src/render/scene.rs @@ -180,14 +180,13 @@ impl Scene { // Calculate the refracted ray, if it was refracted refracted(incident_ray, normal, diffraction_index, index).map_or_else( // Total reflection - || self.reflection(point, 1., reflected, reflection_limit, diffraction_index), + || self.reflection(point, reflected, reflection_limit, diffraction_index), // Refraction (refracted ray, amount of *reflection*) |(r, refl_t)| { let refr_light = self.refraction(point, coef, r, reflection_limit, index) * (1. - refl_t) + self.reflection( point, - refl_t, reflected, reflection_limit, diffraction_index, @@ -197,7 +196,7 @@ impl Scene { ) } Some(ReflTransEnum::Reflectivity { coef }) => { - self.reflection(point, coef, reflected, reflection_limit, diffraction_index) + self.reflection(point, reflected, reflection_limit, diffraction_index) * coef + lighting * (1. - coef) } } @@ -231,12 +230,11 @@ impl Scene { fn reflection( &self, point: Point, - reflectivity: f32, reflected: Vector, reflection_limit: u32, diffraction_index: f32, ) -> LinearColor { - if reflectivity > 1e-5 && reflection_limit > 0 { + if reflection_limit > 0 { let reflection_start = point + reflected * 0.001; if let Some((t, obj)) = self.cast_ray(Ray::new(reflection_start, reflected)) { let resulting_position = reflection_start + reflected * t; @@ -247,7 +245,7 @@ impl Scene { reflection_limit - 1, diffraction_index, ); - return color * reflectivity; + return color; } }; LinearColor::black() From fccf3caef188c5d5628e38d03cc649f5ac293cae Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Fri, 20 Mar 2020 23:46:38 +0100 Subject: [PATCH 2/7] library: render: scene: clean-up ReflTrans handling --- src/render/scene.rs | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/render/scene.rs b/src/render/scene.rs index d3a4894..d073f07 100644 --- a/src/render/scene.rs +++ b/src/render/scene.rs @@ -171,34 +171,30 @@ impl Scene { let object_color = object.texture.texel_color(texel); let normal = object.shape.normal(&point); - let reflected = reflected(incident_ray, normal); + let reflected_ray = reflected(incident_ray, normal); - let lighting = self.illuminate(point, object_color, &properties, normal, reflected); - match properties.refl_trans { - None => lighting, - Some(ReflTransEnum::Transparency { coef, index }) => { + let lighting = self.illuminate(point, object_color, &properties, normal, reflected_ray); + if properties.refl_trans.is_none() { + // Avoid calculating reflection when not needed + return lighting; + } + let reflected = self.reflection(point, reflected_ray, reflection_limit, diffraction_index); + // We can unwrap safely thanks to the check for None before + match properties.refl_trans.unwrap() { + ReflTransEnum::Transparency { coef, index } => { // Calculate the refracted ray, if it was refracted refracted(incident_ray, normal, diffraction_index, index).map_or_else( // Total reflection - || self.reflection(point, reflected, reflection_limit, diffraction_index), + || reflected.clone(), // Refraction (refracted ray, amount of *reflection*) |(r, refl_t)| { - let refr_light = self.refraction(point, coef, r, reflection_limit, index) - * (1. - refl_t) - + self.reflection( - point, - reflected, - reflection_limit, - diffraction_index, - ) * refl_t; + let refracted = self.refraction(point, coef, r, reflection_limit, index); + let refr_light = refracted * (1. - refl_t) + reflected.clone() * refl_t; refr_light * coef + lighting * (1. - coef) }, ) } - Some(ReflTransEnum::Reflectivity { coef }) => { - self.reflection(point, reflected, reflection_limit, diffraction_index) * coef - + lighting * (1. - coef) - } + ReflTransEnum::Reflectivity { coef } => reflected * coef + lighting * (1. - coef), } } From 16066a3c7dd412140800ba3c646ee6d72257672c Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sat, 21 Mar 2020 01:44:54 +0100 Subject: [PATCH 3/7] library: render: scene: normalize vectors --- src/render/scene.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/render/scene.rs b/src/render/scene.rs index d073f07..93d313e 100644 --- a/src/render/scene.rs +++ b/src/render/scene.rs @@ -298,7 +298,7 @@ impl Scene { fn reflected(incident: Vector, normal: Vector) -> Vector { let proj = incident.dot(&normal); let delt = normal * (proj * 2.); - incident - delt + (incident - delt).normalize() } /// Returns None if the ray was totally reflected, Some(refracted_ray, reflected_amount) if not @@ -317,7 +317,7 @@ fn refracted(incident: Vector, normal: Vector, n_1: f32, n_2: f32) -> Option<(Ve let f_t = (n_1 * cos2 - n_2 * cos1) / (n_1 * cos2 + n_2 * cos1); let refl_t = (f_r * f_r + f_t * f_t) / 2.; //Some((refracted, 0.)) - Some((refracted, refl_t)) + Some((refracted.normalize(), refl_t)) } #[derive(Debug, PartialEq, Deserialize)] From 6af36d814f95c0a19568bfa94c17daf69d289fa2 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sat, 21 Mar 2020 01:45:43 +0100 Subject: [PATCH 4/7] library: render: scene: cleanup refracted calculation --- src/render/scene.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/render/scene.rs b/src/render/scene.rs index 93d313e..a9cdedb 100644 --- a/src/render/scene.rs +++ b/src/render/scene.rs @@ -311,8 +311,8 @@ fn refracted(incident: Vector, normal: Vector, n_1: f32, n_2: f32) -> Option<(Ve return None; } let cos1 = cos1.abs(); - let refracted = eta * incident + (eta * cos1 - f32::sqrt(k)) * normal; - let cos2 = -refracted.dot(&normal); // Take the negation because we're on the other side + let cos2 = k.sqrt(); + let refracted = eta * incident + (eta * cos1 - cos2) * normal; let f_r = (n_2 * cos1 - n_1 * cos2) / (n_2 * cos1 + n_1 * cos2); let f_t = (n_1 * cos2 - n_2 * cos1) / (n_1 * cos2 + n_2 * cos1); let refl_t = (f_r * f_r + f_t * f_t) / 2.; From 70c0e0cdf386958053dd38e64b8d66daf69f4790 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sat, 21 Mar 2020 00:30:23 +0100 Subject: [PATCH 5/7] library: render: scene: handle refraction cleanly The refraction information was incorrect in the previous implementation. We never updated the medium information when going out of a transparent material. It is now handled using a small structure holding both the old index, and new_index. We can now handle any number of successive medium changes, with the associated change of indices correctly. This implementation assumes that any ray going into an object is going to come out in the same medium that it was originally travelling in before entering said object. This is an acceptable approximation to me. --- src/render/scene.rs | 66 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 12 deletions(-) diff --git a/src/render/scene.rs b/src/render/scene.rs index a9cdedb..a4b906c 100644 --- a/src/render/scene.rs +++ b/src/render/scene.rs @@ -121,6 +121,7 @@ impl Scene { let (x, y) = self.camera.film().pixel_ratio(x, y); let pixel = self.camera.film().pixel_at_ratio(x, y); let direction = (pixel - self.camera.origin()).normalize(); + let indices = RefractionInfo::with_index(self.diffraction_index); self.cast_ray(Ray::new(pixel, direction)) .map_or_else(LinearColor::black, |(t, obj)| { self.color_at( @@ -128,7 +129,7 @@ impl Scene { obj, direction, self.reflection_limit, - self.diffraction_index, + indices, ) }) } @@ -164,7 +165,7 @@ impl Scene { object: &Object, incident_ray: Vector, reflection_limit: u32, - diffraction_index: f32, + mut indices: RefractionInfo, ) -> LinearColor { let texel = object.shape.project_texel(&point); let properties = object.material.properties(texel); @@ -178,17 +179,17 @@ impl Scene { // Avoid calculating reflection when not needed return lighting; } - let reflected = self.reflection(point, reflected_ray, reflection_limit, diffraction_index); + let reflected = self.reflection(point, reflected_ray, reflection_limit, indices.clone()); // We can unwrap safely thanks to the check for None before match properties.refl_trans.unwrap() { ReflTransEnum::Transparency { coef, index } => { - // Calculate the refracted ray, if it was refracted - refracted(incident_ray, normal, diffraction_index, index).map_or_else( + // Calculate the refracted ray, if it was refracted, and mutate indices accordingly + refracted(incident_ray, normal, &mut indices, index).map_or_else( // Total reflection || reflected.clone(), // Refraction (refracted ray, amount of *reflection*) |(r, refl_t)| { - let refracted = self.refraction(point, coef, r, reflection_limit, index); + let refracted = self.refraction(point, coef, r, reflection_limit, indices); let refr_light = refracted * (1. - refl_t) + reflected.clone() * refl_t; refr_light * coef + lighting * (1. - coef) }, @@ -204,7 +205,7 @@ impl Scene { transparency: f32, refracted: Vector, reflection_limit: u32, - new_index: f32, + indices: RefractionInfo, ) -> LinearColor { if transparency > 1e-5 && reflection_limit > 0 { let refraction_start = point + refracted * 0.001; @@ -215,7 +216,7 @@ impl Scene { obj, refracted, reflection_limit - 1, - new_index, + indices, ); return refracted * transparency; } @@ -228,7 +229,7 @@ impl Scene { point: Point, reflected: Vector, reflection_limit: u32, - diffraction_index: f32, + indices: RefractionInfo, ) -> LinearColor { if reflection_limit > 0 { let reflection_start = point + reflected * 0.001; @@ -239,7 +240,7 @@ impl Scene { obj, reflected, reflection_limit - 1, - diffraction_index, + indices, ); return color; } @@ -302,9 +303,24 @@ fn reflected(incident: Vector, normal: Vector) -> Vector { } /// Returns None if the ray was totally reflected, Some(refracted_ray, reflected_amount) if not -fn refracted(incident: Vector, normal: Vector, n_1: f32, n_2: f32) -> Option<(Vector, f32)> { +/// Adds an element to the top of indices that should be removed +fn refracted( + incident: Vector, + normal: Vector, + indices: &mut RefractionInfo, + new_index: f32, +) -> Option<(Vector, f32)> { let cos1 = incident.dot(&normal); - let normal = if cos1 < 0. { normal } else { -normal }; + let normal = if cos1 < 0. { + // Entering object, change the medium + indices.enter_medium(new_index); // The old index is now in old_index + normal + } else { + // Exiting object, exit the medium + indices.exit_medium(); // We swapped the indices + -normal + }; + let (n_1, n_2) = (indices.old_index, indices.new_index); let eta = n_1 / n_2; let k = 1. - eta * eta * (1. - cos1 * cos1); if k < 0. { @@ -320,6 +336,32 @@ fn refracted(incident: Vector, normal: Vector, n_1: f32, n_2: f32) -> Option<(Ve Some((refracted.normalize(), refl_t)) } +#[derive(Debug, PartialEq, Clone)] +struct RefractionInfo { + pub old_index: f32, + pub new_index: f32, +} + +impl RefractionInfo { + pub fn with_index(index: f32) -> Self { + RefractionInfo { + old_index: index, + new_index: index, + } + } + + pub fn enter_medium(&mut self, index: f32) { + *self = RefractionInfo { + old_index: self.new_index, + new_index: index, + } + } + + pub fn exit_medium(&mut self) { + std::mem::swap(&mut self.old_index, &mut self.new_index) + } +} + #[derive(Debug, PartialEq, Deserialize)] struct SerializedScene { camera: Camera, From c0c332e3fac1009fd7bbe613117e7fad6c7938bd Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sat, 21 Mar 2020 01:53:25 +0100 Subject: [PATCH 6/7] examples: triangles: fix ball diffraction index The ball was intended to be a glass ball, so an index of 1.5 --- examples/triangles.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/triangles.yaml b/examples/triangles.yaml index f4ca91d..c7905be 100644 --- a/examples/triangles.yaml +++ b/examples/triangles.yaml @@ -35,7 +35,7 @@ objects: g: 1.0 b: 1.0 transparency: 1.0 - index: 1.9 + index: 1.5 texture: type: uniform color: From 8e09f45f698bc79f97aaf2a19ae6054ca9f4d283 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Mon, 23 Mar 2020 16:45:58 +0100 Subject: [PATCH 7/7] library: render: move helpers to utils module Those helper functions are used by `Scene` to render the scene, but they have no use being in the same file. Instead make it a crate-public module of `render`. --- src/render/mod.rs | 2 ++ src/render/scene.rs | 68 +-------------------------------------------- src/render/utils.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 67 deletions(-) create mode 100644 src/render/utils.rs diff --git a/src/render/mod.rs b/src/render/mod.rs index 769c70c..46bdffa 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -8,3 +8,5 @@ pub use object::*; pub mod scene; pub use scene::*; + +pub(crate) mod utils; diff --git a/src/render/scene.rs b/src/render/scene.rs index a4b906c..f50a6eb 100644 --- a/src/render/scene.rs +++ b/src/render/scene.rs @@ -2,7 +2,7 @@ use std::cmp::Ordering; -use super::{light_aggregate::LightAggregate, object::Object}; +use super::{light_aggregate::LightAggregate, object::Object, utils::*}; use crate::{ core::{Camera, LightProperties, LinearColor, ReflTransEnum}, material::Material, @@ -296,72 +296,6 @@ impl Scene { } } -fn reflected(incident: Vector, normal: Vector) -> Vector { - let proj = incident.dot(&normal); - let delt = normal * (proj * 2.); - (incident - delt).normalize() -} - -/// Returns None if the ray was totally reflected, Some(refracted_ray, reflected_amount) if not -/// Adds an element to the top of indices that should be removed -fn refracted( - incident: Vector, - normal: Vector, - indices: &mut RefractionInfo, - new_index: f32, -) -> Option<(Vector, f32)> { - let cos1 = incident.dot(&normal); - let normal = if cos1 < 0. { - // Entering object, change the medium - indices.enter_medium(new_index); // The old index is now in old_index - normal - } else { - // Exiting object, exit the medium - indices.exit_medium(); // We swapped the indices - -normal - }; - let (n_1, n_2) = (indices.old_index, indices.new_index); - let eta = n_1 / n_2; - let k = 1. - eta * eta * (1. - cos1 * cos1); - if k < 0. { - return None; - } - let cos1 = cos1.abs(); - let cos2 = k.sqrt(); - let refracted = eta * incident + (eta * cos1 - cos2) * normal; - let f_r = (n_2 * cos1 - n_1 * cos2) / (n_2 * cos1 + n_1 * cos2); - let f_t = (n_1 * cos2 - n_2 * cos1) / (n_1 * cos2 + n_2 * cos1); - let refl_t = (f_r * f_r + f_t * f_t) / 2.; - //Some((refracted, 0.)) - Some((refracted.normalize(), refl_t)) -} - -#[derive(Debug, PartialEq, Clone)] -struct RefractionInfo { - pub old_index: f32, - pub new_index: f32, -} - -impl RefractionInfo { - pub fn with_index(index: f32) -> Self { - RefractionInfo { - old_index: index, - new_index: index, - } - } - - pub fn enter_medium(&mut self, index: f32) { - *self = RefractionInfo { - old_index: self.new_index, - new_index: index, - } - } - - pub fn exit_medium(&mut self) { - std::mem::swap(&mut self.old_index, &mut self.new_index) - } -} - #[derive(Debug, PartialEq, Deserialize)] struct SerializedScene { camera: Camera, diff --git a/src/render/utils.rs b/src/render/utils.rs new file mode 100644 index 0000000..185765a --- /dev/null +++ b/src/render/utils.rs @@ -0,0 +1,67 @@ +use crate::Vector; + +pub fn reflected(incident: Vector, normal: Vector) -> Vector { + let proj = incident.dot(&normal); + let delt = normal * (proj * 2.); + (incident - delt).normalize() +} + +/// Returns None if the ray was totally reflected, Some(refracted_ray, reflected_amount) if not +/// Adds an element to the top of indices that should be removed +pub fn refracted( + incident: Vector, + normal: Vector, + indices: &mut RefractionInfo, + new_index: f32, +) -> Option<(Vector, f32)> { + let cos1 = incident.dot(&normal); + let normal = if cos1 < 0. { + // Entering object, change the medium + indices.enter_medium(new_index); // The old index is now in old_index + normal + } else { + // Exiting object, exit the medium + indices.exit_medium(); // We swapped the indices + -normal + }; + let (n_1, n_2) = (indices.old_index, indices.new_index); + let eta = n_1 / n_2; + let k = 1. - eta * eta * (1. - cos1 * cos1); + if k < 0. { + return None; + } + let cos1 = cos1.abs(); + let cos2 = k.sqrt(); + let refracted = eta * incident + (eta * cos1 - cos2) * normal; + let f_r = (n_2 * cos1 - n_1 * cos2) / (n_2 * cos1 + n_1 * cos2); + let f_t = (n_1 * cos2 - n_2 * cos1) / (n_1 * cos2 + n_2 * cos1); + let refl_t = (f_r * f_r + f_t * f_t) / 2.; + //Some((refracted, 0.)) + Some((refracted.normalize(), refl_t)) +} + +#[derive(Debug, PartialEq, Clone)] +pub struct RefractionInfo { + pub old_index: f32, + pub new_index: f32, +} + +impl RefractionInfo { + pub fn with_index(index: f32) -> Self { + RefractionInfo { + old_index: index, + new_index: index, + } + } + + pub fn enter_medium(&mut self, index: f32) { + *self = RefractionInfo { + old_index: self.new_index, + new_index: index, + } + } + + pub fn exit_medium(&mut self) { + std::mem::swap(&mut self.old_index, &mut self.new_index) + } +}