Skip to content

Commit 431d950

Browse files
committed
Refactor Plastic material with new Composite PDF
1 parent b732b7c commit 431d950

7 files changed

Lines changed: 58 additions & 38 deletions

File tree

src/basis.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use glam::Vec3A;
66
/// Orthonormal bases are used to calculate where to scatter the ray
77
/// when leaving a hit object. ONBs allow for quick and easy computation for
88
/// such cases as we can use the normal as one of the vectors.
9+
#[derive(Copy, Clone)]
910
pub struct OrthonormalBasis {
1011
axis: [Vec3A; 3],
1112
}

src/materials.rs

Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rand_pcg::Pcg64Mcg;
77

88
use basis::OrthonormalBasis;
99
use results::{HitResult, ScatterResult};
10-
use ggx::{ggx_distribution, ggx_geometry, ggx_sample_vndf};
10+
use ggx::{ggx_distribution, ggx_geometry};
1111
use pdf::PDF;
1212
use ray::{find_offset_point, Ray};
1313
use sampling::pick_sphere_point;
@@ -541,10 +541,7 @@ impl Plastic {
541541

542542
/// Generate either a specular or diffuse response to a surface hit, dependent
543543
/// on a random reflect probability.
544-
fn generate_response(&self, ray: &Ray, result: &HitResult, rng: &mut Pcg64Mcg) -> Option<ScatterResult> {
545-
let cos_theta_i = (-ray.direction).dot(result.shading_normal).max(0.0);
546-
let fresnel = schlick_from_ior(cos_theta_i, self.ior);
547-
544+
fn generate_response(&self, ray: &Ray, result: &HitResult, _rng: &mut Pcg64Mcg) -> Option<ScatterResult> {
548545
let geometric_normal = if ray.direction.dot(result.geometric_normal) < 0.0 {
549546
result.geometric_normal
550547
} else {
@@ -557,49 +554,56 @@ impl Plastic {
557554
result.shading_normal
558555
};
559556

560-
let offset_point = find_offset_point(result.point, geometric_normal);
561-
562-
// probabilistically pick specular or diffuse based on Fresnel.
563-
// this material has two lobes, specular and diffuse. in the future
564-
// we need to work on returning both lobes rather than picking one randomly.
565-
if rng.random::<f32>() < fresnel {
566-
// this is the specular path
567-
let alpha = self.roughness;
568-
let microfacet_normal = ggx_sample_vndf(&shading_normal, &-ray.direction, &alpha, rng);
569-
let reflected = reflect(ray.direction, microfacet_normal);
570-
571-
if shading_normal.dot(reflected) <= 0.0 {
572-
return None;
573-
}
557+
let cos_theta_i = (-ray.direction).dot(result.shading_normal).max(0.0);
558+
let fresnel = schlick_from_ior(cos_theta_i, self.ior);
574559

575-
let specular_ray = Ray::new(offset_point, reflected);
576-
let pdf = PDF::GGX { wi: -ray.direction, normal: shading_normal, alpha };
560+
let offset_point = find_offset_point(result.point, geometric_normal);
561+
let scattered_ray = Ray::new(offset_point, ray.direction);
562+
563+
let pdf = PDF::Composite {
564+
uvw: OrthonormalBasis::new(&shading_normal),
565+
wi: -ray.direction,
566+
normal: shading_normal,
567+
alpha: self.roughness,
568+
specular_weight: fresnel,
569+
};
577570

578-
Some(ScatterResult::new(specular_ray, Vec3A::ONE, pdf, true, true))
579-
} else {
580-
// this is the diffuse path
581-
// even though ray.direction is given, a new ray with offset is generated in the integrator
582-
let scattered_ray = Ray::new(offset_point, ray.direction);
583-
let contribution = self.albedo.sample_texture(result.u, result.v, &result.point);
584-
let pdf = PDF::Cosine { uvw: OrthonormalBasis::new(&result.shading_normal) };
585-
Some(ScatterResult::new(scattered_ray, contribution, pdf, false, false))
586-
}
571+
Some(ScatterResult::new(scattered_ray, Vec3A::ONE, pdf, false, false))
587572
}
588573

589574
/// Compute how the Plastic material handles reflectance.
590-
fn compute_reflectance(&self, wo: &Ray, result: &HitResult, wi: &Ray) -> Vec3A {
591-
// we only need to compute the reflectance for the diffuse branch since
592-
// the specular branch is pre_weighted and this method is only called
593-
// for non-pre_weighted branches
575+
fn compute_reflectance(&self, ray: &Ray, result: &HitResult, scattered: &Ray) -> Vec3A {
576+
if self.roughness == 0.0 { return Vec3A::ZERO; }
577+
578+
let wi = -ray.direction;
579+
let wo = scattered.direction;
594580
let n = result.shading_normal;
595581

596-
let cos_o = n.dot(wi.direction);
597-
let cos_theta_i = (-wo.direction).dot(n);
582+
let cos_i = n.dot(wi);
583+
let cos_o = n.dot(wo);
598584

599-
if cos_o <= 0.0 || cos_theta_i < 0.0 {
585+
if cos_i <= 0.0 || cos_o <= 0.0 {
600586
return Vec3A::ZERO;
601587
}
602588

603-
Vec3A::splat(cos_o / PI)
589+
let h = (wi + wo).normalize();
590+
let cos_h = n.dot(h);
591+
592+
if cos_h <= 0.0 {
593+
return Vec3A::ZERO;
594+
}
595+
596+
let v_dot_h = wi.dot(h).max(0.0);
597+
let micro_fresnel = schlick_from_ior(v_dot_h, self.ior);
598+
599+
let d = ggx_distribution(cos_h, self.roughness);
600+
let g = ggx_geometry(cos_i, cos_o, self.roughness);
601+
let specular = Vec3A::splat(micro_fresnel * d * g / (4.0 * cos_i));
602+
603+
let macro_fresnel = schlick_from_ior(cos_i, self.ior);
604+
let albedo = self.albedo.sample_texture(result.u, result.v, &result.point);
605+
let diffuse = albedo * (1.0 - macro_fresnel) * cos_o / PI;
606+
607+
specular + diffuse
604608
}
605609
}

src/pdf.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::f32::consts::PI;
22

33
use glam::Vec3A;
4+
use rand::RngExt;
45
use rand_pcg::Pcg64Mcg;
56

67
use basis::OrthonormalBasis;
@@ -30,6 +31,7 @@ pub fn power_heuristic(f_pdf: f32, g_pdf: f32) -> f32 {
3031
/// to determine how likely a ray is to be sampled in that direction.
3132
pub enum PDF {
3233
Cosine { uvw: OrthonormalBasis },
34+
Composite { uvw: OrthonormalBasis, wi: Vec3A, normal: Vec3A, alpha: f32, specular_weight: f32 },
3335
Delta,
3436
GGX { wi: Vec3A, normal: Vec3A, alpha: f32 },
3537
Uniform,
@@ -43,6 +45,12 @@ impl PDF {
4345
let cosine = direction.dot(uvw.w());
4446
if cosine > 0.0 { cosine / PI } else { 0.0 }
4547
},
48+
PDF::Composite { uvw, wi, normal, alpha, specular_weight } => {
49+
let diffuse_pdf = PDF::Cosine { uvw: *uvw }.calculate_probability(direction);
50+
let specular_pdf = PDF::GGX { wi: *wi, normal: *normal, alpha: *alpha }.calculate_probability(direction);
51+
52+
(*specular_weight * specular_pdf) + ((1.0 - *specular_weight) * diffuse_pdf)
53+
}
4654
PDF::Delta => panic!("Delta PDF has no meaningful probability."),
4755
PDF::GGX { wi, normal, alpha } => {
4856
let cos_i = normal.dot(*wi);
@@ -73,6 +81,13 @@ impl PDF {
7381
PDF::Cosine { uvw } => {
7482
uvw.local(&cosine_sample_hemisphere(rng))
7583
},
84+
PDF::Composite { uvw, wi, normal, alpha, specular_weight } => {
85+
if rng.random::<f32>() < *specular_weight {
86+
PDF::GGX { wi: *wi, normal: *normal, alpha: *alpha }.pick_direction(rng)
87+
} else {
88+
PDF::Cosine { uvw: *uvw }.pick_direction(rng)
89+
}
90+
}
7691
PDF::Delta => panic!("Delta PDF should never be sampled directly."),
7792
PDF::GGX { wi, normal, alpha } => {
7893
let h = ggx_sample_vndf(normal, wi, alpha, rng);
22.3 KB
Binary file not shown.
-1 Bytes
Binary file not shown.

tests/images/hyperion.exr

3 Bytes
Binary file not shown.

tests/images/three_spheres.exr

0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)