Broken state
This commit is contained in:
parent
8b93ce3d4b
commit
f18aed2c91
3 changed files with 213 additions and 253 deletions
|
|
@ -1,6 +1,6 @@
|
|||
use crate::core::bxdf::BxDFFlags;
|
||||
use crate::core::geometry::{
|
||||
Normal3f, Point2f, Point2i, Point3f, Point3fi, RayDifferential, Vector3f,
|
||||
Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray, RayDifferential, Vector3f,
|
||||
};
|
||||
use crate::core::light::Light;
|
||||
use crate::core::light::LightSampleContext;
|
||||
|
|
@ -64,12 +64,7 @@ impl SoA for PixelSampleState {
|
|||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RayWorkItem {
|
||||
pub ray_o: Point3f,
|
||||
pub ray_d: Vector3f,
|
||||
pub ray_medium: Ptr<Medium>,
|
||||
pub differential: RayDifferential,
|
||||
pub has_differentials: bool,
|
||||
pub ray_time: Float,
|
||||
pub ray: Ray,
|
||||
pub depth: u32,
|
||||
pub lambda: SampledWavelengths,
|
||||
pub pixel_index: u32,
|
||||
|
|
@ -85,12 +80,7 @@ pub struct RayWorkItem {
|
|||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct RayWorkItemSoA {
|
||||
pub ray_o: SoABuffer<Point3f>,
|
||||
pub ray_d: SoABuffer<Vector3f>,
|
||||
pub ray_time: SoABuffer<Float>,
|
||||
pub ray_medium: SoABuffer<Ptr<Medium>>,
|
||||
pub has_differentials: SoABuffer<bool>,
|
||||
pub differential: SoABuffer<RayDifferential>,
|
||||
pub ray: SoABuffer<Ray>,
|
||||
pub depth: SoABuffer<u32>,
|
||||
pub lambda: SoABuffer<SampledWavelengths>,
|
||||
pub pixel_index: SoABuffer<u32>,
|
||||
|
|
@ -101,7 +91,6 @@ pub struct RayWorkItemSoA {
|
|||
pub eta_scale: SoABuffer<Float>,
|
||||
pub specular_bounce: SoABuffer<u8>,
|
||||
pub any_non_specular_bounces: SoABuffer<u8>,
|
||||
|
||||
}
|
||||
|
||||
impl SoA for RayWorkItemSoA {
|
||||
|
|
@ -109,12 +98,7 @@ impl SoA for RayWorkItemSoA {
|
|||
|
||||
fn allocate(n: u32, alloc: &dyn SoAAllocator) -> Self {
|
||||
Self {
|
||||
ray_o: alloc_soa_buffer(n, alloc),
|
||||
ray_d: alloc_soa_buffer(n, alloc),
|
||||
ray_time: alloc_soa_buffer(n, alloc),
|
||||
ray_medium: alloc_soa_buffer(n, alloc),
|
||||
has_differentials: alloc_soa_buffer(n, alloc),
|
||||
differential: alloc_soa_buffer(n, alloc),
|
||||
ray: alloc_soa_buffer(n, alloc),
|
||||
depth: alloc_soa_buffer(n, alloc),
|
||||
lambda: alloc_soa_buffer(n, alloc),
|
||||
pixel_index: alloc_soa_buffer(n, alloc),
|
||||
|
|
@ -130,12 +114,7 @@ impl SoA for RayWorkItemSoA {
|
|||
|
||||
unsafe fn get(&self, i: usize) -> RayWorkItem {
|
||||
RayWorkItem {
|
||||
ray_o: self.ray_o.get(i),
|
||||
ray_d: self.ray_d.get(i),
|
||||
ray_time: self.ray_time.get(i),
|
||||
ray_medium: self.ray_medium.get(i),
|
||||
has_differentials: self.has_differentials.get(i),
|
||||
differential: self.differential.get(i),
|
||||
ray: self.ray.get(i),
|
||||
depth: self.depth.get(i),
|
||||
lambda: self.lambda.get(i),
|
||||
pixel_index: self.pixel_index.get(i),
|
||||
|
|
@ -146,17 +125,11 @@ impl SoA for RayWorkItemSoA {
|
|||
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),
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn set(&self, i: usize, v: RayWorkItem) {
|
||||
self.ray_o.set(i, v.ray_o);
|
||||
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.has_differentials.set(i, v.has_differentials);
|
||||
self.differential.set(i, v.differential);
|
||||
self.ray.set(i, v.ray);
|
||||
self.depth.set(i, v.depth);
|
||||
self.lambda.set(i, v.lambda);
|
||||
self.pixel_index.set(i, v.pixel_index);
|
||||
|
|
@ -166,7 +139,8 @@ impl SoA for RayWorkItemSoA {
|
|||
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);
|
||||
self.any_non_specular_bounces
|
||||
.set(i, v.any_non_specular_bounces);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -538,6 +512,8 @@ impl SoA for ShadowRayWorkItemSoA {
|
|||
self.lambda.set(i, v.lambda);
|
||||
self.l_d.set(i, v.l_d);
|
||||
self.pixel_index.set(i, v.pixel_index);
|
||||
self.r_u.set(i, v.r_u);
|
||||
self.r_l.set(i, v.r_l);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use crate::globals::get_options;
|
||||
use rayon::prelude::*;
|
||||
use shared::core::geometry::{Bounds3f, Ray, Vector3f, VectorLike};
|
||||
use shared::core::geometry::{Bounds3f, Ray, 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::core::texture::BasicTextureEvaluator;
|
||||
use shared::core::texture::TextureEvaluator;
|
||||
use shared::wavefront::workitems::*;
|
||||
use shared::wavefront::WavefrontAggregate;
|
||||
use shared::Ptr;
|
||||
|
|
@ -33,86 +34,73 @@ impl WavefrontAggregate for CpuAggregate {
|
|||
basic_eval_mtl_q: &MaterialEvalQueue,
|
||||
universal_eval_mtl_q: &MaterialEvalQueue,
|
||||
next_ray_q: &RayQueue,
|
||||
pixel_sample_state: &PixelSampleState,
|
||||
_pixel_sample_state: &PixelSampleState,
|
||||
) {
|
||||
let n_rays = ray_q.size().min(max_rays as u32);
|
||||
let options = get_options();
|
||||
|
||||
// Intersect _r_'s ray with the scene and enqueue resulting work
|
||||
(0..n_rays as usize).into_par_iter().for_each(|i| {
|
||||
let work = unsafe { ray_q.get(i) };
|
||||
let r = unsafe { ray_q.get(i) };
|
||||
|
||||
let ray = Ray::new(work.ray_o, work.ray_d, Some(work.ray_time), work.ray_medium);
|
||||
|
||||
let pi = work.pixel_index as usize;
|
||||
let beta = pixel_sample_state.beta.get(pi);
|
||||
let r_u = pixel_sample_state.r_u.get(pi);
|
||||
let r_l = pixel_sample_state.r_l.get(pi);
|
||||
let lambda = pixel_sample_state.lambda.get(pi);
|
||||
let depth = pixel_sample_state.depth.get(pi);
|
||||
let specular_bounce = pixel_sample_state.specular_bounce.get(pi) != 0;
|
||||
let any_non_specular = pixel_sample_state.any_non_specular_bounces.get(pi) != 0;
|
||||
let eta_scale = pixel_sample_state.eta_scale.get(pi);
|
||||
let prev_intr_ctx = pixel_sample_state.prev_intr_ctx.get(pi);
|
||||
|
||||
let Some(si) = self.aggregate.intersect(&ray, None) else {
|
||||
let Some(si) = self.aggregate.intersect(&r.ray, None) else {
|
||||
// EnqueueMiss - push to escaped queue with r's path state
|
||||
escaped_ray_q.push(EscapedRayWorkItem {
|
||||
ray_o: work.ray_o,
|
||||
ray_d: work.ray_d,
|
||||
lambda,
|
||||
pixel_index: work.pixel_index,
|
||||
beta,
|
||||
r_u,
|
||||
r_l,
|
||||
depth,
|
||||
specular_bounce,
|
||||
prev_intr_ctx,
|
||||
ray_o: r.ray.o,
|
||||
ray_d: r.ray.d,
|
||||
lambda: r.lambda,
|
||||
pixel_index: r.pixel_index,
|
||||
beta: r.beta,
|
||||
r_u: r.r_u,
|
||||
r_l: r.r_l,
|
||||
depth: r.depth,
|
||||
specular_bounce: r.specular_bounce != 0,
|
||||
prev_intr_ctx: r.prev_intr_ctx,
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
let intr = &si.intr;
|
||||
|
||||
// Medium transition — re-push as indirect ray with same path state
|
||||
if intr.material.is_null() {
|
||||
let ray_o = Ray::offset_origin(&intr.pi(), &intr.n(), &work.ray_d);
|
||||
let ray_medium = if work.ray_d.dot(intr.n().into()) > 0.0 {
|
||||
intr.common.medium_interface.outside
|
||||
} else {
|
||||
intr.common.medium_interface.inside
|
||||
};
|
||||
next_ray_q.push(RayWorkItem {
|
||||
ray_o,
|
||||
ray_d: work.ray_d,
|
||||
ray_time: work.ray_time,
|
||||
ray_medium,
|
||||
has_differentials: work.has_differentials,
|
||||
differential: work.differential,
|
||||
pixel_index: work.pixel_index,
|
||||
});
|
||||
let mut next = r;
|
||||
intr.spawn_ray(r.ray.d);
|
||||
next.ray = intr.spawn_ray(r.ray.d);
|
||||
next_ray_q.push(next);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for area light hit
|
||||
// Area light hit
|
||||
if !intr.area_light.is_null() {
|
||||
hit_area_light_q.push(HitAreaLightWorkItem {
|
||||
area_light: intr.area_light,
|
||||
p: intr.p(),
|
||||
n: intr.n(),
|
||||
uv: intr.common.uv,
|
||||
wo: -work.ray_d,
|
||||
lambda,
|
||||
pixel_index: work.pixel_index,
|
||||
beta,
|
||||
r_u,
|
||||
r_l,
|
||||
depth,
|
||||
specular_bounce,
|
||||
prev_intr_ctx,
|
||||
wo: -r.ray.d,
|
||||
lambda: r.lambda,
|
||||
pixel_index: r.pixel_index,
|
||||
beta: r.beta,
|
||||
r_u: r.r_u,
|
||||
r_l: r.r_l,
|
||||
depth: r.depth,
|
||||
specular_bounce: r.specular_bounce != 0,
|
||||
prev_intr_ctx: r.prev_intr_ctx,
|
||||
});
|
||||
}
|
||||
|
||||
// Determine which material evaluation queue to use based on
|
||||
// whether the material's textures can be evaluated with the
|
||||
// basic evaluator (cheaper) or need the universal one.
|
||||
// Material eval queue dispatch
|
||||
// let material = *intr.material.get().unwrap();
|
||||
// let displacement = material.get_displacement();
|
||||
// let eval_q = if material.can_evaluate_textures(&BasicTextureEvaluator)
|
||||
// && (displacement.is_null()
|
||||
// || BasicTextureEvaluator.can_evaluate(&[displacement], &[]))
|
||||
// {
|
||||
// basic_eval_mtl_q
|
||||
// } else {
|
||||
// universal_eval_mtl_q
|
||||
// };
|
||||
//
|
||||
let material = *intr.material.get().unwrap();
|
||||
let eval_q = if material.can_evaluate_textures(&BasicTextureEvaluator) {
|
||||
basic_eval_mtl_q
|
||||
|
|
@ -127,19 +115,19 @@ impl WavefrontAggregate for CpuAggregate {
|
|||
dpdu: intr.shading.dpdu,
|
||||
dpdv: intr.shading.dpdv,
|
||||
uv: intr.common.uv,
|
||||
wo: -work.ray_d,
|
||||
time: work.ray_time,
|
||||
wo: -r.ray.d,
|
||||
time: r.ray.time,
|
||||
face_index: intr.face_index,
|
||||
material: intr.material,
|
||||
area_light: intr.area_light,
|
||||
medium_interface: intr.common.medium_interface,
|
||||
pixel_index: work.pixel_index,
|
||||
lambda,
|
||||
beta,
|
||||
r_u,
|
||||
any_non_specular_bounces: any_non_specular,
|
||||
depth,
|
||||
eta_scale,
|
||||
pixel_index: r.pixel_index,
|
||||
lambda: r.lambda,
|
||||
beta: r.beta,
|
||||
r_u: r.r_u,
|
||||
any_non_specular_bounces: r.any_non_specular_bounces != 0,
|
||||
depth: r.depth,
|
||||
eta_scale: r.eta_scale,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -156,7 +144,6 @@ impl WavefrontAggregate for CpuAggregate {
|
|||
let work = unsafe { shadow_ray_q.get(i) };
|
||||
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use super::CpuAggregate;
|
||||
use crate::globals::get_options;
|
||||
use crate::PbrtProgress;
|
||||
use rayon::prelude::*;
|
||||
use shared::core::bxdf::{FArgs, TransportMode};
|
||||
use shared::core::camera::{Camera, CameraTrait};
|
||||
use shared::core::film::VisibleSurface;
|
||||
|
|
@ -39,6 +40,9 @@ impl DerefMut for CpuWavefrontRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
use std::sync::Mutex;
|
||||
static M: Mutex<()> = Mutex::new(());
|
||||
|
||||
impl CpuWavefrontRenderer {
|
||||
pub fn render(&mut self) {
|
||||
let film = self.camera.get_film();
|
||||
|
|
@ -64,7 +68,7 @@ impl CpuWavefrontRenderer {
|
|||
let current = (depth % 2) as usize;
|
||||
let next = ((depth + 1) % 2) as usize;
|
||||
|
||||
// Reset queues
|
||||
// Reset queues before tracing next batch of rays
|
||||
self.ray_queues[next].reset();
|
||||
self.escaped_ray_queue.reset();
|
||||
self.hit_area_light_queue.reset();
|
||||
|
|
@ -78,14 +82,6 @@ impl CpuWavefrontRenderer {
|
|||
|
||||
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],
|
||||
|
|
@ -122,8 +118,6 @@ impl CpuWavefrontRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
// Enqueue camera ray and set pixel state for sample
|
||||
// Compute pixel coordinates for _pixelIndex_
|
||||
fn generate_camera_rays(
|
||||
&mut self,
|
||||
y0: i32,
|
||||
|
|
@ -132,136 +126,146 @@ impl CpuWavefrontRenderer {
|
|||
pixel_bounds: &Bounds2i,
|
||||
) {
|
||||
// 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();
|
||||
// and push it to the ray queue. Initialize the PixelSampleState.
|
||||
let filter = self.filter;
|
||||
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);
|
||||
let camera = &self.camera;
|
||||
let sampler_proto = &self.sampler;
|
||||
let pixel_sample_state = &self.pixel_sample_state;
|
||||
let ray_queue = &self.ray_queues[0];
|
||||
|
||||
// TODO: proper sampler state per pixel/sample
|
||||
// For now, use a simple approach
|
||||
self.sampler
|
||||
.start_pixel_sample(p_pixel, sample_index as i32, Some(0));
|
||||
let x_resolution = pixel_bounds.p_max.x() - pixel_bounds.p_min.x();
|
||||
let n_scanlines = y1 - y0;
|
||||
let total = (n_scanlines * x_resolution) as usize;
|
||||
|
||||
let lu = self.sampler.get1d();
|
||||
(0..total).into_par_iter().for_each(|idx| {
|
||||
let dx = idx as i32 % x_resolution;
|
||||
let dy = idx as i32 / x_resolution;
|
||||
let p_pixel = Point2i::new(pixel_bounds.p_min.x() + dx, y0 + dy);
|
||||
let pixel_index = idx as u32;
|
||||
let pi = idx;
|
||||
|
||||
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 self.sampler, p_pixel, &filter);
|
||||
let camera_sample = get_camera_sample(&mut sampler, p_pixel, &filter);
|
||||
|
||||
let Some(camera_ray) = self.camera.generate_ray(camera_sample, &lambda) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Compute pixel index for this sample
|
||||
let pixel_index = self.ray_queues[0].size();
|
||||
|
||||
// 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, 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
|
||||
.set(pi, SampledSpectrum::new(1.0));
|
||||
self.pixel_sample_state
|
||||
.r_l
|
||||
.set(pi, SampledSpectrum::new(1.0));
|
||||
self.pixel_sample_state.depth.set(pi, 0);
|
||||
self.pixel_sample_state.specular_bounce.set(pi, 1);
|
||||
self.pixel_sample_state.any_non_specular_bounces.set(pi, 0);
|
||||
self.pixel_sample_state.eta_scale.set(pi, 1.0);
|
||||
self.pixel_sample_state.p_film.set(pi, camera_sample.p_film);
|
||||
self.pixel_sample_state
|
||||
pixel_sample_state.l.set(pi, SampledSpectrum::new(0.0));
|
||||
pixel_sample_state.lambda.set(pi, lambda);
|
||||
pixel_sample_state
|
||||
.filter_weight
|
||||
.set(pi, camera_sample.filter_weight);
|
||||
self.pixel_sample_state
|
||||
.prev_intr_ctx
|
||||
.set(pi, LightSampleContext::default());
|
||||
self.pixel_sample_state.p_pixel.set(pi, p_pixel);
|
||||
pixel_sample_state.p_film.set(pi, camera_sample.p_film);
|
||||
pixel_sample_state.p_pixel.set(pi, p_pixel);
|
||||
|
||||
// Push ray to queue
|
||||
self.ray_queues[0].push(RayWorkItem {
|
||||
ray_o: camera_ray.ray.o,
|
||||
ray_d: camera_ray.ray.d,
|
||||
ray_time: camera_ray.ray.time,
|
||||
ray_medium: camera_ray.ray.medium,
|
||||
pixel_index: pixel_index,
|
||||
has_differentials: true,
|
||||
differential: RayDifferential::default(),
|
||||
let Some(camera_ray) = camera.generate_ray(camera_sample, &lambda) else {
|
||||
pixel_sample_state
|
||||
.camera_ray_weight
|
||||
.set(pi, SampledSpectrum::new(0.0));
|
||||
return;
|
||||
};
|
||||
|
||||
pixel_sample_state
|
||||
.camera_ray_weight
|
||||
.set(pi, camera_ray.weight);
|
||||
|
||||
ray_queue.push(RayWorkItem {
|
||||
ray: camera_ray.ray,
|
||||
depth: 0,
|
||||
pixel_index,
|
||||
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();
|
||||
for i in 0..n as usize {
|
||||
let w = unsafe { self.escaped_ray_queue.storage.get(i) };
|
||||
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);
|
||||
|
||||
// Evaluate all infinite lights
|
||||
for light_ptr in &self.infinite_lights {
|
||||
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;
|
||||
return;
|
||||
}
|
||||
|
||||
if w.depth == 0 || w.specular_bounce {
|
||||
// No MIS for direct camera rays or specular bounces
|
||||
l_contrib += w.beta * le / w.r_u.average();
|
||||
} else {
|
||||
// MIS with light sampling
|
||||
// TODO: compute light PDF for MIS weight
|
||||
// For now, use unidirectional weight only
|
||||
l_contrib += w.beta * le / w.r_u.average();
|
||||
// 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 = self.pixel_sample_state.l.get(pi);
|
||||
let mut l = pixel_sample_state.l.get(pi);
|
||||
l += l_contrib;
|
||||
self.pixel_sample_state.l.set(pi, l);
|
||||
}
|
||||
pixel_sample_state.l.set(pi, l);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Handle emissive intersections — area light contribution with MIS.
|
||||
fn handle_emissive_intersections(&self) {
|
||||
let n = self.hit_area_light_queue.size();
|
||||
for i in 0..n as usize {
|
||||
let w = unsafe { self.hit_area_light_queue.storage.get(i) };
|
||||
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() {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
let l_contrib = if w.depth == 0 || w.specular_bounce {
|
||||
w.beta * le / w.r_u.average()
|
||||
} else {
|
||||
// MIS: combine BSDF and light sampling weights
|
||||
// TODO: full MIS with light sampler PDF
|
||||
w.beta * le / w.r_u.average()
|
||||
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 = self.pixel_sample_state.l.get(pi);
|
||||
let mut l = pixel_sample_state.l.get(pi);
|
||||
l += l_contrib;
|
||||
self.pixel_sample_state.l.set(pi, l);
|
||||
}
|
||||
pixel_sample_state.l.set(pi, l);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn evaluate_materials_and_bsdfs(&mut self, depth: u32) {
|
||||
|
|
@ -279,13 +283,20 @@ impl CpuWavefrontRenderer {
|
|||
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| {
|
||||
for i in 0..n as usize {
|
||||
let w = unsafe { queue.storage.get(i) };
|
||||
let pi = w.pixel_index as usize;
|
||||
let rs = self.pixel_sample_state.samples.get(pi);
|
||||
let rs = pixel_sample_state.samples.get(pi);
|
||||
|
||||
let Some(material) = w.material.get() else {
|
||||
continue;
|
||||
return;
|
||||
};
|
||||
|
||||
let tex_eval = UniversalTextureEvaluator;
|
||||
|
|
@ -309,13 +320,13 @@ impl CpuWavefrontRenderer {
|
|||
let lambda = w.lambda;
|
||||
let mut bsdf = material.get_bsdf(&tex_eval, &ctx, &lambda);
|
||||
if bsdf.flags().is_empty() {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if self.regularize && w.any_non_specular_bounces {
|
||||
bsdf.regularize();
|
||||
}
|
||||
|
||||
// BSDF sampling for indirect ray
|
||||
// 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()) {
|
||||
|
|
@ -333,7 +344,6 @@ impl CpuWavefrontRenderer {
|
|||
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);
|
||||
|
|
@ -353,34 +363,23 @@ impl CpuWavefrontRenderer {
|
|||
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(),
|
||||
next_ray_queue.push(RayWorkItem {
|
||||
ray,
|
||||
depth: w.depth + 1,
|
||||
pixel_index: w.pixel_index,
|
||||
has_differentials: false,
|
||||
differential: RayDifferential::default(),
|
||||
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,
|
||||
});
|
||||
|
||||
// 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) ---
|
||||
// Direct lighting — independent
|
||||
let flags = bsdf.flags();
|
||||
if flags.is_non_specular() {
|
||||
let light_ctx = LightSampleContext {
|
||||
|
|
@ -388,7 +387,6 @@ impl CpuWavefrontRenderer {
|
|||
n: w.n,
|
||||
ns,
|
||||
};
|
||||
|
||||
if let Some(sampled_light) = self
|
||||
.light_sampler
|
||||
.sample_with_context(&light_ctx, rs.direct.uc)
|
||||
|
|
@ -441,48 +439,47 @@ impl CpuWavefrontRenderer {
|
|||
}
|
||||
}
|
||||
}
|
||||
// });
|
||||
}
|
||||
|
||||
fn update_film(&self, y0: i32, y1: i32, pixel_bounds: &Bounds2i) {
|
||||
// The pixel_sample_state indices map to rays generated in
|
||||
// generate_camera_rays. We need to walk the same pixel order
|
||||
// and read back the accumulated L values.
|
||||
let mut pi = 0usize;
|
||||
for y in y0..y1 {
|
||||
for x in pixel_bounds.p_min.x()..pixel_bounds.p_max.x() {
|
||||
let x_resolution = pixel_bounds.p_max.x() - pixel_bounds.p_min.x();
|
||||
let n_scanlines = y1 - y0;
|
||||
let total = (n_scanlines * x_resolution) as usize;
|
||||
|
||||
(0..total).into_par_iter().for_each(|idx| {
|
||||
let pi = idx;
|
||||
let p_pixel = self.pixel_sample_state.p_pixel.get(pi);
|
||||
if !pixel_bounds.contains_exclusive(p_pixel) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 weighted_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);
|
||||
|
||||
// Add sample to film
|
||||
self.film.add_sample(
|
||||
Point2i::new(p_film.x() as i32, p_film.y() as i32),
|
||||
weigthed_l,
|
||||
&lambda,
|
||||
Some(&VisibleSurface::default()),
|
||||
filter_weight,
|
||||
);
|
||||
|
||||
pi += 1;
|
||||
}
|
||||
}
|
||||
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 n = self.ray_queues[current].size();
|
||||
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;
|
||||
|
||||
for i in 0..n as usize {
|
||||
let w = unsafe { self.ray_queues[current].storage.get(i) };
|
||||
(0..n as usize).into_par_iter().for_each(|i| {
|
||||
let _g = M.lock().unwrap();
|
||||
let w = unsafe { ray_queue.storage.get(i) };
|
||||
let pi = w.pixel_index as usize;
|
||||
let p_pixel = self.pixel_sample_state.p_pixel.get(pi);
|
||||
let p_pixel = pixel_sample_state.p_pixel.get(pi);
|
||||
|
||||
let mut sampler = self.sampler.clone();
|
||||
let mut sampler = sampler_proto.clone();
|
||||
sampler.start_pixel_sample(p_pixel, sample_index as i32, Some(dimension));
|
||||
|
||||
self.pixel_sample_state.samples.set(
|
||||
|
|
@ -499,6 +496,6 @@ impl CpuWavefrontRenderer {
|
|||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue