Moving to index based access of resources and instances

This commit is contained in:
Wito Wiala 2026-08-31 13:10:53 +01:00
parent 7db434535b
commit 8e8a3845f8
16 changed files with 287 additions and 122 deletions

4
.gitignore vendored
View file

@ -15,7 +15,9 @@ tests/
scenes/ scenes/
compile.sh compile.sh
output/ output/
*.md
!README.md !README.md
!INSTALL.md !INSTALL.md
docs/ docs/
GTAGS
GPATH
GRTAGS

View file

@ -18,7 +18,7 @@ use crate::core::texture::{FloatTexture, UniversalTextureEvaluator};
use crate::core::{LightIdx, MaterialIdx}; use crate::core::{LightIdx, MaterialIdx};
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{clamp, difference_of_products, square}; use crate::utils::math::{clamp, difference_of_products, square};
use crate::{GVec, Ptr, Float}; use crate::{Ptr, Float};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
#[repr(C)] #[repr(C)]
@ -243,7 +243,7 @@ impl SurfaceInteraction {
&self, &self,
w: Vector3f, w: Vector3f,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
lights: &GVec<Light>, lights: &[Light],
) -> SampledSpectrum { ) -> SampledSpectrum {
if self.area_light.is_none() { if self.area_light.is_none() {
return SampledSpectrum::new(0.); return SampledSpectrum::new(0.);
@ -253,7 +253,7 @@ impl SurfaceInteraction {
} }
pub fn compute_differentials(&mut self, r: &Ray, camera: &Camera, samples_per_pixel: i32) { pub fn compute_differentials(&mut self, r: &Ray, camera: &Camera, samples_per_pixel: i32) {
let computed = if !r.has_differentials { let computed = if r.has_differentials {
let diff = r.differential; let diff = r.differential;
let dot_rx = self.common.n.dot(diff.rx_direction.into()); let dot_rx = self.common.n.dot(diff.rx_direction.into());
let dot_ry = self.common.n.dot(diff.ry_direction.into()); let dot_ry = self.common.n.dot(diff.ry_direction.into());
@ -341,7 +341,7 @@ impl SurfaceInteraction {
let new_ray = Ray::spawn(&self.pi(), &self.n(), ray.time, ray.d); let new_ray = Ray::spawn(&self.pi(), &self.n(), ray.time, ray.d);
ray.o = new_ray.o; ray.o = new_ray.o;
// Skipping other variables, since they should not change when passing through surface // Skipping other variables, since they should not change when passing through surface
if !ray.has_differentials { if ray.has_differentials {
let mut diff = ray.differential; let mut diff = ray.differential;
diff.rx_origin += diff.rx_direction * t; diff.rx_origin += diff.rx_direction * t;
diff.ry_origin += diff.ry_direction * t; diff.ry_origin += diff.ry_direction * t;

View file

@ -37,12 +37,12 @@ unsafe impl Send for DiffuseAreaLight {}
unsafe impl Sync for DiffuseAreaLight {} unsafe impl Sync for DiffuseAreaLight {}
impl DiffuseAreaLight { impl DiffuseAreaLight {
fn l_base(&self, n: Normal3f, wo: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { // fn l_base(&self, n: Normal3f, wo: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
if !self.two_sided && n.dot(wo.into()) <= 0.0 { // if !self.two_sided && n.dot(wo.into()) <= 0.0 {
return SampledSpectrum::new(0.0); // return SampledSpectrum::new(0.0);
} // }
self.lemit.sample(lambda) * self.scale // self.lemit.sample(lambda) * self.scale
} // }
fn alpha_masked(&self, intr: &Interaction) -> bool { fn alpha_masked(&self, intr: &Interaction) -> bool {
if self.alpha.is_null() { if self.alpha.is_null() {
@ -73,7 +73,12 @@ impl LightTrait for DiffuseAreaLight {
_allow_incomplete_pdf: bool, _allow_incomplete_pdf: bool,
) -> Option<LightLiSample> { ) -> Option<LightLiSample> {
let shape_ctx = ShapeSampleContext::new(ctx.pi, ctx.n, ctx.ns, 0.0); let shape_ctx = ShapeSampleContext::new(ctx.pi, ctx.n, ctx.ns, 0.0);
let ss = self.shape.sample_from_context(&shape_ctx, u)?; let ss = self.shape.sample_from_context(&shape_ctx, u)?;
if ss.pdf == 0.0 || (ss.intr.p() - ctx.p()).norm_squared() == 0.0 {
return None;
}
let mut intr = ss.intr; let mut intr = ss.intr;
intr.set_medium_interface(self.base.medium_interface); intr.set_medium_interface(self.base.medium_interface);
let p = intr.p(); let p = intr.p();
@ -108,9 +113,10 @@ impl LightTrait for DiffuseAreaLight {
w: Vector3f, w: Vector3f,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum {
if self.two_sided && n.dot(w.into()) < 0. { if !self.two_sided && n.dot(w.into()) < 0. {
return SampledSpectrum::new(0.); return SampledSpectrum::new(0.);
} }
let intr = Interaction::Surface(SurfaceInteraction::new_minimal( let intr = Interaction::Surface(SurfaceInteraction::new_minimal(
Point3fi::new_from_point(p), Point3fi::new_from_point(p),
uv, uv,
@ -149,7 +155,8 @@ impl LightTrait for DiffuseAreaLight {
rgb[c] = self.image.get_channel(Point2i::new(x, y), c as i32); rgb[c] = self.image.get_channel(Point2i::new(x, y), c as i32);
} }
l += RGBIlluminantSpectrum::new(&self.colorspace, rgb.clamp_zero()).sample(&lambda); l += RGBIlluminantSpectrum::new(&self.colorspace, rgb.clamp_zero())
.sample(&lambda);
} }
} }
l *= self.scale / (self.image.resolution().x() * self.image.resolution().y()) as Float; l *= self.scale / (self.image.resolution().x() * self.image.resolution().y()) as Float;
@ -162,7 +169,7 @@ impl LightTrait for DiffuseAreaLight {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
fn preprocess(&mut self, _scene_bounds: &Bounds3f) { fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
return return;
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]

View file

@ -393,7 +393,7 @@ impl BilinearPatchShape {
let Some(normals) = shading_normals else { let Some(normals) = shading_normals else {
return; return;
}; };
let n00 = normals[1]; let n00 = normals[0];
let n10 = normals[1]; let n10 = normals[1];
let n01 = normals[2]; let n01 = normals[2];
let n11 = normals[3]; let n11 = normals[3];

View file

@ -306,7 +306,7 @@ impl TriangleShape {
isect.common.n = ng; isect.common.n = ng;
isect.shading.n = ng; isect.shading.n = ng;
if !self.mesh.p.is_empty() || !self.mesh.s.is_empty() { if !self.mesh.n.is_empty() || !self.mesh.s.is_empty() {
self.compute_shading_geometry(&mut isect, &ti, uv, dpdu, determinant, degenerate); self.compute_shading_geometry(&mut isect, &ti, uv, dpdu, determinant, degenerate);
} }
isect isect
@ -455,13 +455,8 @@ impl ShapeTrait for TriangleShape {
fn sample_from_context(&self, ctx: &ShapeSampleContext, mut u: Point2f) -> Option<ShapeSample> { fn sample_from_context(&self, ctx: &ShapeSampleContext, mut u: Point2f) -> Option<ShapeSample> {
let [p0, p1, p2] = self.get_points(); let [p0, p1, p2] = self.get_points();
let (b, tri_pdf) = sample_spherical_triangle(&[p0, p1, p2], ctx.p(), u)?;
if tri_pdf == 0. {
return None;
}
let solid_angle = self.solid_angle(ctx.p()); let solid_angle = self.solid_angle(ctx.p());
if solid_angle < Self::MIN_SPHERICAL_SAMPLE_AREA if solid_angle < Self::MIN_SPHERICAL_SAMPLE_AREA
|| solid_angle > Self::MAX_SPHERICAL_SAMPLE_AREA || solid_angle > Self::MAX_SPHERICAL_SAMPLE_AREA
{ {
@ -497,6 +492,11 @@ impl ShapeTrait for TriangleShape {
pdf = bilinear_pdf(u, &w); pdf = bilinear_pdf(u, &w);
} }
let (b, tri_pdf) = sample_spherical_triangle(&[p0, p1, p2], ctx.p(), u)?;
if tri_pdf == 0. {
return None;
}
let p0_v = Vector3f::from(p0); let p0_v = Vector3f::from(p0);
let p1_v = Vector3f::from(p1); let p1_v = Vector3f::from(p1);
let p2_v = Vector3f::from(p2); let p2_v = Vector3f::from(p2);

View file

@ -8,6 +8,13 @@ use crate::spectra::{
}; };
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::Float; use crate::Float;
use core::sync::atomic::{AtomicU32, Ordering};
pub static DIAG_IMG_COUNT: AtomicU32 = AtomicU32::new(0);
pub static DIAG_IMG_SCALE_BITS: AtomicU32 = AtomicU32::new(0);
pub static DIAG_IMG_PIXEL0_BITS: AtomicU32 = AtomicU32::new(0);
pub static DIAG_IMG_RGB0_BITS: AtomicU32 = AtomicU32::new(0);
pub static DIAG_IMG_RESULT0_BITS: AtomicU32 = AtomicU32::new(0);
/* GPU heavy code, dont know if this will ever work the way Im doing things. /* GPU heavy code, dont know if this will ever work the way Im doing things.
* Leaving it here isolated, for careful handling */ * Leaving it here isolated, for careful handling */
@ -49,16 +56,22 @@ impl SpectrumImageTexture {
let wrap = WrapMode2D { let wrap = WrapMode2D {
uv: [self.wrap_mode; 2], uv: [self.wrap_mode; 2],
}; };
let pixel0 = image.bilerp_channel_with_wrap(c.st, 0, wrap);
let rgb = if image.n_channels == 1 { let rgb = if image.n_channels == 1 {
let v = image.bilerp_channel_with_wrap(c.st, 0, wrap); RGB::new(pixel0, pixel0, pixel0)
RGB::new(v, v, v)
} else { } else {
RGB::new( RGB::new(
image.bilerp_channel_with_wrap(c.st, 0, wrap), pixel0,
image.bilerp_channel_with_wrap(c.st, 1, wrap), image.bilerp_channel_with_wrap(c.st, 1, wrap),
image.bilerp_channel_with_wrap(c.st, 2, wrap), image.bilerp_channel_with_wrap(c.st, 2, wrap),
) )
}; };
let n = DIAG_IMG_COUNT.fetch_add(1, Ordering::Relaxed);
if n < 10 {
DIAG_IMG_SCALE_BITS.store(self.scale.to_bits(), Ordering::Relaxed);
DIAG_IMG_PIXEL0_BITS.store(pixel0.to_bits(), Ordering::Relaxed);
DIAG_IMG_RGB0_BITS.store((rgb[0] as f32).to_bits(), Ordering::Relaxed);
}
let mut rgb = rgb * self.scale; let mut rgb = rgb * self.scale;
if self.invert { if self.invert {
rgb = (RGB::new(1.0, 1.0, 1.0) - rgb); rgb = (RGB::new(1.0, 1.0, 1.0) - rgb);
@ -68,11 +81,15 @@ impl SpectrumImageTexture {
.color_space .color_space
.get() .get()
.expect("color_space must not be null"); .expect("color_space must not be null");
match self.spectrum_type { let result = match self.spectrum_type {
SpectrumType::Unbounded => RGBUnboundedSpectrum::new(cs, rgb).sample(lambda), SpectrumType::Unbounded => RGBUnboundedSpectrum::new(cs, rgb).sample(lambda),
SpectrumType::Albedo => RGBAlbedoSpectrum::new(cs, rgb.clamp(0.0, 1.0)).sample(lambda), SpectrumType::Albedo => RGBAlbedoSpectrum::new(cs, rgb.clamp(0.0, 1.0)).sample(lambda),
_ => RGBIlluminantSpectrum::new(cs, rgb).sample(lambda), _ => RGBIlluminantSpectrum::new(cs, rgb).sample(lambda),
};
if n < 10 {
DIAG_IMG_RESULT0_BITS.store(result[0].to_bits(), Ordering::Relaxed);
} }
result
} }
} }

View file

@ -4,7 +4,7 @@ use crate::core::pbrt::{Float, FloatBitOps, FloatBits, ONE_MINUS_EPSILON, PI, PI
use crate::utils::gpu_array_from_fn; use crate::utils::gpu_array_from_fn;
use crate::utils::hash::{hash_buffer, mix_bits}; use crate::utils::hash::{hash_buffer, mix_bits};
use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV}; use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV};
use crate::{gvec, gvec_with_capacity, GVec, Ptr}; use crate::{GVec, Ptr, gvec, gvec_with_capacity};
use core::fmt::{self, Display, Write}; use core::fmt::{self, Display, Write};
use core::iter::{Product, Sum}; use core::iter::{Product, Sum};
use core::mem; use core::mem;
@ -139,7 +139,7 @@ pub fn windowed_sinc(x: Float, radius: Float, tau: Float) -> Float {
return 0.; return 0.;
} }
if x < 1e-5 { if x.abs() < 1e-5 {
1.0 1.0
} else { } else {
sinc(x) * sinc(x / tau) sinc(x) * sinc(x / tau)
@ -545,7 +545,7 @@ pub fn next_float_up(v: Float) -> Float {
if v >= 0.0 { if v >= 0.0 {
ui = ui.wrapping_add(1); ui = ui.wrapping_add(1);
} else { } else {
ui.wrapping_sub(1); ui = ui.wrapping_sub(1);
} }
bits_to_float(ui) bits_to_float(ui)
} }
@ -1505,11 +1505,7 @@ where
} }
let det: T = (0..N).map(|i| lum[i][i]).product(); let det: T = (0..N).map(|i| lum[i][i]).product();
if parity < 0 { if parity < 0 { -det } else { det }
-det
} else {
det
}
} }
} }
} }
@ -1600,7 +1596,7 @@ mod tests {
let m = SquareMatrix { let m = SquareMatrix {
m: [[1.0, 2.0], [2.0, 4.0]], m: [[1.0, 2.0], [2.0, 4.0]],
}; // Determinant is 0 }; // Determinant is 0
assert!(m.inverse().is_ok()); assert!(m.inverse().is_none());
} }
#[test] #[test]

View file

@ -16,7 +16,7 @@ use shared::Float;
pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> {
let media = scene.create_media(); let media = scene.create_media();
let textures = scene.create_textures(arena); let textures = scene.create_textures(arena);
let (named_materials, materials) = scene.create_materials(&textures, arena)?; let (named_materials, materials, _default_mtl) = scene.create_materials(&textures, arena)?;
let (lights, al_map) = scene.create_lights(&textures, &media, arena); let (lights, al_map) = scene.create_lights(&textures, &media, arena);
let _have_scattering = { let _have_scattering = {
@ -30,10 +30,8 @@ pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> {
.any(|sh| !sh.inside_medium.is_empty() || !sh.outside_medium.is_empty()) .any(|sh| !sh.inside_medium.is_empty() || !sh.outside_medium.is_empty())
}; };
let aggregate = scene.create_aggregate(&textures, &named_materials, &materials, al_map, &media, arena); let aggregate = scene.create_aggregate(&textures, &named_materials, &materials, &al_map, &media, arena);
let all_lights = lights;
let mut all_lights = lights;
all_lights.extend(area_lights);
let camera = scene.get_camera().unwrap(); let camera = scene.get_camera().unwrap();
let _film = camera.get_film(); let _film = camera.get_film();
@ -114,6 +112,7 @@ pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> {
sampler.clone(), sampler.clone(),
aggregate.clone(), aggregate.clone(),
all_lights, all_lights,
materials,
arena, arena,
); );
render( render(

View file

@ -483,9 +483,8 @@ impl BasicScene {
&self, &self,
textures: &NamedTextures, textures: &NamedTextures,
arena: &Arena, arena: &Arena,
) -> Result<(HashMap<String, Material>, Vec<Material>)> { ) -> Result<(HashMap<String, MaterialIdx>, Vec<Material>, MaterialIdx)> {
let mut state = self.material_state.lock(); let mut state = self.material_state.lock();
// Finish async normal map loads // Finish async normal map loads
let finished: Vec<_> = state.normal_map_jobs.drain().collect(); let finished: Vec<_> = state.normal_map_jobs.drain().collect();
for (filename, job) in finished { for (filename, job) in finished {
@ -499,8 +498,10 @@ impl BasicScene {
} }
} }
// Named materials let mut materials: Vec<Material> = Vec::new();
let mut named_materials: HashMap<String, Material> = HashMap::new(); let mut named_materials: HashMap<String, MaterialIdx> = HashMap::new();
// Value map for resolving named sub-materials (e.g. mix) during creation.
let mut named_values: HashMap<String, Material> = HashMap::new();
for (name, entity) in &state.named_materials { for (name, entity) in &state.named_materials {
if named_materials.contains_key(name) { if named_materials.contains_key(name) {
@ -511,29 +512,29 @@ impl BasicScene {
); );
continue; continue;
} }
let mat_type = entity.parameters.get_one_string("type", "")?; let mat_type = entity.parameters.get_one_string("type", "")?;
if mat_type.is_empty() { if mat_type.is_empty() {
log::error!("{}: missing material type for '{}'", entity.loc, name); log::error!("{}: missing material type for '{}'", entity.loc, name);
continue; continue;
} }
let normal_map = self.get_normal_map(&state, &entity.parameters)?; let normal_map = self.get_normal_map(&state, &entity.parameters)?;
let tex_dict = TextureParameterDictionary::new( let tex_dict = TextureParameterDictionary::new(
Arc::new(entity.parameters.clone()), Arc::new(entity.parameters.clone()),
Some(textures), Some(textures),
); );
match Material::create( match Material::create(
&mat_type, &mat_type,
&tex_dict, &tex_dict,
normal_map, normal_map,
&named_materials, &named_values, // value map, not index map
entity.loc.clone(), entity.loc.clone(),
arena, arena,
) { ) {
Ok(mat) => { Ok(mat) => {
named_materials.insert(name.clone(), mat); let idx = MaterialIdx(materials.len() as u32);
materials.push(mat);
named_values.insert(name.clone(), mat);
named_materials.insert(name.clone(), idx);
} }
Err(e) => { Err(e) => {
log::error!( log::error!(
@ -546,11 +547,8 @@ impl BasicScene {
} }
} }
// Indexed materials // Indexed (anonymous) materials, appended after named ones.
let materials: Vec<Material> = state for entity in &state.materials {
.materials
.iter()
.map(|entity| {
let result: Result<Material> = (|| { let result: Result<Material> = (|| {
let normal_map = self.get_normal_map(&state, &entity.parameters)?; let normal_map = self.get_normal_map(&state, &entity.parameters)?;
let tex_dict = TextureParameterDictionary::new( let tex_dict = TextureParameterDictionary::new(
@ -561,22 +559,25 @@ impl BasicScene {
&entity.name, &entity.name,
&tex_dict, &tex_dict,
normal_map, normal_map,
&named_materials, &named_values,
entity.loc.clone(), entity.loc.clone(),
arena, arena,
) )
})(); })();
match result { let mat = match result {
Ok(mat) => mat, Ok(mat) => mat,
Err(e) => { Err(e) => {
log::error!("{}: failed to create material: {}", entity.loc, e); log::error!("{}: failed to create material: {}", entity.loc, e);
crate::core::material::default_diffuse_material(arena) crate::core::material::default_diffuse_material(arena)
} }
};
materials.push(mat);
} }
})
.collect();
Ok((named_materials, materials)) let default_mtl = MaterialIdx(materials.len() as u32);
materials.push(crate::core::material::default_diffuse_material(arena));
Ok((named_materials, materials, default_mtl))
} }
pub fn create_media(&self) -> HashMap<String, Arc<Medium>> { pub fn create_media(&self) -> HashMap<String, Arc<Medium>> {
@ -810,7 +811,8 @@ impl BasicScene {
camera: Arc<Camera>, camera: Arc<Camera>,
sampler: Arc<Sampler>, sampler: Arc<Sampler>,
aggregate: Arc<Primitive>, aggregate: Arc<Primitive>,
lights: Vec<Arc<Light>>, lights: Vec<Light>,
materials: Vec<Material>,
arena: &Arena, arena: &Arena,
) -> PathIntegrator { ) -> PathIntegrator {
let integrator_entity = self.integrator.lock().clone().unwrap(); let integrator_entity = self.integrator.lock().clone().unwrap();
@ -823,6 +825,7 @@ impl BasicScene {
sampler, sampler,
aggregate, aggregate,
lights, lights,
materials,
PathConfig::FULL, PathConfig::FULL,
arena, arena,
) )

View file

@ -4,6 +4,7 @@ use shared::core::interaction::{Interaction, InteractionTrait};
use shared::core::light::{Light, LightTrait}; use shared::core::light::{Light, LightTrait};
use shared::core::primitive::{Primitive, PrimitiveTrait}; use shared::core::primitive::{Primitive, PrimitiveTrait};
use shared::core::shape::ShapeIntersection; use shared::core::shape::ShapeIntersection;
use shared::core::LightIdx;
use shared::lights::sampler::{LightSampler, LightSamplerTrait}; use shared::lights::sampler::{LightSampler, LightSamplerTrait};
use shared::spectra::SampledWavelengths; use shared::spectra::SampledWavelengths;
use shared::utils::sampling::power_heuristic; use shared::utils::sampling::power_heuristic;
@ -13,18 +14,16 @@ use std::sync::Arc;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct IntegratorBase { pub struct IntegratorBase {
pub aggregate: Arc<Primitive>, pub aggregate: Arc<Primitive>,
pub lights: Vec<Arc<Light>>, pub lights: Vec<Light>,
pub infinite_lights: Vec<Arc<Light>>, pub infinite_lights: Vec<LightIdx>,
} }
impl IntegratorBase { impl IntegratorBase {
pub fn new(aggregate: Arc<Primitive>, mut lights: Vec<Arc<Light>>) -> Self { pub fn new(aggregate: Arc<Primitive>, mut lights: Vec<Light>) -> Self {
let scene_bounds = aggregate.bounds(); let scene_bounds = aggregate.bounds();
for light in &mut lights { for light in &mut lights {
Arc::get_mut(light) light.preprocess(&scene_bounds);
.expect("Light has multiple owners during setup")
.preprocess(&scene_bounds);
} }
println!( println!(
@ -36,10 +35,11 @@ impl IntegratorBase {
println!(" light[{}]: type={:?}", i, l.light_type()); println!(" light[{}]: type={:?}", i, l.light_type());
} }
let infinite_lights = lights let infinite_lights: Vec<LightIdx> = lights
.iter() .iter()
.filter(|light| light.light_type().is_infinite()) .enumerate()
.cloned() .filter(|(_, l)| l.light_type().is_infinite())
.map(|(i, _)| LightIdx(i as u32))
.collect(); .collect();
Self { Self {
@ -72,7 +72,8 @@ impl IntegratorBase {
light_sampler: Option<&LightSampler>, light_sampler: Option<&LightSampler>,
use_mis: bool, use_mis: bool,
) { ) {
for light in &self.infinite_lights { for &idx in &self.infinite_lights {
let light = &self.lights[idx.0 as usize];
let le = light.le(ray, lambda); let le = light.le(ray, lambda);
if le.is_black() { if le.is_black() {
continue; continue;
@ -81,7 +82,7 @@ impl IntegratorBase {
if state.depth == 0 || state.specular_bounce || !use_mis { if state.depth == 0 || state.specular_bounce || !use_mis {
state.l += state.beta * le; state.l += state.beta * le;
} else if let Some(sampler) = light_sampler { } else if let Some(sampler) = light_sampler {
let p_l = sampler.pmf_with_context(&state.prev_ctx, light) let p_l = sampler.pmf_with_context(&state.prev_ctx, idx)
* light.pdf_li(&state.prev_ctx, ray.d, true); * light.pdf_li(&state.prev_ctx, ray.d, true);
let w_b = power_heuristic(1, state.prev_pdf, 1, p_l); let w_b = power_heuristic(1, state.prev_pdf, 1, p_l);
state.l += state.beta * w_b * le; state.l += state.beta * w_b * le;

View file

@ -13,6 +13,7 @@ use shared::core::camera::Camera;
use shared::core::film::VisibleSurface; use shared::core::film::VisibleSurface;
use shared::core::geometry::{Point2i, Ray}; use shared::core::geometry::{Point2i, Ray};
use shared::core::light::Light; use shared::core::light::Light;
use shared::core::material::Material;
use shared::core::primitive::Primitive; use shared::core::primitive::Primitive;
use shared::core::sampler::Sampler; use shared::core::sampler::Sampler;
use shared::spectra::{SampledSpectrum, SampledWavelengths}; use shared::spectra::{SampledSpectrum, SampledWavelengths};
@ -47,7 +48,8 @@ pub trait CreateIntegrator {
camera: Arc<Camera>, camera: Arc<Camera>,
sampler: Arc<Sampler>, sampler: Arc<Sampler>,
aggregate: Arc<Primitive>, aggregate: Arc<Primitive>,
lights: Vec<Arc<Light>>, lights: Vec<Light>,
materials: Vec<Material>,
config: PathConfig, config: PathConfig,
arena: &Arena, arena: &Arena,
) -> Result<PathIntegrator>; ) -> Result<PathIntegrator>;
@ -59,14 +61,15 @@ impl CreateIntegrator for PathIntegrator {
camera: Arc<Camera>, camera: Arc<Camera>,
_sampler: Arc<Sampler>, _sampler: Arc<Sampler>,
aggregate: Arc<Primitive>, aggregate: Arc<Primitive>,
lights: Vec<Arc<Light>>, lights: Vec<Light>,
materials: Vec<Material>,
config: PathConfig, config: PathConfig,
arena: &Arena, arena: &Arena,
) -> Result<PathIntegrator> { ) -> Result<PathIntegrator> {
let _max_depth = parameters.get_one_int("maxdepth", 5)?; let _max_depth = parameters.get_one_int("maxdepth", 5)?;
let _regularize = parameters.get_one_bool("regularize", false)?; let _regularize = parameters.get_one_bool("regularize", false)?;
let light_sampler = create_light_sampler("power", &lights, arena); let light_sampler = create_light_sampler("power", &lights, arena);
let integrator = PathIntegrator::new(aggregate, lights, camera, light_sampler, config); let integrator = PathIntegrator::new(aggregate, lights, camera, light_sampler, config, materials);
Ok(integrator) Ok(integrator)
} }
} }

View file

@ -12,6 +12,7 @@ use shared::core::geometry::{Point2i, Ray, Vector3f, VectorLike};
use shared::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; use shared::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use shared::core::light::LightTrait; use shared::core::light::LightTrait;
use shared::core::light::{Light, LightSampleContext}; use shared::core::light::{Light, LightSampleContext};
use shared::core::material::Material;
use shared::core::primitive::Primitive; use shared::core::primitive::Primitive;
use shared::core::sampler::{Sampler, SamplerTrait}; use shared::core::sampler::{Sampler, SamplerTrait};
use shared::lights::sampler::LightSampler; use shared::lights::sampler::LightSampler;
@ -68,6 +69,7 @@ pub struct PathIntegrator {
camera: Arc<Camera>, camera: Arc<Camera>,
sampler: LightSampler, sampler: LightSampler,
config: PathConfig, config: PathConfig,
materials: Vec<Material>,
} }
unsafe impl Send for PathIntegrator {} unsafe impl Send for PathIntegrator {}
@ -76,10 +78,11 @@ unsafe impl Sync for PathIntegrator {}
impl PathIntegrator { impl PathIntegrator {
pub fn new( pub fn new(
aggregate: Arc<Primitive>, aggregate: Arc<Primitive>,
lights: Vec<Arc<Light>>, lights: Vec<Light>,
camera: Arc<Camera>, camera: Arc<Camera>,
sampler: LightSampler, sampler: LightSampler,
config: PathConfig, config: PathConfig,
materials: Vec<Material>,
) -> Self { ) -> Self {
let base = IntegratorBase::new(aggregate, lights); let base = IntegratorBase::new(aggregate, lights);
Self { Self {
@ -87,6 +90,7 @@ impl PathIntegrator {
camera, camera,
sampler, sampler,
config, config,
materials,
} }
} }
@ -104,7 +108,9 @@ impl PathIntegrator {
return SampledSpectrum::zero(); return SampledSpectrum::zero();
}; };
let Some(ls) = sampled.light.sample_li(&ctx, sampler.get2d(), lambda, true) else { let light = &self.base.lights[sampled.light.0 as usize];
let Some(ls) = light.sample_li(&ctx, sampler.get2d(), lambda, true) else {
return SampledSpectrum::zero(); return SampledSpectrum::zero();
}; };
@ -133,7 +139,7 @@ impl PathIntegrator {
let p_l = sampled.p * ls.pdf; let p_l = sampled.p * ls.pdf;
if !self.config.use_mis || sampled.light.light_type().is_delta_light() { if !self.config.use_mis || light.light_type().is_delta_light() {
ls.l * f / p_l ls.l * f / p_l
} else { } else {
let p_b = bsdf.pdf(wo, wi, FArgs::default()); let p_b = bsdf.pdf(wo, wi, FArgs::default());
@ -228,14 +234,14 @@ impl RayIntegratorTrait for PathIntegrator {
let isect = &mut si.intr; let isect = &mut si.intr;
// Emission from hit surface // Emission from hit surface
let le = isect.le(-ray.d, lambda); let le = isect.le(-ray.d, lambda, &self.base.lights);
if !le.is_black() { if !le.is_black() {
if state.depth == 0 || state.specular_bounce { if state.depth == 0 || state.specular_bounce {
state.l += state.beta * le; state.l += state.beta * le;
} else if self.config.use_mis } else if self.config.use_mis && !isect.area_light.is_none() {
&& !isect.area_light.is_none() { let idx = isect.area_light;
let light = &isect.area_light; let light = &self.base.lights[idx.0 as usize];
let p_l = self.sampler.pmf_with_context(&state.prev_ctx, light) let p_l = self.sampler.pmf_with_context(&state.prev_ctx, idx)
* light.pdf_li(&state.prev_ctx, ray.d, true); * light.pdf_li(&state.prev_ctx, ray.d, true);
let w_b = power_heuristic(1, state.prev_pdf, 1, p_l); let w_b = power_heuristic(1, state.prev_pdf, 1, p_l);
state.l += state.beta * w_b * le; state.l += state.beta * w_b * le;
@ -243,7 +249,7 @@ impl RayIntegratorTrait for PathIntegrator {
} }
// Get BSDF // Get BSDF
let Some(mut bsdf) = isect.get_bsdf(&ray, lambda, &self.camera, sampler) else { let Some(mut bsdf) = isect.get_bsdf(&ray, lambda, &self.camera, sampler, &self.materials) else {
state.specular_bounce = true; state.specular_bounce = true;
isect.skip_intersection(&mut ray, t_hit); isect.skip_intersection(&mut ray, t_hit);
continue; continue;

View file

@ -248,14 +248,6 @@ pub fn evaluate_pixel_sample<T: RayIntegratorTrait>(
eprintln!(" camera_sample.p_film: {:?}", camera_sample.p_film); eprintln!(" camera_sample.p_film: {:?}", camera_sample.p_film);
} }
eprintln!(
"PATH p=({},{}) l={:?} lambda={:?} fw={}",
pixel.x(),
pixel.y(),
l,
lambda,
camera_sample.filter_weight,
);
film.add_sample( film.add_sample(
pixel, pixel,
l, l,

View file

@ -131,12 +131,26 @@ impl SpectrumImageTexture {
impl SpectrumTextureTrait for SpectrumImageTexture { impl SpectrumTextureTrait for SpectrumImageTexture {
fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum { fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum {
use std::sync::atomic::{AtomicU32, Ordering};
static PATH_IMG_COUNT: AtomicU32 = AtomicU32::new(0);
let pn = PATH_IMG_COUNT.fetch_add(1, Ordering::Relaxed);
let mut c = self.base.mapping.map(ctx); let mut c = self.base.mapping.map(ctx);
c.st[1] = 1. - c.st[1]; c.st[1] = 1. - c.st[1];
let dst0 = Vector2f::new(c.dsdx, c.dtdx); let dst0 = Vector2f::new(c.dsdx, c.dtdx);
let dst1 = Vector2f::new(c.dsdy, c.dtdy); let dst1 = Vector2f::new(c.dsdy, c.dtdy);
let rgb_unclamp = self.base.scale * self.base.mipmap.filter::<RGB>(c.st, dst0, dst1); let raw_rgb = self.base.mipmap.filter::<RGB>(c.st, dst0, dst1);
let rgb_unclamp = self.base.scale * raw_rgb;
let rgb = RGB::clamp_zero(&rgb_unclamp); let rgb = RGB::clamp_zero(&rgb_unclamp);
if pn < 5 {
eprintln!("PATH_IMG[{pn}] scale={:.6} raw_rgb[0]={:.6} rgb_after_scale[0]={:.6} \
st={:?} dst0={:?} dst1={:?} has_cs={}",
self.base.scale, raw_rgb[0], rgb_unclamp[0],
c.st, dst0, dst1,
self.base.mipmap.get_rgb_colorspace().is_some());
}
if let Some(cs) = self.base.mipmap.get_rgb_colorspace() { if let Some(cs) = self.base.mipmap.get_rgb_colorspace() {
match self.spectrum_type { match self.spectrum_type {
SpectrumType::Unbounded => { SpectrumType::Unbounded => {
@ -148,8 +162,11 @@ impl SpectrumTextureTrait for SpectrumImageTexture {
_ => return RGBIlluminantSpectrum::new(&cs, rgb).sample(lambda), _ => return RGBIlluminantSpectrum::new(&cs, rgb).sample(lambda),
} }
} }
assert!(rgb[0] == rgb[1] && rgb[1] == rgb[2]); let result = SampledSpectrum::new(rgb[0]);
SampledSpectrum::new(rgb[0]) if pn < 5 {
eprintln!("PATH_IMG[{pn}] no-cs branch result[0]={:.6}", result[0]);
}
result
} }
} }

View file

@ -1,5 +1,8 @@
use crate::globals::get_options; use crate::globals::get_options;
use log::debug; use log::debug;
use std::sync::atomic::{AtomicU32, Ordering};
pub static DIAG_SHADOW_UNOCCLUDED: AtomicU32 = AtomicU32::new(0);
use rayon::prelude::*; use rayon::prelude::*;
use shared::core::geometry::{Bounds3f, Ray, VectorLike}; use shared::core::geometry::{Bounds3f, Ray, VectorLike};
use shared::core::interaction::{InteractionTrait, SurfaceInteraction}; use shared::core::interaction::{InteractionTrait, SurfaceInteraction};
@ -113,7 +116,7 @@ impl WavefrontAggregate for CpuAggregate {
); );
} }
eval_q.push(MaterialEvalWorkItem { let item = MaterialEvalWorkItem {
p: intr.pi(), p: intr.pi(),
n: intr.n(), n: intr.n(),
ns: intr.shading.n, ns: intr.shading.n,
@ -137,7 +140,23 @@ impl WavefrontAggregate for CpuAggregate {
dpdvs: intr.shading.dpdv, dpdvs: intr.shading.dpdv,
dndus: intr.shading.dndu, dndus: intr.shading.dndu,
dndvs: intr.shading.dndv, dndvs: intr.shading.dndv,
}); };
if let Some(slot) = eval_q.push(item) {
if slot < 10 {
eprintln!(
"ENQUEUE[{slot}] pixel={:?} depth={} \
p={:?} n={:?} ns={:?} \
dpdu={:?} dpdv={:?} \
dpdus={:?} dpdvs={:?} \
uv={:?} material={:?} area_light={:?} face_index={}",
item.pixel_index, item.depth,
item.p, item.n, item.ns,
item.dpdu, item.dpdv,
item.dpdus, item.dpdvs,
item.uv, item.material, item.area_light, item.face_index,
);
}
}
}); });
} }
@ -156,6 +175,15 @@ impl WavefrontAggregate for CpuAggregate {
if !self.aggregate.intersect_p(&ray, Some(work.t_max)) { if !self.aggregate.intersect_p(&ray, Some(work.t_max)) {
let pi = work.pixel_index as usize; let pi = work.pixel_index as usize;
let ld = work.l_d / (work.r_u + work.r_l).average(); let ld = work.l_d / (work.r_u + work.r_l).average();
let n = DIAG_SHADOW_UNOCCLUDED.fetch_add(1, Ordering::Relaxed);
if n < 10 {
eprintln!(
"SHADOW_UNOCCLUDED[{n}] pixel={} l_d={:?} r_u={:?} r_l={:?} \
denom={:.6} ld={:?}",
pi, work.l_d, work.r_u, work.r_l,
(work.r_u + work.r_l).average(), ld
);
}
let mut l = pixel_sample_state.l.get(pi); let mut l = pixel_sample_state.l.get(pi);
l += ld; l += ld;
pixel_sample_state.l.set(pi, l); pixel_sample_state.l.set(pi, l);

View file

@ -29,9 +29,26 @@ use shared::utils::soa::{SoA, SoAAllocator, WorkQueue};
use shared::wavefront::workitems::*; use shared::wavefront::workitems::*;
use shared::wavefront::{WavefrontAggregate, WavefrontPathIntegrator, WavefrontRenderer}; use shared::wavefront::{WavefrontAggregate, WavefrontPathIntegrator, WavefrontRenderer};
use shared::{gvec, gvec_from_slice, GVec, Ptr, SHADOW_EPSILON}; use shared::{gvec, gvec_from_slice, GVec, Ptr, SHADOW_EPSILON};
use shared::textures::image::{
DIAG_IMG_COUNT, DIAG_IMG_SCALE_BITS, DIAG_IMG_PIXEL0_BITS,
DIAG_IMG_RGB0_BITS, DIAG_IMG_RESULT0_BITS,
};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc; use std::sync::Arc;
static DIAG_EVAL_ENTER: AtomicU32 = AtomicU32::new(0);
static DIAG_BSDF_EMPTY: AtomicU32 = AtomicU32::new(0);
static DIAG_NON_SPECULAR_SKIP: AtomicU32 = AtomicU32::new(0);
static DIAG_SAMPLE_LIGHT_NONE: AtomicU32 = AtomicU32::new(0);
static DIAG_SAMPLE_LI_NONE: AtomicU32 = AtomicU32::new(0);
static DIAG_LS_L_BLACK: AtomicU32 = AtomicU32::new(0);
static DIAG_LS_PDF_ZERO: AtomicU32 = AtomicU32::new(0);
static DIAG_F_NONE: AtomicU32 = AtomicU32::new(0);
static DIAG_F_BLACK: AtomicU32 = AtomicU32::new(0);
static DIAG_SHADOW_PUSH: AtomicU32 = AtomicU32::new(0);
static DIAG_NEE_D0_PRINT: AtomicU32 = AtomicU32::new(0);
pub struct CpuWavefrontRenderer(pub WavefrontPathIntegrator<CpuAggregate>); pub struct CpuWavefrontRenderer(pub WavefrontPathIntegrator<CpuAggregate>);
impl Deref for CpuWavefrontRenderer { impl Deref for CpuWavefrontRenderer {
@ -84,7 +101,7 @@ where
} }
} }
let cpu_aggregate = CpuAggregate::new(*aggregate); let cpu_aggregate = CpuAggregate::new(*aggregate, &materials);
let bounds = aggregate.bounds(); let bounds = aggregate.bounds();
for light in &mut lights { for light in &mut lights {
@ -210,6 +227,30 @@ impl CpuWavefrontRenderer {
&self.shadow_ray_queue, &self.shadow_ray_queue,
&self.pixel_sample_state, &self.pixel_sample_state,
); );
if sample_index == 0 && y0 == pixel_bounds.p_min.y() {
eprintln!("=== DIAG s=0 y0={} depth={} ===", y0, depth);
eprintln!(" eval_enter={}", DIAG_EVAL_ENTER.load(Ordering::Relaxed));
eprintln!(" bsdf_empty={}", DIAG_BSDF_EMPTY.load(Ordering::Relaxed));
eprintln!(" non_specular_skip={}", DIAG_NON_SPECULAR_SKIP.load(Ordering::Relaxed));
eprintln!(" sample_light_none={}", DIAG_SAMPLE_LIGHT_NONE.load(Ordering::Relaxed));
eprintln!(" sample_li_none={}", DIAG_SAMPLE_LI_NONE.load(Ordering::Relaxed));
eprintln!(" ls_l_black={}", DIAG_LS_L_BLACK.load(Ordering::Relaxed));
eprintln!(" ls_pdf_zero={}", DIAG_LS_PDF_ZERO.load(Ordering::Relaxed));
eprintln!(" f_none={}", DIAG_F_NONE.load(Ordering::Relaxed));
eprintln!(" f_black={}", DIAG_F_BLACK.load(Ordering::Relaxed));
eprintln!(" shadow_push={}", DIAG_SHADOW_PUSH.load(Ordering::Relaxed));
eprintln!(" shadow_unoccluded={}", super::aggregate::DIAG_SHADOW_UNOCCLUDED.load(Ordering::Relaxed));
let img_n = DIAG_IMG_COUNT.load(Ordering::Relaxed);
if img_n > 0 {
let scale = f32::from_bits(DIAG_IMG_SCALE_BITS.load(Ordering::Relaxed));
let pixel0 = f32::from_bits(DIAG_IMG_PIXEL0_BITS.load(Ordering::Relaxed));
let rgb0 = f32::from_bits(DIAG_IMG_RGB0_BITS.load(Ordering::Relaxed));
let result0 = f32::from_bits(DIAG_IMG_RESULT0_BITS.load(Ordering::Relaxed));
eprintln!(" img_tex_calls={} scale={:.6} pixel0={:.6} rgb0_pre_scale={:.6} result[0]={:.6}",
img_n, scale, pixel0, rgb0, result0);
}
}
} }
self.update_film(y0, y1, &pixel_bounds); self.update_film(y0, y1, &pixel_bounds);
let batch_pixels = let batch_pixels =
@ -219,6 +260,18 @@ impl CpuWavefrontRenderer {
y0 = y1; y0 = y1;
} }
} }
eprintln!("=== NEE DIAG COUNTS ===");
eprintln!("eval_enter={}", DIAG_EVAL_ENTER.load(Ordering::Relaxed));
eprintln!("bsdf_empty={}", DIAG_BSDF_EMPTY.load(Ordering::Relaxed));
eprintln!("non_specular_skip={}", DIAG_NON_SPECULAR_SKIP.load(Ordering::Relaxed));
eprintln!("sample_light_none={}", DIAG_SAMPLE_LIGHT_NONE.load(Ordering::Relaxed));
eprintln!("sample_li_none={}", DIAG_SAMPLE_LI_NONE.load(Ordering::Relaxed));
eprintln!("ls_l_black={}", DIAG_LS_L_BLACK.load(Ordering::Relaxed));
eprintln!("ls_pdf_zero={}", DIAG_LS_PDF_ZERO.load(Ordering::Relaxed));
eprintln!("f_none={}", DIAG_F_NONE.load(Ordering::Relaxed));
eprintln!("f_black={}", DIAG_F_BLACK.load(Ordering::Relaxed));
eprintln!("shadow_push={}", DIAG_SHADOW_PUSH.load(Ordering::Relaxed));
eprintln!("shadow_unoccluded={}", super::aggregate::DIAG_SHADOW_UNOCCLUDED.load(Ordering::Relaxed));
} }
fn generate_camera_rays( fn generate_camera_rays(
@ -404,6 +457,21 @@ impl CpuWavefrontRenderer {
(0..n as usize).into_par_iter().for_each(|i| { (0..n as usize).into_par_iter().for_each(|i| {
let w = unsafe { queue.storage.get(i) }; let w = unsafe { queue.storage.get(i) };
if i < 10 {
eprintln!(
"DEQUEUE[{i}] pixel={:?} depth={} \
p={:?} n={:?} ns={:?} \
dpdu={:?} dpdv={:?} \
dpdus={:?} dpdvs={:?} \
uv={:?} material={:?} area_light={:?} face_index={}",
w.pixel_index, w.depth,
w.p, w.n, w.ns,
w.dpdu, w.dpdv,
w.dpdus, w.dpdvs,
w.uv, w.material, w.area_light, w.face_index,
);
}
DIAG_EVAL_ENTER.fetch_add(1, Ordering::Relaxed);
if w.material.is_none() { if w.material.is_none() {
return; return;
} }
@ -445,6 +513,7 @@ impl CpuWavefrontRenderer {
} }
if bsdf.flags().is_empty() { if bsdf.flags().is_empty() {
DIAG_BSDF_EMPTY.fetch_add(1, Ordering::Relaxed);
return; return;
} }
if regularize && w.any_non_specular_bounces { if regularize && w.any_non_specular_bounces {
@ -513,6 +582,9 @@ impl CpuWavefrontRenderer {
// Direct lighting // Direct lighting
let flags = bsdf.flags(); let flags = bsdf.flags();
if !flags.is_non_specular() {
DIAG_NON_SPECULAR_SKIP.fetch_add(1, Ordering::Relaxed);
}
if flags.is_non_specular() { if flags.is_non_specular() {
let mut light_ctx = LightSampleContext { let mut light_ctx = LightSampleContext {
pi: w.p, pi: w.p,
@ -529,24 +601,33 @@ impl CpuWavefrontRenderer {
let Some(sampled_light) = let Some(sampled_light) =
light_sampler.sample_with_context(&light_ctx, rs.direct.uc) light_sampler.sample_with_context(&light_ctx, rs.direct.uc)
else { else {
DIAG_SAMPLE_LIGHT_NONE.fetch_add(1, Ordering::Relaxed);
return; return;
}; };
let light = &self.lights[sampled_light.light.0 as usize]; let light = &self.lights[sampled_light.light.0 as usize];
let Some(ls) = light.sample_li(&light_ctx, rs.direct.u, &lambda, true) else { let Some(ls) = light.sample_li(&light_ctx, rs.direct.u, &lambda, true) else {
DIAG_SAMPLE_LI_NONE.fetch_add(1, Ordering::Relaxed);
return; return;
}; };
if ls.l.is_black() || ls.pdf <= 0.0 { if ls.l.is_black() {
DIAG_LS_L_BLACK.fetch_add(1, Ordering::Relaxed);
return;
}
if ls.pdf <= 0.0 {
DIAG_LS_PDF_ZERO.fetch_add(1, Ordering::Relaxed);
return; return;
} }
let wi = ls.wi; let wi = ls.wi;
let Some(f) = bsdf.f(wo, wi, TransportMode::Radiance) else { let Some(f) = bsdf.f(wo, wi, TransportMode::Radiance) else {
DIAG_F_NONE.fetch_add(1, Ordering::Relaxed);
return; return;
}; };
if f.is_black() { if f.is_black() {
DIAG_F_BLACK.fetch_add(1, Ordering::Relaxed);
return; return;
} }
@ -569,6 +650,19 @@ impl CpuWavefrontRenderer {
&ls.p_light.n(), &ls.p_light.n(),
); );
DIAG_SHADOW_PUSH.fetch_add(1, Ordering::Relaxed);
if w.depth == 0 {
let n = DIAG_NEE_D0_PRINT.fetch_add(1, Ordering::Relaxed);
if n < 10 {
eprintln!(
"NEE_D0[{n}] pixel={:?} ls.l={:?} ls.pdf={:.6} f={:?} \
beta={:?} light_pdf={:.6} bsdf_pdf={:.6} \
r_u={:?} r_l={:?} l_d={:?}",
w.pixel_index, ls.l, ls.pdf, f, beta,
light_pdf, bsdf_pdf, r_u, r_l, l_d
);
}
}
shadow_ray_queue.push(ShadowRayWorkItem { shadow_ray_queue.push(ShadowRayWorkItem {
ray, ray,
t_max: 1.0 - SHADOW_EPSILON, t_max: 1.0 - SHADOW_EPSILON,