basic_intersect for DiskShape computed the plane hit with interval arithmetic where
pbrt uses a scalar (shapes.h: Float tShapeHit = (height - Float(oi.z)) / Float(di.z)). It produced NaN, and since every NaN comparison is false neither the t range test nor the subsequent dist2 > radius^2 test could reject it. The disk reported a hit on every ray tested against it, and the NaN reached dpdu/ dpdv, the shading normal, and the area-light MIS pdf. NaN is absorbing in the film, so one bad sample poisoned pixel RgbSum permantenly. Wavefront now works
This commit is contained in:
parent
1b1344c393
commit
5038ed8270
4 changed files with 34 additions and 10 deletions
4
.cargo/config.toml
Normal file
4
.cargo/config.toml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[build]
|
||||
# Enable the host's AVX2/FMA. Measured ~12% on the CPU wavefront (13700H).
|
||||
# NOTE: pins binaries to this CPU family. Use "x86-64-v3" if you ship them.
|
||||
rustflags = ["-C", "target-cpu=native"]
|
||||
|
|
@ -63,14 +63,22 @@ impl DiskShape {
|
|||
if Float::from(di.z()) == 0. {
|
||||
return None;
|
||||
}
|
||||
let t_shape_hit: Interval = (self.height - oi.z()) / di.z();
|
||||
if t_shape_hit.high <= 0. || t_shape_hit.low >= t_max {
|
||||
// pbrt computes the plane hit as a SCALAR:
|
||||
// Float tShapeHit = (height - Float(oi.z)) / Float(di.z);
|
||||
// if (tShapeHit <= 0 || tShapeHit >= tMax) return {};
|
||||
// Interval arithmetic here produced NaN, and neither `high <= 0` nor
|
||||
// `low >= t_max` rejects a NaN (every NaN comparison is false). The NaN then
|
||||
// flowed into p_hit -- so the `dist2 > radius^2` test could not reject it either,
|
||||
// and the disk swallowed every ray it was tested against -- and on into dpdu/dpdv
|
||||
// and the shading normal, poisoning the area-light MIS pdf.
|
||||
let t_shape_hit: Float = (self.height - Float::from(oi.z())) / Float::from(di.z());
|
||||
if t_shape_hit <= 0. || t_shape_hit >= t_max {
|
||||
return None;
|
||||
}
|
||||
|
||||
let oi_f = Point3f::from(oi);
|
||||
let di_f = Vector3f::from(di);
|
||||
let t = Float::from(t_shape_hit);
|
||||
let t = t_shape_hit;
|
||||
let p_hit: Point3f = oi_f + di_f * t;
|
||||
|
||||
let dist2 = square(p_hit.x()) + square(p_hit.y());
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::wavefront::integrator::RAYON_MIN_LEN;
|
||||
use crate::globals::get_options;
|
||||
use log::debug;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
|
@ -51,7 +52,7 @@ impl WavefrontAggregate for CpuAggregate {
|
|||
let n_rays = ray_q.size().min(max_rays as u32);
|
||||
|
||||
// Intersect ray with the scene and enqueue resulting work
|
||||
(0..n_rays as usize).into_par_iter().for_each(|i| {
|
||||
(0..n_rays as usize).into_par_iter().with_min_len(RAYON_MIN_LEN).for_each(|i| {
|
||||
let r = unsafe { ray_q.get(i) };
|
||||
|
||||
let Some(si) = self.aggregate.intersect(&r.ray, None) else {
|
||||
|
|
@ -172,7 +173,7 @@ impl WavefrontAggregate for CpuAggregate {
|
|||
) {
|
||||
let n_rays = shadow_ray_q.size().min(max_rays as u32);
|
||||
|
||||
(0..n_rays as usize).into_par_iter().for_each(|i| {
|
||||
(0..n_rays as usize).into_par_iter().with_min_len(RAYON_MIN_LEN).for_each(|i| {
|
||||
let work = unsafe { shadow_ray_q.get(i) };
|
||||
|
||||
let ray = Ray::new(work.ray.o, work.ray.d, Some(work.ray.time), Ptr::null());
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ use std::ops::{Deref, DerefMut};
|
|||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
/// Minimum rayon chunk. The per-item bodies are short enough that splitting all the way
|
||||
/// down costs more in work-stealing than it recovers in balance (~3x on this scene).
|
||||
pub(crate) const RAYON_MIN_LEN: usize = 64;
|
||||
|
||||
static DIAG_EVAL_ENTER: AtomicU32 = AtomicU32::new(0);
|
||||
static DIAG_BSDF_EMPTY: AtomicU32 = AtomicU32::new(0);
|
||||
static DIAG_NON_SPECULAR_SKIP: AtomicU32 = AtomicU32::new(0);
|
||||
|
|
@ -90,8 +94,13 @@ where
|
|||
let filter = Ptr::from(&film.base().filter);
|
||||
let light_sampler = create_light_sampler("power", &lights, arena);
|
||||
let res_x = pixel_bounds.diagonal().x() as u32;
|
||||
// pbrt (wavefront/integrator.cpp:231) rebalances so the last pass isn't mostly
|
||||
// empty; without it every queue is oversized.
|
||||
let res_y = pixel_bounds.diagonal().y() as u32;
|
||||
let max_samples = 1024u32 * 1024;
|
||||
let scanlines_per_pass = (max_samples / res_x).max(1);
|
||||
let mut scanlines_per_pass = (max_samples / res_x).max(1);
|
||||
let n_passes = res_y.div_ceil(scanlines_per_pass).max(1);
|
||||
scanlines_per_pass = res_y.div_ceil(n_passes).max(1);
|
||||
let max_queue_size = res_x * scanlines_per_pass;
|
||||
|
||||
let mut infinite_lights: GVec<LightIdx> = gvec();
|
||||
|
|
@ -323,6 +332,7 @@ impl CpuWavefrontRenderer {
|
|||
// and every later kernel addresses state by this same absolute index.
|
||||
(0..self.max_queue_size as usize)
|
||||
.into_par_iter()
|
||||
.with_min_len(RAYON_MIN_LEN)
|
||||
.for_each(|pixel_index| {
|
||||
let p_pixel = Point2i::new(
|
||||
pixel_bounds.p_min.x() + (pixel_index as i32 % x_resolution),
|
||||
|
|
@ -386,7 +396,7 @@ impl CpuWavefrontRenderer {
|
|||
let pixel_sample_state = &self.pixel_sample_state;
|
||||
let escaped_ray_queue = &self.escaped_ray_queue;
|
||||
|
||||
(0..n as usize).into_par_iter().for_each(|i| {
|
||||
(0..n as usize).into_par_iter().with_min_len(RAYON_MIN_LEN).for_each(|i| {
|
||||
let w = unsafe { escaped_ray_queue.storage.get(i) };
|
||||
|
||||
let mut l_contrib = SampledSpectrum::new(0.0);
|
||||
|
|
@ -425,7 +435,7 @@ impl CpuWavefrontRenderer {
|
|||
let pixel_sample_state = &self.pixel_sample_state;
|
||||
let hit_area_light_queue = &self.hit_area_light_queue;
|
||||
|
||||
(0..n as usize).into_par_iter().for_each(|i| {
|
||||
(0..n as usize).into_par_iter().with_min_len(RAYON_MIN_LEN).for_each(|i| {
|
||||
let w = unsafe { hit_area_light_queue.get(i) };
|
||||
if w.area_light.is_none() {
|
||||
return;
|
||||
|
|
@ -483,7 +493,7 @@ impl CpuWavefrontRenderer {
|
|||
let next_ray_queue = &self.ray_queues[next];
|
||||
let regularize = self.regularize;
|
||||
|
||||
(0..n as usize).into_par_iter().for_each(|i| {
|
||||
(0..n as usize).into_par_iter().with_min_len(RAYON_MIN_LEN).for_each(|i| {
|
||||
let w = unsafe { queue.storage.get(i) };
|
||||
// Fires on every material-queue pass (reset each depth/batch/sample), so it
|
||||
// ran continuously and dominated render time. Gated behind cpu_debug.
|
||||
|
|
@ -726,6 +736,7 @@ impl CpuWavefrontRenderer {
|
|||
fn update_film(&self, _y0: i32, _y1: i32, pixel_bounds: &Bounds2i) {
|
||||
(0..self.max_queue_size as usize)
|
||||
.into_par_iter()
|
||||
.with_min_len(RAYON_MIN_LEN)
|
||||
.for_each(|pixel_index| {
|
||||
let p_pixel = self.pixel_sample_state.pixel.get(pixel_index);
|
||||
if !pixel_bounds.contains_exclusive(p_pixel) {
|
||||
|
|
@ -749,7 +760,7 @@ impl CpuWavefrontRenderer {
|
|||
let pixel_sample_state = &self.pixel_sample_state;
|
||||
let sampler_proto = &self.sampler;
|
||||
|
||||
(0..n as usize).into_par_iter().for_each(|i| {
|
||||
(0..n as usize).into_par_iter().with_min_len(RAYON_MIN_LEN).for_each(|i| {
|
||||
let w = unsafe { ray_queue.storage.get(i) };
|
||||
let dimension = 6 + 7 * w.depth;
|
||||
let pi = w.pixel_index as usize;
|
||||
|
|
|
|||
Loading…
Reference in a new issue