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
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)
}
}

View File

@ -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,
}
}

View File

@ -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 {

View File

@ -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,
))
}
}
}
}

View File

@ -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);

View File

@ -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;
}