diff --git a/src/accel/light_array.rs b/src/accel/light_array.rs index 4039ac2..307e4b7 100644 --- a/src/accel/light_array.rs +++ b/src/accel/light_array.rs @@ -45,11 +45,12 @@ impl<'a> LightAccel for LightArray<'a> { inc: Vector, pos: Point, nor: Normal, + nor_g: Normal, sc: &SurfaceClosure, time: f32, n: f32, ) -> Option<(usize, f32, f32)> { - let _ = (inc, pos, nor, sc, time); // Not using these, silence warnings + let _ = (inc, pos, nor, nor_g, sc, time); // Not using these, silence warnings assert!(n >= 0.0 && n <= 1.0); diff --git a/src/accel/light_tree.rs b/src/accel/light_tree.rs index 9fe94da..2a42a65 100644 --- a/src/accel/light_tree.rs +++ b/src/accel/light_tree.rs @@ -134,6 +134,7 @@ impl<'a> LightAccel for LightTree<'a> { inc: Vector, pos: Point, nor: Normal, + nor_g: Normal, sc: &SurfaceClosure, time: f32, n: f32, @@ -160,7 +161,7 @@ impl<'a> LightAccel for LightTree<'a> { let sin_theta_max2 = (r2 / dist2).min(1.0); (1.0 - sin_theta_max2).sqrt() }; - sc.estimate_eval_over_solid_angle(inc, d, nor, cos_theta_max) + sc.estimate_eval_over_solid_angle(inc, d, nor, nor_g, cos_theta_max) }; node_ref.energy() * inv_surface_area * approx_contrib diff --git a/src/accel/mod.rs b/src/accel/mod.rs index b11b3ad..f2dd5e0 100644 --- a/src/accel/mod.rs +++ b/src/accel/mod.rs @@ -28,6 +28,7 @@ pub trait LightAccel { inc: Vector, pos: Point, nor: Normal, + nor_g: Normal, sc: &SurfaceClosure, time: f32, n: f32, diff --git a/src/renderer.rs b/src/renderer.rs index 1610d03..8d3f4c1 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -467,36 +467,41 @@ impl LightPath { { // Check if pdf is zero, to avoid NaN's. if light_pdf > 0.0 { - // Calculate and store the light that will be contributed - // to the film plane if the light is not in shadow. - self.pending_color_addition = { - let material = closure.as_surface_closure(); - let la = material.evaluate( - ray.dir, - shadow_vec, - idata.nor, - self.wavelength, - ); - light_color.e * la.e * self.light_attenuation / - (light_pdf * light_sel_pdf) - }; - - // Calculate the shadow ray for testing if the light is - // in shadow or not. - let offset_pos = robust_ray_origin( - idata.pos, - idata.pos_err, - idata.nor_g.normalized(), + let material = closure.as_surface_closure(); + let attenuation = material.evaluate( + ray.dir, shadow_vec, + idata.nor, + idata.nor_g, + self.wavelength, ); - *ray = Ray::new(offset_pos, shadow_vec, self.time, true); - // For distant lights - if is_infinite { - ray.max_t = std::f32::INFINITY; + if attenuation.e.h_max() > 0.0 { + // Calculate and store the light that will be contributed + // to the film plane if the light is not in shadow. + self.pending_color_addition = light_color.e * attenuation.e * + self.light_attenuation / + (light_pdf * light_sel_pdf); + + // Calculate the shadow ray for testing if the light is + // in shadow or not. + let offset_pos = robust_ray_origin( + idata.pos, + idata.pos_err, + idata.nor_g.normalized(), + shadow_vec, + ); + *ray = Ray::new(offset_pos, shadow_vec, self.time, true); + + // For distant lights + if is_infinite { + ray.max_t = std::f32::INFINITY; + } + + true + } else { + false } - - true } else { false } @@ -513,11 +518,17 @@ impl LightPath { let material = closure.as_surface_closure(); let u = self.next_lds_samp(); let v = self.next_lds_samp(); - material.sample(idata.incoming, idata.nor, (u, v), self.wavelength) + material.sample( + idata.incoming, + idata.nor, + idata.nor_g, + (u, v), + self.wavelength, + ) }; // Check if pdf is zero, to avoid NaN's. - if pdf > 0.0 { + if (pdf > 0.0) && (filter.e.h_max() > 0.0) { // Account for the additional light attenuation from // this bounce self.next_attentuation_fac = filter.e / pdf; diff --git a/src/scene/assembly.rs b/src/scene/assembly.rs index 19c271e..d4c4a22 100644 --- a/src/scene/assembly.rs +++ b/src/scene/assembly.rs @@ -60,6 +60,7 @@ impl<'a> Assembly<'a> { idata.incoming * sel_xform, idata.pos * sel_xform, idata.nor * sel_xform, + idata.nor_g * sel_xform, closure.as_surface_closure(), time, n, diff --git a/src/shading/surface_closure.rs b/src/shading/surface_closure.rs index 403b073..8f754fa 100644 --- a/src/shading/surface_closure.rs +++ b/src/shading/surface_closure.rs @@ -36,9 +36,10 @@ pub trait SurfaceClosure { /// Given an incoming ray and sample values, generates an outgoing ray and /// color filter. /// - /// inc: Incoming light direction. - /// nor: The surface normal at the surface point. - /// uv: The sampling values. + /// inc: Incoming light direction. + /// nor: The shading surface normal at the surface point. + /// nor_g: The geometric surface normal at the surface point. + /// uv: The sampling values. /// wavelength: The wavelength of light to sample at. /// /// Returns a tuple with the generated outgoing light direction, color filter, and pdf. @@ -46,27 +47,37 @@ pub trait SurfaceClosure { &self, inc: Vector, nor: Normal, + nor_g: Normal, uv: (f32, f32), wavelength: f32, ) -> (Vector, SpectralSample, f32); /// Evaluates the closure for the given incoming and outgoing rays. /// - /// inc: The incoming light direction. - /// out: The outgoing light direction. - /// nor: The surface normal of the reflecting/transmitting surface point. + /// inc: The incoming light direction. + /// out: The outgoing light direction. + /// nor: The shading surface normal at the surface point. + /// nor_g: The geometric surface normal at the surface point. /// wavelength: The wavelength of light to evaluate for. /// /// Returns the resulting filter color. - fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, wavelength: f32) -> SpectralSample; + fn evaluate( + &self, + inc: Vector, + out: Vector, + nor: Normal, + nor_g: Normal, + wavelength: f32, + ) -> SpectralSample; /// Returns the pdf for the given 'in' direction producing the given 'out' /// direction with the given differential geometry. /// /// inc: The incoming light direction. /// out: The outgoing light direction. - /// nor: The surface normal of the reflecting/transmitting surface point. - fn sample_pdf(&self, inc: Vector, out: Vector, nor: Normal) -> f32; + /// nor: The shading surface normal at the surface point. + /// nor_g: The geometric surface normal at the surface point. + fn sample_pdf(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> f32; /// Returns an estimate of the sum total energy that evaluate() would return /// when 'out' is evaluated over a circular solid angle. @@ -78,6 +89,7 @@ pub trait SurfaceClosure { inc: Vector, out: Vector, nor: Normal, + nor_g: Normal, cos_theta: f32, ) -> f32; } @@ -180,10 +192,11 @@ impl SurfaceClosure for EmitClosure { &self, inc: Vector, nor: Normal, + nor_g: Normal, uv: (f32, f32), wavelength: f32, ) -> (Vector, SpectralSample, f32) { - let _ = (inc, nor, uv); // Not using these, silence warning + let _ = (inc, nor, nor_g, uv); // Not using these, silence warning ( Vector::new(0.0, 0.0, 0.0), @@ -192,14 +205,21 @@ impl SurfaceClosure for EmitClosure { ) } - fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, wavelength: f32) -> SpectralSample { - let _ = (inc, out, nor); // Not using these, silence warning + fn evaluate( + &self, + inc: Vector, + out: Vector, + nor: Normal, + nor_g: Normal, + wavelength: f32, + ) -> SpectralSample { + let _ = (inc, out, nor, nor_g); // Not using these, silence warning SpectralSample::new(wavelength) } - fn sample_pdf(&self, inc: Vector, out: Vector, nor: Normal) -> f32 { - let _ = (inc, out, nor); // Not using these, silence warning + fn sample_pdf(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> f32 { + let _ = (inc, out, nor, nor_g); // Not using these, silence warning 1.0 } @@ -209,9 +229,10 @@ impl SurfaceClosure for EmitClosure { inc: Vector, out: Vector, nor: Normal, + nor_g: Normal, cos_theta: f32, ) -> f32 { - let _ = (inc, out, nor, cos_theta); // Not using these, silence warning + let _ = (inc, out, nor, nor_g, cos_theta); // Not using these, silence warning // TODO: what to do here? unimplemented!() @@ -240,10 +261,11 @@ impl SurfaceClosure for LambertClosure { &self, inc: Vector, nor: Normal, + nor_g: Normal, uv: (f32, f32), wavelength: f32, ) -> (Vector, SpectralSample, f32) { - let nn = if dot(nor.into_vector(), inc) <= 0.0 { + let nn = if dot(nor_g.into_vector(), inc) <= 0.0 { nor.normalized() } else { -nor.normalized() @@ -254,14 +276,21 @@ impl SurfaceClosure for LambertClosure { let dir = cosine_sample_hemisphere(uv.0, uv.1); let pdf = dir.z() * INV_PI; let out = zup_to_vec(dir, nn); - let filter = self.evaluate(inc, out, nor, wavelength); + let filter = self.evaluate(inc, out, nor, nor_g, wavelength); (out, filter, pdf) } - fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, wavelength: f32) -> SpectralSample { + fn evaluate( + &self, + inc: Vector, + out: Vector, + nor: Normal, + nor_g: Normal, + wavelength: f32, + ) -> SpectralSample { let v = out.normalized(); - let nn = if dot(nor.into_vector(), inc) <= 0.0 { + let nn = if dot(nor_g.into_vector(), inc) <= 0.0 { nor.normalized() } else { -nor.normalized() @@ -271,9 +300,9 @@ impl SurfaceClosure for LambertClosure { self.col.to_spectral_sample(wavelength) * fac } - fn sample_pdf(&self, inc: Vector, out: Vector, nor: Normal) -> f32 { + fn sample_pdf(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> f32 { let v = out.normalized(); - let nn = if dot(nor.into_vector(), inc) <= 0.0 { + let nn = if dot(nor_g.into_vector(), inc) <= 0.0 { nor.normalized() } else { -nor.normalized() @@ -287,8 +316,11 @@ impl SurfaceClosure for LambertClosure { inc: Vector, out: Vector, nor: Normal, + nor_g: Normal, cos_theta: f32, ) -> f32 { + let _ = nor_g; // Not using this, silence warning + assert!(cos_theta >= -1.0 && cos_theta <= 1.0); // Analytically calculates lambert shading from a uniform light source @@ -449,11 +481,12 @@ impl SurfaceClosure for GTRClosure { &self, inc: Vector, nor: Normal, + nor_g: Normal, uv: (f32, f32), wavelength: f32, ) -> (Vector, SpectralSample, f32) { // Get normalized surface normal - let nn = if dot(nor.into_vector(), inc) < 0.0 { + let nn = if dot(nor_g.into_vector(), inc) < 0.0 { nor.normalized() } else { -nor.normalized() // If back-facing, flip normal @@ -468,26 +501,37 @@ impl SurfaceClosure for GTRClosure { half_dir = zup_to_vec(half_dir, nn).normalized(); let out = inc - (half_dir * 2.0 * dot(inc, half_dir)); - let filter = self.evaluate(inc, out, nor, wavelength); - let pdf = self.sample_pdf(inc, out, nor); + let filter = self.evaluate(inc, out, nor, nor_g, wavelength); + let pdf = self.sample_pdf(inc, out, nor, nor_g); (out, filter, pdf) } - fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, wavelength: f32) -> SpectralSample { + fn evaluate( + &self, + inc: Vector, + out: Vector, + nor: Normal, + nor_g: Normal, + wavelength: f32, + ) -> SpectralSample { // Calculate needed vectors, normalized let aa = -inc.normalized(); // Vector pointing to where "in" came from let bb = out.normalized(); // Out let hh = (aa + bb).normalized(); // Half-way between aa and bb // Surface normal - let nn = if dot(nor.into_vector(), hh) < 0.0 { + let nn = if dot(nor_g.into_vector(), hh) < 0.0 { -nor.normalized() // If back-facing, flip normal } else { nor.normalized() }.into_vector(); + if dot(nn, aa) < 0.0 { + return SpectralSample::from_value(0.0, 0.0); + } + // Calculate needed dot products let na = clamp(dot(nn, aa), -1.0, 1.0); let nb = clamp(dot(nn, bb), -1.0, 1.0); @@ -571,19 +615,23 @@ impl SurfaceClosure for GTRClosure { } - fn sample_pdf(&self, inc: Vector, out: Vector, nor: Normal) -> f32 { + fn sample_pdf(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> f32 { // Calculate needed vectors, normalized let aa = -inc.normalized(); // Vector pointing to where "in" came from let bb = out.normalized(); // Out let hh = (aa + bb).normalized(); // Half-way between aa and bb // Surface normal - let nn = if dot(nor.into_vector(), hh) < 0.0 { + let nn = if dot(nor_g.into_vector(), hh) < 0.0 { -nor.normalized() // If back-facing, flip normal } else { nor.normalized() }.into_vector(); + if dot(nn, aa) < 0.0 { + return 0.0; + } + // Calculate needed dot products let nh = clamp(dot(nn, hh), -1.0, 1.0); @@ -596,11 +644,15 @@ impl SurfaceClosure for GTRClosure { inc: Vector, out: Vector, nor: Normal, + nor_g: Normal, cos_theta: f32, ) -> f32 { // TODO: all of the stuff in this function is horribly hacky. // Find a proper way to approximate the light contribution from a // solid angle. + + let _ = nor_g; // Not using this, silence warning + assert!(cos_theta >= -1.0); assert!(cos_theta <= 1.0);