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.
This commit is contained in:
Nathan Vegdahl 2017-08-03 16:16:36 -07:00
parent 6413308c7c
commit f4d4152543
6 changed files with 118 additions and 120 deletions

View File

@ -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 // Get time-interpolated camera settings
let transform = lerp_slice(self.transforms, time); let transform = lerp_slice(self.transforms, time);
let tfov = lerp_slice(self.tfovs, time); let tfov = lerp_slice(self.tfovs, time);
@ -89,6 +89,6 @@ impl<'a> Camera<'a> {
1.0, 1.0,
).normalized(); ).normalized();
Ray::new(orig * transform, dir * transform, time, false) Ray::new(orig * transform, dir * transform, time, wavelength, false)
} }
} }

View File

@ -15,17 +15,19 @@ pub struct Ray {
pub dir: Vector, pub dir: Vector,
pub max_t: f32, pub max_t: f32,
pub time: f32, pub time: f32,
pub wavelength: f32,
pub flags: u32, pub flags: u32,
} }
impl Ray { 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 { if !is_occ {
Ray { Ray {
orig: orig, orig: orig,
dir: dir, dir: dir,
max_t: std::f32::INFINITY, max_t: std::f32::INFINITY,
time: time, time: time,
wavelength: wavelength,
flags: 0, flags: 0,
} }
} else { } else {
@ -34,6 +36,7 @@ impl Ray {
dir: dir, dir: dir,
max_t: 1.0, max_t: 1.0,
time: time, time: time,
wavelength: wavelength,
flags: OCCLUSION_FLAG, flags: OCCLUSION_FLAG,
} }
} }

View File

