From 8b93ce3d4b34f1007bc75820cbae0d6cf0bc7917 Mon Sep 17 00:00:00 2001 From: Wito Wiala Date: Fri, 29 May 2026 13:00:20 +0100 Subject: [PATCH] Fixing issue with work item definitions and light sampling on wavefront --- shared/src/wavefront/workitems.rs | 101 ++++++++- src/core/render.rs | 5 +- src/core/scene/scene.rs | 3 +- src/integrators/pipeline.rs | 54 +---- src/lib.rs | 4 +- src/utils/mod.rs | 39 ++++ src/wavefront/aggregate.rs | 27 ++- src/wavefront/integrator.rs | 341 +++++++++++++++++------------- 8 files changed, 353 insertions(+), 221 deletions(-) diff --git a/shared/src/wavefront/workitems.rs b/shared/src/wavefront/workitems.rs index 2524dfc..5f5e5ea 100644 --- a/shared/src/wavefront/workitems.rs +++ b/shared/src/wavefront/workitems.rs @@ -1,7 +1,9 @@ use crate::core::bxdf::BxDFFlags; -use crate::core::geometry::{Normal3f, Point2f, Point3f, Point3fi, Vector3f, RayDifferential}; -use crate::core::light::LightSampleContext; +use crate::core::geometry::{ + Normal3f, Point2f, Point2i, Point3f, Point3fi, RayDifferential, Vector3f, +}; use crate::core::light::Light; +use crate::core::light::LightSampleContext; use crate::core::material::Material; use crate::core::medium::{Medium, MediumInterface}; use crate::spectra::{SampledSpectrum, SampledWavelengths}; @@ -27,6 +29,8 @@ pub struct PixelSampleState { pub eta_scale: SoABuffer, pub camera_ray_weight: SoABuffer, pub visible_surface_idx: SoABuffer, + pub samples: SoABuffer, + pub p_pixel: SoABuffer, } impl SoA for PixelSampleState { @@ -48,6 +52,8 @@ impl SoA for PixelSampleState { eta_scale: alloc_soa_buffer(n, alloc), camera_ray_weight: alloc_soa_buffer(n, alloc), visible_surface_idx: alloc_soa_buffer(n, alloc), + samples: alloc_soa_buffer(n, alloc), + p_pixel: alloc_soa_buffer(n, alloc), } } @@ -60,11 +66,20 @@ impl SoA for PixelSampleState { pub struct RayWorkItem { pub ray_o: Point3f, pub ray_d: Vector3f, - pub ray_time: Float, pub ray_medium: Ptr, - pub pixel_index: u32, + pub differential: RayDifferential, pub has_differentials: bool, - pub differential: RayDifferential + pub ray_time: Float, + pub depth: u32, + pub lambda: SampledWavelengths, + pub pixel_index: u32, + pub beta: SampledSpectrum, + pub r_u: SampledSpectrum, + pub r_l: SampledSpectrum, + pub prev_intr_ctx: LightSampleContext, + pub eta_scale: Float, + pub specular_bounce: u8, + pub any_non_specular_bounces: u8, } #[repr(C)] @@ -74,9 +89,19 @@ pub struct RayWorkItemSoA { pub ray_d: SoABuffer, pub ray_time: SoABuffer, pub ray_medium: SoABuffer>, - pub pixel_index: SoABuffer, pub has_differentials: SoABuffer, pub differential: SoABuffer, + pub depth: SoABuffer, + pub lambda: SoABuffer, + pub pixel_index: SoABuffer, + pub beta: SoABuffer, + pub r_u: SoABuffer, + pub r_l: SoABuffer, + pub prev_intr_ctx: SoABuffer, + pub eta_scale: SoABuffer, + pub specular_bounce: SoABuffer, + pub any_non_specular_bounces: SoABuffer, + } impl SoA for RayWorkItemSoA { @@ -88,9 +113,18 @@ impl SoA for RayWorkItemSoA { ray_d: alloc_soa_buffer(n, alloc), ray_time: alloc_soa_buffer(n, alloc), ray_medium: alloc_soa_buffer(n, alloc), - pixel_index: alloc_soa_buffer(n, alloc), has_differentials: alloc_soa_buffer(n, alloc), differential: alloc_soa_buffer(n, alloc), + depth: alloc_soa_buffer(n, alloc), + lambda: alloc_soa_buffer(n, alloc), + pixel_index: alloc_soa_buffer(n, alloc), + beta: alloc_soa_buffer(n, alloc), + r_u: alloc_soa_buffer(n, alloc), + r_l: alloc_soa_buffer(n, alloc), + prev_intr_ctx: alloc_soa_buffer(n, alloc), + eta_scale: alloc_soa_buffer(n, alloc), + specular_bounce: alloc_soa_buffer(n, alloc), + any_non_specular_bounces: alloc_soa_buffer(n, alloc), } } @@ -100,9 +134,19 @@ impl SoA for RayWorkItemSoA { ray_d: self.ray_d.get(i), ray_time: self.ray_time.get(i), ray_medium: self.ray_medium.get(i), - pixel_index: self.pixel_index.get(i), has_differentials: self.has_differentials.get(i), differential: self.differential.get(i), + depth: self.depth.get(i), + lambda: self.lambda.get(i), + pixel_index: self.pixel_index.get(i), + beta: self.beta.get(i), + r_u: self.r_u.get(i), + r_l: self.r_l.get(i), + prev_intr_ctx: self.prev_intr_ctx.get(i), + eta_scale: self.eta_scale.get(i), + specular_bounce: self.specular_bounce.get(i), + any_non_specular_bounces: self.any_non_specular_bounces.get(i), + } } @@ -111,9 +155,18 @@ impl SoA for RayWorkItemSoA { self.ray_d.set(i, v.ray_d); self.ray_time.set(i, v.ray_time); self.ray_medium.set(i, v.ray_medium); - self.pixel_index.set(i, v.pixel_index); self.has_differentials.set(i, v.has_differentials); self.differential.set(i, v.differential); + self.depth.set(i, v.depth); + self.lambda.set(i, v.lambda); + self.pixel_index.set(i, v.pixel_index); + self.beta.set(i, v.beta); + self.r_u.set(i, v.r_u); + self.r_l.set(i, v.r_l); + self.prev_intr_ctx.set(i, v.prev_intr_ctx); + self.eta_scale.set(i, v.eta_scale); + self.specular_bounce.set(i, v.specular_bounce); + self.any_non_specular_bounces.set(i, v.any_non_specular_bounces); } } @@ -427,6 +480,8 @@ pub struct ShadowRayWorkItem { pub t_max: Float, pub lambda: SampledWavelengths, pub l_d: SampledSpectrum, + pub r_u: SampledSpectrum, + pub r_l: SampledSpectrum, pub pixel_index: u32, } @@ -439,6 +494,8 @@ pub struct ShadowRayWorkItemSoA { pub t_max: SoABuffer, pub lambda: SoABuffer, pub l_d: SoABuffer, + pub r_u: SoABuffer, + pub r_l: SoABuffer, pub pixel_index: SoABuffer, } @@ -454,6 +511,8 @@ impl SoA for ShadowRayWorkItemSoA { lambda: alloc_soa_buffer(n, alloc), l_d: alloc_soa_buffer(n, alloc), pixel_index: alloc_soa_buffer(n, alloc), + r_u: alloc_soa_buffer(n, alloc), + r_l: alloc_soa_buffer(n, alloc), } } @@ -466,6 +525,8 @@ impl SoA for ShadowRayWorkItemSoA { lambda: self.lambda.get(i), l_d: self.l_d.get(i), pixel_index: self.pixel_index.get(i), + r_u: self.r_u.get(i), + r_l: self.r_l.get(i), } } @@ -480,6 +541,28 @@ impl SoA for ShadowRayWorkItemSoA { } } +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct RaySamples { + pub direct: DirectSamples, + pub indirect: IndirectSamples, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct DirectSamples { + pub uc: Float, + pub u: Point2f, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct IndirectSamples { + pub uc: Float, + pub u: Point2f, + pub rr: Float, +} + #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct MediumSampleWorkItem { diff --git a/src/core/render.rs b/src/core/render.rs index a10651c..406419b 100644 --- a/src/core/render.rs +++ b/src/core/render.rs @@ -10,6 +10,7 @@ use shared::core::interaction::InteractionTrait; use shared::core::primitive::PrimitiveTrait; use shared::core::sampler::CameraSample; use shared::spectra::{SampledWavelengths, LAMBDA_MAX, LAMBDA_MIN}; +use crate::wavefront::integrator::CpuWavefrontRenderer; use shared::Float; pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { @@ -105,7 +106,8 @@ pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { all_lights, arena, ); - wf.render(); + let mut renderer = CpuWavefrontRenderer(wf); + renderer.render(); } else { eprintln!("RENDER: Path integrator backend"); let integrator = scene.create_integrator( @@ -125,5 +127,4 @@ pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { } Ok(()) - } diff --git a/src/core/scene/scene.rs b/src/core/scene/scene.rs index 48595fe..1fbfa92 100644 --- a/src/core/scene/scene.rs +++ b/src/core/scene/scene.rs @@ -15,12 +15,13 @@ use crate::lights::sampler::create_light_sampler; use crate::utils::parallel::{run_async, AsyncJob}; use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary}; use crate::utils::resolve_filename; +use crate::wavefront::CpuAggregate; use crate::{Arena, ArenaUpload, FileLoc}; use anyhow::{anyhow, Result}; use parking_lot::Mutex; use shared::core::aggregates::{BVHAggregate, SplitMethod}; -use shared::core::camera::CameraTrait; use shared::core::camera::Camera; +use shared::core::camera::CameraTrait; use shared::core::color::LINEAR; use shared::core::film::Film; use shared::core::filter::Filter; diff --git a/src/integrators/pipeline.rs b/src/integrators/pipeline.rs index 3a87cfd..eaf8877 100644 --- a/src/integrators/pipeline.rs +++ b/src/integrators/pipeline.rs @@ -5,8 +5,7 @@ use crate::core::film::FilmTrait; use crate::core::image::{HostImage, ImageIO, ImageMetadata}; use crate::globals::get_options; use crate::spectra::get_spectra_context; -use crate::Arena; -use indicatif::{ProgressBar, ProgressStyle}; +use crate::{Arena, PbrtProgress}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use shared::core::camera::{Camera, CameraTrait}; use shared::core::geometry::{Bounds2i, Point2i}; @@ -17,45 +16,6 @@ use shared::Float; use std::io::Write; use std::path::Path; -struct PbrtProgress { - bar: ProgressBar, -} - -impl PbrtProgress { - fn new(total_work: u64, description: &str, quiet: bool) -> Self { - if quiet { - return Self { - bar: ProgressBar::hidden(), - }; - } - - let bar = ProgressBar::new(total_work); - - bar.set_style( - ProgressStyle::default_bar() - .template("[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}") - .unwrap() - .progress_chars("=>-"), - ); - - bar.set_message(description.to_string()); - - Self { bar } - } - - fn update(&self, amount: u64) { - self.bar.inc(amount); - } - - fn done(&self) { - self.bar.finish_with_message("Done"); - } - - fn elapsed_seconds(&self) -> f32 { - self.bar.elapsed().as_secs_f32() - } -} - fn generate_tiles(bounds: Bounds2i) -> Vec { let mut tiles = Vec::new(); const TILE_SIZE: i32 = 16; @@ -101,9 +61,15 @@ pub fn render( let sample_bounds = camera.get_film().sample_bounds(); let pixel_bounds = Bounds2i::from_points( - Point2i::new(sample_bounds.p_min.x().floor() as i32, sample_bounds.p_min.y().floor() as i32), - Point2i::new(sample_bounds.p_max.x().ceil() as i32, sample_bounds.p_max.y().ceil() as i32), -); + Point2i::new( + sample_bounds.p_min.x().floor() as i32, + sample_bounds.p_min.y().floor() as i32, + ), + Point2i::new( + sample_bounds.p_max.x().ceil() as i32, + sample_bounds.p_max.y().ceil() as i32, + ), + ); println!( "pixel_bounds: {:?}, area: {}", pixel_bounds, diff --git a/src/lib.rs b/src/lib.rs index 9e30345..9cb6ebe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,7 @@ pub mod textures; pub mod utils; pub mod wavefront; -pub use utils::{Arena, FileLoc, ParameterDictionary, Upload, ArenaUpload}; +pub use utils::{Arena, ArenaUpload, FileLoc, ParameterDictionary, PbrtProgress, Upload}; pub const MAX_TAGS: u32 = 16; -pub use shared::{BasicPBRTOptions, PBRTOptions}; pub use globals::{get_options, init_pbrt}; +pub use shared::{BasicPBRTOptions, PBRTOptions}; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 78ab32a..280c55f 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -53,3 +53,42 @@ pub fn f16_to_f32(bits: u16) -> f32 { f16::from_bits(bits).to_f32() } } + +pub struct PbrtProgress { + bar: indicatif::ProgressBar, +} + +impl PbrtProgress { + pub fn new(total_work: u64, description: &str, quiet: bool) -> Self { + if quiet { + return Self { + bar: indicatif::ProgressBar::hidden(), + }; + } + + let bar = indicatif::ProgressBar::new(total_work); + + bar.set_style( + indicatif::ProgressStyle::default_bar() + .template("[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}") + .unwrap() + .progress_chars("=>-"), + ); + + bar.set_message(description.to_string()); + + Self { bar } + } + + pub fn update(&self, amount: u64) { + self.bar.inc(amount); + } + + pub fn done(&self) { + self.bar.finish_with_message("Done"); + } + + pub fn elapsed_seconds(&self) -> f32 { + self.bar.elapsed().as_secs_f32() + } +} diff --git a/src/wavefront/aggregate.rs b/src/wavefront/aggregate.rs index 17200bd..1982edc 100644 --- a/src/wavefront/aggregate.rs +++ b/src/wavefront/aggregate.rs @@ -1,11 +1,13 @@ -use crate::core::texture::{BasicTextureEvaluator, TextureEvaluator, UniversalTextureEvaluator}; -use crate::core::primitive::{Primitive, PrimitiveTrait}; -use crate::core::geometry::{Bounds3f, Ray, Vector3f, VectorLike}; -use crate::core::interaction::InteractionTrait; -use crate::core::material::MaterialTrait; use crate::globals::get_options; use rayon::prelude::*; +use shared::core::geometry::{Bounds3f, Ray, Vector3f, VectorLike}; +use shared::core::interaction::InteractionTrait; +use shared::core::material::MaterialTrait; +use shared::core::primitive::{Primitive, PrimitiveTrait}; +use shared::core::texture::{BasicTextureEvaluator, TextureEvaluator, UniversalTextureEvaluator}; +use shared::wavefront::workitems::*; use shared::wavefront::WavefrontAggregate; +use shared::Ptr; pub struct CpuAggregate { pub aggregate: Primitive, @@ -150,23 +152,19 @@ impl WavefrontAggregate for CpuAggregate { ) { let n_rays = shadow_ray_q.size().min(max_rays as u32); - for i in 0..n_rays as usize { + (0..n_rays as usize).into_par_iter().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), - crate::Ptr::null(), - ); + let ray = Ray::new(work.ray_o, work.ray_d, Some(work.ray_time), Ptr::null()); if !self.aggregate.intersect_p(&ray, Some(work.t_max)) { let pi = work.pixel_index as usize; + let ld = work.l_d / (work.r_u + work.r_l).average(); let mut l = pixel_sample_state.l.get(pi); - l += work.l_d; + l += ld; pixel_sample_state.l.set(pi, l); } - } + }); } fn intersect_shadow_tr( @@ -178,4 +176,3 @@ impl WavefrontAggregate for CpuAggregate { self.intersect_shadow(max_rays, shadow_ray_q, pixel_sample_state); } } - diff --git a/src/wavefront/integrator.rs b/src/wavefront/integrator.rs index e31eb61..6dbb341 100644 --- a/src/wavefront/integrator.rs +++ b/src/wavefront/integrator.rs @@ -1,7 +1,9 @@ use super::CpuAggregate; use crate::globals::get_options; +use crate::PbrtProgress; use shared::core::bxdf::{FArgs, TransportMode}; use shared::core::camera::{Camera, CameraTrait}; +use shared::core::film::VisibleSurface; use shared::core::filter::{Filter, FilterTrait}; use shared::core::geometry::{ Bounds2i, Point2f, Point2i, Point3f, Point3fi, Ray, RayDifferential, Vector2f, Vector3f, @@ -10,16 +12,34 @@ use shared::core::geometry::{ use shared::core::interaction::InteractionTrait; use shared::core::light::{Light, LightSampleContext, LightTrait}; use shared::core::material::{MaterialEvalContext, MaterialTrait}; -use shared::core::sampler::{CameraSample, Sampler, SamplerTrait}; +use shared::core::sampler::{get_camera_sample, CameraSample, Sampler, SamplerTrait}; use shared::core::texture::{TextureEvalContext, UniversalTextureEvaluator}; use shared::lights::sampler::{LightSampler, LightSamplerTrait}; use shared::spectra::{SampledSpectrum, SampledWavelengths}; use shared::utils::math::square; use shared::utils::sampling::power_heuristic; use shared::utils::soa::{SoA, SoAAllocator, WorkQueue}; +use shared::wavefront::workitems::*; use shared::wavefront::{WavefrontAggregate, WavefrontPathIntegrator, WavefrontRenderer}; +use shared::Ptr; +use std::ops::{Deref, DerefMut}; -impl WavefrontRenderer for WavefrontPathIntegrator { +pub struct CpuWavefrontRenderer(pub WavefrontPathIntegrator); + +impl Deref for CpuWavefrontRenderer { + type Target = WavefrontPathIntegrator; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for CpuWavefrontRenderer { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl CpuWavefrontRenderer { pub fn render(&mut self) { let film = self.camera.get_film(); let filter = film.get_filter(); @@ -31,7 +51,6 @@ impl WavefrontRenderer for WavefrontPathIntegrator { let progress = PbrtProgress::new(total_work, "Rendering", options.quiet); for sample_index in 0..self.samples_per_pixel { - // Process image in scanline batches let mut y0 = pixel_bounds.p_min.y(); while y0 < pixel_bounds.p_max.y() { let y1 = (y0 + self.scanlines_per_pass as i32).min(pixel_bounds.p_max.y()); @@ -45,7 +64,7 @@ impl WavefrontRenderer for WavefrontPathIntegrator { let current = (depth % 2) as usize; let next = ((depth + 1) % 2) as usize; - // Reset output queues before intersection + // Reset queues self.ray_queues[next].reset(); self.escaped_ray_queue.reset(); self.hit_area_light_queue.reset(); @@ -53,12 +72,20 @@ impl WavefrontRenderer for WavefrontPathIntegrator { self.universal_eval_material_queue.reset(); self.shadow_ray_queue.reset(); - // Skip if no rays to trace if self.ray_queues[current].size() == 0 { break; } - // Sorting of rays into output queues + self.generate_ray_samples(depth, sample_index); + + if depth == 0 { + let rs = self.pixel_sample_state.samples.get(0); + eprintln!( + "sample check: direct.uc={} indirect.uc={} indirect.rr={}", + rs.direct.uc, rs.indirect.uc, rs.indirect.rr + ); + } + self.aggregate.intersect_closest( self.max_queue_size as usize, &self.ray_queues[current], @@ -70,40 +97,33 @@ impl WavefrontRenderer for WavefrontPathIntegrator { &self.pixel_sample_state, ); - // Infinite light contributions self.handle_escaped_rays(); - - // Area light contributions self.handle_emissive_intersections(); - // Last depth — don't evaluate materials or sample lights if depth == self.max_depth { break; } - // Evaluate materials, sample BSDFs, sample direct lighting - // This pushes to shadow_ray_queue and ray_queues[next] self.evaluate_materials_and_bsdfs(depth); - // Add direct lighting to pixels self.aggregate.intersect_shadow( self.max_queue_size as usize, &self.shadow_ray_queue, &self.pixel_sample_state, ); } - self.update_film(y0, y1, &pixel_bounds); let batch_pixels = ((y1 - y0) * (pixel_bounds.p_max.x() - pixel_bounds.p_min.x())) as u64; - progress.inc(batch_pixels); + progress.update(batch_pixels); y0 = y1; } } } - /// Stage 1: Generate camera rays for scanlines [y0, y1). + // Enqueue camera ray and set pixel state for sample + // Compute pixel coordinates for _pixelIndex_ fn generate_camera_rays( &mut self, y0: i32, @@ -113,6 +133,8 @@ impl WavefrontRenderer for WavefrontPathIntegrator { ) { // For each pixel in the scanline range, generate a camera ray // and push it to the ray queue. Also initialize the PixelSampleState. + let filter = self.filter.clone(); + let film = self.film; for y in y0..y1 { for x in pixel_bounds.p_min.x()..pixel_bounds.p_max.x() { let p_pixel = Point2i::new(x, y); @@ -122,13 +144,9 @@ impl WavefrontRenderer for WavefrontPathIntegrator { self.sampler .start_pixel_sample(p_pixel, sample_index as i32, Some(0)); - let lambda = SampledWavelengths::sample_visible(self.sampler.get1d()); - - let camera_sample = crate::core::sampler::get_camera_sample( - &mut self.sampler, - p_pixel, - &self.filter, - ); + let lu = self.sampler.get1d(); + let lambda = film.sample_wavelengths(lu); + let camera_sample = get_camera_sample(&mut self.sampler, p_pixel, &filter); let Some(camera_ray) = self.camera.generate_ray(camera_sample, &lambda) else { continue; @@ -140,7 +158,12 @@ impl WavefrontRenderer for WavefrontPathIntegrator { // Initialize persistent pixel state let pi = pixel_index as usize; self.pixel_sample_state.l.set(pi, SampledSpectrum::new(0.0)); - self.pixel_sample_state.beta.set(pi, camera_ray.weight); + self.pixel_sample_state + .beta + .set(pi, SampledSpectrum::new(1.0)); + self.pixel_sample_state + .camera_ray_weight + .set(pi, camera_ray.weight); self.pixel_sample_state.lambda.set(pi, lambda); self.pixel_sample_state .r_u @@ -159,6 +182,7 @@ impl WavefrontRenderer for WavefrontPathIntegrator { self.pixel_sample_state .prev_intr_ctx .set(pi, LightSampleContext::default()); + self.pixel_sample_state.p_pixel.set(pi, p_pixel); // Push ray to queue self.ray_queues[0].push(RayWorkItem { @@ -174,7 +198,7 @@ impl WavefrontRenderer for WavefrontPathIntegrator { } } - /// Handle escaped rays — evaluate infinite lights. + /// Evaluate infinite lights. fn handle_escaped_rays(&self) { let n = self.escaped_ray_queue.size(); for i in 0..n as usize { @@ -185,7 +209,7 @@ impl WavefrontRenderer for WavefrontPathIntegrator { // Evaluate all infinite lights for light_ptr in &self.infinite_lights { let light = light_ptr.get().unwrap(); - let ray = crate::core::geometry::Ray::new(w.ray_o, w.ray_d, None, Ptr::null()); + let ray = Ray::new(w.ray_o, w.ray_d, None, Ptr::null()); let le = light.le(&ray, &w.lambda); if le.is_black() { continue; @@ -258,11 +282,7 @@ impl WavefrontRenderer for WavefrontPathIntegrator { for i in 0..n as usize { let w = unsafe { queue.storage.get(i) }; let pi = w.pixel_index as usize; - - let lambda = self.pixel_sample_state.lambda.get(pi); - let beta = self.pixel_sample_state.beta.get(pi); - let any_non_specular = self.pixel_sample_state.any_non_specular_bounces.get(pi) != 0; - let eta_scale = self.pixel_sample_state.eta_scale.get(pi); + let rs = self.pixel_sample_state.samples.get(pi); let Some(material) = w.material.get() else { continue; @@ -286,148 +306,140 @@ impl WavefrontRenderer for WavefrontPathIntegrator { ns: w.ns, dpdus: w.dpdu, }; + let lambda = w.lambda; let mut bsdf = material.get_bsdf(&tex_eval, &ctx, &lambda); - if bsdf.flags().is_empty() { continue; } - - if self.regularize && any_non_specular { + if self.regularize && w.any_non_specular_bounces { bsdf.regularize(); } - if depth >= self.max_depth { - continue; + // BSDF sampling for indirect ray + let wo = w.wo; + let ns = w.ns; + if let Some(bs) = bsdf.sample_f(wo, rs.indirect.uc, rs.indirect.u, FArgs::default()) { + let wi = bs.wi; + let mut beta = w.beta * bs.f * wi.abs_dot(ns.into()) / bs.pdf; + let r_u = w.r_u; + let r_l = if bs.pdf_is_proportional { + r_u / bsdf.pdf(wo, wi, FArgs::default()) + } else { + r_u / bs.pdf + }; + + let mut eta_scale = w.eta_scale; + if bs.is_transmissive() { + eta_scale *= square(bs.eta); + } + + // Russian roulette + let rr_beta = (beta * eta_scale / r_u.average()).max_component_value(); + if rr_beta < 1.0 && w.depth >= 1 { + let q = (1.0 - rr_beta).max(0.0_f32); + if rs.indirect.rr < q { + beta = SampledSpectrum::new(0.0); + } else { + beta /= 1.0 - q; + } + } + + if !beta.is_black() { + let ray = Ray::spawn(&Point3fi::new_from_point(w.p), &w.n, w.time, wi); + let any_non_specular = !bs.is_specular() || w.any_non_specular_bounces; + let ctx = LightSampleContext { + pi: Point3fi::new_from_point(w.p), + n: w.n, + ns, + }; + + // Push indirect ray with updated path state + self.ray_queues[next].push(RayWorkItem { + ray_o: ray.o, + ray_d: ray.d, + ray_time: w.time, + ray_medium: Ptr::null(), + pixel_index: w.pixel_index, + has_differentials: false, + differential: RayDifferential::default(), + }); + + // Update PixelSampleState for next bounce + self.pixel_sample_state.beta.set(pi, beta); + self.pixel_sample_state.r_u.set(pi, r_u); + self.pixel_sample_state.r_l.set(pi, r_l); + self.pixel_sample_state.depth.set(pi, w.depth + 1); + self.pixel_sample_state.eta_scale.set(pi, eta_scale); + self.pixel_sample_state + .specular_bounce + .set(pi, bs.is_specular() as u8); + self.pixel_sample_state + .any_non_specular_bounces + .set(pi, any_non_specular as u8); + self.pixel_sample_state.prev_intr_ctx.set(pi, ctx); + } } - // Sample a light, compute contribution, - // push shadow ray with deferred visibility - if bsdf.flags().is_non_specular() { + // --- Direct lighting (independent of BSDF sample) --- + let flags = bsdf.flags(); + if flags.is_non_specular() { let light_ctx = LightSampleContext { pi: Point3fi::new_from_point(w.p), n: w.n, - ns: w.ns, + ns, }; if let Some(sampled_light) = self .light_sampler - .sample_with_context(&light_ctx, self.sampler.get1d()) + .sample_with_context(&light_ctx, rs.direct.uc) { - if let Some(ls) = sampled_light.light.sample_li( - &light_ctx, - self.sampler.get2d(), - &lambda, - true, - ) { + if let Some(ls) = + sampled_light + .light + .sample_li(&light_ctx, rs.direct.u, &lambda, true) + { if !ls.l.is_black() && ls.pdf > 0.0 { let wi = ls.wi; - if let Some(f_val) = bsdf.f(w.wo, wi, TransportMode::Radiance) { - let f_cos = f_val * wi.abs_dot(w.ns.into()); - if !f_cos.is_black() { - let p_l = sampled_light.p * ls.pdf; - let l_d = if sampled_light.light.light_type().is_delta_light() { - beta * ls.l * f_cos / p_l - } else { - let p_b = bsdf.pdf(w.wo, wi, FArgs::default()); - let w_l = power_heuristic(1, p_l, 1, p_b); - beta * w_l * ls.l * f_cos / p_l - }; + if let Some(f) = bsdf.f(wo, wi, TransportMode::Radiance) { + if !f.is_black() { + let beta = w.beta * f * wi.abs_dot(ns.into()); + let light_pdf = ls.pdf * sampled_light.p; + let bsdf_pdf = + if sampled_light.light.light_type().is_delta_light() { + 0.0 + } else { + bsdf.pdf(wo, wi, FArgs::default()) + }; + let r_u = w.r_u * bsdf_pdf; + let r_l = w.r_u * light_pdf; + let ld = beta * ls.l; - if !l_d.is_black() { - let ray_o = Ray::offset_origin( - &Point3fi::new_from_point(w.p), - &w.n, - &wi, - ); - let t_max = (1.0 - 1e-4) - * (Point3f::from(ls.p_light.p()) - ray_o).norm() - / wi.norm(); + let ray_o = Ray::offset_origin( + &Point3fi::new_from_point(w.p), + &w.n, + &wi, + ); + let t_max = (1.0 - 1e-4) + * (Point3f::from(ls.p_light.p()) - ray_o).norm() + / wi.norm(); - self.shadow_ray_queue.push(ShadowRayWorkItem { - ray_o, - ray_d: wi, - ray_time: w.time, - t_max, - lambda, - l_d, - pixel_index: w.pixel_index, - }); - } + self.shadow_ray_queue.push(ShadowRayWorkItem { + ray_o, + ray_d: wi, + ray_time: w.time, + t_max, + lambda, + l_d: ld, + r_u, + r_l, + pixel_index: w.pixel_index, + }); } } } } } } - - // Sample BSDF for next bounce - let wo = w.wo; - let Some(bs) = bsdf.sample_f( - wo, - self.sampler.get1d(), - self.sampler.get2d(), - FArgs::default(), - ) else { - continue; - }; - - let f_cos = bs.f * bs.wi.abs_dot(w.ns.into()); - if f_cos.is_black() || bs.pdf == 0.0 { - continue; - } - let new_beta = beta * f_cos / bs.pdf; - - let new_depth = depth + 1; - - // Russian roulette - if new_depth > 3 { - let rr_beta = new_beta.max_component_value(); - if rr_beta < 0.25 { - let q = (1.0 - rr_beta).max(0.0_f32); - if self.sampler.get1d() < q { - continue; - } - } - } - - let ray_o = Ray::offset_origin(&Point3fi::new_from_point(w.p), &w.n, &bs.wi); - - // Update PixelSampleState - self.pixel_sample_state.beta.set(pi, new_beta); - self.pixel_sample_state.depth.set(pi, new_depth); - self.pixel_sample_state - .specular_bounce - .set(pi, bs.is_specular() as u8); - self.pixel_sample_state - .any_non_specular_bounces - .set(pi, (any_non_specular || !bs.is_specular()) as u8); - self.pixel_sample_state.eta_scale.set( - pi, - if bs.is_transmissive() { - eta_scale * square(bs.eta) - } else { - eta_scale - }, - ); - self.pixel_sample_state.prev_intr_ctx.set( - pi, - LightSampleContext { - pi: Point3fi::new_from_point(w.p), - n: w.n, - ns: w.ns, - }, - ); - - // Push next bounce ray - self.ray_queues[next].push(RayWorkItem { - ray_o, - ray_d: bs.wi, - ray_time: w.time, - ray_medium: Ptr::null(), - pixel_index: w.pixel_index, - has_differentials: true, - differential: RayDifferential::default(), - }); } } @@ -439,6 +451,9 @@ impl WavefrontRenderer for WavefrontPathIntegrator { for y in y0..y1 { for x in pixel_bounds.p_min.x()..pixel_bounds.p_max.x() { let l = self.pixel_sample_state.l.get(pi); + let camera_weight = self.pixel_sample_state.camera_ray_weight.get(pi); + let weigthed_l = l * camera_weight; + let lambda = self.pixel_sample_state.lambda.get(pi); let filter_weight = self.pixel_sample_state.filter_weight.get(pi); let p_film = self.pixel_sample_state.p_film.get(pi); @@ -446,9 +461,9 @@ impl WavefrontRenderer for WavefrontPathIntegrator { // Add sample to film self.film.add_sample( Point2i::new(p_film.x() as i32, p_film.y() as i32), - l, + weigthed_l, &lambda, - Some(&crate::core::film::VisibleSurface::default()), + Some(&VisibleSurface::default()), filter_weight, ); @@ -456,4 +471,34 @@ impl WavefrontRenderer for WavefrontPathIntegrator { } } } + + fn generate_ray_samples(&mut self, depth: u32, sample_index: u32) { + let current = (depth % 2) as usize; + let n = self.ray_queues[current].size(); + let dimension = 6 + 7 * depth; + + for i in 0..n as usize { + let w = unsafe { self.ray_queues[current].storage.get(i) }; + let pi = w.pixel_index as usize; + let p_pixel = self.pixel_sample_state.p_pixel.get(pi); + + let mut sampler = self.sampler.clone(); + sampler.start_pixel_sample(p_pixel, sample_index as i32, Some(dimension)); + + self.pixel_sample_state.samples.set( + pi, + RaySamples { + direct: DirectSamples { + uc: sampler.get1d(), + u: sampler.get2d(), + }, + indirect: IndirectSamples { + uc: sampler.get1d(), + u: sampler.get2d(), + rr: sampler.get1d(), + }, + }, + ); + } + } }