Replaced SurfaceClosure trait with a SurfaceClosure enum.
Also moved surface closures to using Color internally for color specification.
This commit is contained in:
parent
e5a12cd498
commit
caa4ea3e44
23
src/color.rs
23
src/color.rs
|
@ -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 {
|
||||
/// 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
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::{
|
|||
spherical_triangle_solid_angle, triangle_surface_area, uniform_sample_spherical_triangle,
|
||||
uniform_sample_triangle,
|
||||
},
|
||||
shading::surface_closure::{EmitClosure, SurfaceClosureUnion},
|
||||
shading::surface_closure::SurfaceClosure,
|
||||
shading::SurfaceShader,
|
||||
surface::{triangle, Surface, SurfaceIntersection, SurfaceIntersectionData},
|
||||
};
|
||||
|
@ -312,10 +312,8 @@ impl<'a> Surface for RectangleLight<'a> {
|
|||
|
||||
let closure = {
|
||||
let inv_surface_area = (1.0 / (dim.0 as f64 * dim.1 as f64)) as f32;
|
||||
let color = lerp_slice(self.colors, r.time)
|
||||
.to_spectral_sample(wr.wavelength)
|
||||
* inv_surface_area;
|
||||
SurfaceClosureUnion::EmitClosure(EmitClosure::new(color))
|
||||
let color = lerp_slice(self.colors, r.time) * inv_surface_area;
|
||||
SurfaceClosure::Emit(color)
|
||||
};
|
||||
|
||||
// Fill in intersection
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::{
|
|||
math::{coordinate_system_from_vector, dot, Matrix4x4, Normal, Point, Vector},
|
||||
ray::{AccelRay, Ray},
|
||||
sampling::{uniform_sample_cone, uniform_sample_cone_pdf, uniform_sample_sphere},
|
||||
shading::surface_closure::{EmitClosure, SurfaceClosureUnion},
|
||||
shading::surface_closure::SurfaceClosure,
|
||||
shading::SurfaceShader,
|
||||
surface::{Surface, SurfaceIntersection, SurfaceIntersectionData},
|
||||
};
|
||||
|
@ -322,9 +322,8 @@ impl<'a> Surface for SphereLight<'a> {
|
|||
let closure = {
|
||||
let inv_surface_area =
|
||||
(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)
|
||||
* inv_surface_area;
|
||||
SurfaceClosureUnion::EmitClosure(EmitClosure::new(color))
|
||||
let color = lerp_slice(self.colors, r.time) * inv_surface_area;
|
||||
SurfaceClosure::Emit(color)
|
||||
};
|
||||
|
||||
// Fill in intersection
|
||||
|
|
|
@ -34,30 +34,6 @@ pub fn parse_surface_shader<'a>(
|
|||
};
|
||||
|
||||
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" => {
|
||||
let color = if let Some((_, contents, byte_offset)) =
|
||||
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 })
|
||||
}
|
||||
"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" => {
|
||||
// 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!(),
|
||||
};
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ use float4::Float4;
|
|||
use crate::{
|
||||
accel::{ACCEL_NODE_RAY_TESTS, ACCEL_TRAV_TIME},
|
||||
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,
|
||||
hash::hash_u32,
|
||||
hilbert,
|
||||
|
@ -447,14 +447,15 @@ impl LightPath {
|
|||
// If it's an emission closure, handle specially:
|
||||
// - Collect light from the emission.
|
||||
// - Terminate the path.
|
||||
use crate::shading::surface_closure::SurfaceClosureUnion;
|
||||
if let SurfaceClosureUnion::EmitClosure(ref clsr) = *closure {
|
||||
use crate::shading::surface_closure::SurfaceClosure;
|
||||
if let SurfaceClosure::Emit(color) = *closure {
|
||||
let color = color.to_spectral_sample(self.wavelength).e;
|
||||
if let LightPathEvent::CameraRay = self.event {
|
||||
self.color += clsr.emitted_color().e;
|
||||
self.color += color;
|
||||
} else {
|
||||
let mis_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;
|
||||
|
@ -487,7 +488,6 @@ impl LightPath {
|
|||
} else {
|
||||
let light_pdf = light_info.pdf();
|
||||
let light_sel_pdf = light_info.selection_pdf();
|
||||
let material = closure.as_surface_closure();
|
||||
|
||||
// Calculate the shadow ray and surface closure stuff
|
||||
let (attenuation, closure_pdf, shadow_ray) = match light_info {
|
||||
|
@ -495,8 +495,13 @@ impl LightPath {
|
|||
|
||||
// Distant light
|
||||
SceneLightSample::Distant { direction, .. } => {
|
||||
let (attenuation, closure_pdf) =
|
||||
material.evaluate(ray.dir, direction, idata.nor, idata.nor_g);
|
||||
let (attenuation, closure_pdf) = closure.evaluate(
|
||||
ray.dir,
|
||||
direction,
|
||||
idata.nor,
|
||||
idata.nor_g,
|
||||
self.wavelength,
|
||||
);
|
||||
let mut shadow_ray = {
|
||||
// Calculate the shadow ray for testing if the light is
|
||||
// in shadow or not.
|
||||
|
@ -521,8 +526,13 @@ impl LightPath {
|
|||
// Surface light
|
||||
SceneLightSample::Surface { sample_geo, .. } => {
|
||||
let dir = sample_geo.0 - idata.pos;
|
||||
let (attenuation, closure_pdf) =
|
||||
material.evaluate(ray.dir, dir, idata.nor, idata.nor_g);
|
||||
let (attenuation, closure_pdf) = closure.evaluate(
|
||||
ray.dir,
|
||||
dir,
|
||||
idata.nor,
|
||||
idata.nor_g,
|
||||
self.wavelength,
|
||||
);
|
||||
let shadow_ray = {
|
||||
// Calculate the shadow ray for testing if the light is
|
||||
// in shadow or not.
|
||||
|
@ -572,12 +582,17 @@ impl LightPath {
|
|||
let do_bounce = if self.bounce_count < 2 {
|
||||
self.bounce_count += 1;
|
||||
|
||||
// Sample material
|
||||
// Sample closure
|
||||
let (dir, filter, pdf) = {
|
||||
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))
|
||||
closure.sample(
|
||||
idata.incoming,
|
||||
idata.nor,
|
||||
idata.nor_g,
|
||||
(u, v),
|
||||
self.wavelength,
|
||||
)
|
||||
};
|
||||
|
||||
// Check if pdf is zero, to avoid NaN's.
|
||||
|
|
|
@ -67,7 +67,7 @@ impl<'a> Assembly<'a> {
|
|||
idata.pos * sel_xform,
|
||||
idata.nor * sel_xform,
|
||||
idata.nor_g * sel_xform,
|
||||
closure.as_surface_closure(),
|
||||
&closure,
|
||||
time,
|
||||
n,
|
||||
) {
|
||||
|
|
|
@ -4,20 +4,13 @@ use std::fmt::Debug;
|
|||
|
||||
use crate::{color::Color, surface::SurfaceIntersectionData};
|
||||
|
||||
use self::surface_closure::{
|
||||
EmitClosure, GGXClosure, GTRClosure, LambertClosure, SurfaceClosureUnion,
|
||||
};
|
||||
use self::surface_closure::SurfaceClosure;
|
||||
|
||||
/// Trait for surface shaders.
|
||||
pub trait SurfaceShader: Debug + Sync {
|
||||
/// Takes the result of a surface intersection and returns the surface
|
||||
/// closure to be evaluated at that intersection point.
|
||||
fn shade(
|
||||
&self,
|
||||
data: &SurfaceIntersectionData,
|
||||
time: f32,
|
||||
wavelength: f32,
|
||||
) -> SurfaceClosureUnion;
|
||||
fn shade(&self, data: &SurfaceIntersectionData, time: f32, wavelength: f32) -> SurfaceClosure;
|
||||
}
|
||||
|
||||
/// Clearly we must eat this brownie before the world ends, lest it
|
||||
|
@ -39,12 +32,6 @@ pub enum SimpleSurfaceShader {
|
|||
Lambert {
|
||||
color: Color,
|
||||
},
|
||||
GTR {
|
||||
color: Color,
|
||||
roughness: f32,
|
||||
tail_shape: f32,
|
||||
fresnel: f32,
|
||||
},
|
||||
GGX {
|
||||
color: Color,
|
||||
roughness: f32,
|
||||
|
@ -53,41 +40,23 @@ pub enum SimpleSurfaceShader {
|
|||
}
|
||||
|
||||
impl SurfaceShader for SimpleSurfaceShader {
|
||||
fn shade(
|
||||
&self,
|
||||
data: &SurfaceIntersectionData,
|
||||
time: f32,
|
||||
wavelength: f32,
|
||||
) -> SurfaceClosureUnion {
|
||||
fn shade(&self, data: &SurfaceIntersectionData, time: f32, wavelength: f32) -> SurfaceClosure {
|
||||
let _ = (data, time); // Silence "unused" compiler warning
|
||||
|
||||
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,
|
||||
)),
|
||||
SimpleSurfaceShader::Emit { color } => SurfaceClosure::Emit(color),
|
||||
|
||||
SimpleSurfaceShader::Lambert { color } => SurfaceClosure::Lambert(color),
|
||||
|
||||
SimpleSurfaceShader::GGX {
|
||||
color,
|
||||
roughness,
|
||||
fresnel,
|
||||
} => SurfaceClosureUnion::GGXClosure(GGXClosure::new(
|
||||
color.to_spectral_sample(wavelength),
|
||||
roughness,
|
||||
fresnel,
|
||||
)),
|
||||
} => SurfaceClosure::GGX {
|
||||
color: color,
|
||||
roughness: roughness,
|
||||
fresnel: fresnel,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
use std::f32::consts::PI as PI_32;
|
||||
|
||||
use float4::Float4;
|
||||
|
||||
use crate::{
|
||||
color::SpectralSample,
|
||||
color::{Color, SpectralSample},
|
||||
lerp::lerp,
|
||||
math::{clamp, dot, zup_to_vec, Normal, Vector},
|
||||
sampling::cosine_sample_hemisphere,
|
||||
|
@ -12,71 +14,97 @@ use crate::{
|
|||
const INV_PI: f32 = 1.0 / PI_32;
|
||||
const H_PI: f32 = PI_32 / 2.0;
|
||||
|
||||
/// A surface closure, specifying a BSDF for a point on a surface.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum SurfaceClosureUnion {
|
||||
EmitClosure(EmitClosure),
|
||||
LambertClosure(LambertClosure),
|
||||
GTRClosure(GTRClosure),
|
||||
GGXClosure(GGXClosure),
|
||||
pub enum SurfaceClosure {
|
||||
// Normal surface closures.
|
||||
Lambert(Color),
|
||||
GGX {
|
||||
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 {
|
||||
pub fn as_surface_closure(&self) -> &SurfaceClosure {
|
||||
use 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 {
|
||||
SurfaceClosureUnion::EmitClosure(ref closure) => closure as &SurfaceClosure,
|
||||
SurfaceClosureUnion::LambertClosure(ref closure) => closure as &SurfaceClosure,
|
||||
SurfaceClosureUnion::GTRClosure(ref closure) => closure as &SurfaceClosure,
|
||||
SurfaceClosureUnion::GGXClosure(ref closure) => closure as &SurfaceClosure,
|
||||
Lambert(_) => false,
|
||||
GGX { roughness, .. } => roughness == 0.0,
|
||||
Emit(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// color filter.
|
||||
///
|
||||
/// inc: Incoming light direction.
|
||||
/// nor: The shading surface normal at the surface point.
|
||||
/// nor_g: The geometric surface normal at the surface point.
|
||||
/// uv: The sampling values.
|
||||
/// inc: Incoming light direction.
|
||||
/// nor: The shading surface normal at the surface point.
|
||||
/// nor_g: The geometric surface normal at the surface point.
|
||||
/// 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.
|
||||
fn sample(
|
||||
pub fn sample(
|
||||
&self,
|
||||
inc: Vector,
|
||||
nor: Normal,
|
||||
nor_g: Normal,
|
||||
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.
|
||||
///
|
||||
/// inc: The incoming light direction.
|
||||
/// out: The outgoing light direction.
|
||||
/// nor: The shading surface normal at the surface point.
|
||||
/// nor_g: The geometric surface normal at the surface point.
|
||||
/// inc: The incoming light direction.
|
||||
/// out: The outgoing light direction.
|
||||
/// nor: The shading 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
|
||||
/// by `sample()`.
|
||||
fn evaluate(
|
||||
pub fn evaluate(
|
||||
&self,
|
||||
inc: Vector,
|
||||
out: Vector,
|
||||
nor: 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
|
||||
/// 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,
|
||||
/// but it does need to be non-zero anywhere that an exact solution would
|
||||
/// be non-zero.
|
||||
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(
|
||||
pub fn estimate_eval_over_sphere_light(
|
||||
&self,
|
||||
inc: Vector,
|
||||
to_light_center: Vector,
|
||||
|
@ -219,37 +120,52 @@ impl SurfaceClosure for EmitClosure {
|
|||
nor: Normal,
|
||||
nor_g: Normal,
|
||||
) -> f32 {
|
||||
// Not using these, silence warning
|
||||
let _ = (inc, to_light_center, light_radius_squared, nor, nor_g);
|
||||
|
||||
// TODO: what to do here?
|
||||
unimplemented!()
|
||||
match *self {
|
||||
Lambert(color) => lambert_closure::estimate_eval_over_sphere_light(
|
||||
color,
|
||||
inc,
|
||||
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
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct LambertClosure {
|
||||
col: SpectralSample,
|
||||
}
|
||||
/// Lambert closure code.
|
||||
mod lambert_closure {
|
||||
use super::*;
|
||||
|
||||
impl LambertClosure {
|
||||
pub fn new(col: SpectralSample) -> LambertClosure {
|
||||
LambertClosure { col: col }
|
||||
}
|
||||
}
|
||||
|
||||
impl SurfaceClosure for LambertClosure {
|
||||
fn is_delta(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn sample(
|
||||
&self,
|
||||
pub fn sample(
|
||||
color: Color,
|
||||
inc: Vector,
|
||||
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())
|
||||
|
@ -265,18 +181,19 @@ 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 {
|
||||
(out, self.col * pdf, pdf)
|
||||
(out, color.to_spectral_sample(wavelength) * pdf, pdf)
|
||||
} else {
|
||||
(out, SpectralSample::new(0.0), 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate(
|
||||
&self,
|
||||
pub fn evaluate(
|
||||
color: Color,
|
||||
inc: Vector,
|
||||
out: Vector,
|
||||
nor: Normal,
|
||||
nor_g: Normal,
|
||||
wavelength: f32,
|
||||
) -> (SpectralSample, f32) {
|
||||
let (nn, flipped_nor_g) = if dot(nor_g.into_vector(), inc) <= 0.0 {
|
||||
(nor.normalized().into_vector(), nor_g.into_vector())
|
||||
|
@ -286,14 +203,14 @@ 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 * fac, fac)
|
||||
(color.to_spectral_sample(wavelength) * fac, fac)
|
||||
} else {
|
||||
(SpectralSample::new(0.0), 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
fn estimate_eval_over_sphere_light(
|
||||
&self,
|
||||
pub fn estimate_eval_over_sphere_light(
|
||||
color: Color,
|
||||
inc: Vector,
|
||||
to_light_center: Vector,
|
||||
light_radius_squared: f32,
|
||||
|
@ -369,113 +286,24 @@ impl SurfaceClosure for LambertClosure {
|
|||
}
|
||||
}
|
||||
|
||||
/// The GTR microfacet BRDF from the Disney Principled BRDF paper.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
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
|
||||
}
|
||||
mod ggx_closure {
|
||||
use super::*;
|
||||
|
||||
// Makes sure values are in a valid range
|
||||
fn validate(&mut self) {
|
||||
debug_assert!(self.fresnel >= 0.0 && self.fresnel <= 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);
|
||||
pub fn validate(roughness: f32, fresnel: f32) {
|
||||
debug_assert!(fresnel >= 0.0 && fresnel <= 1.0);
|
||||
debug_assert!(roughness >= 0.0 && roughness <= 1.0);
|
||||
}
|
||||
|
||||
// Returns the cosine of the half-angle that should be sampled, given
|
||||
// a random variable in [0,1]
|
||||
fn half_theta_sample(&self, u: f32) -> f32 {
|
||||
let roughness2 = self.roughness * self.roughness;
|
||||
|
||||
// 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,
|
||||
pub fn sample(
|
||||
col: Color,
|
||||
roughness: f32,
|
||||
fresnel: f32,
|
||||
inc: Vector,
|
||||
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 {
|
||||
|
@ -486,7 +314,7 @@ impl SurfaceClosure for GTRClosure {
|
|||
|
||||
// Generate a random ray direction in the hemisphere
|
||||
// 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 angle = uv.1 * PI_32 * 2.0;
|
||||
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.
|
||||
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)
|
||||
} else {
|
||||
(out, SpectralSample::new(0.0), 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate(
|
||||
&self,
|
||||
pub fn evaluate(
|
||||
col: Color,
|
||||
roughness: f32,
|
||||
fresnel: f32,
|
||||
inc: Vector,
|
||||
out: Vector,
|
||||
nor: Normal,
|
||||
nor_g: Normal,
|
||||
wavelength: f32,
|
||||
) -> (SpectralSample, f32) {
|
||||
// Calculate needed vectors, normalized
|
||||
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 nh = clamp(dot(nn, hh), -1.0, 1.0);
|
||||
|
||||
// Other useful numbers
|
||||
let roughness2 = self.roughness * self.roughness;
|
||||
|
||||
// Calculate F - Fresnel
|
||||
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(
|
||||
schlick_fresnel_from_fac(self.col.e.get_0(), hb),
|
||||
self.col.e.get_0(),
|
||||
schlick_fresnel_from_fac(spectrum_sample.e.get_0(), hb),
|
||||
spectrum_sample.e.get_0(),
|
||||
rev_fresnel,
|
||||
);
|
||||
let c1 = lerp(
|
||||
schlick_fresnel_from_fac(self.col.e.get_1(), hb),
|
||||
self.col.e.get_1(),
|
||||
schlick_fresnel_from_fac(spectrum_sample.e.get_1(), hb),
|
||||
spectrum_sample.e.get_1(),
|
||||
rev_fresnel,
|
||||
);
|
||||
let c2 = lerp(
|
||||
schlick_fresnel_from_fac(self.col.e.get_2(), hb),
|
||||
self.col.e.get_2(),
|
||||
schlick_fresnel_from_fac(spectrum_sample.e.get_2(), hb),
|
||||
spectrum_sample.e.get_2(),
|
||||
rev_fresnel,
|
||||
);
|
||||
let c3 = lerp(
|
||||
schlick_fresnel_from_fac(self.col.e.get_3(), hb),
|
||||
self.col.e.get_3(),
|
||||
schlick_fresnel_from_fac(spectrum_sample.e.get_3(), hb),
|
||||
spectrum_sample.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
|
||||
SpectralSample::from_parts(Float4::new(c0, c1, c2, c3), wavelength)
|
||||
};
|
||||
|
||||
// Calculate everything else
|
||||
if self.roughness == 0.0 {
|
||||
if roughness == 0.0 {
|
||||
// If sharp mirror, just return col * fresnel factor
|
||||
return (col_f, 0.0);
|
||||
} else {
|
||||
// Calculate D - Distribution
|
||||
let dist = self.dist(nh, self.roughness) / na;
|
||||
let dist = ggx_d(nh, roughness) / na;
|
||||
|
||||
// Calculate G1 - Geometric microfacet shadowing
|
||||
let g1 = {
|
||||
let na2 = na * na;
|
||||
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)
|
||||
};
|
||||
// Calculate G1 and G2- Geometric microfacet shadowing
|
||||
let g1 = ggx_g(ha, na, roughness);
|
||||
let g2 = ggx_g(hb, nb, roughness);
|
||||
|
||||
// Final result
|
||||
(col_f * (dist * g1 * g2) * INV_PI, dist * INV_PI)
|
||||
}
|
||||
}
|
||||
|
||||
fn estimate_eval_over_sphere_light(
|
||||
&self,
|
||||
pub fn estimate_eval_over_sphere_light(
|
||||
col: Color,
|
||||
roughness: f32,
|
||||
fresnel: f32,
|
||||
inc: Vector,
|
||||
to_light_center: Vector,
|
||||
light_radius_squared: f32,
|
||||
|
@ -645,7 +454,7 @@ impl SurfaceClosure for GTRClosure {
|
|||
// 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 += ggx_d(dot(nn, hh), roughness);
|
||||
// }
|
||||
//}
|
||||
//fac /= N * N;
|
||||
|
@ -654,41 +463,12 @@ impl SurfaceClosure for GTRClosure {
|
|||
let theta = cos_theta_max.acos();
|
||||
let hh = (aa + bb).normalized();
|
||||
let nh = clamp(dot(nn, hh), -1.0, 1.0);
|
||||
let fac = self.dist(
|
||||
nh,
|
||||
(1.0f32).min(self.roughness.sqrt() + (2.0 * theta / PI_32)),
|
||||
);
|
||||
let fac = ggx_d(nh, (1.0f32).min(roughness.sqrt() + (2.0 * theta / PI_32)));
|
||||
|
||||
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
|
||||
// a random variable in [0,1]
|
||||
|
@ -730,181 +510,126 @@ impl GGXClosure {
|
|||
}
|
||||
}
|
||||
|
||||
impl SurfaceClosure for GGXClosure {
|
||||
fn is_delta(&self) -> bool {
|
||||
self.roughness == 0.0
|
||||
}
|
||||
/// Emit closure code.
|
||||
///
|
||||
/// 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(
|
||||
&self,
|
||||
pub fn sample(
|
||||
color: Color,
|
||||
inc: Vector,
|
||||
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 {
|
||||
(nor.normalized().into_vector(), nor_g.into_vector())
|
||||
} else {
|
||||
(-nor.normalized().into_vector(), -nor_g.into_vector())
|
||||
};
|
||||
let _ = (inc, nor, nor_g, uv); // Not using these, silence warning
|
||||
|
||||
// Generate a random ray direction in the hemisphere
|
||||
// of the surface.
|
||||
let theta_cos = Self::half_theta_sample(uv.0, self.roughness);
|
||||
let theta_sin = (1.0 - (theta_cos * theta_cos)).sqrt();
|
||||
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)
|
||||
}
|
||||
(
|
||||
Vector::new(0.0, 0.0, 0.0),
|
||||
color.to_spectral_sample(wavelength),
|
||||
1.0,
|
||||
)
|
||||
}
|
||||
|
||||
fn evaluate(
|
||||
&self,
|
||||
pub fn evaluate(
|
||||
color: Color,
|
||||
inc: Vector,
|
||||
out: Vector,
|
||||
nor: Normal,
|
||||
nor_g: Normal,
|
||||
wavelength: f32,
|
||||
) -> (SpectralSample, f32) {
|
||||
// Calculate needed vectors, normalized
|
||||
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
|
||||
let _ = (inc, out, nor, nor_g); // Not using these, silence warning
|
||||
|
||||
// Surface normal
|
||||
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)
|
||||
}
|
||||
(color.to_spectral_sample(wavelength), 1.0)
|
||||
}
|
||||
|
||||
fn estimate_eval_over_sphere_light(
|
||||
&self,
|
||||
pub fn estimate_eval_over_sphere_light(
|
||||
color: Color,
|
||||
inc: Vector,
|
||||
to_light_center: Vector,
|
||||
light_radius_squared: f32,
|
||||
nor: Normal,
|
||||
nor_g: Normal,
|
||||
) -> f32 {
|
||||
// TODO: all of the stuff in this function is horribly hacky.
|
||||
// Find a proper way to approximate the light contribution from a
|
||||
// solid angle.
|
||||
// Not using these, silence warning
|
||||
let _ = (inc, to_light_center, light_radius_squared, nor, nor_g);
|
||||
|
||||
let _ = nor_g; // Not using this, silence warning
|
||||
|
||||
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
|
||||
// TODO: what to do here?
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
boundable::Boundable,
|
||||
math::{Matrix4x4, Normal, Point, Vector},
|
||||
ray::{AccelRay, Ray},
|
||||
shading::surface_closure::SurfaceClosureUnion,
|
||||
shading::surface_closure::SurfaceClosure,
|
||||
shading::SurfaceShader,
|
||||
};
|
||||
|
||||
|
@ -31,7 +31,7 @@ pub enum SurfaceIntersection {
|
|||
Occlude,
|
||||
Hit {
|
||||
intersection_data: SurfaceIntersectionData,
|
||||
closure: SurfaceClosureUnion,
|
||||
closure: SurfaceClosure,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user