Fixing issue with work item definitions and light sampling on wavefront

This commit is contained in:
Wito Wiala 2026-05-29 13:00:20 +01:00
parent 5ff8044158
commit 8b93ce3d4b
8 changed files with 353 additions and 221 deletions

View file

@ -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<Float>,
pub camera_ray_weight: SoABuffer<SampledSpectrum>,
pub visible_surface_idx: SoABuffer<u32>,
pub samples: SoABuffer<RaySamples>,
pub p_pixel: SoABuffer<Point2i>,
}
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<Medium>,
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<Vector3f>,
pub ray_time: SoABuffer<Float>,
pub ray_medium: SoABuffer<Ptr<Medium>>,
pub pixel_index: SoABuffer<u32>,
pub has_differentials: SoABuffer<bool>,
pub differential: SoABuffer<RayDifferential>,
pub depth: SoABuffer<u32>,
pub lambda: SoABuffer<SampledWavelengths>,
pub pixel_index: SoABuffer<u32>,
pub beta: SoABuffer<SampledSpectrum>,
pub r_u: SoABuffer<SampledSpectrum>,
pub r_l: SoABuffer<SampledSpectrum>,
pub prev_intr_ctx: SoABuffer<LightSampleContext>,
pub eta_scale: SoABuffer<Float>,
pub specular_bounce: SoABuffer<u8>,
pub any_non_specular_bounces: SoABuffer<u8>,
}
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<Float>,
pub lambda: SoABuffer<SampledWavelengths>,
pub l_d: SoABuffer<SampledSpectrum>,
pub r_u: SoABuffer<SampledSpectrum>,
pub r_l: SoABuffer<SampledSpectrum>,
pub pixel_index: SoABuffer<u32>,
}
@ -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 {

View file

@ -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(())
}

View file

@ -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;

View file

@ -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<Bounds2i> {
let mut tiles = Vec::new();
const TILE_SIZE: i32 = 16;
@ -101,9 +61,15 @@ pub fn render<T>(
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,

View file

@ -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};

View file

@ -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()
}
}

View file

@ -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);
}
}

View file

@ -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<CpuAggregate> {
pub struct CpuWavefrontRenderer(pub WavefrontPathIntegrator<CpuAggregate>);
impl Deref for CpuWavefrontRenderer {
type Target = WavefrontPathIntegrator<CpuAggregate>;
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<CpuAggregate> {
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<CpuAggregate> {
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<CpuAggregate> {
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<CpuAggregate> {
&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<CpuAggregate> {
) {
// 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<CpuAggregate> {
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<CpuAggregate> {
// 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<CpuAggregate> {
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<CpuAggregate> {
}
}
/// 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<CpuAggregate> {
// 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<CpuAggregate> {
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,54 +306,114 @@ impl WavefrontRenderer for WavefrontPathIntegrator<CpuAggregate> {
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);
}
// Sample a light, compute contribution,
// push shadow ray with deferred visibility
if bsdf.flags().is_non_specular() {
// 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);
}
}
// --- 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, rs.direct.u, &lambda, true)
{
if let Some(ls) = sampled_light.light.sample_li(
&light_ctx,
self.sampler.get2d(),
&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
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 {
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
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,
@ -349,7 +429,9 @@ impl WavefrontRenderer for WavefrontPathIntegrator<CpuAggregate> {
ray_time: w.time,
t_max,
lambda,
l_d,
l_d: ld,
r_u,
r_l,
pixel_index: w.pixel_index,
});
}
@ -359,76 +441,6 @@ impl WavefrontRenderer for WavefrontPathIntegrator<CpuAggregate> {
}
}
}
// 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(),
});
}
}
fn update_film(&self, y0: i32, y1: i32, pixel_bounds: &Bounds2i) {
@ -439,6 +451,9 @@ impl WavefrontRenderer for WavefrontPathIntegrator<CpuAggregate> {
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<CpuAggregate> {
// 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<CpuAggregate> {
}
}
}
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(),
},
},
);
}
}
}