Replaced SurfaceClosure trait with a SurfaceClosure enum.

Also moved surface closures to using Color internally for color
specification.
This commit is contained in:
Nathan Vegdahl 2018-12-27 22:57:44 -08:00
parent e5a12cd498
commit caa4ea3e44
9 changed files with 346 additions and 694 deletions

View File

@ -99,6 +99,29 @@ impl Color {
} }
} }
impl Mul<f32> for Color {
type Output = Self;
fn mul(self, rhs: f32) -> Self {
match self {
Color::XYZ(x, y, z) => Color::XYZ(x * rhs, y * rhs, z * rhs),
Color::Blackbody {
temperature,
factor,
} => Color::Blackbody {
temperature: temperature,
factor: factor * rhs,
},
}
}
}
impl MulAssign<f32> for Color {
fn mul_assign(&mut self, rhs: f32) {
*self = *self * rhs;
}
}
impl Lerp for Color { impl Lerp for Color {
/// Note that this isn't a proper lerp in spectral space. However, /// Note that this isn't a proper lerp in spectral space. However,
/// for our purposes that should be fine: all we care about is that /// for our purposes that should be fine: all we care about is that

View File

@ -11,7 +11,7 @@ use crate::{
spherical_triangle_solid_angle, triangle_surface_area, uniform_sample_spherical_triangle, spherical_triangle_solid_angle, triangle_surface_area, uniform_sample_spherical_triangle,
uniform_sample_triangle, uniform_sample_triangle,
}, },
shading::surface_closure::{EmitClosure, SurfaceClosureUnion}, shading::surface_closure::SurfaceClosure,
shading::SurfaceShader, shading::SurfaceShader,
surface::{triangle, Surface, SurfaceIntersection, SurfaceIntersectionData}, surface::{triangle, Surface, SurfaceIntersection, SurfaceIntersectionData},
}; };
@ -312,10 +312,8 @@ impl<'a> Surface for RectangleLight<'a> {
let closure = { let closure = {
let inv_surface_area = (1.0 / (dim.0 as f64 * dim.1 as f64)) as f32; let inv_surface_area = (1.0 / (dim.0 as f64 * dim.1 as f64)) as f32;
let color = lerp_slice(self.colors, r.time) let color = lerp_slice(self.colors, r.time) * inv_surface_area;
.to_spectral_sample(wr.wavelength) SurfaceClosure::Emit(color)
* inv_surface_area;
SurfaceClosureUnion::EmitClosure(EmitClosure::new(color))
}; };
// Fill in intersection // Fill in intersection

View File

@ -10,7 +10,7 @@ use crate::{
math::{coordinate_system_from_vector, dot, Matrix4x4, Normal, Point, Vector}, math::{coordinate_system_from_vector, dot, Matrix4x4, Normal, Point, Vector},
ray::{AccelRay, Ray}, ray::{AccelRay, Ray},
sampling::{uniform_sample_cone, uniform_sample_cone_pdf, uniform_sample_sphere}, sampling::{uniform_sample_cone, uniform_sample_cone_pdf, uniform_sample_sphere},
shading::surface_closure::{EmitClosure, SurfaceClosureUnion}, shading::surface_closure::SurfaceClosure,
shading::SurfaceShader, shading::SurfaceShader,
surface::{Surface, SurfaceIntersection, SurfaceIntersectionData}, surface::{Surface, SurfaceIntersection, SurfaceIntersectionData},
}; };
@ -322,9 +322,8 @@ impl<'a> Surface for SphereLight<'a> {
let closure = { let closure = {
let inv_surface_area = let inv_surface_area =
(1.0 / (4.0 * PI_64 * radius as f64 * radius as f64)) as f32; (1.0 / (4.0 * PI_64 * radius as f64 * radius as f64)) as f32;
let color = lerp_slice(self.colors, r.time).to_spectral_sample(wr.wavelength) let color = lerp_slice(self.colors, r.time) * inv_surface_area;
* inv_surface_area; SurfaceClosure::Emit(color)
SurfaceClosureUnion::EmitClosure(EmitClosure::new(color))
}; };
// Fill in intersection // Fill in intersection

View File

@ -34,30 +34,6 @@ pub fn parse_surface_shader<'a>(
}; };
let shader = match type_name { let shader = match type_name {
"Emit" => {
let color = if let Some((_, contents, byte_offset)) =
tree.iter_leaf_children_with_type("Color").nth(0)
{
if let IResult::Done(_, color) =
closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes())
{
// TODO: handle color space conversions properly.
// Probably will need a special color type with its
// own parser...?
Color::new_xyz(rec709_e_to_xyz(color))
} else {
// Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError(byte_offset));
}
} else {
return Err(PsyParseError::MissingNode(
tree.byte_offset(),
"Expected a Color field in Emit SurfaceShader.",
));
};
arena.alloc(SimpleSurfaceShader::Emit { color: color })
}
"Lambert" => { "Lambert" => {
let color = if let Some((_, contents, byte_offset)) = let color = if let Some((_, contents, byte_offset)) =
tree.iter_leaf_children_with_type("Color").nth(0) tree.iter_leaf_children_with_type("Color").nth(0)
@ -82,84 +58,6 @@ pub fn parse_surface_shader<'a>(
arena.alloc(SimpleSurfaceShader::Lambert { color: color }) arena.alloc(SimpleSurfaceShader::Lambert { color: color })
} }
"GTR" => {
// Color
let color = if let Some((_, contents, byte_offset)) =
tree.iter_leaf_children_with_type("Color").nth(0)
{
if let IResult::Done(_, color) =
closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes())
{
// TODO: handle color space conversions properly.
// Probably will need a special color type with its
// own parser...?
Color::new_xyz(rec709_e_to_xyz(color))
} else {
// Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError(byte_offset));
}
} else {
return Err(PsyParseError::MissingNode(
tree.byte_offset(),
"Expected a Color field in GTR SurfaceShader.",
));
};
// Roughness
let roughness = if let Some((_, contents, byte_offset)) =
tree.iter_leaf_children_with_type("Roughness").nth(0)
{
if let IResult::Done(_, roughness) = ws_f32(contents.as_bytes()) {
roughness
} else {
return Err(PsyParseError::UnknownError(byte_offset));
}
} else {
return Err(PsyParseError::MissingNode(
tree.byte_offset(),
"Expected a Roughness field in GTR SurfaceShader.",
));
};
// TailShape
let tail_shape = if let Some((_, contents, byte_offset)) =
tree.iter_leaf_children_with_type("TailShape").nth(0)
{
if let IResult::Done(_, tail_shape) = ws_f32(contents.as_bytes()) {
tail_shape
} else {
return Err(PsyParseError::UnknownError(byte_offset));
}
} else {
return Err(PsyParseError::MissingNode(
tree.byte_offset(),
"Expected a TailShape field in GTR SurfaceShader.",
));
};
// Fresnel
let fresnel = if let Some((_, contents, byte_offset)) =
tree.iter_leaf_children_with_type("Fresnel").nth(0)
{
if let IResult::Done(_, fresnel) = ws_f32(contents.as_bytes()) {
fresnel
} else {
return Err(PsyParseError::UnknownError(byte_offset));
}
} else {
return Err(PsyParseError::MissingNode(
tree.byte_offset(),
"Expected a Fresnel field in GTR SurfaceShader.",
));
};
arena.alloc(SimpleSurfaceShader::GTR {
color: color,
roughness: roughness,
tail_shape: tail_shape,
fresnel: fresnel,
})
}
"GGX" => { "GGX" => {
// Color // Color
@ -223,6 +121,31 @@ pub fn parse_surface_shader<'a>(
}) })
} }
"Emit" => {
let color = if let Some((_, contents, byte_offset)) =
tree.iter_leaf_children_with_type("Color").nth(0)
{
if let IResult::Done(_, color) =
closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes())
{
// TODO: handle color space conversions properly.
// Probably will need a special color type with its
// own parser...?
Color::new_xyz(rec709_e_to_xyz(color))
} else {
// Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError(byte_offset));
}
} else {
return Err(PsyParseError::MissingNode(
tree.byte_offset(),
"Expected a Color field in Emit SurfaceShader.",
));
};
arena.alloc(SimpleSurfaceShader::Emit { color: color })
}
_ => unimplemented!(), _ => unimplemented!(),
}; };

