pbrt/src/wavefront/integrator.rs
2026-06-02 21:46:11 +01:00

594 lines
23 KiB
Rust

use super::CpuAggregate;
use crate::globals::get_options;
use crate::lights::sampler::create_light_sampler;
use crate::Arena;
use crate::ParameterDictionary;
use crate::PbrtProgress;
use rayon::prelude::*;
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,
VectorLike,
};
use shared::core::interaction::InteractionTrait;
use shared::core::light::{Light, LightSampleContext, LightTrait};
use shared::core::material::{MaterialEvalContext, MaterialTrait};
use shared::core::primitive::Primitive;
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::{gvec, Ptr};
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
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
}
}
pub trait CreateWavefront
where
Self: Sized,
{
fn create(
parameters: ParameterDictionary,
camera: Arc<Camera>,
sampler: Arc<Sampler>,
aggregate: Arc<Primitive>,
lights: Vec<Arc<Light>>,
arena: &Arena,
) -> CpuWavefrontRenderer {
let max_depth = parameters
.get_one_int("maxdepth", 5)
.expect("Could not obtain depth value");
let regularize = parameters
.get_one_bool("regularize", false)
.expect("Could not obtain regularize flag value");
let spp = sampler.samples_per_pixel() as u32;
let film = camera.base().film;
let pixel_bounds = film.pixel_bounds();
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;
let max_samples = 1024u32 * 1024;
let scanlines_per_pass = (max_samples / res_x).max(1);
let max_queue_size = res_x * scanlines_per_pass;
let mut infinite_lights = gvec();
for light in &lights {
if light.light_type().is_infinite() {
infinite_lights.push(arena.alloc(**light));
}
}
// for light in
let cpu_aggregate = CpuAggregate::new(*aggregate);
CpuWavefrontRenderer(WavefrontPathIntegrator {
aggregate: cpu_aggregate,
camera: (*camera).clone(),
sampler: (*sampler).clone(),
max_depth: max_depth.try_into().unwrap(),
film,
filter,
samples_per_pixel: spp,
regularize,
infinite_lights,
max_queue_size,
scanlines_per_pass,
light_sampler,
ray_queues: [
WorkQueue::new(
RayWorkItemSoA::allocate(max_queue_size, arena),
max_queue_size,
),
WorkQueue::new(
RayWorkItemSoA::allocate(max_queue_size, arena),
max_queue_size,
),
],
shadow_ray_queue: WorkQueue::new(
ShadowRayWorkItemSoA::allocate(max_queue_size, arena),
max_queue_size,
),
escaped_ray_queue: WorkQueue::new(
EscapedRayWorkItemSoA::allocate(max_queue_size, arena),
max_queue_size,
),
hit_area_light_queue: WorkQueue::new(
HitAreaLightWorkItemSoA::allocate(max_queue_size, arena),
max_queue_size,
),
basic_eval_material_queue: WorkQueue::new(
MaterialEvalWorkItemSoA::allocate(max_queue_size, arena),
max_queue_size,
),
universal_eval_material_queue: WorkQueue::new(
MaterialEvalWorkItemSoA::allocate(max_queue_size, arena),
max_queue_size,
),
pixel_sample_state: PixelSampleState::allocate(max_queue_size, arena),
})
}
}
impl CreateWavefront for CpuWavefrontRenderer {}
impl CpuWavefrontRenderer {
pub fn render(&mut self) {
let film = self.camera.get_film();
let filter = film.get_filter();
let pixel_bounds = film.pixel_bounds();
let resolution = pixel_bounds.diagonal();
let total_work = (pixel_bounds.area() as u64) * (self.samples_per_pixel as u64);
let options = get_options();
let progress = PbrtProgress::new(total_work, "Rendering", options.quiet);
for sample_index in 0..self.samples_per_pixel {
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());
// Reset the primary ray queue for this set
self.ray_queues[0].reset();
self.generate_camera_rays(y0, y1, sample_index, &pixel_bounds);
for depth in 0..=self.max_depth {
let current = (depth % 2) as usize;
let next = ((depth + 1) % 2) as usize;
// Reset queues before tracing next batch of rays
self.ray_queues[next].reset();
self.escaped_ray_queue.reset();
self.hit_area_light_queue.reset();
self.basic_eval_material_queue.reset();
self.universal_eval_material_queue.reset();
self.shadow_ray_queue.reset();
if self.ray_queues[current].size() == 0 {
break;
}
self.generate_ray_samples(depth, sample_index);
self.aggregate.intersect_closest(
self.max_queue_size as usize,
&self.ray_queues[current],
&self.escaped_ray_queue,
&self.hit_area_light_queue,
&self.basic_eval_material_queue,
&self.universal_eval_material_queue,
&self.ray_queues[next],
&self.pixel_sample_state,
);
self.handle_escaped_rays();
self.handle_emissive_intersections();
if depth == self.max_depth {
break;
}
self.evaluate_materials_and_bsdfs(depth);
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.update(batch_pixels);
y0 = y1;
}
}
}
fn generate_camera_rays(
&mut self,
y0: i32,
y1: i32,
sample_index: u32,
pixel_bounds: &Bounds2i,
) {
let filter = self.filter;
let film = self.film;
let camera = &self.camera;
let sampler_proto = &self.sampler;
let pixel_sample_state = &self.pixel_sample_state;
let ray_queue = &self.ray_queues[0];
let x_resolution = pixel_bounds.p_max.x() - pixel_bounds.p_min.x();
// Iterate the whole queue, exactly like pbrt's ParallelFor(maxQueueSize).
// The loop index IS the pixelSampleState key; pPixel is derived from it,
// and every later kernel addresses state by this same absolute index.
(0..self.max_queue_size as usize)
.into_par_iter()
.for_each(|pixel_index| {
let p_pixel = Point2i::new(
pixel_bounds.p_min.x() + (pixel_index as i32 % x_resolution),
y0 + (pixel_index as i32 / x_resolution),
);
pixel_sample_state.p_pixel.set(pixel_index, p_pixel);
// Skipped pixels contribute nothing; their slots are simply never
// populated, and update_film filters them by the same bounds test.
if !pixel_bounds.contains_exclusive(p_pixel) {
return;
}
let mut sampler = sampler_proto.clone();
sampler.start_pixel_sample(p_pixel, sample_index as i32, Some(0));
let lu = sampler.get1d();
let lambda = film.sample_wavelengths(lu);
let camera_sample = get_camera_sample(&mut sampler, p_pixel, &filter);
pixel_sample_state
.l
.set(pixel_index, SampledSpectrum::new(0.0));
pixel_sample_state.lambda.set(pixel_index, lambda);
pixel_sample_state
.filter_weight
.set(pixel_index, camera_sample.filter_weight);
pixel_sample_state
.p_film
.set(pixel_index, camera_sample.p_film);
let Some(camera_ray) = camera.generate_ray(camera_sample, &lambda) else {
pixel_sample_state
.camera_ray_weight
.set(pixel_index, SampledSpectrum::new(0.0));
return;
};
pixel_sample_state
.camera_ray_weight
.set(pixel_index, camera_ray.weight);
ray_queue.push(RayWorkItem {
ray: camera_ray.ray,
depth: 0,
pixel_index: pixel_index as u32,
lambda,
beta: SampledSpectrum::new(1.0),
r_u: SampledSpectrum::new(1.0),
r_l: SampledSpectrum::new(1.0),
prev_intr_ctx: LightSampleContext::default(),
eta_scale: 1.0,
specular_bounce: 0,
any_non_specular_bounces: 0,
});
});
}
/// Evaluate infinite lights.
fn handle_escaped_rays(&self) {
let n = self.escaped_ray_queue.size();
let infinite_lights = &self.infinite_lights;
let light_sampler = &self.light_sampler;
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| {
let w = unsafe { escaped_ray_queue.storage.get(i) };
let mut l_contrib = SampledSpectrum::new(0.0);
for light_ptr in infinite_lights {
let light = light_ptr.get().unwrap();
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;
}
if w.depth == 0 || w.specular_bounce {
l_contrib += w.beta * le / w.r_u.average();
} else {
// MIS: combine BSDF and light sampling weights via ratio tracking
let ctx = w.prev_intr_ctx;
let light_choice_pdf = light_sampler.pmf_with_context(&ctx, light);
let r_l = w.r_l * light_choice_pdf * light.pdf_li(&ctx, w.ray_d, true);
l_contrib += w.beta * le / (w.r_u + r_l).average();
}
}
if !l_contrib.is_black() {
let pi = w.pixel_index as usize;
let mut l = pixel_sample_state.l.get(pi);
l += l_contrib;
pixel_sample_state.l.set(pi, l);
}
});
}
fn handle_emissive_intersections(&self) {
let n = self.hit_area_light_queue.size();
let light_sampler = &self.light_sampler;
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| {
let w = unsafe { hit_area_light_queue.storage.get(i) };
let light = w.area_light.get().unwrap();
let le = light.l(w.p, w.n, w.uv, w.wo, &w.lambda);
if le.is_black() {
return;
}
let l_contrib = if w.depth == 0 || w.specular_bounce {
w.beta * le / w.r_u.average()
} else {
let ctx = w.prev_intr_ctx;
let light_choice_pdf = light_sampler.pmf_with_context(&ctx, light);
// wi from previous interaction to this light hit
let wi = (w.p - Point3f::from(ctx.pi)).normalize();
let light_pdf = light_choice_pdf * light.pdf_li(&ctx, wi, true);
let r_l = w.r_l * light_pdf;
w.beta * le / (w.r_u + r_l).average()
};
if !l_contrib.is_black() {
let pi = w.pixel_index as usize;
let mut l = pixel_sample_state.l.get(pi);
l += l_contrib;
pixel_sample_state.l.set(pi, l);
}
});
}
fn evaluate_materials_and_bsdfs(&mut self, depth: u32) {
self.evaluate_material_queue_impl(depth, false);
self.evaluate_material_queue_impl(depth, true);
}
fn evaluate_material_queue_impl(&mut self, depth: u32, use_universal: bool) {
let queue = if use_universal {
&self.universal_eval_material_queue
} else {
&self.basic_eval_material_queue
};
let n = queue.size();
let next = ((depth + 1) % 2) as usize;
let pixel_sample_state = &self.pixel_sample_state;
let light_sampler = &self.light_sampler;
let shadow_ray_queue = &self.shadow_ray_queue;
let next_ray_queue = &self.ray_queues[next];
let regularize = self.regularize;
(0..n as usize).into_par_iter().for_each(|i| {
let w = unsafe { queue.storage.get(i) };
let pi = w.pixel_index as usize;
let rs = pixel_sample_state.samples.get(pi);
let Some(material) = w.material.get() else {
return;
};
let tex_eval = UniversalTextureEvaluator;
let ctx = MaterialEvalContext {
texture: TextureEvalContext {
p: w.p.into(),
dpdx: Vector3f::zero(),
dpdy: Vector3f::zero(),
n: w.n,
uv: w.uv,
dudx: 0.0,
dudy: 0.0,
dvdx: 0.0,
dvdy: 0.0,
face_index: w.face_index,
},
wo: w.wo,
ns: w.ns,
dpdus: w.dpdus,
};
let lambda = w.lambda;
let mut bsdf = material.get_bsdf(&tex_eval, &ctx, &lambda);
if bsdf.flags().is_empty() {
return;
}
if regularize && w.any_non_specular_bounces {
bsdf.regularize();
}
// BSDF sample 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);
}
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(&w.p, &w.n, w.time, wi);
let any_non_specular = !bs.is_specular() || w.any_non_specular_bounces;
let ctx = LightSampleContext {
pi: w.p,
n: w.n,
ns,
};
next_ray_queue.push(RayWorkItem {
ray,
depth: w.depth + 1,
pixel_index: w.pixel_index,
lambda,
beta,
r_u,
r_l,
prev_intr_ctx: ctx,
eta_scale,
specular_bounce: bs.is_specular() as u8,
any_non_specular_bounces: any_non_specular as u8,
});
}
}
// Direct lighting
let flags = bsdf.flags();
if flags.is_non_specular() {
let light_ctx = LightSampleContext {
pi: w.p,
n: w.n,
ns,
};
if let Some(sampled_light) =
light_sampler.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 !ls.l.is_black() && ls.pdf > 0.0 {
let wi = ls.wi;
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;
let ray_o = Ray::spawn_to_interaction(
&w.p,
&w.n,
w.time,
&ls.p_light.pi(),
&ls.p_light.n(),
);
let t_max = (1.0 - 1e-4)
* (Point3f::from(ls.p_light.p()) - ray_o.o).norm()
/ wi.norm();
shadow_ray_queue.push(ShadowRayWorkItem {
ray_o: ray_o.o,
ray_d: ray_o.d,
ray_time: w.time,
t_max: 1.0 - 1e-4,
lambda,
l_d: ld,
r_u,
r_l,
pixel_index: w.pixel_index,
});
}
}
}
}
}
}
});
}
fn update_film(&self, _y0: i32, _y1: i32, pixel_bounds: &Bounds2i) {
(0..self.max_queue_size as usize)
.into_par_iter()
.for_each(|pixel_index| {
let p_pixel = self.pixel_sample_state.p_pixel.get(pixel_index);
if !pixel_bounds.contains_exclusive(p_pixel) {
return;
}
let l = self.pixel_sample_state.l.get(pixel_index);
let camera_weight = self.pixel_sample_state.camera_ray_weight.get(pixel_index);
let weighted_l = l * camera_weight;
let lambda = self.pixel_sample_state.lambda.get(pixel_index);
let filter_weight = self.pixel_sample_state.filter_weight.get(pixel_index);
self.film
.add_sample(p_pixel, weighted_l, &lambda, None, filter_weight);
});
}
fn generate_ray_samples(&mut self, depth: u32, sample_index: u32) {
let current = (depth % 2) as usize;
let ray_queue = &self.ray_queues[current];
let n = ray_queue.size();
let dimension = 6 + 7 * depth;
let pixel_sample_state = &self.pixel_sample_state;
let sampler_proto = &self.sampler;
(0..n as usize).into_par_iter().for_each(|i| {
let w = unsafe { ray_queue.storage.get(i) };
let pi = w.pixel_index as usize;
let p_pixel = pixel_sample_state.p_pixel.get(pi);
let mut sampler = sampler_proto.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(),
},
},
);
});
}
}