@ -412,6 +412,7 @@ impl LightPath {
image_plane_co.0, image_plane_co.0,
image_plane_co.1, image_plane_co.1,
time, time,
wavelength,
lens_uv.0, lens_uv.0,
lens_uv.1, lens_uv.1,
), ),
@ -468,13 +469,8 @@ impl LightPath {
// Check if pdf is zero, to avoid NaN's. // Check if pdf is zero, to avoid NaN's.
if light_pdf > 0.0 { if light_pdf > 0.0 {
let material = closure.as_surface_closure(); let material = closure.as_surface_closure();
let attenuation = material.evaluate( let attenuation =
ray.dir, material.evaluate(ray.dir, shadow_vec, idata.nor, idata.nor_g);
shadow_vec,
idata.nor,
idata.nor_g,
self.wavelength,
);
if attenuation.e.h_max() > 0.0 { if attenuation.e.h_max() > 0.0 {
// Calculate and store the light that will be contributed // Calculate and store the light that will be contributed
@ -491,7 +487,13 @@ impl LightPath {
idata.nor_g.normalized(), idata.nor_g.normalized(),
shadow_vec, 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 // For distant lights
if is_infinite { if is_infinite {
@ -518,13 +520,7 @@ impl LightPath {
let material = closure.as_surface_closure(); let material = closure.as_surface_closure();
let u = self.next_lds_samp(); let u = self.next_lds_samp();
let v = self.next_lds_samp(); let v = self.next_lds_samp();
material.sample( material.sample(idata.incoming, idata.nor, idata.nor_g, (u, v))
idata.incoming,
idata.nor,
idata.nor_g,
(u, v),
self.wavelength,
)
}; };
// Check if pdf is zero, to avoid NaN's. // Check if pdf is zero, to avoid NaN's.
@ -541,7 +537,7 @@ impl LightPath {
dir, dir,
); );
self.next_bounce_ray = 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 true
} else { } else {

View File

@ -2,14 +2,15 @@ pub mod surface_closure;
use std::fmt::Debug; use std::fmt::Debug;
use self::surface_closure::SurfaceClosureUnion; use color::{XYZ, Color};
use self::surface_closure::{SurfaceClosureUnion, EmitClosure, LambertClosure, GTRClosure};
use surface::SurfaceIntersectionData; use surface::SurfaceIntersectionData;
/// Trait for surface shaders. /// Trait for surface shaders.
pub trait SurfaceShader: Debug { pub trait SurfaceShader: Debug {
/// Takes the result of a surface intersection and returns the surface /// Takes the result of a surface intersection and returns the surface
/// closure to be evaluated at that intersection point. /// 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 /// 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 /// them a great injustice, for they are each the size of a small
/// building. /// building.
#[derive(Debug)] #[derive(Debug)]
pub struct SimpleSurfaceShader { pub enum SimpleSurfaceShader {
closure: SurfaceClosureUnion, Emit { color: XYZ },
} Lambert { color: XYZ },
GTR {
impl SimpleSurfaceShader { color: XYZ,
fn new(closure: SurfaceClosureUnion) -> SimpleSurfaceShader { roughness: f32,
SimpleSurfaceShader { closure: closure } tail_shape: f32,
} fresnel: f32,
},
} }
impl SurfaceShader for SimpleSurfaceShader { impl SurfaceShader for SimpleSurfaceShader {
fn shade(&self, data: &SurfaceIntersectionData) -> SurfaceClosureUnion { fn shade(&self, data: &SurfaceIntersectionData, wavelength: f32) -> SurfaceClosureUnion {
let _ = data; // Silence "unused" compiler warning 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,
))
}
}
} }
} }

View File

@ -2,7 +2,7 @@
use std::f32::consts::PI as PI_32; 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 math::{Vector, Normal, dot, clamp, zup_to_vec};
use sampling::cosine_sample_hemisphere; use sampling::cosine_sample_hemisphere;
use lerp::lerp; use lerp::lerp;
@ -29,6 +29,10 @@ impl SurfaceClosureUnion {
} }
/// Trait for surface closures. /// 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 { pub trait SurfaceClosure {
/// Returns whether the closure has a delta distribution or not. /// Returns whether the closure has a delta distribution or not.
fn is_delta(&self) -> bool; fn is_delta(&self) -> bool;
@ -40,7 +44,6 @@ pub trait SurfaceClosure {
/// nor: The shading surface normal at the surface point. /// nor: The shading surface normal at the surface point.
/// nor_g: The geometric surface normal at the surface point. /// nor_g: The geometric surface normal at the surface point.
/// uv: The sampling values. /// 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. /// Returns a tuple with the generated outgoing light direction, color filter, and pdf.
fn sample( fn sample(
@ -49,7 +52,6 @@ pub trait SurfaceClosure {
nor: Normal, nor: Normal,
nor_g: Normal, nor_g: Normal,
uv: (f32, f32), uv: (f32, f32),
wavelength: f32,
) -> (Vector, SpectralSample, f32); ) -> (Vector, SpectralSample, f32);
/// Evaluates the closure for the given incoming and outgoing rays. /// 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. /// wavelength: The wavelength of light to evaluate for.
/// ///
/// Returns the resulting filter color. /// Returns the resulting filter color.
fn evaluate( fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> SpectralSample;
&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' /// Returns the pdf for the given 'in' direction producing the given 'out'
/// direction with the given differential geometry. /// 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! /// behave like a standard closure!
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct EmitClosure { pub struct EmitClosure {
col: XYZ, col: SpectralSample,
} }
impl EmitClosure { impl EmitClosure {
pub fn emitted_color(&self, wavelength: f32) -> SpectralSample { pub fn new(color: SpectralSample) -> EmitClosure {
self.col.to_spectral_sample(wavelength) EmitClosure { col: color }
}
pub fn emitted_color(&self) -> SpectralSample {
self.col
} }
} }
@ -194,28 +193,16 @@ impl SurfaceClosure for EmitClosure {
nor: Normal, nor: Normal,
nor_g: Normal, nor_g: Normal,
uv: (f32, f32), uv: (f32, f32),
wavelength: f32,
) -> (Vector, SpectralSample, f32) { ) -> (Vector, SpectralSample, f32) {
let _ = (inc, nor, nor_g, 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), self.col, 1.0)
Vector::new(0.0, 0.0, 0.0),
SpectralSample::new(wavelength),
1.0,
)
} }
fn evaluate( fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> SpectralSample {
&self,
inc: Vector,
out: Vector,
nor: Normal,
nor_g: Normal,
wavelength: f32,
) -> SpectralSample {
let _ = (inc, out, nor, nor_g); // Not using these, silence warning 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 { 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 /// Lambertian surface closure
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct LambertClosure { pub struct LambertClosure {
col: XYZ, col: SpectralSample,
} }
impl LambertClosure { impl LambertClosure {
pub fn new(col: XYZ) -> LambertClosure { pub fn new(col: SpectralSample) -> LambertClosure {
LambertClosure { col: col } LambertClosure { col: col }
} }
} }
@ -263,7 +250,6 @@ impl SurfaceClosure for LambertClosure {
nor: Normal, nor: Normal,
nor_g: Normal, nor_g: Normal,
uv: (f32, f32), uv: (f32, f32),
wavelength: f32,
) -> (Vector, SpectralSample, f32) { ) -> (Vector, SpectralSample, f32) {
let (nn, flipped_nor_g) = if dot(nor_g.into_vector(), inc) <= 0.0 { let (nn, flipped_nor_g) = if dot(nor_g.into_vector(), inc) <= 0.0 {
(nor.normalized().into_vector(), nor_g.into_vector()) (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. // Make sure it's not on the wrong side of the geometric normal.
if dot(flipped_nor_g, out) >= 0.0 { 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) (out, filter, pdf)
} else { } else {
(out, SpectralSample::from_value(0.0, 0.0), 0.0) (out, SpectralSample::new(0.0), 0.0)
} }
} }
fn evaluate( fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> SpectralSample {
&self,
inc: Vector,
out: Vector,
nor: Normal,
nor_g: Normal,
wavelength: f32,
) -> SpectralSample {
let (nn, flipped_nor_g) = if dot(nor_g.into_vector(), inc) <= 0.0 { let (nn, flipped_nor_g) = if dot(nor_g.into_vector(), inc) <= 0.0 {
(nor.normalized().into_vector(), nor_g.into_vector()) (nor.normalized().into_vector(), nor_g.into_vector())
} else { } else {
@ -302,9 +281,9 @@ impl SurfaceClosure for LambertClosure {
if dot(flipped_nor_g, out) >= 0.0 { if dot(flipped_nor_g, out) >= 0.0 {
let fac = dot(nn, out.normalized()).max(0.0) * INV_PI; let fac = dot(nn, out.normalized()).max(0.0) * INV_PI;
self.col.to_spectral_sample(wavelength) * fac self.col * fac
} else { } 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. /// The GTR microfacet BRDF from the Disney Principled BRDF paper.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct GTRClosure { pub struct GTRClosure {
col: XYZ, col: SpectralSample,
roughness: f32, roughness: f32,
tail_shape: f32, tail_shape: f32,
fresnel: f32, // [0.0, 1.0] determines how much fresnel reflection comes into play fresnel: f32, // [0.0, 1.0] determines how much fresnel reflection comes into play
@ -397,7 +376,7 @@ pub struct GTRClosure {
} }
impl 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 { let mut closure = GTRClosure {
col: col, col: col,
roughness: roughness, roughness: roughness,
@ -494,7 +473,6 @@ impl SurfaceClosure for GTRClosure {
nor: Normal, nor: Normal,
nor_g: Normal, nor_g: Normal,
uv: (f32, f32), uv: (f32, f32),
wavelength: f32,
) -> (Vector, SpectralSample, f32) { ) -> (Vector, SpectralSample, f32) {
// Get normalized surface normal // Get normalized surface normal
let (nn, flipped_nor_g) = if dot(nor_g.into_vector(), inc) <= 0.0 { 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. // Make sure it's not on the wrong side of the geometric normal.
if dot(flipped_nor_g, out) >= 0.0 { 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); let pdf = self.sample_pdf(inc, out, nor, nor_g);
(out, filter, pdf) (out, filter, pdf)
} else { } else {
(out, SpectralSample::from_value(0.0, 0.0), 0.0) (out, SpectralSample::new(0.0), 0.0)
} }
} }
fn evaluate( fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> SpectralSample {
&self,
inc: Vector,
out: Vector,
nor: Normal,
nor_g: Normal,
wavelength: f32,
) -> SpectralSample {
// Calculate needed vectors, normalized // Calculate needed vectors, normalized
let aa = -inc.normalized(); // Vector pointing to where "in" came from let aa = -inc.normalized(); // Vector pointing to where "in" came from
let bb = out.normalized(); // Out let bb = out.normalized(); // Out
@ -546,7 +517,7 @@ impl SurfaceClosure for GTRClosure {
// Make sure everything's on the correct side of the surface // 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 { 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 // Calculate needed dot products
@ -561,30 +532,29 @@ impl SurfaceClosure for GTRClosure {
// Calculate F - Fresnel // Calculate F - Fresnel
let col_f = { let col_f = {
let mut col_f = self.col.to_spectral_sample(wavelength);
let rev_fresnel = 1.0 - self.fresnel; let rev_fresnel = 1.0 - self.fresnel;
let c0 = lerp( let c0 = lerp(
schlick_fresnel_from_fac(col_f.e.get_0(), hb), schlick_fresnel_from_fac(self.col.e.get_0(), hb),
col_f.e.get_0(), self.col.e.get_0(),
rev_fresnel, rev_fresnel,
); );
let c1 = lerp( let c1 = lerp(
schlick_fresnel_from_fac(col_f.e.get_1(), hb), schlick_fresnel_from_fac(self.col.e.get_1(), hb),
col_f.e.get_1(), self.col.e.get_1(),
rev_fresnel, rev_fresnel,
); );
let c2 = lerp( let c2 = lerp(
schlick_fresnel_from_fac(col_f.e.get_2(), hb), schlick_fresnel_from_fac(self.col.e.get_2(), hb),
col_f.e.get_2(), self.col.e.get_2(),
rev_fresnel, rev_fresnel,
); );
let c3 = lerp( let c3 = lerp(
schlick_fresnel_from_fac(col_f.e.get_3(), hb), schlick_fresnel_from_fac(self.col.e.get_3(), hb),
col_f.e.get_3(), self.col.e.get_3(),
rev_fresnel, rev_fresnel,
); );
let mut col_f = self.col;
col_f.e.set_0(c0); col_f.e.set_0(c0);
col_f.e.set_1(c1); col_f.e.set_1(c1);
col_f.e.set_2(c2); col_f.e.set_2(c2);

View File

@ -10,7 +10,7 @@ use fp_utils::fp_gamma;
use lerp::lerp_slice; use lerp::lerp_slice;
use math::{Point, Normal, Matrix4x4, dot, cross}; use math::{Point, Normal, Matrix4x4, dot, cross};
use ray::{Ray, AccelRay}; use ray::{Ray, AccelRay};
use shading::surface_closure::{SurfaceClosureUnion, GTRClosure, LambertClosure}; use shading::{SurfaceShader, SimpleSurfaceShader};
use super::{Surface, SurfaceIntersection, SurfaceIntersectionData}; use super::{Surface, SurfaceIntersection, SurfaceIntersectionData};
use super::triangle; use super::triangle;
@ -235,9 +235,7 @@ impl<'a> Surface for TriangleMesh<'a> {
geo_normal geo_normal
}; };
// Fill in intersection data let intersection_data = SurfaceIntersectionData {
isects[r.id as usize] = SurfaceIntersection::Hit {
intersection_data: SurfaceIntersectionData {
incoming: wr.dir, incoming: wr.dir,
t: t, t: t,
pos: pos, pos: pos,
@ -246,17 +244,21 @@ impl<'a> Surface for TriangleMesh<'a> {
nor_g: geo_normal, nor_g: geo_normal,
uv: (0.0, 0.0), // TODO uv: (0.0, 0.0), // TODO
local_space: mat_space, local_space: mat_space,
}, };
// TODO: get surface closure from surface shader.
closure: SurfaceClosureUnion::LambertClosure( // Fill in intersection data
LambertClosure::new(XYZ::new(0.8, 0.8, 0.8)), isects[r.id as usize] = SurfaceIntersection::Hit {
), intersection_data: intersection_data,
// closure: // TODO: get surface shader from user-defined shader.
// SurfaceClosureUnion::GTRClosure( closure: SimpleSurfaceShader::Lambert {
// GTRClosure::new(XYZ::new(0.8, 0.8, 0.8), color: XYZ::new(0.8, 0.8, 0.8),
// 0.1, }.shade(&intersection_data, wr.wavelength),
// 2.0, // closure: SimpleSurfaceShader::GTR {
// 1.0)), // 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; r.max_t = t;
} }