View File

@ -14,7 +14,7 @@ use float4::Float4;
use crate::{ use crate::{
accel::{ACCEL_NODE_RAY_TESTS, ACCEL_TRAV_TIME}, accel::{ACCEL_NODE_RAY_TESTS, ACCEL_TRAV_TIME},
algorithm::partition_pair, algorithm::partition_pair,
color::{map_0_1_to_wavelength, Color, SpectralSample, XYZ}, color::{map_0_1_to_wavelength, SpectralSample, XYZ},
fp_utils::robust_ray_origin, fp_utils::robust_ray_origin,
hash::hash_u32, hash::hash_u32,
hilbert, hilbert,
@ -447,14 +447,15 @@ impl LightPath {
// If it's an emission closure, handle specially: // If it's an emission closure, handle specially:
// - Collect light from the emission. // - Collect light from the emission.
// - Terminate the path. // - Terminate the path.
use crate::shading::surface_closure::SurfaceClosureUnion; use crate::shading::surface_closure::SurfaceClosure;
if let SurfaceClosureUnion::EmitClosure(ref clsr) = *closure { if let SurfaceClosure::Emit(color) = *closure {
let color = color.to_spectral_sample(self.wavelength).e;
if let LightPathEvent::CameraRay = self.event { if let LightPathEvent::CameraRay = self.event {
self.color += clsr.emitted_color().e; self.color += color;
} else { } else {
let mis_pdf = let mis_pdf =
power_heuristic(self.closure_sample_pdf, idata.sample_pdf); power_heuristic(self.closure_sample_pdf, idata.sample_pdf);
self.color += clsr.emitted_color().e * self.light_attenuation / mis_pdf; self.color += color * self.light_attenuation / mis_pdf;
}; };
return false; return false;
@ -487,7 +488,6 @@ impl LightPath {
} else { } else {
let light_pdf = light_info.pdf(); let light_pdf = light_info.pdf();
let light_sel_pdf = light_info.selection_pdf(); let light_sel_pdf = light_info.selection_pdf();
let material = closure.as_surface_closure();
// Calculate the shadow ray and surface closure stuff // Calculate the shadow ray and surface closure stuff
let (attenuation, closure_pdf, shadow_ray) = match light_info { let (attenuation, closure_pdf, shadow_ray) = match light_info {
@ -495,8 +495,13 @@ impl LightPath {
// Distant light // Distant light
SceneLightSample::Distant { direction, .. } => { SceneLightSample::Distant { direction, .. } => {
let (attenuation, closure_pdf) = let (attenuation, closure_pdf) = closure.evaluate(
material.evaluate(ray.dir, direction, idata.nor, idata.nor_g); ray.dir,
direction,
idata.nor,
idata.nor_g,
self.wavelength,
);
let mut shadow_ray = { let mut shadow_ray = {
// Calculate the shadow ray for testing if the light is // Calculate the shadow ray for testing if the light is
// in shadow or not. // in shadow or not.
@ -521,8 +526,13 @@ impl LightPath {
// Surface light // Surface light
SceneLightSample::Surface { sample_geo, .. } => { SceneLightSample::Surface { sample_geo, .. } => {
let dir = sample_geo.0 - idata.pos; let dir = sample_geo.0 - idata.pos;
let (attenuation, closure_pdf) = let (attenuation, closure_pdf) = closure.evaluate(
material.evaluate(ray.dir, dir, idata.nor, idata.nor_g); ray.dir,
dir,
idata.nor,
idata.nor_g,
self.wavelength,
);
let shadow_ray = { let shadow_ray = {
// Calculate the shadow ray for testing if the light is // Calculate the shadow ray for testing if the light is
// in shadow or not. // in shadow or not.
@ -572,12 +582,17 @@ impl LightPath {
let do_bounce = if self.bounce_count < 2 { let do_bounce = if self.bounce_count < 2 {
self.bounce_count += 1; self.bounce_count += 1;
// Sample material // Sample closure
let (dir, filter, pdf) = { let (dir, filter, pdf) = {
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(idata.incoming, idata.nor, idata.nor_g, (u, v)) closure.sample(
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.

View File

@ -67,7 +67,7 @@ impl<'a> Assembly<'a> {
idata.pos * sel_xform, idata.pos * sel_xform,
idata.nor * sel_xform, idata.nor * sel_xform,
idata.nor_g * sel_xform, idata.nor_g * sel_xform,
closure.as_surface_closure(), &closure,
time, time,
n, n,
) { ) {

View File

@ -4,20 +4,13 @@ use std::fmt::Debug;
use crate::{color::Color, surface::SurfaceIntersectionData}; use crate::{color::Color, surface::SurfaceIntersectionData};
use self::surface_closure::{ use self::surface_closure::SurfaceClosure;
EmitClosure, GGXClosure, GTRClosure, LambertClosure, SurfaceClosureUnion,
};
/// Trait for surface shaders. /// Trait for surface shaders.
pub trait SurfaceShader: Debug + Sync { pub trait SurfaceShader: Debug + Sync {
/// 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( fn shade(&self, data: &SurfaceIntersectionData, time: f32, wavelength: f32) -> SurfaceClosure;
&self,
data: &SurfaceIntersectionData,
time: f32,
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
@ -39,12 +32,6 @@ pub enum SimpleSurfaceShader {
Lambert { Lambert {
color: Color, color: Color,
}, },
GTR {
color: Color,
roughness: f32,
tail_shape: f32,
fresnel: f32,
},
GGX { GGX {
color: Color, color: Color,
roughness: f32, roughness: f32,
@ -53,41 +40,23 @@ pub enum SimpleSurfaceShader {
} }
impl SurfaceShader for SimpleSurfaceShader { impl SurfaceShader for SimpleSurfaceShader {
fn shade( fn shade(&self, data: &SurfaceIntersectionData, time: f32, wavelength: f32) -> SurfaceClosure {
&self,
data: &SurfaceIntersectionData,
time: f32,
wavelength: f32,
) -> SurfaceClosureUnion {
let _ = (data, time); // Silence "unused" compiler warning let _ = (data, time); // Silence "unused" compiler warning
match *self { match *self {
SimpleSurfaceShader::Emit { color } => SurfaceClosureUnion::EmitClosure( SimpleSurfaceShader::Emit { color } => SurfaceClosure::Emit(color),
EmitClosure::new(color.to_spectral_sample(wavelength)),
), SimpleSurfaceShader::Lambert { color } => SurfaceClosure::Lambert(color),
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,
)),
SimpleSurfaceShader::GGX { SimpleSurfaceShader::GGX {
color, color,
roughness, roughness,
fresnel, fresnel,
} => SurfaceClosureUnion::GGXClosure(GGXClosure::new( } => SurfaceClosure::GGX {
color.to_spectral_sample(wavelength), color: color,
roughness, roughness: roughness,
fresnel, fresnel: fresnel,
)), },
} }
} }
} }

View File

@ -2,8 +2,10 @@
use std::f32::consts::PI as PI_32; use std::f32::consts::PI as PI_32;
use float4::Float4;
use crate::{ use crate::{
color::SpectralSample, color::{Color, SpectralSample},
lerp::lerp, lerp::lerp,
math::{clamp, dot, zup_to_vec, Normal, Vector}, math::{clamp, dot, zup_to_vec, Normal, Vector},
sampling::cosine_sample_hemisphere, sampling::cosine_sample_hemisphere,
@ -12,71 +14,97 @@ use crate::{
const INV_PI: f32 = 1.0 / PI_32; const INV_PI: f32 = 1.0 / PI_32;
const H_PI: f32 = PI_32 / 2.0; const H_PI: f32 = PI_32 / 2.0;
/// A surface closure, specifying a BSDF for a point on a surface.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum SurfaceClosureUnion { pub enum SurfaceClosure {
EmitClosure(EmitClosure), // Normal surface closures.
LambertClosure(LambertClosure), Lambert(Color),
GTRClosure(GTRClosure), GGX {
GGXClosure(GGXClosure), color: Color,
roughness: f32,
fresnel: f32, // [0.0, 1.0] determines how much fresnel reflection comes into play
},
// Special closures that need special handling by the renderer.
Emit(Color),
} }
impl SurfaceClosureUnion { use self::SurfaceClosure::*;
pub fn as_surface_closure(&self) -> &SurfaceClosure {
/// Note when implementing new BSDFs: both the the color filter and pdf returned from
/// `sample()` and `evaluate()` should be identical for the same parameters and outgoing
/// light direction.
impl SurfaceClosure {
/// Returns whether the closure has a delta distribution or not.
pub fn is_delta(&self) -> bool {
match *self { match *self {
SurfaceClosureUnion::EmitClosure(ref closure) => closure as &SurfaceClosure, Lambert(_) => false,
SurfaceClosureUnion::LambertClosure(ref closure) => closure as &SurfaceClosure, GGX { roughness, .. } => roughness == 0.0,
SurfaceClosureUnion::GTRClosure(ref closure) => closure as &SurfaceClosure, Emit(_) => false,
SurfaceClosureUnion::GGXClosure(ref closure) => closure as &SurfaceClosure,
} }
} }
}
/// 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.
///
/// Also important is that _both_ the color filter and pdf returned from
/// `sample()` and `evaluate()` should be identical for the same parameters
/// and outgoing light direction.
pub trait SurfaceClosure {
/// Returns whether the closure has a delta distribution or not.
fn is_delta(&self) -> bool;
/// Given an incoming ray and sample values, generates an outgoing ray and /// Given an incoming ray and sample values, generates an outgoing ray and
/// color filter. /// color filter.
/// ///
/// inc: Incoming light direction. /// inc: Incoming light direction.
/// 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: Hero wavelength to generate the color filter for.
/// ///
/// 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( pub fn sample(
&self, &self,
inc: Vector, inc: Vector,
nor: Normal, nor: Normal,
nor_g: Normal, nor_g: Normal,
uv: (f32, f32), uv: (f32, f32),
) -> (Vector, SpectralSample, f32); wavelength: f32,
) -> (Vector, SpectralSample, f32) {
match *self {
Lambert(color) => lambert_closure::sample(color, inc, nor, nor_g, uv, wavelength),
GGX {
color,
roughness,
fresnel,
} => ggx_closure::sample(color, roughness, fresnel, inc, nor, nor_g, uv, wavelength),
Emit(color) => emit_closure::sample(color, inc, nor, nor_g, uv, wavelength),
}
}
/// Evaluates the closure for the given incoming and outgoing rays. /// Evaluates the closure for the given incoming and outgoing rays.
/// ///
/// inc: The incoming light direction. /// inc: The incoming light direction.
/// out: The outgoing light direction. /// out: The outgoing light direction.
/// 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.
/// wavelength: Hero wavelength to generate the color filter for.
/// ///
/// Returns the resulting filter color and pdf of if this had been generated /// Returns the resulting filter color and pdf of if this had been generated
/// by `sample()`. /// by `sample()`.
fn evaluate( pub fn evaluate(
&self, &self,
inc: Vector, inc: Vector,
out: Vector, out: Vector,
nor: Normal, nor: Normal,
nor_g: Normal, nor_g: Normal,
) -> (SpectralSample, f32); wavelength: f32,
) -> (SpectralSample, f32) {
match *self {
Lambert(color) => lambert_closure::evaluate(color, inc, out, nor, nor_g, wavelength),
GGX {
color,
roughness,
fresnel,
} => ggx_closure::evaluate(color, roughness, fresnel, inc, out, nor, nor_g, wavelength),
Emit(color) => emit_closure::evaluate(color, inc, out, nor, nor_g, wavelength),
}
}
/// Returns an estimate of the sum total energy that evaluate() would return /// Returns an estimate of the sum total energy that evaluate() would return
/// when integrated over a spherical light source with a center at relative /// when integrated over a spherical light source with a center at relative
@ -84,134 +112,7 @@ pub trait SurfaceClosure {
/// This is used for importance sampling, so does not need to be exact, /// This is used for importance sampling, so does not need to be exact,
/// but it does need to be non-zero anywhere that an exact solution would /// but it does need to be non-zero anywhere that an exact solution would
/// be non-zero. /// be non-zero.
fn estimate_eval_over_sphere_light( pub fn estimate_eval_over_sphere_light(
&self,
inc: Vector,
to_light_center: Vector,
light_radius_squared: f32,
nor: Normal,
nor_g: Normal,
) -> f32;
}
/// Utility function that calculates the fresnel reflection factor of a given
/// incoming ray against a surface with the given ior outside/inside ratio.
///
/// `ior_ratio`: The ratio of the outside material ior (probably 1.0 for air)
/// over the inside ior.
/// `c`: The cosine of the angle between the incoming light and the
/// surface's normal. Probably calculated e.g. with a normalized
/// dot product.
#[allow(dead_code)]
fn dielectric_fresnel(ior_ratio: f32, c: f32) -> f32 {
let g = (ior_ratio - 1.0 + (c * c)).sqrt();
let f1 = g - c;
let f2 = g + c;
let f3 = (f1 * f1) / (f2 * f2);
let f4 = (c * f2) - 1.0;
let f5 = (c * f1) + 1.0;
let f6 = 1.0 + ((f4 * f4) / (f5 * f5));
0.5 * f3 * f6
}
/// Schlick's approximation of the fresnel reflection factor.
///
/// Same interface as `dielectric_fresnel()`, above.
#[allow(dead_code)]
fn schlick_fresnel(ior_ratio: f32, c: f32) -> f32 {
let f1 = (1.0 - ior_ratio) / (1.0 + ior_ratio);
let f2 = f1 * f1;
let c1 = 1.0 - c;
let c2 = c1 * c1;
f2 + ((1.0 - f2) * c1 * c2 * c2)
}
/// Utility function that calculates the fresnel reflection factor of a given
/// incoming ray against a surface with the given normal-reflectance factor.
///
/// `frensel_fac`: The ratio of light reflected back if the ray were to
/// hit the surface head-on (perpendicular to the surface).
/// `c`: The cosine of the angle between the incoming light and the
/// surface's normal. Probably calculated e.g. with a normalized
/// dot product.
#[allow(dead_code)]
fn dielectric_fresnel_from_fac(fresnel_fac: f32, c: f32) -> f32 {
let tmp1 = fresnel_fac.sqrt() - 1.0;
// Protect against divide by zero.
if tmp1.abs() < 0.000_001 {
return 1.0;
}
// Find the ior ratio
let tmp2 = (-2.0 / tmp1) - 1.0;
let ior_ratio = tmp2 * tmp2;
// Calculate fresnel factor
dielectric_fresnel(ior_ratio, c)
}
/// Schlick's approximation version of `dielectric_fresnel_from_fac()` above.
#[allow(dead_code)]
fn schlick_fresnel_from_fac(frensel_fac: f32, c: f32) -> f32 {
let c1 = 1.0 - c;
let c2 = c1 * c1;
frensel_fac + ((1.0 - frensel_fac) * c1 * c2 * c2)
}
/// Emit closure.
///
/// NOTE: this needs to be handled specially by the integrator! It does not
/// behave like a standard closure!
#[derive(Debug, Copy, Clone)]
pub struct EmitClosure {
col: SpectralSample,
}
impl EmitClosure {
pub fn new(color: SpectralSample) -> EmitClosure {
EmitClosure { col: color }
}
pub fn emitted_color(&self) -> SpectralSample {
self.col
}
}
impl SurfaceClosure for EmitClosure {
fn is_delta(&self) -> bool {
false
}
fn sample(
&self,
inc: Vector,
nor: Normal,
nor_g: Normal,
uv: (f32, f32),
) -> (Vector, SpectralSample, f32) {
let _ = (inc, nor, nor_g, uv); // Not using these, silence warning
(Vector::new(0.0, 0.0, 0.0), self.col, 1.0)
}
fn evaluate(
&self,
inc: Vector,
out: Vector,
nor: Normal,
nor_g: Normal,
) -> (SpectralSample, f32) {
let _ = (inc, out, nor, nor_g); // Not using these, silence warning
(self.col, 1.0)
}
fn estimate_eval_over_sphere_light(
&self, &self,
inc: Vector, inc: Vector,
to_light_center: Vector, to_light_center: Vector,
@ -219,37 +120,52 @@ impl SurfaceClosure for EmitClosure {
nor: Normal, nor: Normal,
nor_g: Normal, nor_g: Normal,
) -> f32 { ) -> f32 {
// Not using these, silence warning match *self {
let _ = (inc, to_light_center, light_radius_squared, nor, nor_g); Lambert(color) => lambert_closure::estimate_eval_over_sphere_light(
color,
// TODO: what to do here? inc,
unimplemented!() to_light_center,
light_radius_squared,
nor,
nor_g,
),
GGX {
color,
roughness,
fresnel,
} => ggx_closure::estimate_eval_over_sphere_light(
color,
roughness,
fresnel,
inc,
to_light_center,
light_radius_squared,
nor,
nor_g,
),
Emit(color) => emit_closure::estimate_eval_over_sphere_light(
color,
inc,
to_light_center,
light_radius_squared,
nor,
nor_g,
),
}
} }
} }
/// Lambertian surface closure /// Lambert closure code.
#[derive(Debug, Copy, Clone)] mod lambert_closure {
pub struct LambertClosure { use super::*;
col: SpectralSample,
}
impl LambertClosure { pub fn sample(
pub fn new(col: SpectralSample) -> LambertClosure { color: Color,
LambertClosure { col: col }
}
}
impl SurfaceClosure for LambertClosure {
fn is_delta(&self) -> bool {
false
}
fn sample(
&self,
inc: Vector, inc: Vector,
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())
@ -265,18 +181,19 @@ 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 {
(out, self.col * pdf, pdf) (out, color.to_spectral_sample(wavelength) * pdf, pdf)
} else { } else {
(out, SpectralSample::new(0.0), 0.0) (out, SpectralSample::new(0.0), 0.0)
} }
} }
fn evaluate( pub fn evaluate(
&self, color: Color,
inc: Vector, inc: Vector,
out: Vector, out: Vector,
nor: Normal, nor: Normal,
nor_g: Normal, nor_g: Normal,
wavelength: f32,
) -> (SpectralSample, f32) { ) -> (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())
@ -286,14 +203,14 @@ 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 * fac, fac) (color.to_spectral_sample(wavelength) * fac, fac)
} else { } else {
(SpectralSample::new(0.0), 0.0) (SpectralSample::new(0.0), 0.0)
} }
} }
fn estimate_eval_over_sphere_light( pub fn estimate_eval_over_sphere_light(
&self, color: Color,
inc: Vector, inc: Vector,
to_light_center: Vector, to_light_center: Vector,
light_radius_squared: f32, light_radius_squared: f32,
@ -369,113 +286,24 @@ impl SurfaceClosure for LambertClosure {
} }
} }
/// The GTR microfacet BRDF from the Disney Principled BRDF paper. mod ggx_closure {
#[derive(Debug, Copy, Clone)] use super::*;
pub struct GTRClosure {
col: SpectralSample,
roughness: f32,
tail_shape: f32,
fresnel: f32, // [0.0, 1.0] determines how much fresnel reflection comes into play
normalization_factor: f32,
}
impl GTRClosure {
pub fn new(col: SpectralSample, roughness: f32, tail_shape: f32, fresnel: f32) -> GTRClosure {
let mut closure = GTRClosure {
col: col,
roughness: roughness,
tail_shape: tail_shape,
fresnel: fresnel,
normalization_factor: GTRClosure::normalization(roughness, tail_shape),
};
closure.validate();
closure
}
// Returns the normalization factor for the distribution function
// of the BRDF.
fn normalization(r: f32, t: f32) -> f32 {
let r2 = r * r;
let top = (t - 1.0) * (r2 - 1.0);
let bottom = PI_32 * (1.0 - r2.powf(1.0 - t));
top / bottom
}
// Makes sure values are in a valid range // Makes sure values are in a valid range
fn validate(&mut self) { pub fn validate(roughness: f32, fresnel: f32) {
debug_assert!(self.fresnel >= 0.0 && self.fresnel <= 1.0); debug_assert!(fresnel >= 0.0 && fresnel <= 1.0);
debug_assert!(roughness >= 0.0 && roughness <= 1.0);
// Clamp values to valid ranges
self.roughness = clamp(self.roughness, 0.0, 0.9999);
self.tail_shape = (0.0001f32).max(self.tail_shape);
// When roughness is too small, but not zero, there are floating point accuracy issues
if self.roughness < 0.000_244_140_625 {
// (2^-12)
self.roughness = 0.0;
}
// If tail_shape is too near 1.0, push it away a tiny bit.
// This avoids having to have a special form of various equations
// due to a singularity at tail_shape = 1.0
// That in turn avoids some branches in the code, and the effect of
// tail_shape is sufficiently subtle that there is no visible
// difference in renders.
const TAIL_EPSILON: f32 = 0.0001;
if (self.tail_shape - 1.0).abs() < TAIL_EPSILON {
self.tail_shape = 1.0 + TAIL_EPSILON;
}
// Precalculate normalization factor
self.normalization_factor = GTRClosure::normalization(self.roughness, self.tail_shape);
} }
// Returns the cosine of the half-angle that should be sampled, given pub fn sample(
// a random variable in [0,1] col: Color,
fn half_theta_sample(&self, u: f32) -> f32 { roughness: f32,
let roughness2 = self.roughness * self.roughness; fresnel: f32,
// Calculate top half of equation
let top = 1.0
- ((roughness2.powf(1.0 - self.tail_shape) * (1.0 - u)) + u)
.powf(1.0 / (1.0 - self.tail_shape));
// Calculate bottom half of equation
let bottom = 1.0 - roughness2;
(top / bottom).sqrt()
}
/// Microfacet distribution function.
///
/// nh: cosine of the angle between the surface normal and the microfacet normal.
fn dist(&self, nh: f32, rough: f32) -> f32 {
// Other useful numbers
let roughness2 = rough * rough;
// Calculate D - Distribution
if nh <= 0.0 {
0.0
} else {
let nh2 = nh * nh;
self.normalization_factor / (1.0 + ((roughness2 - 1.0) * nh2)).powf(self.tail_shape)
}
}
}
impl SurfaceClosure for GTRClosure {
fn is_delta(&self) -> bool {
self.roughness == 0.0
}
fn sample(
&self,
inc: Vector, inc: Vector,
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 {
@ -486,7 +314,7 @@ impl SurfaceClosure for GTRClosure {
// Generate a random ray direction in the hemisphere // Generate a random ray direction in the hemisphere
// of the surface. // of the surface.
let theta_cos = self.half_theta_sample(uv.0); let theta_cos = half_theta_sample(uv.0, roughness);
let theta_sin = (1.0 - (theta_cos * theta_cos)).sqrt(); let theta_sin = (1.0 - (theta_cos * theta_cos)).sqrt();
let angle = uv.1 * PI_32 * 2.0; let angle = uv.1 * PI_32 * 2.0;
let mut half_dir = Vector::new(angle.cos() * theta_sin, angle.sin() * theta_sin, theta_cos); let mut half_dir = Vector::new(angle.cos() * theta_sin, angle.sin() * theta_sin, theta_cos);
@ -496,19 +324,22 @@ 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, pdf) = self.evaluate(inc, out, nor, nor_g); let (filter, pdf) = evaluate(col, roughness, fresnel, inc, out, nor, nor_g, wavelength);
(out, filter, pdf) (out, filter, pdf)
} else { } else {
(out, SpectralSample::new(0.0), 0.0) (out, SpectralSample::new(0.0), 0.0)
} }
} }
fn evaluate( pub fn evaluate(
&self, col: Color,
roughness: f32,
fresnel: f32,
inc: Vector, inc: Vector,
out: Vector, out: Vector,
nor: Normal, nor: Normal,
nor_g: Normal, nor_g: Normal,
wavelength: f32,
) -> (SpectralSample, f32) { ) -> (SpectralSample, f32) {
// 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
@ -534,77 +365,55 @@ impl SurfaceClosure for GTRClosure {
let hb = clamp(dot(hh, bb), -1.0, 1.0); let hb = clamp(dot(hh, bb), -1.0, 1.0);
let nh = clamp(dot(nn, hh), -1.0, 1.0); let nh = clamp(dot(nn, hh), -1.0, 1.0);
// Other useful numbers
let roughness2 = self.roughness * self.roughness;
// Calculate F - Fresnel // Calculate F - Fresnel
let col_f = { let col_f = {
let rev_fresnel = 1.0 - self.fresnel; let spectrum_sample = col.to_spectral_sample(wavelength);
let rev_fresnel = 1.0 - fresnel;
let c0 = lerp( let c0 = lerp(
schlick_fresnel_from_fac(self.col.e.get_0(), hb), schlick_fresnel_from_fac(spectrum_sample.e.get_0(), hb),
self.col.e.get_0(), spectrum_sample.e.get_0(),
rev_fresnel, rev_fresnel,
); );
let c1 = lerp( let c1 = lerp(
schlick_fresnel_from_fac(self.col.e.get_1(), hb), schlick_fresnel_from_fac(spectrum_sample.e.get_1(), hb),
self.col.e.get_1(), spectrum_sample.e.get_1(),
rev_fresnel, rev_fresnel,
); );
let c2 = lerp( let c2 = lerp(
schlick_fresnel_from_fac(self.col.e.get_2(), hb), schlick_fresnel_from_fac(spectrum_sample.e.get_2(), hb),
self.col.e.get_2(), spectrum_sample.e.get_2(),
rev_fresnel, rev_fresnel,
); );
let c3 = lerp( let c3 = lerp(
schlick_fresnel_from_fac(self.col.e.get_3(), hb), schlick_fresnel_from_fac(spectrum_sample.e.get_3(), hb),
self.col.e.get_3(), spectrum_sample.e.get_3(),
rev_fresnel, rev_fresnel,
); );
let mut col_f = self.col; SpectralSample::from_parts(Float4::new(c0, c1, c2, c3), wavelength)
col_f.e.set_0(c0);
col_f.e.set_1(c1);
col_f.e.set_2(c2);
col_f.e.set_3(c3);
col_f
}; };
// Calculate everything else // Calculate everything else
if self.roughness == 0.0 { if roughness == 0.0 {
// If sharp mirror, just return col * fresnel factor // If sharp mirror, just return col * fresnel factor
return (col_f, 0.0); return (col_f, 0.0);
} else { } else {
// Calculate D - Distribution // Calculate D - Distribution
let dist = self.dist(nh, self.roughness) / na; let dist = ggx_d(nh, roughness) / na;
// Calculate G1 - Geometric microfacet shadowing // Calculate G1 and G2- Geometric microfacet shadowing
let g1 = { let g1 = ggx_g(ha, na, roughness);
let na2 = na * na; let g2 = ggx_g(hb, nb, roughness);
let tan_na = ((1.0 - na2) / na2).sqrt();
let g1_pos_char = if (ha * na) > 0.0 { 1.0 } else { 0.0 };
let g1_a = roughness2 * tan_na;
let g1_b = ((1.0 + (g1_a * g1_a)).sqrt() - 1.0) * 0.5;
g1_pos_char / (1.0 + g1_b)
};
// Calculate G2 - Geometric microfacet shadowing
let g2 = {
let nb2 = nb * nb;
let tan_nb = ((1.0 - nb2) / nb2).sqrt();
let g2_pos_char = if (hb * nb) > 0.0 { 1.0 } else { 0.0 };
let g2_a = roughness2 * tan_nb;
let g2_b = ((1.0 + (g2_a * g2_a)).sqrt() - 1.0) * 0.5;
g2_pos_char / (1.0 + g2_b)
};
// Final result // Final result
(col_f * (dist * g1 * g2) * INV_PI, dist * INV_PI) (col_f * (dist * g1 * g2) * INV_PI, dist * INV_PI)
} }
} }
fn estimate_eval_over_sphere_light( pub fn estimate_eval_over_sphere_light(
&self, col: Color,
roughness: f32,
fresnel: f32,
inc: Vector, inc: Vector,
to_light_center: Vector, to_light_center: Vector,
light_radius_squared: f32, light_radius_squared: f32,
@ -645,7 +454,7 @@ impl SurfaceClosure for GTRClosure {
// samp = zup_to_vec(samp, bb).normalized(); // samp = zup_to_vec(samp, bb).normalized();
// if dot(nn, samp) > 0.0 { // if dot(nn, samp) > 0.0 {
// let hh = (aa+samp).normalized(); // let hh = (aa+samp).normalized();
// fac += self.dist(dot(nn, hh), roughness); // fac += ggx_d(dot(nn, hh), roughness);
// } // }
//} //}
//fac /= N * N; //fac /= N * N;
@ -654,41 +463,12 @@ impl SurfaceClosure for GTRClosure {
let theta = cos_theta_max.acos(); let theta = cos_theta_max.acos();
let hh = (aa + bb).normalized(); let hh = (aa + bb).normalized();
let nh = clamp(dot(nn, hh), -1.0, 1.0); let nh = clamp(dot(nn, hh), -1.0, 1.0);
let fac = self.dist( let fac = ggx_d(nh, (1.0f32).min(roughness.sqrt() + (2.0 * theta / PI_32)));
nh,
(1.0f32).min(self.roughness.sqrt() + (2.0 * theta / PI_32)),
);
fac * (1.0f32).min(1.0 - cos_theta_max) * INV_PI fac * (1.0f32).min(1.0 - cos_theta_max) * INV_PI
} }
}
/// The GGX microfacet BRDF. //----------------------------------------------------
#[derive(Debug, Copy, Clone)]
pub struct GGXClosure {
col: SpectralSample,
roughness: f32,
fresnel: f32, // [0.0, 1.0] determines how much fresnel reflection comes into play
}
impl GGXClosure {
pub fn new(col: SpectralSample, roughness: f32, fresnel: f32) -> GGXClosure {
let mut closure = GGXClosure {
col: col,
roughness: roughness,
fresnel: fresnel,
};
closure.validate();
closure
}
// Makes sure values are in a valid range
fn validate(&mut self) {
debug_assert!(self.fresnel >= 0.0 && self.fresnel <= 1.0);
debug_assert!(self.roughness >= 0.0 && self.roughness <= 1.0);
}
// Returns the cosine of the half-angle that should be sampled, given // Returns the cosine of the half-angle that should be sampled, given
// a random variable in [0,1] // a random variable in [0,1]
@ -730,181 +510,126 @@ impl GGXClosure {
} }
} }
impl SurfaceClosure for GGXClosure { /// Emit closure code.
fn is_delta(&self) -> bool { ///
self.roughness == 0.0 /// NOTE: this needs to be handled specially by the integrator! It does not
} /// behave like a standard closure!
mod emit_closure {
use super::*;
fn sample( pub fn sample(
&self, color: Color,
inc: Vector, inc: Vector,
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 let _ = (inc, nor, nor_g, uv); // Not using these, silence warning
let (nn, flipped_nor_g) = if dot(nor_g.into_vector(), inc) <= 0.0 {
(nor.normalized().into_vector(), nor_g.into_vector())
} else {
(-nor.normalized().into_vector(), -nor_g.into_vector())
};
// Generate a random ray direction in the hemisphere (
// of the surface. Vector::new(0.0, 0.0, 0.0),
let theta_cos = Self::half_theta_sample(uv.0, self.roughness); color.to_spectral_sample(wavelength),
let theta_sin = (1.0 - (theta_cos * theta_cos)).sqrt(); 1.0,
let angle = uv.1 * PI_32 * 2.0; )
let mut half_dir = Vector::new(angle.cos() * theta_sin, angle.sin() * theta_sin, theta_cos);
half_dir = zup_to_vec(half_dir, nn).normalized();
let out = inc - (half_dir * 2.0 * dot(inc, half_dir));
// Make sure it's not on the wrong side of the geometric normal.
if dot(flipped_nor_g, out) >= 0.0 {
let (filter, pdf) = self.evaluate(inc, out, nor, nor_g);
(out, filter, pdf)
} else {
(out, SpectralSample::new(0.0), 0.0)
}
} }
fn evaluate( pub fn evaluate(
&self, color: Color,
inc: Vector, inc: Vector,
out: Vector, out: Vector,
nor: Normal, nor: Normal,
nor_g: Normal, nor_g: Normal,
wavelength: f32,
) -> (SpectralSample, f32) { ) -> (SpectralSample, f32) {
// Calculate needed vectors, normalized let _ = (inc, out, nor, nor_g); // Not using these, silence warning
let aa = -inc.normalized(); // Vector pointing to where "in" came from
let bb = out.normalized(); // Out
let hh = (aa + bb).normalized(); // Half-way between aa and bb
// Surface normal (color.to_spectral_sample(wavelength), 1.0)
let (nn, flipped_nor_g) = if dot(nor_g.into_vector(), inc) <= 0.0 {
(nor.normalized().into_vector(), nor_g.into_vector())
} else {
(-nor.normalized().into_vector(), -nor_g.into_vector())
};
// 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::new(0.0), 0.0);
}
// Calculate needed dot products
let na = clamp(dot(nn, aa), -1.0, 1.0);
let nb = clamp(dot(nn, bb), -1.0, 1.0);
let ha = clamp(dot(hh, aa), -1.0, 1.0);
let hb = clamp(dot(hh, bb), -1.0, 1.0);
let nh = clamp(dot(nn, hh), -1.0, 1.0);
// Calculate F - Fresnel
let col_f = {
let rev_fresnel = 1.0 - self.fresnel;
let c0 = lerp(
schlick_fresnel_from_fac(self.col.e.get_0(), hb),
self.col.e.get_0(),
rev_fresnel,
);
let c1 = lerp(
schlick_fresnel_from_fac(self.col.e.get_1(), hb),
self.col.e.get_1(),
rev_fresnel,
);
let c2 = lerp(
schlick_fresnel_from_fac(self.col.e.get_2(), hb),
self.col.e.get_2(),
rev_fresnel,
);
let c3 = lerp(
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);
col_f.e.set_3(c3);
col_f
};
// Calculate everything else
if self.roughness == 0.0 {
// If sharp mirror, just return col * fresnel factor
return (col_f, 0.0);
} else {
// Calculate D - Distribution
let dist = Self::ggx_d(nh, self.roughness) / na;
// Calculate G1 and G2- Geometric microfacet shadowing
let g1 = Self::ggx_g(ha, na, self.roughness);
let g2 = Self::ggx_g(hb, nb, self.roughness);
// Final result
(col_f * (dist * g1 * g2) * INV_PI, dist * INV_PI)
}
} }
fn estimate_eval_over_sphere_light( pub fn estimate_eval_over_sphere_light(
&self, color: Color,
inc: Vector, inc: Vector,
to_light_center: Vector, to_light_center: Vector,
light_radius_squared: f32, light_radius_squared: f32,
nor: Normal, nor: Normal,
nor_g: Normal, nor_g: Normal,
) -> f32 { ) -> f32 {
// TODO: all of the stuff in this function is horribly hacky. // Not using these, silence warning
// Find a proper way to approximate the light contribution from a let _ = (inc, to_light_center, light_radius_squared, nor, nor_g);
// solid angle.
let _ = nor_g; // Not using this, silence warning // TODO: what to do here?
unimplemented!()
let dist2 = to_light_center.length2();
let sin_theta_max2 = (light_radius_squared / dist2).min(1.0);
let cos_theta_max = (1.0 - sin_theta_max2).sqrt();
assert!(cos_theta_max >= -1.0);
assert!(cos_theta_max <= 1.0);
// Surface normal
let nn = if dot(nor.into_vector(), inc) < 0.0 {
nor.normalized()
} else {
-nor.normalized() // If back-facing, flip normal
}
.into_vector();
let aa = -inc.normalized(); // Vector pointing to where "in" came from
let bb = to_light_center.normalized(); // Out
// Brute-force method
//let mut fac = 0.0;
//const N: usize = 256;
//for i in 0..N {
// let uu = Halton::sample(0, i);
// let vv = Halton::sample(1, i);
// let mut samp = uniform_sample_cone(uu, vv, cos_theta_max);
// samp = zup_to_vec(samp, bb).normalized();
// if dot(nn, samp) > 0.0 {
// let hh = (aa+samp).normalized();
// fac += self.dist(dot(nn, hh), roughness);
// }
//}
//fac /= N * N;
// Approximate method
let theta = cos_theta_max.acos();
let hh = (aa + bb).normalized();
let nh = clamp(dot(nn, hh), -1.0, 1.0);
let fac = Self::ggx_d(
nh,
(1.0f32).min(self.roughness.sqrt() + (2.0 * theta / PI_32)),
);
fac * (1.0f32).min(1.0 - cos_theta_max) * INV_PI
} }
} }
//=============================================================================
/// Utility function that calculates the fresnel reflection factor of a given
/// incoming ray against a surface with the given normal-reflectance factor.
///
/// `frensel_fac`: The ratio of light reflected back if the ray were to
/// hit the surface head-on (perpendicular to the surface).
/// `c`: The cosine of the angle between the incoming light and the
/// surface's normal. Probably calculated e.g. with a normalized
/// dot product.
#[allow(dead_code)]
fn dielectric_fresnel_from_fac(fresnel_fac: f32, c: f32) -> f32 {
let tmp1 = fresnel_fac.sqrt() - 1.0;
// Protect against divide by zero.
if tmp1.abs() < 0.000_001 {
return 1.0;
}
// Find the ior ratio
let tmp2 = (-2.0 / tmp1) - 1.0;
let ior_ratio = tmp2 * tmp2;
// Calculate fresnel factor
dielectric_fresnel(ior_ratio, c)
}
/// Schlick's approximation version of `dielectric_fresnel_from_fac()` above.
#[allow(dead_code)]
fn schlick_fresnel_from_fac(frensel_fac: f32, c: f32) -> f32 {
let c1 = 1.0 - c;
let c2 = c1 * c1;
frensel_fac + ((1.0 - frensel_fac) * c1 * c2 * c2)
}
/// Utility function that calculates the fresnel reflection factor of a given
/// incoming ray against a surface with the given ior outside/inside ratio.
///
/// `ior_ratio`: The ratio of the outside material ior (probably 1.0 for air)
/// over the inside ior.
/// `c`: The cosine of the angle between the incoming light and the
/// surface's normal. Probably calculated e.g. with a normalized
/// dot product.
#[allow(dead_code)]
fn dielectric_fresnel(ior_ratio: f32, c: f32) -> f32 {
let g = (ior_ratio - 1.0 + (c * c)).sqrt();
let f1 = g - c;
let f2 = g + c;
let f3 = (f1 * f1) / (f2 * f2);
let f4 = (c * f2) - 1.0;
let f5 = (c * f1) + 1.0;
let f6 = 1.0 + ((f4 * f4) / (f5 * f5));
0.5 * f3 * f6
}
/// Schlick's approximation of the fresnel reflection factor.
///
/// Same interface as `dielectric_fresnel()`, above.
#[allow(dead_code)]
fn schlick_fresnel(ior_ratio: f32, c: f32) -> f32 {
let f1 = (1.0 - ior_ratio) / (1.0 + ior_ratio);
let f2 = f1 * f1;
let c1 = 1.0 - c;
let c2 = c1 * c1;
f2 + ((1.0 - f2) * c1 * c2 * c2)
}

View File

@ -9,7 +9,7 @@ use crate::{
boundable::Boundable, boundable::Boundable,
math::{Matrix4x4, Normal, Point, Vector}, math::{Matrix4x4, Normal, Point, Vector},
ray::{AccelRay, Ray}, ray::{AccelRay, Ray},
shading::surface_closure::SurfaceClosureUnion, shading::surface_closure::SurfaceClosure,
shading::SurfaceShader, shading::SurfaceShader,
}; };
@ -31,7 +31,7 @@ pub enum SurfaceIntersection {
Occlude, Occlude,
Hit { Hit {
intersection_data: SurfaceIntersectionData, intersection_data: SurfaceIntersectionData,
closure: SurfaceClosureUnion, closure: SurfaceClosure,
}, },
} }