From f4d4152543ead4f0f16ed609ab0686cb732d59e0 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Thu, 3 Aug 2017 16:16:36 -0700 Subject: [PATCH] Some refactoring in preparation for a material system. The main change is that SurfaceClosures now have the hero wavelength baked into them. Since surface closures come from surface intersections, and intersections are always specific to a ray or path, and rays/paths have a fixed wavelength, it doesn't make sense for the surface closure to constantly be converting from a more general color representation to spectral samples whenever its used. This is also nice because it keeps surface closures removed from any particular representation of color. All color space handling etc. can be kept inside the shaders. --- src/camera.rs | 4 +- src/ray.rs | 5 +- src/renderer.rs | 28 ++++----- src/shading/mod.rs | 51 ++++++++++++---- src/shading/surface_closure.rs | 106 ++++++++++++--------------------- src/surface/triangle_mesh.rs | 44 +++++++------- 6 files changed, 118 insertions(+), 120 deletions(-) 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; }