diff --git a/src/camera.rs b/src/camera.rs index aeddf93..1701674 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -69,7 +69,7 @@ impl<'a> Camera<'a> { } } - pub fn generate_ray(&self, x: f32, y: f32, time: f32, u: f32, v: f32) -> Ray { + pub fn generate_ray(&self, x: f32, y: f32, time: f32, wavelength: f32, u: f32, v: f32) -> Ray { // Get time-interpolated camera settings let transform = lerp_slice(self.transforms, time); let tfov = lerp_slice(self.tfovs, time); @@ -89,6 +89,6 @@ impl<'a> Camera<'a> { 1.0, ).normalized(); - Ray::new(orig * transform, dir * transform, time, false) + Ray::new(orig * transform, dir * transform, time, wavelength, false) } } diff --git a/src/ray.rs b/src/ray.rs index d75e1d6..16abe46 100644 --- a/src/ray.rs +++ b/src/ray.rs @@ -15,17 +15,19 @@ pub struct Ray { pub dir: Vector, pub max_t: f32, pub time: f32, + pub wavelength: f32, pub flags: u32, } impl Ray { - pub fn new(orig: Point, dir: Vector, time: f32, is_occ: bool) -> Ray { + pub fn new(orig: Point, dir: Vector, time: f32, wavelength: f32, is_occ: bool) -> Ray { if !is_occ { Ray { orig: orig, dir: dir, max_t: std::f32::INFINITY, time: time, + wavelength: wavelength, flags: 0, } } else { @@ -34,6 +36,7 @@ impl Ray { dir: dir, max_t: 1.0, time: time, + wavelength: wavelength, flags: OCCLUSION_FLAG, } } diff --git a/src/renderer.rs b/src/renderer.rs index 8d3f4c1..d0c4d67 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -412,6 +412,7 @@ impl LightPath { image_plane_co.0, image_plane_co.1, time, + wavelength, lens_uv.0, lens_uv.1, ), @@ -468,13 +469,8 @@ impl LightPath { // Check if pdf is zero, to avoid NaN's. if light_pdf > 0.0 { let material = closure.as_surface_closure(); - let attenuation = material.evaluate( - ray.dir, - shadow_vec, - idata.nor, - idata.nor_g, - self.wavelength, - ); + let attenuation = + material.evaluate(ray.dir, shadow_vec, idata.nor, idata.nor_g); if attenuation.e.h_max() > 0.0 { // Calculate and store the light that will be contributed @@ -491,7 +487,13 @@ impl LightPath { idata.nor_g.normalized(), shadow_vec, ); - *ray = Ray::new(offset_pos, shadow_vec, self.time, true); + *ray = Ray::new( + offset_pos, + shadow_vec, + self.time, + self.wavelength, + true, + ); // For distant lights if is_infinite { @@ -518,13 +520,7 @@ 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, - idata.nor_g, - (u, v), - self.wavelength, - ) + material.sample(idata.incoming, idata.nor, idata.nor_g, (u, v)) }; // Check if pdf is zero, to avoid NaN's. @@ -541,7 +537,7 @@ impl LightPath { dir, ); self.next_bounce_ray = - Some(Ray::new(offset_pos, dir, self.time, false)); + Some(Ray::new(offset_pos, dir, self.time, self.wavelength, false)); true } else { diff --git a/src/shading/mod.rs b/src/shading/mod.rs index db94e3e..4ab97b9 100644 --- a/src/shading/mod.rs +++ b/src/shading/mod.rs @@ -2,14 +2,15 @@ pub mod surface_closure; use std::fmt::Debug; -use self::surface_closure::SurfaceClosureUnion; +use color::{XYZ, Color}; +use self::surface_closure::{SurfaceClosureUnion, EmitClosure, LambertClosure, GTRClosure}; use surface::SurfaceIntersectionData; /// Trait for surface shaders. pub trait SurfaceShader: Debug { /// Takes the result of a surface intersection and returns the surface /// closure to be evaluated at that intersection point. - fn shade(&self, data: &SurfaceIntersectionData) -> SurfaceClosureUnion; + fn shade(&self, data: &SurfaceIntersectionData, wavelength: f32) -> SurfaceClosureUnion; } /// Clearly we must eat this brownie before the world ends, lest it @@ -24,19 +25,45 @@ pub trait SurfaceShader: Debug { /// them a great injustice, for they are each the size of a small /// building. #[derive(Debug)] -pub struct SimpleSurfaceShader { - closure: SurfaceClosureUnion, -} - -impl SimpleSurfaceShader { - fn new(closure: SurfaceClosureUnion) -> SimpleSurfaceShader { - SimpleSurfaceShader { closure: closure } - } +pub enum SimpleSurfaceShader { + Emit { color: XYZ }, + Lambert { color: XYZ }, + GTR { + color: XYZ, + roughness: f32, + tail_shape: f32, + fresnel: f32, + }, } impl SurfaceShader for SimpleSurfaceShader { - fn shade(&self, data: &SurfaceIntersectionData) -> SurfaceClosureUnion { + fn shade(&self, data: &SurfaceIntersectionData, wavelength: f32) -> SurfaceClosureUnion { let _ = data; // Silence "unused" compiler warning - self.closure + + match *self { + SimpleSurfaceShader::Emit { color } => { + SurfaceClosureUnion::EmitClosure( + EmitClosure::new(color.to_spectral_sample(wavelength)), + ) + } + SimpleSurfaceShader::Lambert { color } => { + SurfaceClosureUnion::LambertClosure( + LambertClosure::new(color.to_spectral_sample(wavelength)), + ) + } + SimpleSurfaceShader::GTR { + color, + roughness, + tail_shape, + fresnel, + } => { + SurfaceClosureUnion::GTRClosure(GTRClosure::new( + color.to_spectral_sample(wavelength), + roughness, + tail_shape, + fresnel, + )) + } + } } } diff --git a/src/shading/surface_closure.rs b/src/shading/surface_closure.rs index e074fec..e4680a0 100644 --- a/src/shading/surface_closure.rs +++ b/src/shading/surface_closure.rs @@ -2,7 +2,7 @@ use std::f32::consts::PI as PI_32; -use color::{XYZ, SpectralSample, Color}; +use color::SpectralSample; use math::{Vector, Normal, dot, clamp, zup_to_vec}; use sampling::cosine_sample_hemisphere; use lerp::lerp; @@ -29,6 +29,10 @@ impl SurfaceClosureUnion { } /// Trait for surface closures. +/// +/// Note: each surface closure is assumed to be bound to a particular hero +/// wavelength. This is implicit in the `sample`, `evaluate`, and `sample_pdf` +/// functions below. pub trait SurfaceClosure { /// Returns whether the closure has a delta distribution or not. fn is_delta(&self) -> bool; @@ -40,7 +44,6 @@ pub trait SurfaceClosure { /// 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. fn sample( @@ -49,7 +52,6 @@ pub trait SurfaceClosure { nor: Normal, nor_g: Normal, uv: (f32, f32), - wavelength: f32, ) -> (Vector, SpectralSample, f32); /// Evaluates the closure for the given incoming and outgoing rays. @@ -61,14 +63,7 @@ pub trait SurfaceClosure { /// wavelength: The wavelength of light to evaluate for. /// /// Returns the resulting filter color. - fn evaluate( - &self, - inc: Vector, - out: Vector, - nor: Normal, - nor_g: Normal, - wavelength: f32, - ) -> SpectralSample; + fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> SpectralSample; /// Returns the pdf for the given 'in' direction producing the given 'out' /// direction with the given differential geometry. @@ -174,12 +169,16 @@ fn schlick_fresnel_from_fac(frensel_fac: f32, c: f32) -> f32 { /// behave like a standard closure! #[derive(Debug, Copy, Clone)] pub struct EmitClosure { - col: XYZ, + col: SpectralSample, } impl EmitClosure { - pub fn emitted_color(&self, wavelength: f32) -> SpectralSample { - self.col.to_spectral_sample(wavelength) + pub fn new(color: SpectralSample) -> EmitClosure { + EmitClosure { col: color } + } + + pub fn emitted_color(&self) -> SpectralSample { + self.col } } @@ -194,28 +193,16 @@ impl SurfaceClosure for EmitClosure { nor: Normal, nor_g: Normal, uv: (f32, f32), - wavelength: f32, ) -> (Vector, SpectralSample, f32) { let _ = (inc, nor, nor_g, uv); // Not using these, silence warning - ( - Vector::new(0.0, 0.0, 0.0), - SpectralSample::new(wavelength), - 1.0, - ) + (Vector::new(0.0, 0.0, 0.0), self.col, 1.0) } - fn evaluate( - &self, - inc: Vector, - out: Vector, - nor: Normal, - nor_g: Normal, - wavelength: f32, - ) -> SpectralSample { + fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> SpectralSample { let _ = (inc, out, nor, nor_g); // Not using these, silence warning - SpectralSample::new(wavelength) + self.col } fn sample_pdf(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> f32 { @@ -243,11 +230,11 @@ impl SurfaceClosure for EmitClosure { /// Lambertian surface closure #[derive(Debug, Copy, Clone)] pub struct LambertClosure { - col: XYZ, + col: SpectralSample, } impl LambertClosure { - pub fn new(col: XYZ) -> LambertClosure { + pub fn new(col: SpectralSample) -> LambertClosure { LambertClosure { col: col } } } @@ -263,7 +250,6 @@ impl SurfaceClosure for LambertClosure { nor: Normal, nor_g: Normal, uv: (f32, f32), - wavelength: f32, ) -> (Vector, SpectralSample, f32) { let (nn, flipped_nor_g) = if dot(nor_g.into_vector(), inc) <= 0.0 { (nor.normalized().into_vector(), nor_g.into_vector()) @@ -279,21 +265,14 @@ impl SurfaceClosure for LambertClosure { // Make sure it's not on the wrong side of the geometric normal. if dot(flipped_nor_g, out) >= 0.0 { - let filter = self.evaluate(inc, out, nor, nor_g, wavelength); + let filter = self.evaluate(inc, out, nor, nor_g); (out, filter, pdf) } else { - (out, SpectralSample::from_value(0.0, 0.0), 0.0) + (out, SpectralSample::new(0.0), 0.0) } } - fn evaluate( - &self, - inc: Vector, - out: Vector, - nor: Normal, - nor_g: Normal, - wavelength: f32, - ) -> SpectralSample { + fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> SpectralSample { let (nn, flipped_nor_g) = if dot(nor_g.into_vector(), inc) <= 0.0 { (nor.normalized().into_vector(), nor_g.into_vector()) } else { @@ -302,9 +281,9 @@ impl SurfaceClosure for LambertClosure { if dot(flipped_nor_g, out) >= 0.0 { let fac = dot(nn, out.normalized()).max(0.0) * INV_PI; - self.col.to_spectral_sample(wavelength) * fac + self.col * fac } else { - SpectralSample::from_value(0.0, 0.0) + SpectralSample::new(0.0) } } @@ -389,7 +368,7 @@ impl SurfaceClosure for LambertClosure { /// The GTR microfacet BRDF from the Disney Principled BRDF paper. #[derive(Debug, Copy, Clone)] pub struct GTRClosure { - col: XYZ, + col: SpectralSample, roughness: f32, tail_shape: f32, fresnel: f32, // [0.0, 1.0] determines how much fresnel reflection comes into play @@ -397,7 +376,7 @@ pub struct GTRClosure { } impl GTRClosure { - pub fn new(col: XYZ, roughness: f32, tail_shape: f32, fresnel: f32) -> GTRClosure { + pub fn new(col: SpectralSample, roughness: f32, tail_shape: f32, fresnel: f32) -> GTRClosure { let mut closure = GTRClosure { col: col, roughness: roughness, @@ -494,7 +473,6 @@ impl SurfaceClosure for GTRClosure { nor: Normal, nor_g: Normal, uv: (f32, f32), - wavelength: f32, ) -> (Vector, SpectralSample, f32) { // Get normalized surface normal let (nn, flipped_nor_g) = if dot(nor_g.into_vector(), inc) <= 0.0 { @@ -515,23 +493,16 @@ impl SurfaceClosure for GTRClosure { // Make sure it's not on the wrong side of the geometric normal. if dot(flipped_nor_g, out) >= 0.0 { - let filter = self.evaluate(inc, out, nor, nor_g, wavelength); + let filter = self.evaluate(inc, out, nor, nor_g); let pdf = self.sample_pdf(inc, out, nor, nor_g); (out, filter, pdf) } else { - (out, SpectralSample::from_value(0.0, 0.0), 0.0) + (out, SpectralSample::new(0.0), 0.0) } } - fn evaluate( - &self, - inc: Vector, - out: Vector, - nor: Normal, - nor_g: Normal, - wavelength: f32, - ) -> SpectralSample { + fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> SpectralSample { // Calculate needed vectors, normalized let aa = -inc.normalized(); // Vector pointing to where "in" came from let bb = out.normalized(); // Out @@ -546,7 +517,7 @@ impl SurfaceClosure for GTRClosure { // Make sure everything's on the correct side of the surface if dot(nn, aa) < 0.0 || dot(nn, bb) < 0.0 || dot(flipped_nor_g, bb) < 0.0 { - return SpectralSample::from_value(0.0, 0.0); + return SpectralSample::new(0.0); } // Calculate needed dot products @@ -561,30 +532,29 @@ impl SurfaceClosure for GTRClosure { // Calculate F - Fresnel let col_f = { - let mut col_f = self.col.to_spectral_sample(wavelength); - let rev_fresnel = 1.0 - self.fresnel; let c0 = lerp( - schlick_fresnel_from_fac(col_f.e.get_0(), hb), - col_f.e.get_0(), + schlick_fresnel_from_fac(self.col.e.get_0(), hb), + self.col.e.get_0(), rev_fresnel, ); let c1 = lerp( - schlick_fresnel_from_fac(col_f.e.get_1(), hb), - col_f.e.get_1(), + schlick_fresnel_from_fac(self.col.e.get_1(), hb), + self.col.e.get_1(), rev_fresnel, ); let c2 = lerp( - schlick_fresnel_from_fac(col_f.e.get_2(), hb), - col_f.e.get_2(), + schlick_fresnel_from_fac(self.col.e.get_2(), hb), + self.col.e.get_2(), rev_fresnel, ); let c3 = lerp( - schlick_fresnel_from_fac(col_f.e.get_3(), hb), - col_f.e.get_3(), + schlick_fresnel_from_fac(self.col.e.get_3(), hb), + self.col.e.get_3(), rev_fresnel, ); + let mut col_f = self.col; col_f.e.set_0(c0); col_f.e.set_1(c1); col_f.e.set_2(c2); diff --git a/src/surface/triangle_mesh.rs b/src/surface/triangle_mesh.rs index 6a29a28..4ecd8c7 100644 --- a/src/surface/triangle_mesh.rs +++ b/src/surface/triangle_mesh.rs @@ -10,7 +10,7 @@ use fp_utils::fp_gamma; use lerp::lerp_slice; use math::{Point, Normal, Matrix4x4, dot, cross}; use ray::{Ray, AccelRay}; -use shading::surface_closure::{SurfaceClosureUnion, GTRClosure, LambertClosure}; +use shading::{SurfaceShader, SimpleSurfaceShader}; use super::{Surface, SurfaceIntersection, SurfaceIntersectionData}; use super::triangle; @@ -235,28 +235,30 @@ impl<'a> Surface for TriangleMesh<'a> { geo_normal }; + let intersection_data = SurfaceIntersectionData { + incoming: wr.dir, + t: t, + pos: pos, + pos_err: pos_err, + nor: shading_normal, + nor_g: geo_normal, + uv: (0.0, 0.0), // TODO + local_space: mat_space, + }; + // Fill in intersection data isects[r.id as usize] = SurfaceIntersection::Hit { - intersection_data: SurfaceIntersectionData { - incoming: wr.dir, - t: t, - pos: pos, - pos_err: pos_err, - nor: shading_normal, - nor_g: geo_normal, - uv: (0.0, 0.0), // TODO - local_space: mat_space, - }, - // TODO: get surface closure from surface shader. - closure: SurfaceClosureUnion::LambertClosure( - LambertClosure::new(XYZ::new(0.8, 0.8, 0.8)), - ), -// closure: -// SurfaceClosureUnion::GTRClosure( -// GTRClosure::new(XYZ::new(0.8, 0.8, 0.8), -// 0.1, -// 2.0, -// 1.0)), + intersection_data: intersection_data, + // TODO: get surface shader from user-defined shader. + closure: SimpleSurfaceShader::Lambert { + color: XYZ::new(0.8, 0.8, 0.8), + }.shade(&intersection_data, wr.wavelength), + // closure: SimpleSurfaceShader::GTR { + // color: XYZ::new(0.8, 0.8, 0.8), + // roughness: 0.1, + // tail_shape: 2.0, + // fresnel: 1.0, + // }.shade(&intersection_data, wr.wavelength), }; r.max_t = t; }