Added GGX glossy material, and simplified surface closure API.
This commit is contained in:
parent
5c20fa3ea4
commit
caeb1d9c67
|
@ -88,7 +88,7 @@ class PsychopathMesh(bpy.types.PropertyGroup):
|
||||||
class PsychopathMaterial(bpy.types.PropertyGroup):
|
class PsychopathMaterial(bpy.types.PropertyGroup):
|
||||||
surface_shader_type = EnumProperty(
|
surface_shader_type = EnumProperty(
|
||||||
name="Surface Shader Type", description="",
|
name="Surface Shader Type", description="",
|
||||||
items=[('Emit', 'Emit', ""), ('Lambert', 'Lambert', ""), ('GTR', 'GTR', "")],
|
items=[('Emit', 'Emit', ""), ('Lambert', 'Lambert', ""), ('GTR', 'GTR', ""), ('GGX', 'GGX', "")],
|
||||||
default="Lambert"
|
default="Lambert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -327,6 +327,12 @@ class Material:
|
||||||
w.write("Roughness [%f]\n" % self.mat.psychopath.roughness)
|
w.write("Roughness [%f]\n" % self.mat.psychopath.roughness)
|
||||||
w.write("TailShape [%f]\n" % self.mat.psychopath.tail_shape)
|
w.write("TailShape [%f]\n" % self.mat.psychopath.tail_shape)
|
||||||
w.write("Fresnel [%f]\n" % self.mat.psychopath.fresnel)
|
w.write("Fresnel [%f]\n" % self.mat.psychopath.fresnel)
|
||||||
|
elif self.mat.psychopath.surface_shader_type == 'GGX':
|
||||||
|
w.write("Type [GGX]\n")
|
||||||
|
color = self.mat.psychopath.color
|
||||||
|
w.write("Color [%f %f %f]\n" % (color[0], color[1], color[2]))
|
||||||
|
w.write("Roughness [%f]\n" % self.mat.psychopath.roughness)
|
||||||
|
w.write("Fresnel [%f]\n" % self.mat.psychopath.fresnel)
|
||||||
else:
|
else:
|
||||||
raise "Unsupported surface shader type '%s'" % self.mat.psychopath.surface_shader_type
|
raise "Unsupported surface shader type '%s'" % self.mat.psychopath.surface_shader_type
|
||||||
w.unindent()
|
w.unindent()
|
||||||
|
|
|
@ -249,6 +249,10 @@ class MATERIAL_PT_psychopath_surface(PsychopathPanel, bpy.types.Panel):
|
||||||
layout.prop(mat.psychopath, "tail_shape")
|
layout.prop(mat.psychopath, "tail_shape")
|
||||||
layout.prop(mat.psychopath, "fresnel")
|
layout.prop(mat.psychopath, "fresnel")
|
||||||
|
|
||||||
|
if mat.psychopath.surface_shader_type == 'GGX':
|
||||||
|
layout.prop(mat.psychopath, "roughness")
|
||||||
|
layout.prop(mat.psychopath, "fresnel")
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
bpy.utils.register_class(RENDER_PT_psychopath_render_settings)
|
bpy.utils.register_class(RENDER_PT_psychopath_render_settings)
|
||||||
|
|
|
@ -160,6 +160,69 @@ pub fn parse_surface_shader<'a>(
|
||||||
fresnel: fresnel,
|
fresnel: fresnel,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"GGX" => {
|
||||||
|
// 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...?
|
||||||
|
XYZ::from_tuple(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.",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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::GGX {
|
||||||
|
color: color,
|
||||||
|
roughness: roughness,
|
||||||
|
fresnel: fresnel,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -492,10 +492,8 @@ impl LightPath {
|
||||||
|
|
||||||
// Distant light
|
// Distant light
|
||||||
SceneLightSample::Distant { direction, .. } => {
|
SceneLightSample::Distant { direction, .. } => {
|
||||||
let attenuation =
|
let (attenuation, closure_pdf) =
|
||||||
material.evaluate(ray.dir, direction, idata.nor, idata.nor_g);
|
material.evaluate(ray.dir, direction, idata.nor, idata.nor_g);
|
||||||
let closure_pdf =
|
|
||||||
material.sample_pdf(ray.dir, direction, idata.nor, idata.nor_g);
|
|
||||||
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.
|
||||||
|
@ -520,10 +518,8 @@ 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 =
|
let (attenuation, closure_pdf) =
|
||||||
material.evaluate(ray.dir, dir, idata.nor, idata.nor_g);
|
material.evaluate(ray.dir, dir, idata.nor, idata.nor_g);
|
||||||
let closure_pdf =
|
|
||||||
material.sample_pdf(ray.dir, dir, idata.nor, idata.nor_g);
|
|
||||||
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.
|
||||||
|
|
|
@ -2,7 +2,7 @@ pub mod surface_closure;
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use self::surface_closure::{EmitClosure, GTRClosure, LambertClosure, SurfaceClosureUnion};
|
use self::surface_closure::{EmitClosure, GGXClosure, GTRClosure, LambertClosure, SurfaceClosureUnion};
|
||||||
use color::{Color, XYZ};
|
use color::{Color, XYZ};
|
||||||
use surface::SurfaceIntersectionData;
|
use surface::SurfaceIntersectionData;
|
||||||
|
|
||||||
|
@ -43,6 +43,11 @@ pub enum SimpleSurfaceShader {
|
||||||
tail_shape: f32,
|
tail_shape: f32,
|
||||||
fresnel: f32,
|
fresnel: f32,
|
||||||
},
|
},
|
||||||
|
GGX {
|
||||||
|
color: XYZ,
|
||||||
|
roughness: f32,
|
||||||
|
fresnel: f32,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SurfaceShader for SimpleSurfaceShader {
|
impl SurfaceShader for SimpleSurfaceShader {
|
||||||
|
@ -72,6 +77,15 @@ impl SurfaceShader for SimpleSurfaceShader {
|
||||||
tail_shape,
|
tail_shape,
|
||||||
fresnel,
|
fresnel,
|
||||||
)),
|
)),
|
||||||
|
SimpleSurfaceShader::GGX {
|
||||||
|
color,
|
||||||
|
roughness,
|
||||||
|
fresnel,
|
||||||
|
} => SurfaceClosureUnion::GGXClosure(GGXClosure::new(
|
||||||
|
color.to_spectral_sample(wavelength),
|
||||||
|
roughness,
|
||||||
|
fresnel,
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ pub enum SurfaceClosureUnion {
|
||||||
EmitClosure(EmitClosure),
|
EmitClosure(EmitClosure),
|
||||||
LambertClosure(LambertClosure),
|
LambertClosure(LambertClosure),
|
||||||
GTRClosure(GTRClosure),
|
GTRClosure(GTRClosure),
|
||||||
|
GGXClosure(GGXClosure),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SurfaceClosureUnion {
|
impl SurfaceClosureUnion {
|
||||||
|
@ -23,6 +24,7 @@ impl SurfaceClosureUnion {
|
||||||
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,
|
SurfaceClosureUnion::GTRClosure(ref closure) => closure as &SurfaceClosure,
|
||||||
|
SurfaceClosureUnion::GGXClosure(ref closure) => closure as &SurfaceClosure,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +34,10 @@ impl SurfaceClosureUnion {
|
||||||
/// Note: each surface closure is assumed to be bound to a particular hero
|
/// Note: each surface closure is assumed to be bound to a particular hero
|
||||||
/// wavelength. This is implicit in the `sample`, `evaluate`, and `sample_pdf`
|
/// wavelength. This is implicit in the `sample`, `evaluate`, and `sample_pdf`
|
||||||
/// functions below.
|
/// 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 {
|
pub trait SurfaceClosure {
|
||||||
/// Returns whether the closure has a delta distribution or not.
|
/// Returns whether the closure has a delta distribution or not.
|
||||||
fn is_delta(&self) -> bool;
|
fn is_delta(&self) -> bool;
|
||||||
|
@ -59,19 +65,10 @@ pub trait SurfaceClosure {
|
||||||
/// 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: The wavelength of light to evaluate for.
|
|
||||||
///
|
///
|
||||||
/// Returns the resulting filter color.
|
/// Returns the resulting filter color and pdf of if this had been generated
|
||||||
fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> SpectralSample;
|
/// by `sample()`.
|
||||||
|
fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> (SpectralSample, f32);
|
||||||
/// 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 shading surface normal at the surface point.
|
|
||||||
/// nor_g: The geometric surface normal at the surface point.
|
|
||||||
fn sample_pdf(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> f32;
|
|
||||||
|
|
||||||
/// 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
|
||||||
|
@ -194,16 +191,10 @@ impl SurfaceClosure for EmitClosure {
|
||||||
(Vector::new(0.0, 0.0, 0.0), self.col, 1.0)
|
(Vector::new(0.0, 0.0, 0.0), self.col, 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> SpectralSample {
|
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
|
let _ = (inc, out, nor, nor_g); // Not using these, silence warning
|
||||||
|
|
||||||
self.col
|
(self.col, 1.0)
|
||||||
}
|
|
||||||
|
|
||||||
fn sample_pdf(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> f32 {
|
|
||||||
let _ = (inc, out, nor, nor_g); // Not using these, silence warning
|
|
||||||
|
|
||||||
1.0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn estimate_eval_over_sphere_light(
|
fn estimate_eval_over_sphere_light(
|
||||||
|
@ -260,14 +251,13 @@ 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 {
|
||||||
let filter = self.evaluate(inc, out, nor, nor_g);
|
(out, self.col * pdf, pdf)
|
||||||
(out, filter, pdf)
|
|
||||||
} else {
|
} else {
|
||||||
(out, SpectralSample::new(0.0), 0.0)
|
(out, SpectralSample::new(0.0), 0.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> SpectralSample {
|
fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> (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())
|
||||||
} else {
|
} else {
|
||||||
|
@ -276,23 +266,9 @@ 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
|
(self.col * fac, fac)
|
||||||
} else {
|
} else {
|
||||||
SpectralSample::new(0.0)
|
(SpectralSample::new(0.0), 0.0)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sample_pdf(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> f32 {
|
|
||||||
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())
|
|
||||||
};
|
|
||||||
|
|
||||||
if dot(flipped_nor_g, out) >= 0.0 {
|
|
||||||
dot(nn, out.normalized()).max(0.0) * INV_PI
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,6 +332,17 @@ impl SurfaceClosure for LambertClosure {
|
||||||
|
|
||||||
let cos_nv = dot(nn, v).max(-1.0).min(1.0);
|
let cos_nv = dot(nn, v).max(-1.0).min(1.0);
|
||||||
|
|
||||||
|
// Alt implementation from the SPI paper.
|
||||||
|
// Worse sampling, but here for reference.
|
||||||
|
// {
|
||||||
|
// let nl_ang = cos_nv.acos();
|
||||||
|
// let rad_ang = cos_theta_max.acos();
|
||||||
|
// let min_ang = (nl_ang - rad_ang).max(0.0);
|
||||||
|
// let lamb = min_ang.cos().max(0.0);
|
||||||
|
|
||||||
|
// return lamb / dist2;
|
||||||
|
// }
|
||||||
|
|
||||||
return sphere_lambert(cos_nv, cos_theta_max);
|
return sphere_lambert(cos_nv, cos_theta_max);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -487,15 +474,14 @@ 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 = self.evaluate(inc, out, nor, nor_g);
|
let (filter, pdf) = self.evaluate(inc, out, nor, nor_g);
|
||||||
let pdf = self.sample_pdf(inc, out, nor, nor_g);
|
|
||||||
(out, filter, pdf)
|
(out, filter, pdf)
|
||||||
} else {
|
} else {
|
||||||
(out, SpectralSample::new(0.0), 0.0)
|
(out, SpectralSample::new(0.0), 0.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> SpectralSample {
|
fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> (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
|
||||||
let bb = out.normalized(); // Out
|
let bb = out.normalized(); // Out
|
||||||
|
@ -510,7 +496,7 @@ impl SurfaceClosure for GTRClosure {
|
||||||
|
|
||||||
// Make sure everything's on the correct side of the surface
|
// 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 {
|
if dot(nn, aa) < 0.0 || dot(nn, bb) < 0.0 || dot(flipped_nor_g, bb) < 0.0 {
|
||||||
return SpectralSample::new(0.0);
|
return (SpectralSample::new(0.0), 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate needed dot products
|
// Calculate needed dot products
|
||||||
|
@ -559,15 +545,10 @@ impl SurfaceClosure for GTRClosure {
|
||||||
// Calculate everything else
|
// Calculate everything else
|
||||||
if self.roughness == 0.0 {
|
if self.roughness == 0.0 {
|
||||||
// If sharp mirror, just return col * fresnel factor
|
// If sharp mirror, just return col * fresnel factor
|
||||||
return col_f;
|
return (col_f, 0.0);
|
||||||
} else {
|
} else {
|
||||||
// Calculate D - Distribution
|
// Calculate D - Distribution
|
||||||
let dist = if nh > 0.0 {
|
let dist = self.dist(nh, self.roughness);
|
||||||
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
|
// Calculate G1 - Geometric microfacet shadowing
|
||||||
let g1 = {
|
let g1 = {
|
||||||
|
@ -590,34 +571,10 @@ impl SurfaceClosure for GTRClosure {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Final result
|
// Final result
|
||||||
col_f * (dist * g1 * g2) * INV_PI
|
(col_f * (dist * g1 * g2) * INV_PI, dist * INV_PI)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sample_pdf(&self, inc: Vector, out: Vector, nor: Normal, nor_g: 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, 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 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate needed dot products
|
|
||||||
let nh = clamp(dot(nn, hh), -1.0, 1.0);
|
|
||||||
|
|
||||||
self.dist(nh, self.roughness) * INV_PI
|
|
||||||
}
|
|
||||||
|
|
||||||
fn estimate_eval_over_sphere_light(
|
fn estimate_eval_over_sphere_light(
|
||||||
&self,
|
&self,
|
||||||
inc: Vector,
|
inc: Vector,
|
||||||
|
@ -676,3 +633,243 @@ impl SurfaceClosure for GTRClosure {
|
||||||
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
|
||||||
|
// a random variable in [0,1]
|
||||||
|
fn half_theta_sample(u: f32, rough: f32) -> f32 {
|
||||||
|
let rough2 = rough * rough;
|
||||||
|
|
||||||
|
// Calculate top half of equation
|
||||||
|
let top = 1.0 - u;
|
||||||
|
|
||||||
|
// Calculate bottom half of equation
|
||||||
|
let bottom = 1.0 + ((rough2 - 1.0) * u);
|
||||||
|
|
||||||
|
(top / bottom).sqrt()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The GGX microfacet distribution function.
|
||||||
|
///
|
||||||
|
/// nh: cosine of the angle between the surface normal and the microfacet normal.
|
||||||
|
fn ggx_d(nh: f32, rough: f32) -> f32 {
|
||||||
|
if nh <= 0.0 {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rough2 = rough * rough;
|
||||||
|
let tmp = 1.0 + ((rough2 - 1.0) * (nh * nh));
|
||||||
|
rough2 / (PI_32 * tmp * tmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The GGX Smith shadow-masking function.
|
||||||
|
///
|
||||||
|
/// vh: cosine of the angle between the view vector and the microfacet normal.
|
||||||
|
/// vn: cosine of the angle between the view vector and surface normal.
|
||||||
|
fn ggx_g(vh: f32, vn: f32, rough: f32) -> f32 {
|
||||||
|
if (vh * vn) <= 0.0 {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
2.0 / (1.0 + (1.0 + rough * rough * (1.0 - vn * vn) / (vn * vn)).sqrt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SurfaceClosure for GGXClosure {
|
||||||
|
fn is_delta(&self) -> bool {
|
||||||
|
self.roughness == 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample(
|
||||||
|
&self,
|
||||||
|
inc: Vector,
|
||||||
|
nor: Normal,
|
||||||
|
nor_g: Normal,
|
||||||
|
uv: (f32, 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())
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evaluate(&self, inc: Vector, out: Vector, nor: Normal, nor_g: Normal) -> (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
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
&self,
|
||||||
|
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user