diff --git a/src/light/distant_disk_light.rs b/src/light/distant_disk_light.rs index 7448df4..e42ff76 100644 --- a/src/light/distant_disk_light.rs +++ b/src/light/distant_disk_light.rs @@ -31,10 +31,36 @@ impl<'a> DistantDiskLight<'a> { colors: arena.copy_slice(&colors), } } + + // fn sample_pdf(&self, sample_dir: Vector, wavelength: f32, time: f32) -> f32 { + // // We're not using these, silence warnings + // let _ = (sample_dir, wavelength); + + // let radius: f64 = lerp_slice(self.radii, time) as f64; + + // uniform_sample_cone_pdf(radius.cos()) as f32 + // } + + // fn outgoing(&self, dir: Vector, wavelength: f32, time: f32) -> SpectralSample { + // // We're not using this, silence warning + // let _ = dir; + + // let radius = lerp_slice(self.radii, time) as f64; + // let col = lerp_slice(self.colors, time); + // let solid_angle = 2.0 * PI_64 * (1.0 - radius.cos()); + + // (col / solid_angle as f32).to_spectral_sample(wavelength) + // } } impl<'a> WorldLightSource for DistantDiskLight<'a> { - fn sample(&self, u: f32, v: f32, wavelength: f32, time: f32) -> (SpectralSample, Vector, f32) { + fn sample_from_point( + &self, + u: f32, + v: f32, + wavelength: f32, + time: f32, + ) -> (SpectralSample, Vector, f32) { // Calculate time interpolated values let radius: f64 = lerp_slice(self.radii, time) as f64; let direction = lerp_slice(self.directions, time); @@ -56,26 +82,6 @@ impl<'a> WorldLightSource for DistantDiskLight<'a> { (spectral_sample, shadow_vec, pdf as f32) } - fn sample_pdf(&self, sample_dir: Vector, wavelength: f32, time: f32) -> f32 { - // We're not using these, silence warnings - let _ = (sample_dir, wavelength); - - let radius: f64 = lerp_slice(self.radii, time) as f64; - - uniform_sample_cone_pdf(radius.cos()) as f32 - } - - fn outgoing(&self, dir: Vector, wavelength: f32, time: f32) -> SpectralSample { - // We're not using this, silence warning - let _ = dir; - - let radius = lerp_slice(self.radii, time) as f64; - let col = lerp_slice(self.colors, time); - let solid_angle = 2.0 * PI_64 * (1.0 - radius.cos()); - - (col / solid_angle as f32).to_spectral_sample(wavelength) - } - fn is_delta(&self) -> bool { false } diff --git a/src/light/mod.rs b/src/light/mod.rs index 1acf588..0ff1737 100644 --- a/src/light/mod.rs +++ b/src/light/mod.rs @@ -4,11 +4,9 @@ mod sphere_light; use std::fmt::Debug; -use boundable::Boundable; use color::SpectralSample; use math::{Vector, Point, Matrix4x4}; -use ray::{Ray, AccelRay}; -use surface::SurfaceIntersection; +use surface::Surface; pub use self::distant_disk_light::DistantDiskLight; pub use self::rectangle_light::RectangleLight; @@ -16,19 +14,19 @@ pub use self::sphere_light::SphereLight; /// A finite light source that can be bounded in space. -pub trait LightSource: Boundable + Debug + Sync { - /// Samples the light source for a given point to be illuminated. +pub trait SurfaceLight: Surface { + /// Samples the surface given a point to be illuminated. /// - /// - space: The world-to-object space transform of the light. - /// - arr: The point to be illuminated (in world space). - /// - u: Random parameter U. - /// - v: Random parameter V. - /// - wavelength: The wavelength of light to sample at. - /// - time: The time to sample at. + /// - `space`: The world-to-object space transform of the light. + /// - `arr`: The point to be illuminated (in world space). + /// - `u`: Random parameter U. + /// - `v`: Random parameter V. + /// - `wavelength`: The wavelength of light to sample at. + /// - `time`: The time to sample at. /// /// Returns: The light arriving at the point arr, the vector to use for /// shadow testing, and the pdf of the sample. - fn sample( + fn sample_from_point( &self, space: &Matrix4x4, arr: Point, @@ -39,53 +37,6 @@ pub trait LightSource: Boundable + Debug + Sync { ) -> (SpectralSample, Vector, f32); - /// Calculates the pdf of sampling the given - /// `sample_dir`/`sample_u`/`sample_v` from the given point `arr`. This is used - /// primarily to calculate probabilities for multiple importance sampling. - /// - /// NOTE: this function CAN assume that sample_dir, sample_u, and sample_v - /// are a valid sample for the light source (i.e. hits/lies on the light - /// source). No guarantees are made about the correctness of the return - /// value if they are not valid. - /// - /// TODO: this probably shouldn't be part of the public interface. In the - /// rest of the renderer, the PDF is always calculated by the `sample` and - /// and `intersect_rays` methods. - fn sample_pdf( - &self, - space: &Matrix4x4, - arr: Point, - sample_dir: Vector, - sample_u: f32, - sample_v: f32, - wavelength: f32, - time: f32, - ) -> f32; - - - /// Returns the color emitted in the given direction from the - /// given parameters on the light. - /// - /// - dir: The direction of the outgoing light. - /// - u: Random parameter U. - /// - v: Random parameter V. - /// - wavelength: The hero wavelength of light to sample at. - /// - time: The time to sample at. - /// - /// TODO: this probably shouldn't be part of the public interface. In the - /// rest of the renderer, this is handled by the `sample` and - /// `intersect_rays` methods. - fn outgoing( - &self, - space: &Matrix4x4, - dir: Vector, - u: f32, - v: f32, - wavelength: f32, - time: f32, - ) -> SpectralSample; - - /// Returns whether the light has a delta distribution. /// /// If a light has no chance of a ray hitting it through random process @@ -94,18 +45,12 @@ pub trait LightSource: Boundable + Debug + Sync { fn is_delta(&self) -> bool; - /// Returns an approximation of the total energy emitted by the light - /// source. Note that this does not need to be exact: it is used for - /// importance sampling. + /// Returns an approximation of the total energy emitted by the surface. + /// + /// Note: this does not need to be exact, but it does need to be non-zero + /// for any surface that does emit light. This is used for importance + /// sampling. fn approximate_energy(&self) -> f32; - - fn intersect_rays( - &self, - accel_rays: &mut [AccelRay], - wrays: &[Ray], - isects: &mut [SurfaceIntersection], - space: &[Matrix4x4], - ); } @@ -121,25 +66,13 @@ pub trait WorldLightSource: Debug + Sync { /// /// Returns: The light arriving from the shadow-testing direction, the /// vector to use for shadow testing, and the pdf of the sample. - fn sample(&self, u: f32, v: f32, wavelength: f32, time: f32) -> (SpectralSample, Vector, f32); - - - /// Calculates the pdf of sampling the given sample_dir. This is used - /// primarily to calculate probabilities for multiple importance sampling. - /// - /// NOTE: this function CAN assume that sample_dir is a valid sample for - /// the light source (i.e. hits/lies on the light source). No guarantees - /// are made about the correctness of the return value if it isn't valid. - fn sample_pdf(&self, sample_dir: Vector, wavelength: f32, time: f32) -> f32; - - - /// Returns the color emitted in the given direction from the - /// given parameters on the light. - /// - /// - dir: The direction of the outgoing light. - /// - wavelength: The hero wavelength of light to sample at. - /// - time: The time to sample at. - fn outgoing(&self, dir: Vector, wavelength: f32, time: f32) -> SpectralSample; + fn sample_from_point( + &self, + u: f32, + v: f32, + wavelength: f32, + time: f32, + ) -> (SpectralSample, Vector, f32); /// Returns whether the light has a delta distribution. @@ -151,7 +84,10 @@ pub trait WorldLightSource: Debug + Sync { /// Returns an approximation of the total energy emitted by the light - /// source. Note that this does not need to be exact: it is used for - /// importance sampling. + /// source. + /// + /// Note: this does not need to be exact, but it does need to be non-zero + /// for any light that emits any light. This is used for importance + /// sampling. fn approximate_energy(&self) -> f32; } diff --git a/src/light/rectangle_light.rs b/src/light/rectangle_light.rs index 53c6002..2c4d779 100644 --- a/src/light/rectangle_light.rs +++ b/src/light/rectangle_light.rs @@ -7,9 +7,10 @@ use lerp::lerp_slice; use math::{Vector, Point, Matrix4x4}; use ray::{Ray, AccelRay}; use sampling::{spherical_triangle_solid_angle, uniform_sample_spherical_triangle}; -use surface::SurfaceIntersection; +use shading::SurfaceShader; +use surface::{Surface, SurfaceIntersection}; -use super::LightSource; +use super::SurfaceLight; #[derive(Copy, Clone, Debug)] @@ -40,10 +41,67 @@ impl<'a> RectangleLight<'a> { bounds_: arena.copy_slice(&bbs), } } + + // fn sample_pdf( + // &self, + // space: &Matrix4x4, + // arr: Point, + // sample_dir: Vector, + // sample_u: f32, + // sample_v: f32, + // wavelength: f32, + // time: f32, + // ) -> f32 { + // // We're not using these, silence warnings + // let _ = (sample_dir, sample_u, sample_v, wavelength); + + // let dim = lerp_slice(self.dimensions, time); + + // // Get the four corners of the rectangle, transformed into world space + // let space_inv = space.inverse(); + // let p1 = Point::new(dim.0 * 0.5, dim.1 * 0.5, 0.0) * space_inv; + // let p2 = Point::new(dim.0 * -0.5, dim.1 * 0.5, 0.0) * space_inv; + // let p3 = Point::new(dim.0 * -0.5, dim.1 * -0.5, 0.0) * space_inv; + // let p4 = Point::new(dim.0 * 0.5, dim.1 * -0.5, 0.0) * space_inv; + + // // Get the four corners of the rectangle, projected on to the unit + // // sphere centered around arr. + // let sp1 = (p1 - arr).normalized(); + // let sp2 = (p2 - arr).normalized(); + // let sp3 = (p3 - arr).normalized(); + // let sp4 = (p4 - arr).normalized(); + + // // Get the solid angles of the rectangle split into two triangles + // let area_1 = spherical_triangle_solid_angle(sp2, sp1, sp3); + // let area_2 = spherical_triangle_solid_angle(sp4, sp1, sp3); + + // 1.0 / (area_1 + area_2) + // } + + // fn outgoing( + // &self, + // space: &Matrix4x4, + // dir: Vector, + // u: f32, + // v: f32, + // wavelength: f32, + // time: f32, + // ) -> SpectralSample { + // // We're not using these, silence warnings + // let _ = (space, dir, u, v); + + // let dim = lerp_slice(self.dimensions, time); + // let col = lerp_slice(self.colors, time); + + // // TODO: Is this right? Do we need to get the surface area post-transform? + // let surface_area_inv: f64 = 1.0 / (dim.0 as f64 * dim.1 as f64); + + // (col * surface_area_inv as f32 * 0.5).to_spectral_sample(wavelength) + // } } -impl<'a> LightSource for RectangleLight<'a> { - fn sample( +impl<'a> SurfaceLight for RectangleLight<'a> { + fn sample_from_point( &self, space: &Matrix4x4, arr: Point, @@ -110,63 +168,6 @@ impl<'a> LightSource for RectangleLight<'a> { (spectral_sample, shadow_vec, pdf as f32) } - fn sample_pdf( - &self, - space: &Matrix4x4, - arr: Point, - sample_dir: Vector, - sample_u: f32, - sample_v: f32, - wavelength: f32, - time: f32, - ) -> f32 { - // We're not using these, silence warnings - let _ = (sample_dir, sample_u, sample_v, wavelength); - - let dim = lerp_slice(self.dimensions, time); - - // Get the four corners of the rectangle, transformed into world space - let space_inv = space.inverse(); - let p1 = Point::new(dim.0 * 0.5, dim.1 * 0.5, 0.0) * space_inv; - let p2 = Point::new(dim.0 * -0.5, dim.1 * 0.5, 0.0) * space_inv; - let p3 = Point::new(dim.0 * -0.5, dim.1 * -0.5, 0.0) * space_inv; - let p4 = Point::new(dim.0 * 0.5, dim.1 * -0.5, 0.0) * space_inv; - - // Get the four corners of the rectangle, projected on to the unit - // sphere centered around arr. - let sp1 = (p1 - arr).normalized(); - let sp2 = (p2 - arr).normalized(); - let sp3 = (p3 - arr).normalized(); - let sp4 = (p4 - arr).normalized(); - - // Get the solid angles of the rectangle split into two triangles - let area_1 = spherical_triangle_solid_angle(sp2, sp1, sp3); - let area_2 = spherical_triangle_solid_angle(sp4, sp1, sp3); - - 1.0 / (area_1 + area_2) - } - - fn outgoing( - &self, - space: &Matrix4x4, - dir: Vector, - u: f32, - v: f32, - wavelength: f32, - time: f32, - ) -> SpectralSample { - // We're not using these, silence warnings - let _ = (space, dir, u, v); - - let dim = lerp_slice(self.dimensions, time); - let col = lerp_slice(self.colors, time); - - // TODO: Is this right? Do we need to get the surface area post-transform? - let surface_area_inv: f64 = 1.0 / (dim.0 as f64 * dim.1 as f64); - - (col * surface_area_inv as f32 * 0.5).to_spectral_sample(wavelength) - } - fn is_delta(&self) -> bool { false } @@ -178,14 +179,19 @@ impl<'a> LightSource for RectangleLight<'a> { ) / self.colors.len() as f32; color.y } +} + +impl<'a> Surface for RectangleLight<'a> { fn intersect_rays( &self, accel_rays: &mut [AccelRay], wrays: &[Ray], isects: &mut [SurfaceIntersection], + shader: &SurfaceShader, space: &[Matrix4x4], ) { + let _ = (accel_rays, wrays, isects, shader, space); unimplemented!() } } diff --git a/src/light/sphere_light.rs b/src/light/sphere_light.rs index 08f5fe8..3a94ddc 100644 --- a/src/light/sphere_light.rs +++ b/src/light/sphere_light.rs @@ -9,10 +9,11 @@ use lerp::lerp_slice; use math::{Vector, Point, Matrix4x4, dot, coordinate_system_from_vector}; use ray::{Ray, AccelRay}; use sampling::{uniform_sample_cone, uniform_sample_cone_pdf, uniform_sample_sphere}; -use surface::{SurfaceIntersection, SurfaceIntersectionData}; use shading::surface_closure::{SurfaceClosureUnion, EmitClosure}; +use shading::SurfaceShader; +use surface::{Surface, SurfaceIntersection, SurfaceIntersectionData}; -use super::LightSource; +use super::SurfaceLight; // TODO: handle case where radius = 0.0. @@ -40,10 +41,45 @@ impl<'a> SphereLight<'a> { bounds_: arena.copy_slice(&bbs), } } + + // TODO: this is only used from within `intersect_rays`, and could be done + // more efficiently by inlining it there. + fn sample_pdf( + &self, + space: &Matrix4x4, + arr: Point, + sample_dir: Vector, + sample_u: f32, + sample_v: f32, + wavelength: f32, + time: f32, + ) -> f32 { + // We're not using these, silence warnings + let _ = (sample_dir, sample_u, sample_v, wavelength); + + let arr = arr * *space; + let pos = Point::new(0.0, 0.0, 0.0); + let radius: f64 = lerp_slice(self.radii, time) as f64; + + let d2: f64 = (pos - arr).length2() as f64; // Distance from center of sphere squared + let d: f64 = d2.sqrt(); // Distance from center of sphere + + if d > radius { + // Calculate the portion of the sphere visible from the point + let sin_theta_max2: f64 = ((radius * radius) / d2).min(1.0); + let cos_theta_max2: f64 = 1.0 - sin_theta_max2; + let cos_theta_max: f64 = cos_theta_max2.sqrt(); + + uniform_sample_cone_pdf(cos_theta_max) as f32 + } else { + (1.0 / (4.0 * PI_64)) as f32 + } + } } -impl<'a> LightSource for SphereLight<'a> { - fn sample( + +impl<'a> SurfaceLight for SphereLight<'a> { + fn sample_from_point( &self, space: &Matrix4x4, arr: Point, @@ -61,7 +97,6 @@ impl<'a> LightSource for SphereLight<'a> { let col = lerp_slice(self.colors, time); let surface_area_inv: f64 = 1.0 / (4.0 * PI_64 * radius * radius); - // Create a coordinate system from the vector between the // point and the center of the light let z = pos - arr; @@ -119,57 +154,6 @@ impl<'a> LightSource for SphereLight<'a> { } } - fn sample_pdf( - &self, - space: &Matrix4x4, - arr: Point, - sample_dir: Vector, - sample_u: f32, - sample_v: f32, - wavelength: f32, - time: f32, - ) -> f32 { - // We're not using these, silence warnings - let _ = (sample_dir, sample_u, sample_v, wavelength); - - let arr = arr * *space; - let pos = Point::new(0.0, 0.0, 0.0); - let radius: f64 = lerp_slice(self.radii, time) as f64; - - let d2: f64 = (pos - arr).length2() as f64; // Distance from center of sphere squared - let d: f64 = d2.sqrt(); // Distance from center of sphere - - if d > radius { - // Calculate the portion of the sphere visible from the point - let sin_theta_max2: f64 = ((radius * radius) / d2).min(1.0); - let cos_theta_max2: f64 = 1.0 - sin_theta_max2; - let cos_theta_max: f64 = cos_theta_max2.sqrt(); - - uniform_sample_cone_pdf(cos_theta_max) as f32 - } else { - (1.0 / (4.0 * PI_64)) as f32 - } - } - - fn outgoing( - &self, - space: &Matrix4x4, - dir: Vector, - u: f32, - v: f32, - wavelength: f32, - time: f32, - ) -> SpectralSample { - // We're not using these, silence warnings - let _ = (space, dir, u, v); - - // TODO: use transform space correctly - let radius = lerp_slice(self.radii, time) as f64; - let col = lerp_slice(self.colors, time); - let surface_area = 4.0 * PI_64 * radius * radius; - (col / surface_area as f32).to_spectral_sample(wavelength) - } - fn is_delta(&self) -> bool { false } @@ -181,14 +165,20 @@ impl<'a> LightSource for SphereLight<'a> { ) / self.colors.len() as f32; color.y } +} + +impl<'a> Surface for SphereLight<'a> { fn intersect_rays( &self, accel_rays: &mut [AccelRay], wrays: &[Ray], isects: &mut [SurfaceIntersection], + shader: &SurfaceShader, space: &[Matrix4x4], ) { + let _ = shader; // Silence 'unused' warning + for r in accel_rays.iter_mut() { let wr = &wrays[r.id as usize]; @@ -316,6 +306,7 @@ impl<'a> LightSource for SphereLight<'a> { } } + impl<'a> Boundable for SphereLight<'a> { fn bounds(&self) -> &[BBox] { self.bounds_ diff --git a/src/parse/psy_assembly.rs b/src/parse/psy_assembly.rs index 8fea94c..86cb659 100644 --- a/src/parse/psy_assembly.rs +++ b/src/parse/psy_assembly.rs @@ -119,7 +119,7 @@ pub fn parse_assembly<'a>( if let DataTree::Internal { ident: Some(ident), .. } = *child { builder.add_object( ident, - Object::Light(arena.alloc(parse_sphere_light(arena, child)?)), + Object::SurfaceLight(arena.alloc(parse_sphere_light(arena, child)?)), ); } else { // No ident @@ -132,7 +132,9 @@ pub fn parse_assembly<'a>( if let DataTree::Internal { ident: Some(ident), .. } = *child { builder.add_object( ident, - Object::Light(arena.alloc(parse_rectangle_light(arena, child)?)), + Object::SurfaceLight( + arena.alloc(parse_rectangle_light(arena, child)?), + ), ); } else { // No ident diff --git a/src/scene/assembly.rs b/src/scene/assembly.rs index ba7ba4c..a522a5c 100644 --- a/src/scene/assembly.rs +++ b/src/scene/assembly.rs @@ -8,7 +8,7 @@ use bbox::{BBox, transform_bbox_slice_from}; use boundable::Boundable; use color::SpectralSample; use lerp::lerp_slice; -use light::LightSource; +use light::SurfaceLight; use math::{Matrix4x4, Vector}; use surface::{Surface, SurfaceIntersection}; use shading::SurfaceShader; @@ -75,7 +75,7 @@ impl<'a> Assembly<'a> { InstanceType::Object => { match self.objects[inst.data_index] { - Object::Light(light) => { + Object::SurfaceLight(light) => { // Get the world-to-object space transform of the light let xform = if let Some((a, b)) = inst.transform_indices { let pxforms = xform_stack.top(); @@ -95,8 +95,14 @@ impl<'a> Assembly<'a> { }; // Sample the light - let (color, shadow_vec, pdf) = - light.sample(&xform, idata.pos, uvw.0, uvw.1, wavelength, time); + let (color, shadow_vec, pdf) = light.sample_from_point( + &xform, + idata.pos, + uvw.0, + uvw.1, + wavelength, + time, + ); return Some((color, shadow_vec, pdf, sel_pdf)); } @@ -303,7 +309,7 @@ impl<'a> AssemblyBuilder<'a> { .iter() .filter(|inst| match inst.instance_type { InstanceType::Object => { - if let Object::Light(_) = self.objects[inst.data_index] { + if let Object::SurfaceLight(_) = self.objects[inst.data_index] { true } else { false @@ -324,7 +330,7 @@ impl<'a> AssemblyBuilder<'a> { let bounds = &bbs[bis[inst.id]..bis[inst.id + 1]]; let energy = match inst.instance_type { InstanceType::Object => { - if let Object::Light(light) = self.objects[inst.data_index] { + if let Object::SurfaceLight(light) = self.objects[inst.data_index] { light.approximate_energy() } else { 0.0 @@ -370,7 +376,7 @@ impl<'a> AssemblyBuilder<'a> { let obj = &self.objects[inst.data_index]; match *obj { Object::Surface(s) => bbs.extend(s.bounds()), - Object::Light(l) => bbs.extend(l.bounds()), + Object::SurfaceLight(l) => bbs.extend(l.bounds()), } } @@ -404,7 +410,7 @@ impl<'a> AssemblyBuilder<'a> { #[derive(Copy, Clone, Debug)] pub enum Object<'a> { Surface(&'a Surface), - Light(&'a LightSource), + SurfaceLight(&'a SurfaceLight), } diff --git a/src/scene/scene.rs b/src/scene/scene.rs index 7dd6db9..44a5cbb 100644 --- a/src/scene/scene.rs +++ b/src/scene/scene.rs @@ -60,7 +60,8 @@ impl<'a> Scene<'a> { // World lights let n = n / wl_prob; let (i, p) = weighted_choice(self.world.lights, n, |l| l.approximate_energy()); - let (ss, sv, pdf) = self.world.lights[i].sample(uvw.0, uvw.1, wavelength, time); + let (ss, sv, pdf) = + self.world.lights[i].sample_from_point(uvw.0, uvw.1, wavelength, time); return Some((ss, sv, pdf, p * wl_prob, true)); } else { // Local lights diff --git a/src/surface/mod.rs b/src/surface/mod.rs index a7931b0..076175d 100644 --- a/src/surface/mod.rs +++ b/src/surface/mod.rs @@ -8,8 +8,8 @@ use std::fmt::Debug; use boundable::Boundable; use math::{Point, Vector, Normal, Matrix4x4}; use ray::{Ray, AccelRay}; -use shading::SurfaceShader; use shading::surface_closure::SurfaceClosureUnion; +use shading::SurfaceShader; pub trait Surface: Boundable + Debug + Sync { diff --git a/src/tracer.rs b/src/tracer.rs index 4159b40..cade2d8 100644 --- a/src/tracer.rs +++ b/src/tracer.rs @@ -199,7 +199,7 @@ impl<'a> TracerInner<'a> { ); } - Object::Light(_) => { + Object::SurfaceLight(_) => { // TODO } }