psychopath/src/shading/surface_closure.rs
Nathan Vegdahl 0880a0f19d Make renderer actually use the material system properly.
The intersection code still isn't using any kind of shading,
and materials aren't parsed by the parser, but the renderer
class itself is using them.
2016-07-07 19:51:19 -07:00

245 lines
7.2 KiB
Rust

use math::{Vector, Normal, dot, zup_to_vec};
use color::{XYZ, SpectralSample, Color};
use sampling::cosine_sample_hemisphere;
use std::f32::consts::PI as PI_32;
const INV_PI: f32 = 1.0 / PI_32;
#[derive(Debug, Copy, Clone)]
pub enum SurfaceClosureUnion {
EmitClosure(EmitClosure),
LambertClosure(LambertClosure),
}
impl SurfaceClosureUnion {
pub fn as_surface_closure(&self) -> &SurfaceClosure {
match self {
&SurfaceClosureUnion::EmitClosure(ref closure) => closure as &SurfaceClosure,
&SurfaceClosureUnion::LambertClosure(ref closure) => closure as &SurfaceClosure,
}
}
}
/// Trait for surface closures.
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 surface normal at the surface point.
/// uv: The sampling values.
/// wavelength: The wavelength of light to sample at.
///
/// Returns a tuple with the generated outgoing light direction, color filter, and pdf.
fn sample(&self,
inc: Vector,
nor: Normal,
uv: (f32, f32),
wavelength: f32)
-> (Vector, SpectralSample, f32);
/// Evaluates the closure for the given incoming and outgoing rays.
///
/// inc: The incoming light direction.
/// out: The outgoing light direction.
/// nor: The surface normal of the reflecting/transmitting surface point.
/// wavelength: The wavelength of light to evaluate for.
///
/// Returns the resulting filter color.
fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, wavelength: f32) -> SpectralSample;
/// Returns the pdf for the given 'in' direction producing the given 'out'
/// direction with the given differential geometry.
///
/// inc: The incoming light direction.
/// out: The outgoing light direction.
/// nor: The surface normal of the reflecting/transmitting surface point.
fn sample_pdf(&self, inc: Vector, out: Vector, nor: 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));
return 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;
return 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.000001 {
return 1.0;
}
// Find the ior ratio
let tmp2 = (-2.0 / tmp1) - 1.0;
let ior_ratio = tmp2 * tmp2;
// Calculate fresnel factor
return 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;
return 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: XYZ,
}
impl EmitClosure {
pub fn emitted_color(&self, wavelength: f32) -> SpectralSample {
self.col.to_spectral_sample(wavelength)
}
}
impl SurfaceClosure for EmitClosure {
fn is_delta(&self) -> bool {
false
}
fn sample(&self,
inc: Vector,
nor: Normal,
uv: (f32, f32),
wavelength: f32)
-> (Vector, SpectralSample, f32) {
let _ = (inc, nor, uv); // Not using these, silence warning
(Vector::new(0.0, 0.0, 0.0), SpectralSample::new(wavelength), 1.0)
}
fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, wavelength: f32) -> SpectralSample {
let _ = (inc, out, nor); // Not using these, silence warning
SpectralSample::new(wavelength)
}
fn sample_pdf(&self, inc: Vector, out: Vector, nor: Normal) -> f32 {
let _ = (inc, out, nor); // Not using these, silence warning
1.0
}
}
/// Lambertian surface closure
#[derive(Debug, Copy, Clone)]
pub struct LambertClosure {
col: XYZ,
}
impl LambertClosure {
pub fn new(col: XYZ) -> LambertClosure {
LambertClosure { col: col }
}
}
impl SurfaceClosure for LambertClosure {
fn is_delta(&self) -> bool {
false
}
fn sample(&self,
inc: Vector,
nor: Normal,
uv: (f32, f32),
wavelength: f32)
-> (Vector, SpectralSample, f32) {
let nn = if dot(nor.into_vector(), inc) <= 0.0 {
nor.normalized()
} else {
-nor.normalized()
}
.into_vector();
// Generate a random ray direction in the hemisphere
// of the surface.
let dir = cosine_sample_hemisphere(uv.0, uv.1);
let pdf = dir[2] * INV_PI;
let out = zup_to_vec(dir, nn);
let filter = self.evaluate(inc, out, nor, wavelength);
(out, filter, pdf)
}
fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, wavelength: f32) -> SpectralSample {
let v = out.normalized();
let nn = if dot(nor.into_vector(), inc) <= 0.0 {
nor.normalized()
} else {
-nor.normalized()
}
.into_vector();
let fac = dot(nn, v).max(0.0) * INV_PI;
self.col.to_spectral_sample(wavelength) * fac
}
fn sample_pdf(&self, inc: Vector, out: Vector, nor: Normal) -> f32 {
let v = out.normalized();
let nn = if dot(nor.into_vector(), inc) <= 0.0 {
nor.normalized()
} else {
-nor.normalized()
}
.into_vector();
dot(nn, v).max(0.0) * INV_PI
}
}