Added GTR surface closure.
Not tested yet, just a straightforward conversion from the C++ Psychopath codebase. So there are probably bugs in it from the conversion. But it compiles!
This commit is contained in:
parent
3842a8a9a6
commit
0b05d364e4
|
@ -42,7 +42,7 @@ fn nth_wavelength(hero_wavelength: f32, n: usize) -> f32 {
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct SpectralSample {
|
pub struct SpectralSample {
|
||||||
e: Float4,
|
pub e: Float4,
|
||||||
hero_wavelength: f32,
|
hero_wavelength: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,18 @@ pub fn cross<T: CrossProduct>(a: T, b: T) -> T {
|
||||||
a.cross(b)
|
a.cross(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Clamps a value between a min and max.
|
||||||
|
pub fn clamp<T: PartialOrd>(v: T, lower: T, upper: T) -> T {
|
||||||
|
if v < lower {
|
||||||
|
lower
|
||||||
|
} else if v > upper {
|
||||||
|
upper
|
||||||
|
} else {
|
||||||
|
v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Adapted from from http://fastapprox.googlecode.com
|
// Adapted from from http://fastapprox.googlecode.com
|
||||||
pub fn fast_ln(x: f32) -> f32 {
|
pub fn fast_ln(x: f32) -> f32 {
|
||||||
use std::mem::transmute_copy;
|
use std::mem::transmute_copy;
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::f32::consts::PI as PI_32;
|
use std::f32::consts::PI as PI_32;
|
||||||
|
|
||||||
use color::{XYZ, SpectralSample, Color};
|
use color::{XYZ, SpectralSample, Color};
|
||||||
use math::{Vector, Normal, dot, zup_to_vec};
|
use math::{Vector, Normal, dot, clamp, zup_to_vec};
|
||||||
use sampling::cosine_sample_hemisphere;
|
use sampling::cosine_sample_hemisphere;
|
||||||
|
use lerp::lerp;
|
||||||
|
|
||||||
|
|
||||||
const INV_PI: f32 = 1.0 / PI_32;
|
const INV_PI: f32 = 1.0 / PI_32;
|
||||||
|
@ -12,6 +15,7 @@ const H_PI: f32 = PI_32 / 2.0;
|
||||||
pub enum SurfaceClosureUnion {
|
pub enum SurfaceClosureUnion {
|
||||||
EmitClosure(EmitClosure),
|
EmitClosure(EmitClosure),
|
||||||
LambertClosure(LambertClosure),
|
LambertClosure(LambertClosure),
|
||||||
|
GTRClosure(GTRClosure),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SurfaceClosureUnion {
|
impl SurfaceClosureUnion {
|
||||||
|
@ -19,6 +23,7 @@ impl SurfaceClosureUnion {
|
||||||
match self {
|
match self {
|
||||||
&SurfaceClosureUnion::EmitClosure(ref closure) => closure as &SurfaceClosure,
|
&SurfaceClosureUnion::EmitClosure(ref closure) => closure as &SurfaceClosure,
|
||||||
&SurfaceClosureUnion::LambertClosure(ref closure) => closure as &SurfaceClosure,
|
&SurfaceClosureUnion::LambertClosure(ref closure) => closure as &SurfaceClosure,
|
||||||
|
&SurfaceClosureUnion::GTRClosure(ref closure) => closure as &SurfaceClosure,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -329,3 +334,292 @@ impl SurfaceClosure for LambertClosure {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// The GTR microfacet BRDF from the Disney Principled BRDF paper.
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct GTRClosure {
|
||||||
|
col: XYZ,
|
||||||
|
roughness: f32,
|
||||||
|
tail_shape: f32,
|
||||||
|
fresnel: f32,
|
||||||
|
normalization_factor: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GTRClosure {
|
||||||
|
pub fn new(col: XYZ, 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
|
||||||
|
fn validate(&mut self) {
|
||||||
|
// 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.000244140625 {
|
||||||
|
// (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
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dist(&self, nh: f32, rough: f32) -> f32 {
|
||||||
|
// Other useful numbers
|
||||||
|
let roughness2 = rough * rough;
|
||||||
|
|
||||||
|
// Calculate D - Distribution
|
||||||
|
let dist = if nh <= 0.0 {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
let nh2 = nh * nh;
|
||||||
|
self.normalization_factor / (1.0 + ((roughness2 - 1.0) * nh2)).powf(self.tail_shape)
|
||||||
|
};
|
||||||
|
|
||||||
|
dist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SurfaceClosure for GTRClosure {
|
||||||
|
fn is_delta(&self) -> bool {
|
||||||
|
self.roughness == 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn sample(&self,
|
||||||
|
inc: Vector,
|
||||||
|
nor: Normal,
|
||||||
|
uv: (f32, f32),
|
||||||
|
wavelength: f32)
|
||||||
|
-> (Vector, SpectralSample, f32) {
|
||||||
|
// Get normalized surface normal
|
||||||
|
let nn = if dot(nor.into_vector(), inc) <= 0.0 {
|
||||||
|
nor.normalized()
|
||||||
|
} else {
|
||||||
|
-nor.normalized() // If back-facing, flip normal
|
||||||
|
}
|
||||||
|
.into_vector();
|
||||||
|
|
||||||
|
// Generate a random ray direction in the hemisphere
|
||||||
|
// of the surface.
|
||||||
|
let theta_cos = self.half_theta_sample(uv.0);
|
||||||
|
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));
|
||||||
|
let filter = self.evaluate(inc, out, nor, wavelength);
|
||||||
|
let pdf = self.sample_pdf(inc, out, nor);
|
||||||
|
|
||||||
|
(out, filter, pdf)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, wavelength: f32) -> SpectralSample {
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// Surface normal
|
||||||
|
let nn = if dot(nor.into_vector(), hh) <= 0.0 {
|
||||||
|
nor.normalized()
|
||||||
|
} else {
|
||||||
|
-nor.normalized() // If back-facing, flip normal
|
||||||
|
}
|
||||||
|
.into_vector();
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Other useful numbers
|
||||||
|
let roughness2 = self.roughness * self.roughness;
|
||||||
|
|
||||||
|
// Calculate F - Fresnel
|
||||||
|
let col_f = {
|
||||||
|
let mut col_f = self.col.to_spectral_sample(wavelength);
|
||||||
|
|
||||||
|
let rev_fresnel = 1.0 - self.fresnel;
|
||||||
|
let c0 = lerp(schlick_fresnel_from_fac(col_f.e.get_0(), hb),
|
||||||
|
col_f.e.get_0(),
|
||||||
|
rev_fresnel);
|
||||||
|
let c1 = lerp(schlick_fresnel_from_fac(col_f.e.get_1(), hb),
|
||||||
|
col_f.e.get_1(),
|
||||||
|
rev_fresnel);
|
||||||
|
let c2 = lerp(schlick_fresnel_from_fac(col_f.e.get_2(), hb),
|
||||||
|
col_f.e.get_2(),
|
||||||
|
rev_fresnel);
|
||||||
|
let c3 = lerp(schlick_fresnel_from_fac(col_f.e.get_3(), hb),
|
||||||
|
col_f.e.get_3(),
|
||||||
|
rev_fresnel);
|
||||||
|
|
||||||
|
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;
|
||||||
|
} else {
|
||||||
|
// Calculate D - Distribution
|
||||||
|
let dist = if nh > 0.0 {
|
||||||
|
let nh2 = nh * nh;
|
||||||
|
self.normalization_factor / (1.0 + ((roughness2 - 1.0) * nh2)).powf(self.tail_shape)
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Final result
|
||||||
|
return col_f * (dist * g1 * g2) * INV_PI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn sample_pdf(&self, inc: Vector, out: Vector, nor: Normal) -> 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
|
||||||
|
|
||||||
|
// Surface normal
|
||||||
|
let nn = if dot(nor.into_vector(), hh) <= 0.0 {
|
||||||
|
nor.normalized()
|
||||||
|
} else {
|
||||||
|
-nor.normalized() // If back-facing, flip normal
|
||||||
|
}
|
||||||
|
.into_vector();
|
||||||
|
|
||||||
|
// Calculate needed dot products
|
||||||
|
let nh = clamp(dot(nn, hh), -1.0, 1.0);
|
||||||
|
|
||||||
|
return self.dist(nh, self.roughness) * INV_PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn estimate_eval_over_solid_angle(&self,
|
||||||
|
inc: Vector,
|
||||||
|
out: Vector,
|
||||||
|
nor: Normal,
|
||||||
|
cos_theta: f32)
|
||||||
|
-> 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.
|
||||||
|
assert!(cos_theta >= -1.0);
|
||||||
|
assert!(cos_theta <= 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 = out.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);
|
||||||
|
// 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.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)));
|
||||||
|
|
||||||
|
return fac * (1.0f32).min(1.0 - cos_theta) * INV_PI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user