I've been working through PBRT, implementing my own version of it in Rust. Right now I'm working on shadow rays, and it looks like pbrt doesn't do anything to account for specular behavior, and treats materials like perfect glass as opaque with respect to shadows. I decided to try to implement a version that accounts for this behavior, but I don't see a difference visually. Ignoring the bug that causes some of my threads to get stuck, the top one is my attempt at accounting for specular behavior and the bottom one is the 'normal' implementation that casts a single ray to see if it hits the light or not.
I can't see any difference in how the ground looks, and I don't believe that's how it should look for a perfectly specular glass model. My implementation is as follows (it's in Rust, so I've commented to get the general idea across):
pub fn occlusion_intersects(&self, ray: &Ray, tmin: f64, tmax: f64, depth: i32) -> Option<HitRecord> {
// find intersection with scene
let ans = self.intersects(ray, tmin, tmax);
// if no intersection was found, return None
if ans.is_none() {
return None
}
let mut ans = ans.unwrap(); // rust stuff, ignore this
// compute the bsdf for the material
Material::compute_scattering_default(&mut ans);
// we only want to specular transmission or reflection
let bsdf_type = BSDF_TRANSMISSION | BSDF_REFLECTION | BSDF_SPECULAR;
// sample_f returns a new outgoing direction according to a bsdf that matches
// one of the types above, or (0, 0, 0) if no matching bsdfs.
let (wi, .. ) = ans.bsdf.sample_f(&ans.wo, bsdf_type);
// if there was no specular bsdf or we've recursed too many times:
if (wi == util::zero_vector() || depth == MAX_DEPTH {
// then return the current intersection to check if it
// hit the expected light object
return Some(ans);
}
// otherwise make a new ray going in the new specular direction
let new_ray = ans.spawn_ray(&wi);
// and return that answer
return self.occlusion_intersects(&new_ray, tmin, tmax, depth + 1);
}
And for reference, the default implementation would be:
pub fn occlusion_intersects(&self, ray: &Ray, tmin: f64, tmax: f64, depth: i32) -> Option<HitRecord> {
// return the first intersection, ignore all specular behavior
return self.intersects(ray, tmin, tmax);
}
Is there something wrong with my implementation? I kind of guessed as to how it would work, but the logic written in the comments makes sense to me.