Cleaning unused imports, refactoring wavefront integration

This commit is contained in:
Wito Wiala 2026-05-28 14:40:45 +01:00
parent a6ee0a1b52
commit 5ff8044158
53 changed files with 973 additions and 851 deletions

View file

@ -193,7 +193,7 @@ impl SamplerTrait for HaltonSampler {
} }
fn get1d(&mut self) -> Float { fn get1d(&mut self) -> Float {
if self.dim > PRIME_TABLE_SIZE as u32 { if self.dim >= PRIME_TABLE_SIZE as u32 {
self.dim = 2; self.dim = 2;
} }
self.sample_dimension(self.dim) self.sample_dimension(self.dim)

View file

@ -1,6 +1,6 @@
use crate::core::color::ColorEncoding; use crate::core::color::ColorEncoding;
use crate::core::geometry::{ use crate::core::geometry::{
Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike, spherical_phi, spherical_theta, spherical_phi, spherical_theta, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike,
}; };
use crate::core::image::WrapMode; use crate::core::image::WrapMode;
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
@ -9,9 +9,9 @@ use crate::spectra::{
SampledWavelengths, SampledWavelengths,
}; };
use crate::utils::math::square;
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::utils::Transform; use crate::utils::Transform;
use crate::utils::math::square;
use crate::{Float, INV_2_PI, INV_PI, PI}; use crate::{Float, INV_2_PI, INV_PI, PI};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use num_traits::Float as NumFloat; use num_traits::Float as NumFloat;
@ -393,7 +393,6 @@ pub enum GPUSpectrumTexture {
Dots(SpectrumDotsTexture), Dots(SpectrumDotsTexture),
Scaled(GPUSpectrumScaledTexture), Scaled(GPUSpectrumScaledTexture),
Image(GPUSpectrumImageTexture), Image(GPUSpectrumImageTexture),
Ptex(GPUSpectrumPtexTexture),
Mix(GPUSpectrumMixTexture), Mix(GPUSpectrumMixTexture),
} }
@ -411,7 +410,6 @@ impl GPUSpectrumTexture {
GPUSpectrumTexture::DirectionMix(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::DirectionMix(t) => t.evaluate(ctx, lambda),
GPUSpectrumTexture::Dots(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Dots(t) => t.evaluate(ctx, lambda),
GPUSpectrumTexture::Scaled(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Scaled(t) => t.evaluate(ctx, lambda),
GPUSpectrumTexture::Ptex(t) => t.evaluate(ctx, lambda),
GPUSpectrumTexture::Image(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Image(t) => t.evaluate(ctx, lambda),
GPUSpectrumTexture::Mix(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Mix(t) => t.evaluate(ctx, lambda),
} }
@ -460,3 +458,58 @@ impl TextureEvaluator for UniversalTextureEvaluator {
true true
} }
} }
#[repr(C)]
#[derive(Copy, Clone, Default)]
pub struct BasicTextureEvaluator;
impl TextureEvaluator for BasicTextureEvaluator {
fn evaluate_float(&self, tex: &GPUFloatTexture, ctx: &TextureEvalContext) -> Float {
match tex {
GPUFloatTexture::Constant(t) => t.evaluate(ctx),
GPUFloatTexture::Image(t) => t.evaluate(ctx),
_ => 0.0,
}
}
fn evaluate_spectrum(
&self,
tex: &GPUSpectrumTexture,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum {
match tex {
GPUSpectrumTexture::Constant(t) => t.evaluate(ctx, lambda),
GPUSpectrumTexture::Image(t) => t.evaluate(ctx, lambda),
_ => SampledSpectrum::new(0.0),
}
}
fn can_evaluate(
&self,
ftex: &[Ptr<GPUFloatTexture>],
stex: &[Ptr<GPUSpectrumTexture>],
) -> bool {
for t in ftex {
if t.is_null() {
continue;
}
match t.get().unwrap() {
GPUFloatTexture::Constant(_)
| GPUFloatTexture::Image(_) => {}
_ => return false,
}
}
for t in stex {
if t.is_null() {
continue;
}
match t.get().unwrap() {
GPUSpectrumTexture::Constant(_)
| GPUSpectrumTexture::Image(_) => {}
_ => return false,
}
}
true
}
}

View file

@ -20,6 +20,6 @@ pub mod wavefront;
pub use core::pbrt::*; pub use core::pbrt::*;
pub use utils::alloc::{gbox, gvec, gvec_from_slice, gvec_with_capacity, leak, GBox, GVec}; pub use utils::alloc::{gbox, gvec, gvec_from_slice, gvec_with_capacity, leak, GBox, GVec};
pub use utils::{Array2D, PBRTOptions, Ptr, Transform}; pub use utils::{Array2D, BasicPBRTOptions, PBRTOptions, Ptr, Transform};
pub use utils::soa::WorkQueue; pub use utils::soa::WorkQueue;
pub use wavefront::{WavefrontAggregate}; pub use wavefront::{WavefrontAggregate};

View file

@ -42,8 +42,9 @@ impl TriangleShape {
pub const MAX_SPHERICAL_SAMPLE_AREA: Float = 6.22; pub const MAX_SPHERICAL_SAMPLE_AREA: Float = 6.22;
fn mesh(&self) -> &TriangleMesh { fn mesh(&self) -> &TriangleMesh {
&*self.mesh self.mesh.get().unwrap()
} }
fn get_vertex_indices(&self) -> [usize; 3] { fn get_vertex_indices(&self) -> [usize; 3] {
let mesh = self.mesh(); let mesh = self.mesh();
let base = (self.tri_index as usize) * 3; let base = (self.tri_index as usize) * 3;

View file

@ -18,7 +18,7 @@ pub mod transform;
pub use atomic::{AtomicFloat, AtomicU32}; pub use atomic::{AtomicFloat, AtomicU32};
pub use containers::Array2D; pub use containers::Array2D;
pub use options::PBRTOptions; pub use options::{BasicPBRTOptions, PBRTOptions};
pub use ptr::Ptr; pub use ptr::Ptr;
pub use transform::{AnimatedTransform, Transform, TransformGeneric}; pub use transform::{AnimatedTransform, Transform, TransformGeneric};

View file

@ -9,7 +9,7 @@ pub enum RenderingCoordinateSystem {
World, World,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub struct BasicPBRTOptions { pub struct BasicPBRTOptions {
pub seed: i32, pub seed: i32,
pub quiet: bool, pub quiet: bool,
@ -42,7 +42,7 @@ impl Default for BasicPBRTOptions {
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub struct PBRTOptions { pub struct PBRTOptions {
pub basic: BasicPBRTOptions, pub basic: BasicPBRTOptions,

View file

@ -1,9 +1,5 @@
use crate::core::geometry::{Bounds3f, Ray, Vector3f};
use crate::core::interaction::InteractionTrait;
use crate::core::material::MaterialTrait;
use crate::core::primitive::{Primitive, PrimitiveTrait};
use crate::core::texture::{TextureEvaluator, UniversalTextureEvaluator};
use crate::wavefront::workitems::*; use crate::wavefront::workitems::*;
use crate::core::geometry::Bounds3f;
pub trait WavefrontAggregate { pub trait WavefrontAggregate {
fn bounds(&self) -> Bounds3f; fn bounds(&self) -> Bounds3f;
@ -33,184 +29,5 @@ pub trait WavefrontAggregate {
shadow_ray_q: &ShadowRayQueue, shadow_ray_q: &ShadowRayQueue,
pixel_sample_state: &PixelSampleState, pixel_sample_state: &PixelSampleState,
); );
// fn intersect_one_random(
// &self,
// max_rays: usize,
// subsurface_scatte_q: &mut SubsurfaceScatterQueue,
// ) {
// todo!()
// }
} }
pub struct CpuAggregate {
pub aggregate: Primitive,
}
impl CpuAggregate {
pub fn new(aggregate: Primitive) -> Self {
Self { aggregate }
}
}
impl WavefrontAggregate for CpuAggregate {
fn bounds(&self) -> Bounds3f {
self.aggregate.bounds()
}
fn intersect_closest(
&self,
max_rays: usize,
ray_q: &RayQueue,
escaped_ray_q: &EscapedRayQueue,
hit_area_light_q: &HitAreaLightQueue,
basic_eval_mtl_q: &MaterialEvalQueue,
universal_eval_mtl_q: &MaterialEvalQueue,
next_ray_q: &RayQueue,
pixel_sample_state: &PixelSampleState,
) {
let n_rays = ray_q.size().min(max_rays as u32);
for i in 0..n_rays as usize {
let work = unsafe { ray_q.get(i) };
let ray = Ray::new(work.ray_o, work.ray_d, Some(work.ray_time), work.ray_medium);
// Read path state from PixelSampleState
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 {
// Ray escaped — push to escaped ray queue
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,
});
continue;
};
let intr = &si.intr;
// Check for null material (medium interface) — re-queue the ray
if intr.material.is_null() {
// Skip intersection and continue ray
// TODO: offset ray origin past the intersection
next_ray_q.push(RayWorkItem {
ray_o: intr.p(),
ray_d: work.ray_d,
ray_time: work.ray_time,
ray_medium: work.ray_medium,
has_differentials: work.has_differentials,
differential: work.differential,
pixel_index: work.pixel_index,
});
continue;
}
// Check for 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,
});
}
// 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.
let material = *intr.material.get().unwrap();
let eval_q = if material.can_evaluate_textures(&UniversalTextureEvaluator) {
basic_eval_mtl_q
} else {
universal_eval_mtl_q
};
eval_q.push(MaterialEvalWorkItem {
p: intr.p(),
n: intr.n(),
ns: intr.shading.n,
dpdu: intr.shading.dpdu,
dpdv: intr.shading.dpdv,
uv: intr.common.uv,
wo: -work.ray_d,
time: work.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,
});
}
}
fn intersect_shadow(
&self,
max_rays: usize,
shadow_ray_q: &ShadowRayQueue,
pixel_sample_state: &PixelSampleState,
) {
let n_rays = shadow_ray_q.size().min(max_rays as u32);
for i in 0..n_rays as usize {
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(),
);
// If the shadow ray is NOT occluded, add the direct lighting
// contribution to the pixel's accumulated radiance.
if !self.aggregate.intersect_p(&ray, Some(work.t_max)) {
let pi = work.pixel_index as usize;
let mut l = pixel_sample_state.l.get(pi);
l += work.l_d;
pixel_sample_state.l.set(pi, l);
}
}
}
fn intersect_shadow_tr(
&self,
max_rays: usize,
shadow_ray_q: &ShadowRayQueue,
pixel_sample_state: &PixelSampleState,
) {
self.intersect_shadow(max_rays, shadow_ray_q, pixel_sample_state);
}
}

View file

@ -1,22 +1,9 @@
use crate::core::bxdf::FArgs; use crate::core::camera::Camera;
use crate::core::bxdf::TransportMode;
use crate::core::camera::{Camera, CameraTrait};
use crate::core::film::Film; use crate::core::film::Film;
use crate::core::filter::{Filter, FilterTrait}; use crate::core::filter::Filter;
use crate::core::geometry::{ use crate::core::light::Light;
Bounds2i, Point2f, Point2i, Point3f, Point3fi, Ray, RayDifferential, Vector2f, Vector3f, use crate::core::sampler::Sampler;
VectorLike, use crate::lights::sampler::LightSampler;
};
use crate::core::interaction::InteractionTrait;
use crate::core::light::{Light, LightSampleContext, LightTrait};
use crate::core::material::{MaterialEvalContext, MaterialTrait};
use crate::core::sampler::{CameraSample, Sampler, SamplerTrait};
use crate::core::texture::{TextureEvalContext, UniversalTextureEvaluator};
use crate::lights::sampler::{LightSampler, LightSamplerTrait};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::square;
use crate::utils::sampling::power_heuristic;
use crate::utils::soa::{SoA, SoAAllocator, WorkQueue};
use crate::wavefront::aggregate::WavefrontAggregate; use crate::wavefront::aggregate::WavefrontAggregate;
use crate::wavefront::workitems::*; use crate::wavefront::workitems::*;
use crate::{Float, GVec, Ptr}; use crate::{Float, GVec, Ptr};
@ -24,460 +11,26 @@ use crate::{Float, GVec, Ptr};
pub struct WavefrontPathIntegrator<A: WavefrontAggregate> { pub struct WavefrontPathIntegrator<A: WavefrontAggregate> {
pub aggregate: A, pub aggregate: A,
pub camera: Camera, pub camera: Camera,
pub film: Film,
pub filter: Filter,
pub sampler: Sampler, pub sampler: Sampler,
pub max_depth: u32, pub max_depth: u32,
pub samples_per_pixel: u32, pub samples_per_pixel: u32,
pub regularize: bool, pub regularize: bool,
// Lights
pub infinite_lights: GVec<Ptr<Light>>, pub infinite_lights: GVec<Ptr<Light>>,
// Queue capacity = resolution.x * scanlines_per_pass
pub max_queue_size: u32, pub max_queue_size: u32,
pub scanlines_per_pass: u32, pub scanlines_per_pass: u32,
pub light_sampler: LightSampler,
pub film: Ptr<Film>,
pub filter: Ptr<Filter>,
pub ray_queues: [RayQueue; 2], pub ray_queues: [RayQueue; 2],
pub shadow_ray_queue: ShadowRayQueue, pub shadow_ray_queue: ShadowRayQueue,
pub escaped_ray_queue: EscapedRayQueue, pub escaped_ray_queue: EscapedRayQueue,
pub hit_area_light_queue: HitAreaLightQueue, pub hit_area_light_queue: HitAreaLightQueue,
pub basic_eval_material_queue: MaterialEvalQueue, pub basic_eval_material_queue: MaterialEvalQueue,
pub universal_eval_material_queue: MaterialEvalQueue, pub universal_eval_material_queue: MaterialEvalQueue,
pub light_sampler: LightSampler,
// Persistent per-path state
pub pixel_sample_state: PixelSampleState, pub pixel_sample_state: PixelSampleState,
} }
impl<A: WavefrontAggregate> WavefrontPathIntegrator<A> { pub trait WavefrontRenderer {
pub fn render(&mut self) { fn render(&mut self);
let pixel_bounds = self.film.pixel_bounds();
let resolution = pixel_bounds.diagonal();
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());
// 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 output queues before intersection
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();
// Skip if no rays to trace
if self.ray_queues[current].size() == 0 {
break;
}
// Sorting of rays into output queues
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,
);
// 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,
);
}
// Update film from accumulated pixel sample state
self.update_film(y0, y1, &pixel_bounds);
y0 = y1;
}
}
}
/// Stage 1: Generate camera rays for scanlines [y0, y1).
fn generate_camera_rays(
&mut self,
y0: i32,
y1: i32,
sample_index: u32,
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.
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);
// 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 lambda = SampledWavelengths::sample_visible(self.sampler.get1d());
let camera_sample = crate::core::sampler::get_camera_sample(
&mut self.sampler,
p_pixel,
&self.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, 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
.filter_weight
.set(pi, camera_sample.filter_weight);
self.pixel_sample_state
.prev_intr_ctx
.set(pi, LightSampleContext::default());
// 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(),
});
}
}
}
/// Handle escaped rays — 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 mut l_contrib = SampledSpectrum::new(0.0);
// 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 le = light.le(&ray, &w.lambda);
if le.is_black() {
continue;
}
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();
}
}
if !l_contrib.is_black() {
let pi = w.pixel_index as usize;
let mut l = self.pixel_sample_state.l.get(pi);
l += l_contrib;
self.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 = w.area_light.get().unwrap();
let le = light.l(w.p, w.n, w.uv, w.wo, &w.lambda);
if le.is_black() {
continue;
}
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()
};
if !l_contrib.is_black() {
let pi = w.pixel_index as usize;
let mut l = self.pixel_sample_state.l.get(pi);
l += l_contrib;
self.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;
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 Some(material) = w.material.get() else {
continue;
};
let tex_eval = UniversalTextureEvaluator;
let ctx = MaterialEvalContext {
texture: TextureEvalContext {
p: w.p,
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.dpdu,
};
let mut bsdf = material.get_bsdf(&tex_eval, &ctx, &lambda);
if bsdf.flags().is_empty() {
continue;
}
if self.regularize && any_non_specular {
bsdf.regularize();
}
if depth >= self.max_depth {
continue;
}
// Sample a light, compute contribution,
// push shadow ray with deferred visibility
if bsdf.flags().is_non_specular() {
let light_ctx = LightSampleContext {
pi: Point3fi::new_from_point(w.p),
n: w.n,
ns: w.ns,
};
if let Some(sampled_light) = self
.light_sampler
.sample_with_context(&light_ctx, self.sampler.get1d())
{
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
} else {
let p_b = bsdf.pdf(w.wo, wi, FArgs::default());
let w_l = power_heuristic(1, p_l, 1, p_b);
beta * w_l * ls.l * f_cos / p_l
};
if !l_d.is_black() {
let ray_o = Ray::offset_origin(
&Point3fi::new_from_point(w.p),
&w.n,
&wi,
);
let t_max = (1.0 - 1e-4)
* (Point3f::from(ls.p_light.p()) - ray_o).norm()
/ wi.norm();
self.shadow_ray_queue.push(ShadowRayWorkItem {
ray_o,
ray_d: wi,
ray_time: w.time,
t_max,
lambda,
l_d,
pixel_index: w.pixel_index,
});
}
}
}
}
}
}
}
// 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(),
});
}
}
/// Update film — write accumulated radiance to film pixels.
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 l = self.pixel_sample_state.l.get(pi);
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),
l,
&lambda,
Some(&crate::core::film::VisibleSurface::default()),
filter_weight,
);
pi += 1;
}
}
}
} }

View file

@ -4,4 +4,5 @@ pub mod integrator;
pub use workitems::*; pub use workitems::*;
pub use aggregate::WavefrontAggregate; pub use aggregate::WavefrontAggregate;
pub use integrator::{WavefrontPathIntegrator, WavefrontRenderer};

View file

@ -1,9 +1,7 @@
use crate::Arena;
use rayon::prelude::*; use rayon::prelude::*;
use shared::core::aggregates::{BVHAggregate, LinearBVHNode, SplitMethod}; use shared::core::aggregates::{BVHAggregate, LinearBVHNode, SplitMethod};
use shared::core::geometry::{Bounds3f, Point3f, Ray, Vector3f}; use shared::core::geometry::{Bounds3f, Point3f};
use shared::core::primitive::{Primitive, PrimitiveTrait}; use shared::core::primitive::{Primitive, PrimitiveTrait};
use shared::core::shape::ShapeIntersection;
use shared::utils::math::encode_morton_3; use shared::utils::math::encode_morton_3;
use shared::utils::{find_interval, partition_slice}; use shared::utils::{find_interval, partition_slice};
use shared::{gvec, gvec_from_slice, Float}; use shared::{gvec, gvec_from_slice, Float};

View file

@ -44,7 +44,7 @@ impl CameraBaseParameters {
} }
Ok(CameraBaseParameters { Ok(CameraBaseParameters {
camera_transform: camera_transform.clone(), camera_transform: *camera_transform,
shutter_open, shutter_open,
shutter_close, shutter_close,
film, film,
@ -56,7 +56,7 @@ impl CameraBaseParameters {
pub trait CameraBaseFactory { pub trait CameraBaseFactory {
fn create(p: CameraBaseParameters) -> CameraBase { fn create(p: CameraBaseParameters) -> CameraBase {
CameraBase { CameraBase {
camera_transform: p.camera_transform.clone(), camera_transform: p.camera_transform,
shutter_open: p.shutter_open, shutter_open: p.shutter_open,
shutter_close: p.shutter_close, shutter_close: p.shutter_close,
film: Ptr::from(p.film.clone().as_ref()), film: Ptr::from(p.film.clone().as_ref()),
@ -144,18 +144,16 @@ impl CameraFactory for Camera {
if !sw.is_empty() { if !sw.is_empty() {
if get_options().fullscreen { if get_options().fullscreen {
eprint!("Screenwindow is ignored in fullscreen mode"); eprint!("Screenwindow is ignored in fullscreen mode");
} else if sw.len() == 4 {
screen = Bounds2f::from_points(
Point2f::new(sw[0], sw[2]),
Point2f::new(sw[1], sw[3]),
);
} else { } else {
if sw.len() == 4 { return Err(anyhow!(
screen = Bounds2f::from_points( "{}: screenwindow param must have four values",
Point2f::new(sw[0], sw[2]), loc
Point2f::new(sw[1], sw[3]), ));
);
} else {
return Err(anyhow!(
"{}: screenwindow param must have four values",
loc
));
}
} }
} }
@ -191,18 +189,16 @@ impl CameraFactory for Camera {
if !sw.is_empty() { if !sw.is_empty() {
if get_options().fullscreen { if get_options().fullscreen {
eprint!("Screenwindow is ignored in fullscreen mode"); eprint!("Screenwindow is ignored in fullscreen mode");
} else if sw.len() == 4 {
screen = Bounds2f::from_points(
Point2f::new(sw[0], sw[2]),
Point2f::new(sw[1], sw[3]),
);
} else { } else {
if sw.len() == 4 { return Err(anyhow!(
screen = Bounds2f::from_points( "{}: screenwindow param must have four values",
Point2f::new(sw[0], sw[2]), loc
Point2f::new(sw[1], sw[3]), ));
);
} else {
return Err(anyhow!(
"{}: screenwindow param must have four values",
loc
));
}
} }
} }
let camera = OrthographicCamera::new(base, screen, lens_radius, focal_distance); let camera = OrthographicCamera::new(base, screen, lens_radius, focal_distance);
@ -236,7 +232,7 @@ impl CameraFactory for Camera {
PixelFormat::F32, PixelFormat::F32,
Point2i::new(builtin_res, builtin_res), Point2i::new(builtin_res, builtin_res),
&["Y"], &["Y"],
SRGB.into(), SRGB,
); );
let res = image.resolution(); let res = image.resolution();
@ -287,7 +283,7 @@ impl CameraFactory for Camera {
PixelFormat::F32, PixelFormat::F32,
Point2i::new(builtin_res, builtin_res), Point2i::new(builtin_res, builtin_res),
&["Y"], &["Y"],
SRGB.into(), SRGB,
); );
let res = img.resolution(); let res = img.resolution();
for y in 0..res.y() { for y in 0..res.y() {
@ -309,7 +305,7 @@ impl CameraFactory for Camera {
PixelFormat::F32, PixelFormat::F32,
Point2i::new(builtin_res, builtin_res), Point2i::new(builtin_res, builtin_res),
&["Y"], &["Y"],
SRGB.into(), SRGB,
); );
let low = (0.25 * builtin_res as Float) as i32; let low = (0.25 * builtin_res as Float) as i32;
let high = (0.75 * builtin_res as Float) as i32; let high = (0.75 * builtin_res as Float) as i32;
@ -358,7 +354,7 @@ impl CameraFactory for Camera {
PixelFormat::F32, PixelFormat::F32,
im.image.resolution(), im.image.resolution(),
&["Y"], &["Y"],
SRGB.into(), SRGB,
); );
let res = mono.resolution(); let res = mono.resolution();
for y in 0..res.y() { for y in 0..res.y() {

View file

@ -1,8 +1,7 @@
use crate::utils::read_float_file; use crate::utils::read_float_file;
use anyhow::Result; use anyhow::Result;
use shared::core::color::{Coeffs, RES, RGBToSpectrumTable}; use shared::core::color::{Coeffs, RES, RGBToSpectrumTable};
use shared::{Float, Ptr, gvec_from_slice}; use shared::{Float, gvec_from_slice};
use std::ops::Deref;
use std::path::Path; use std::path::Path;
pub trait CreateRGBToSpectrumTable { pub trait CreateRGBToSpectrumTable {

View file

@ -96,12 +96,12 @@ impl CreatePixelSensor for PixelSensor {
}; };
if sensor_name == "cie1931" { if sensor_name == "cie1931" {
return Ok(Self::new_with_white_balance( Ok(Self::new_with_white_balance(
output_colorspace.as_ref(), output_colorspace.as_ref(),
sensor_illum.as_deref(), sensor_illum.as_deref(),
imaging_ratio, imaging_ratio,
arena arena
)); ))
} else { } else {
let r_opt = get_named_spectrum(&format!("{}_r", sensor_name)); let r_opt = get_named_spectrum(&format!("{}_r", sensor_name));
let g_opt = get_named_spectrum(&format!("{}_g", sensor_name)); let g_opt = get_named_spectrum(&format!("{}_g", sensor_name));
@ -118,7 +118,7 @@ impl CreatePixelSensor for PixelSensor {
let g = g_opt.unwrap(); let g = g_opt.unwrap();
let b = b_opt.unwrap(); let b = b_opt.unwrap();
return Ok(Self::new( Ok(Self::new(
&r, &r,
&g, &g,
&b, &b,
@ -130,7 +130,7 @@ impl CreatePixelSensor for PixelSensor {
), ),
imaging_ratio, imaging_ratio,
arena arena
)); ))
} }
} }
@ -176,7 +176,7 @@ impl CreatePixelSensor for PixelSensor {
let sensor_white_g = illum.inner_product(&Spectrum::Dense(g_ptr)); let sensor_white_g = illum.inner_product(&Spectrum::Dense(g_ptr));
let sensor_white_y = illum.inner_product(&Spectrum::Dense(spectra.y)); let sensor_white_y = illum.inner_product(&Spectrum::Dense(spectra.y));
for i in 0..N_SWATCH_REFLECTANCES { for i in 0..N_SWATCH_REFLECTANCES {
let s = swatches[i].clone(); let s = swatches[i];
let xyz = PixelSensor::project_reflectance::<XYZ>( let xyz = PixelSensor::project_reflectance::<XYZ>(
&s, &s,
illum, illum,
@ -184,7 +184,7 @@ impl CreatePixelSensor for PixelSensor {
&Spectrum::Dense(spectra.y), &Spectrum::Dense(spectra.y),
&Spectrum::Dense(spectra.z), &Spectrum::Dense(spectra.z),
) * (sensor_white_y / sensor_white_g); ) * (sensor_white_y / sensor_white_g);
for c in 0..3 as u32 { for c in 0..3_u32 {
xyz_output[i][c as usize] = xyz[c].try_into().unwrap(); xyz_output[i][c as usize] = xyz[c].try_into().unwrap();
} }
} }
@ -362,7 +362,7 @@ pub trait FilmTrait: Sync {
}) })
.collect(); .collect();
let mut image = HostImage::new(format, resolution, channel_names, SRGB.into()); let mut image = HostImage::new(format, resolution, channel_names, SRGB);
let _rgb_desc = ImageChannelDesc::new(&[0, 1, 2]); let _rgb_desc = ImageChannelDesc::new(&[0, 1, 2]);
for (iy, row_data) in processed_rows.into_iter().enumerate() { for (iy, row_data) in processed_rows.into_iter().enumerate() {

View file

@ -2,10 +2,8 @@ use crate::Arena;
use crate::{FileLoc, ParameterDictionary}; use crate::{FileLoc, ParameterDictionary};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use shared::core::filter::Filter; use shared::core::filter::Filter;
use shared::core::geometry::{Bounds2f, Point2f, Vector2f}; use shared::core::geometry::Vector2f;
use shared::filters::*; use shared::filters::*;
use shared::utils::sampling::PiecewiseConstant2D;
use shared::{Array2D, Float};
pub trait FilterFactory { pub trait FilterFactory {
fn create( fn create(

View file

@ -302,7 +302,7 @@ fn read_pfm(path: &Path) -> Result<ImageAndMetadata> {
let names: &[&str] = if channels == 1 { &["Y"] } else { &["R", "G", "B"] }; let names: &[&str] = if channels == 1 { &["Y"] } else { &["R", "G", "B"] };
let image = HostImage::new(PixelFormat::F32, Point2i::new(w, h), names, LINEAR.into()); let image = HostImage::new(PixelFormat::F32, Point2i::new(w, h), names, LINEAR);
let metadata = ImageMetadata::default(); let metadata = ImageMetadata::default();
Ok(ImageAndMetadata { image, metadata }) Ok(ImageAndMetadata { image, metadata })

View file

@ -1,14 +1,12 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use half::f16;
use rayon::prelude::{IndexedParallelIterator, ParallelIterator, ParallelSliceMut}; use rayon::prelude::{IndexedParallelIterator, ParallelIterator, ParallelSliceMut};
use shared::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR}; use shared::core::color::ColorEncoding;
use shared::core::geometry::{Bounds2f, Point2f, Point2i}; use shared::core::geometry::{Bounds2f, Point2f, Point2i};
use shared::core::image::{Image, ImageBase, PixelFormat, Pixels, WrapMode, WrapMode2D}; use shared::core::image::{Image, PixelFormat, WrapMode, WrapMode2D};
use shared::utils::math::square; use shared::utils::math::square;
use shared::{Array2D, Float, Ptr}; use shared::{Array2D, Float};
use smallvec::{smallvec, SmallVec}; use smallvec::SmallVec;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::sync::Arc;
pub mod io; pub mod io;
pub mod metadata; pub mod metadata;

View file

@ -1,6 +1,5 @@
use super::HostImage; use super::HostImage;
use rayon::prelude::*; use rayon::prelude::*;
use shared::core::color::ColorEncoding;
use shared::core::geometry::{Bounds2i, Point2i}; use shared::core::geometry::{Bounds2i, Point2i};
use shared::core::image::{PixelFormat, WrapMode, WrapMode2D}; use shared::core::image::{PixelFormat, WrapMode, WrapMode2D};
use shared::utils::math::windowed_sinc; use shared::utils::math::windowed_sinc;
@ -109,7 +108,7 @@ impl HostImage {
PixelFormat::F32, PixelFormat::F32,
new_res, new_res,
&self.channel_names, &self.channel_names,
self.encoding().into(), self.encoding(),
))); )));
let x_weights = resample_weights(res.x() as usize, new_res.x() as usize); let x_weights = resample_weights(res.x() as usize, new_res.x() as usize);

View file

@ -37,7 +37,7 @@ impl InteractionGetter for SurfaceInteraction {
camera: &Camera, camera: &Camera,
sampler: &mut Sampler, sampler: &mut Sampler,
) -> Option<BSDF> { ) -> Option<BSDF> {
self.compute_differentials(r, camera, sampler.samples_per_pixel() as i32); self.compute_differentials(r, camera, sampler.samples_per_pixel());
let material = { let material = {
let Some(mut active_mat) = self.material.get() else { let Some(mut active_mat) = self.material.get() else {
return None; return None;
@ -47,7 +47,7 @@ impl InteractionGetter for SurfaceInteraction {
let ctx = MaterialEvalContext::from(&*self); let ctx = MaterialEvalContext::from(&*self);
active_mat = mix.choose_material(&tex_eval, &ctx)?; active_mat = mix.choose_material(&tex_eval, &ctx)?;
} }
active_mat.clone() *active_mat
}; };
let ctx = MaterialEvalContext::from(&*self); let ctx = MaterialEvalContext::from(&*self);
let tex_eval = UniversalTextureEvaluator; let tex_eval = UniversalTextureEvaluator;
@ -82,7 +82,7 @@ impl InteractionGetter for SurfaceInteraction {
let ctx = MaterialEvalContext::from(self); let ctx = MaterialEvalContext::from(self);
active_mat = mix.choose_material(&tex_eval, &ctx)?; active_mat = mix.choose_material(&tex_eval, &ctx)?;
} }
let material = active_mat.clone(); let material = *active_mat;
let ctx = MaterialEvalContext::from(self); let ctx = MaterialEvalContext::from(self);
material.get_bssrdf(&tex_eval, &ctx, lambda) material.get_bssrdf(&tex_eval, &ctx, lambda)
} }

View file

@ -14,7 +14,7 @@ use std::sync::Arc;
pub fn lookup_spectrum(s: &Spectrum) -> Arc<DenselySampledSpectrum> { pub fn lookup_spectrum(s: &Spectrum) -> Arc<DenselySampledSpectrum> {
let cache = &SPECTRUM_CACHE; let cache = &SPECTRUM_CACHE;
let dense_spectrum = DenselySampledSpectrum::from_spectrum(s); let dense_spectrum = DenselySampledSpectrum::from_spectrum(s);
cache.lookup(dense_spectrum).into() cache.lookup(dense_spectrum)
} }
fn dummy_shape() -> Shape { fn dummy_shape() -> Shape {
@ -60,7 +60,7 @@ pub fn create_light(
&shape, &alpha, None, arena, &shape, &alpha, None, arena,
), ),
"infinite" => crate::lights::infinite::create( "infinite" => crate::lights::infinite::create(
render_from_light, medium.into(), camera_transform, render_from_light, medium, camera_transform,
parameters, None, loc, arena, parameters, None, loc, arena,
), ),
"diffuse" => Err(anyhow!( "diffuse" => Err(anyhow!(

View file

@ -11,7 +11,6 @@ use shared::core::primitive::PrimitiveTrait;
use shared::core::sampler::CameraSample; use shared::core::sampler::CameraSample;
use shared::spectra::{SampledWavelengths, LAMBDA_MAX, LAMBDA_MIN}; use shared::spectra::{SampledWavelengths, LAMBDA_MAX, LAMBDA_MIN};
use shared::Float; use shared::Float;
use std::sync::Arc;
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();
@ -19,7 +18,7 @@ pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> {
let (named_materials, materials) = scene.create_materials(&textures, arena)?; let (named_materials, materials) = scene.create_materials(&textures, arena)?;
let lights = scene.create_lights(&textures, arena); let lights = scene.create_lights(&textures, arena);
let have_scattering = { let _have_scattering = {
let shapes = scene.shapes.lock(); let shapes = scene.shapes.lock();
let animated = scene.animated_shapes.lock(); let animated = scene.animated_shapes.lock();
shapes shapes
@ -37,16 +36,9 @@ pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> {
all_lights.extend(area_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();
warn!("Creating integrator"); warn!("Creating integrator");
let sampler = scene.get_sampler()?; let sampler = scene.get_sampler()?;
let integrator = scene.create_integrator(
camera.clone(),
sampler.clone(),
aggregate.clone(),
all_lights,
arena,
);
if get_options().pixel_material.is_some() { if get_options().pixel_material.is_some() {
let lambda = let lambda =
@ -96,22 +88,42 @@ pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> {
depth += 1; depth += 1;
ray = intr.spawn_ray(ray.d); ray = intr.spawn_ray(ray.d);
} }
} else if depth == 1 {
bail!("No geometry visible from pixel")
} else { } else {
if depth == 1 { break;
bail!("No geometry visible from pixel")
} else {
break;
}
} }
} }
} }
render( if get_options().wavefront {
&integrator, eprintln!("RENDER: Wavefront backend");
&integrator.base, let mut wf = scene.create_wavefront_integrator(
&camera, camera.clone(),
sampler.as_ref(), sampler.clone(),
arena, aggregate.clone(),
); all_lights,
arena,
);
wf.render();
} else {
eprintln!("RENDER: Path integrator backend");
let integrator = scene.create_integrator(
camera.clone(),
sampler.clone(),
aggregate.clone(),
all_lights,
arena,
);
render(
&integrator,
&integrator.base,
&camera,
sampler.as_ref(),
arena,
);
}
Ok(()) Ok(())
} }

View file

@ -260,7 +260,7 @@ impl ParserTarget for BasicSceneBuilder {
fn color_space(&mut self, name: &str, loc: FileLoc) -> Result<(), ParserError> { fn color_space(&mut self, name: &str, loc: FileLoc) -> Result<(), ParserError> {
let stdcs = get_colorspace_device(); let stdcs = get_colorspace_device();
let _ = match stdcs.get_named(name) { match stdcs.get_named(name) {
Some(cs) => { Some(cs) => {
self.graphics_state.color_space = Some(Arc::new(*cs.get().unwrap())); self.graphics_state.color_space = Some(Arc::new(*cs.get().unwrap()));
} }
@ -591,7 +591,7 @@ impl ParserTarget for BasicSceneBuilder {
self.current_accelerator self.current_accelerator
.take() .take()
.expect("Accelerator not set before WorldBegin"), .expect("Accelerator not set before WorldBegin"),
&arena, arena,
); );
Ok(()) Ok(())
} }

View file

@ -11,6 +11,7 @@ use crate::core::sampler::SamplerFactory;
use crate::core::shape::{ShapeFactory, ShapeWithContext}; use crate::core::shape::{ShapeFactory, ShapeWithContext};
use crate::core::texture::{FloatTexture, SpectrumTexture}; use crate::core::texture::{FloatTexture, SpectrumTexture};
use crate::integrators::{CreateIntegrator, PathConfig, PathIntegrator}; use crate::integrators::{CreateIntegrator, PathConfig, PathIntegrator};
use crate::lights::sampler::create_light_sampler;
use crate::utils::parallel::{run_async, AsyncJob}; use crate::utils::parallel::{run_async, AsyncJob};
use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary}; use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary};
use crate::utils::resolve_filename; use crate::utils::resolve_filename;
@ -19,21 +20,22 @@ use anyhow::{anyhow, Result};
use parking_lot::Mutex; use parking_lot::Mutex;
use shared::core::aggregates::{BVHAggregate, SplitMethod}; use shared::core::aggregates::{BVHAggregate, SplitMethod};
use shared::core::camera::CameraTrait; use shared::core::camera::CameraTrait;
use shared::core::camera::{Camera, CameraTransform}; use shared::core::camera::Camera;
use shared::core::color::LINEAR; use shared::core::color::LINEAR;
use shared::core::film::Film; use shared::core::film::Film;
use shared::core::filter::Filter; use shared::core::filter::Filter;
use shared::core::light::Light; use shared::core::light::{Light, LightTrait};
use shared::core::material::Material; use shared::core::material::Material;
use shared::core::medium::{Medium, MediumInterface}; use shared::core::medium::{Medium, MediumInterface};
use shared::core::primitive::{AnimatedPrimitive, GeometricPrimitive, Primitive, SimplePrimitive}; use shared::core::primitive::{AnimatedPrimitive, GeometricPrimitive, Primitive, SimplePrimitive};
use shared::core::sampler::Sampler; use shared::core::sampler::{Sampler, SamplerTrait};
use shared::core::shape::Shape; use shared::core::shape::Shape;
use shared::core::texture::{GPUFloatTexture, SpectrumType}; use shared::core::texture::SpectrumType;
use shared::lights::sampler::LightSampler;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::textures::FloatConstantTexture; use shared::textures::FloatConstantTexture;
use shared::{Ptr, Transform}; use shared::utils::soa::SoA;
use shared::wavefront::*;
use shared::{gvec, Ptr, WorkQueue};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
@ -80,7 +82,7 @@ fn resolve_material(
arena: &Arena, arena: &Arena,
) -> Material { ) -> Material {
match mat_ref { match mat_ref {
MaterialRef::Name(name) => match named_materials.get(name) { MaterialRef::Name(name) => match named_materials.get(name) {
Some(m) => *m, Some(m) => *m,
None => { None => {
log::error!("{}: named material '{}' not found", loc, name); log::error!("{}: named material '{}' not found", loc, name);
@ -146,6 +148,12 @@ pub struct BasicScene {
pub film_state: Mutex<SingletonState<Film>>, pub film_state: Mutex<SingletonState<Film>>,
} }
impl Default for BasicScene {
fn default() -> Self {
Self::new()
}
}
impl BasicScene { impl BasicScene {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
@ -196,7 +204,7 @@ impl BasicScene {
&film.parameters, &film.parameters,
exposure_time, exposure_time,
filter, filter,
Some(camera.camera_transform.clone()), Some(camera.camera_transform),
&film.loc, &film.loc,
arena, arena,
) )
@ -508,12 +516,12 @@ impl BasicScene {
state.map.clone() state.map.clone()
} }
pub fn create_lights(&self, textures: &NamedTextures, arena: &Arena) -> Vec<Arc<Light>> { pub fn create_lights(&self, _textures: &NamedTextures, arena: &Arena) -> Vec<Arc<Light>> {
let light_state = self.light_state.lock(); let light_state = self.light_state.lock();
let camera = self let camera = self
.get_camera() .get_camera()
.expect("Camera must be initialized before lights"); .expect("Camera must be initialized before lights");
let camera_transform = camera.base().camera_transform.clone(); let camera_transform = camera.base().camera_transform;
let mut lights: Vec<Arc<Light>> = Vec::new(); let mut lights: Vec<Arc<Light>> = Vec::new();
// Non-area lights created from stored entities // Non-area lights created from stored entities
@ -533,7 +541,7 @@ impl BasicScene {
medium.map(|m| *m), medium.map(|m| *m),
&entity.transformed_base.base.parameters, &entity.transformed_base.base.parameters,
&entity.transformed_base.base.loc, &entity.transformed_base.base.loc,
camera_transform.clone(), camera_transform,
arena, arena,
) { ) {
Ok(light) => lights.push(Arc::new(light)), Ok(light) => lights.push(Arc::new(light)),
@ -709,6 +717,90 @@ impl BasicScene {
} }
} }
pub fn create_wavefront_integrator(
&self,
camera: Arc<Camera>,
sampler: Arc<Sampler>,
aggregate: Arc<Primitive>,
lights: Vec<Arc<Light>>,
arena: &Arena,
) -> WavefrontPathIntegrator<CpuAggregate> {
let entity = self.integrator.lock().clone().unwrap();
let max_depth = entity
.parameters
.get_one_int("maxdepth", 5)
.expect("Could not obtain depth value");
let regularize = entity
.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));
}
}
let cpu_aggregate = CpuAggregate::new(*aggregate);
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),
}
}
// Getters // Getters
pub fn get_camera(&self) -> Result<Arc<Camera>> { pub fn get_camera(&self) -> Result<Arc<Camera>> {
@ -847,7 +939,7 @@ impl BasicScene {
shape, shape,
arena.alloc(mtl), arena.alloc(mtl),
light_ptr, light_ptr,
mi.clone(), mi,
alpha_ptr, alpha_ptr,
)) ))
}; };
@ -866,9 +958,9 @@ impl BasicScene {
materials: &[Material], materials: &[Material],
light_state: &LightState, light_state: &LightState,
media: &HashMap<String, Arc<Medium>>, media: &HashMap<String, Arc<Medium>>,
film_cs: Option<&RGBColorSpace>, _film_cs: Option<&RGBColorSpace>,
arena: &Arena, arena: &Arena,
area_lights: &mut Vec<Arc<Light>>, _area_lights: &mut Vec<Arc<Light>>,
) -> Vec<Primitive> { ) -> Vec<Primitive> {
let mut primitives = Vec::new(); let mut primitives = Vec::new();
@ -945,7 +1037,7 @@ impl BasicScene {
shape, shape,
arena.alloc(mtl), arena.alloc(mtl),
Ptr::null(), // no area light on animated shapes Ptr::null(), // no area light on animated shapes
mi.clone(), mi,
alpha_ptr, alpha_ptr,
)) ))
}; };
@ -1103,7 +1195,6 @@ impl BasicScene {
Err(anyhow!("{} requested but not initialized!", name)) Err(anyhow!("{} requested but not initialized!", name))
} }
#[allow(dead_code)] #[allow(dead_code)]
fn upload_shapes( fn upload_shapes(
&self, &self,

View file

@ -3,7 +3,6 @@ use crate::core::film::{CreateFilmBase, CreatePixelSensor};
use shared::core::film::PixelSensor; use shared::core::film::PixelSensor;
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use shared::core::film::{FilmBase, GBufferFilm}; use shared::core::film::{FilmBase, GBufferFilm};
use shared::spectra::RGBColorSpace;
use shared::utils::AnimatedTransform; use shared::utils::AnimatedTransform;
use std::path::Path; use std::path::Path;
@ -26,7 +25,7 @@ impl CreateFilm for GBufferFilm {
let filename = params.get_one_string("filename", "pbrt.exr")?; let filename = params.get_one_string("filename", "pbrt.exr")?;
if Path::new(&filename).extension() != Some("exr".as_ref()) { if Path::new(&filename).extension() != Some("exr".as_ref()) {
return Err(anyhow!("{}: EXR is the only format supported by GBufferFilm", loc).into()); return Err(anyhow!("{}: EXR is the only format supported by GBufferFilm", loc));
} }
let coords_system = params.get_one_string("coordinatesystem", "camera")?; let coords_system = params.get_one_string("coordinatesystem", "camera")?;
@ -43,8 +42,7 @@ impl CreateFilm for GBufferFilm {
"{}: unknown coordinate system for GBufferFilm. (Expecting camera "{}: unknown coordinate system for GBufferFilm. (Expecting camera
or world", or world",
loc loc
) ));
.into());
}; };
let film = GBufferFilm::new( let film = GBufferFilm::new(

View file

@ -9,9 +9,6 @@ pub mod gbuffer;
pub mod rgb; pub mod rgb;
pub mod spectral; pub mod spectral;
pub use gbuffer::*;
pub use rgb::*;
pub use spectral::*;
pub trait CreateFilm { pub trait CreateFilm {
fn create( fn create(

View file

@ -3,8 +3,7 @@ use crate::core::film::{CreateFilmBase, CreatePixelSensor};
use crate::Arena; use crate::Arena;
use anyhow::Result; use anyhow::Result;
use shared::core::camera::CameraTransform; use shared::core::camera::CameraTransform;
use shared::core::film::{Film, FilmBase, RGBFilm, RGBPixel, PixelSensor}; use shared::core::film::{Film, FilmBase, RGBFilm, PixelSensor};
use shared::spectra::RGBColorSpace;
impl CreateFilm for RGBFilm { impl CreateFilm for RGBFilm {
fn create( fn create(

View file

@ -5,7 +5,6 @@ use anyhow::{anyhow, Result};
use shared::core::camera::CameraTransform; use shared::core::camera::CameraTransform;
use shared::core::film::{FilmBase, PixelSensor, SpectralFilm}; use shared::core::film::{FilmBase, PixelSensor, SpectralFilm};
use shared::spectra::{LAMBDA_MAX, LAMBDA_MIN}; use shared::spectra::{LAMBDA_MAX, LAMBDA_MIN};
use shared::utils::math::SquareMatrix;
use shared::Float; use shared::Float;
use std::path::Path; use std::path::Path;
@ -28,7 +27,7 @@ impl CreateFilm for SpectralFilm {
let filename = params.get_one_string("filename", "pbrt.exr")?; let filename = params.get_one_string("filename", "pbrt.exr")?;
if Path::new(&filename).extension() != Some("exr".as_ref()) { if Path::new(&filename).extension() != Some("exr".as_ref()) {
return Err(anyhow!("{}: EXR is the only format supported by GBufferFilm", loc).into()); return Err(anyhow!("{}: EXR is the only format supported by GBufferFilm", loc));
} }
let n_buckets = params.get_one_int("nbuckets", 16)? as usize; let n_buckets = params.get_one_int("nbuckets", 16)? as usize;

View file

@ -15,7 +15,6 @@ use shared::core::geometry::{Point2i, Ray};
use shared::core::light::Light; use shared::core::light::Light;
use shared::core::primitive::Primitive; use shared::core::primitive::Primitive;
use shared::core::sampler::Sampler; use shared::core::sampler::Sampler;
use shared::lights::sampler::LightSampler;
use shared::spectra::{SampledSpectrum, SampledWavelengths}; use shared::spectra::{SampledSpectrum, SampledWavelengths};
use std::sync::Arc; use std::sync::Arc;
@ -58,7 +57,7 @@ impl CreateIntegrator for PathIntegrator {
fn create( fn create(
parameters: ParameterDictionary, parameters: ParameterDictionary,
camera: Arc<Camera>, camera: Arc<Camera>,
sampler: Arc<Sampler>, _sampler: Arc<Sampler>,
aggregate: Arc<Primitive>, aggregate: Arc<Primitive>,
lights: Vec<Arc<Light>>, lights: Vec<Arc<Light>>,
config: PathConfig, config: PathConfig,

View file

@ -126,7 +126,7 @@ impl PathIntegrator {
if !self if !self
.base .base
.unoccluded(&Interaction::Surface(intr.clone()), &ls.p_light) .unoccluded(&Interaction::Surface(*intr), &ls.p_light)
{ {
return SampledSpectrum::zero(); return SampledSpectrum::zero();
} }
@ -232,15 +232,14 @@ impl RayIntegratorTrait for PathIntegrator {
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
if !isect.area_light.is_null() { && !isect.area_light.is_null() {
let light = &isect.area_light; let light = &isect.area_light;
let p_l = self.sampler.pmf_with_context(&state.prev_ctx, light) let p_l = self.sampler.pmf_with_context(&state.prev_ctx, light)
* 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;
} }
}
} }
// Get BSDF // Get BSDF

View file

@ -9,14 +9,13 @@ use crate::Arena;
use indicatif::{ProgressBar, ProgressStyle}; use indicatif::{ProgressBar, ProgressStyle};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use shared::core::camera::{Camera, CameraTrait}; use shared::core::camera::{Camera, CameraTrait};
use shared::core::geometry::{Bounds2i, Point2i, VectorLike}; use shared::core::geometry::{Bounds2i, Point2i};
use shared::core::sampler::get_camera_sample; use shared::core::sampler::get_camera_sample;
use shared::core::sampler::{Sampler, SamplerTrait}; use shared::core::sampler::{Sampler, SamplerTrait};
use shared::spectra::SampledSpectrum; use shared::spectra::SampledSpectrum;
use shared::Float; use shared::Float;
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::Path;
use std::sync::Arc;
struct PbrtProgress { struct PbrtProgress {
bar: ProgressBar, bar: ProgressBar,
@ -95,7 +94,7 @@ pub fn render<T>(
&mut tile_sampler, &mut tile_sampler,
p_pixel, p_pixel,
s_index, s_index,
&arena, arena,
); );
return; return;
} }
@ -158,7 +157,7 @@ pub fn render<T>(
if let Some(out_path) = &options.mse_reference_output { if let Some(out_path) = &options.mse_reference_output {
mse_out_file = Some( mse_out_file = Some(
std::fs::File::create(out_path) std::fs::File::create(out_path)
.expect(&format!("Failed to create MSE output file: {}", out_path)), .unwrap_or_else(|_| panic!("Failed to create MSE output file: {}", out_path)),
); );
} }
} }
@ -177,7 +176,7 @@ pub fn render<T>(
&mut sampler, &mut sampler,
p_pixel, p_pixel,
sample_index.try_into().unwrap(), sample_index.try_into().unwrap(),
&arena, arena,
); );
} }
} }
@ -200,7 +199,7 @@ pub fn render<T>(
if wave_start == spp || options.write_partial_images || reference_image.is_some() { if wave_start == spp || options.write_partial_images || reference_image.is_some() {
let mut metadata = ImageMetadata { let mut metadata = ImageMetadata {
render_time_seconds: Some(progress.elapsed_seconds()), render_time_seconds: Some(progress.elapsed_seconds()),
samples_per_pixel: Some(wave_start as i32), samples_per_pixel: Some(wave_start),
..Default::default() ..Default::default()
}; };

View file

@ -14,6 +14,12 @@ pub struct PathState {
pub prev_pdf: Float, pub prev_pdf: Float,
} }
impl Default for PathState {
fn default() -> Self {
Self::new()
}
}
impl PathState { impl PathState {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {

View file

@ -15,3 +15,5 @@ pub mod wavefront;
pub use utils::{Arena, FileLoc, ParameterDictionary, Upload, ArenaUpload}; pub use utils::{Arena, FileLoc, ParameterDictionary, Upload, ArenaUpload};
pub const MAX_TAGS: u32 = 16; pub const MAX_TAGS: u32 = 16;
pub use shared::{BasicPBRTOptions, PBRTOptions};
pub use globals::{get_options, init_pbrt};

View file

@ -11,7 +11,7 @@ use shared::core::spectrum::Spectrum;
use shared::core::texture::SpectrumType; use shared::core::texture::SpectrumType;
use shared::lights::DistantLight; use shared::lights::DistantLight;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::{Ptr, Transform}; use shared::utils::Transform;
use shared::Float; use shared::Float;
trait CreateDistantLight { trait CreateDistantLight {

View file

@ -69,12 +69,11 @@ pub fn create(
scale /= spectrum_to_photometric(i); scale /= spectrum_to_photometric(i);
let phi_v = params.get_one_float("power", -1.0)?; let phi_v = params.get_one_float("power", -1.0)?;
if phi_v > 0.0 { if phi_v > 0.0
if let Some(ref img) = host_image { && let Some(ref img) = host_image {
let k_e = compute_emissive_power(img); let k_e = compute_emissive_power(img);
scale *= phi_v / k_e; scale *= phi_v / k_e;
} }
}
let swap_yz: [Float; 16] = [ let swap_yz: [Float; 16] = [
1., 0., 0., 0., 0., 0., 1., 0., 0., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 1., 0., 0., 1., 0., 0., 0., 0., 0., 1.,
@ -146,7 +145,7 @@ fn convert_to_luminance_image(
} }
} }
Ok(HostImage::from_f32(&y_pixels, res, &["Y"].to_vec())) Ok(HostImage::from_f32(&y_pixels, res, ["Y"].as_ref()))
} }
(Err(_), Ok(_)) => { (Err(_), Ok(_)) => {

View file

@ -9,7 +9,7 @@ use rayon::prelude::*;
use shared::core::camera::CameraTransform; use shared::core::camera::CameraTransform;
use shared::core::geometry::{cos_theta, Bounds2f, Frame, Point2f, Point2i, Point3f, VectorLike}; use shared::core::geometry::{cos_theta, Bounds2f, Frame, Point2f, Point2i, Point3f, VectorLike};
use shared::core::image::WrapMode; use shared::core::image::WrapMode;
use shared::core::light::{Light, LightBase, LightType}; use shared::core::light::Light;
use shared::core::medium::Medium; use shared::core::medium::Medium;
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;
use shared::core::texture::SpectrumType; use shared::core::texture::SpectrumType;
@ -17,7 +17,7 @@ use shared::lights::{ImageInfiniteLight, PortalInfiniteLight, UniformInfiniteLig
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::math::{equal_area_sphere_to_square, equal_area_square_to_sphere}; use shared::utils::math::{equal_area_sphere_to_square, equal_area_square_to_sphere};
use shared::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D}; use shared::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D};
use shared::{Float, Ptr, Transform, PI}; use shared::{Float, Transform, PI};
use std::path::Path; use std::path::Path;
pub fn create( pub fn create(
@ -275,7 +275,7 @@ fn load_image(
let rgb = l[0].to_rgb(colorspace, &stdspec); let rgb = l[0].to_rgb(colorspace, &stdspec);
let image = let image =
HostImage::new_constant(Point2i::new(1, 1), &["R", "G", "B"], &[rgb.r, rgb.g, rgb.b]); HostImage::new_constant(Point2i::new(1, 1), &["R", "G", "B"], &[rgb.r, rgb.g, rgb.b]);
return Ok((image, colorspace.clone())); return Ok((image, *colorspace));
} }
let im = HostImage::read(Path::new(filename), None) let im = HostImage::read(Path::new(filename), None)
@ -290,7 +290,7 @@ fn load_image(
.get_channel_desc(&["R", "G", "B"]) .get_channel_desc(&["R", "G", "B"])
.map_err(|_| anyhow!("image '{}' must have R, G, B channels", filename))?; .map_err(|_| anyhow!("image '{}' must have R, G, B channels", filename))?;
let cs = im.metadata.colorspace.unwrap_or_else(|| colorspace.clone()); let cs = im.metadata.colorspace.unwrap_or_else(|| *colorspace);
Ok((im.image.select_channels(&desc), cs)) Ok((im.image.select_channels(&desc), cs))
} }

View file

@ -11,7 +11,7 @@ use shared::core::spectrum::Spectrum;
use shared::core::texture::SpectrumType; use shared::core::texture::SpectrumType;
use shared::lights::PointLight; use shared::lights::PointLight;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::{Float, PI, Ptr, Transform}; use shared::{Float, PI, Transform};
pub trait CreatePointLight { pub trait CreatePointLight {
fn new( fn new(

View file

@ -1,7 +1,7 @@
use crate::Arena; use crate::Arena;
use shared::core::light::{Light, LightTrait}; use shared::core::light::{Light, LightTrait};
use shared::lights::sampler::{ use shared::lights::sampler::{
BVHLightSampler, LightSampler, PowerLightSampler, UniformLightSampler, LightSampler, PowerLightSampler, UniformLightSampler,
}; };
use shared::utils::sampling::AliasTable; use shared::utils::sampling::AliasTable;
use shared::spectra::{SampledSpectrum, SampledWavelengths}; use shared::spectra::{SampledSpectrum, SampledWavelengths};

View file

@ -12,7 +12,7 @@ use shared::core::texture::SpectrumType;
use shared::lights::SpotLight; use shared::lights::SpotLight;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::math::radians; use shared::utils::math::radians;
use shared::{Float, Ptr, Transform, PI}; use shared::{Float, Transform, PI};
trait CreateSpotLight { trait CreateSpotLight {
fn new( fn new(

View file

@ -23,7 +23,7 @@ impl CreateHaltonSampler for HaltonSampler {
full_res: Point2i, full_res: Point2i,
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
seed: u64, seed: u64,
arena: &Arena, _arena: &Arena,
) -> Self { ) -> Self {
let digit_permutations = compute_radical_inverse_permutations(seed); let digit_permutations = compute_radical_inverse_permutations(seed);
let leaked = Box::leak(gbox(digit_permutations)); let leaked = Box::leak(gbox(digit_permutations));

View file

@ -63,7 +63,7 @@ impl CreateShape for BilinearPatchShape {
n.clear(); n.clear();
} }
for (_, &idx) in vertex_indices.iter().enumerate() { for &idx in vertex_indices.iter() {
if idx < 0 || idx as usize >= p.len() { if idx < 0 || idx as usize >= p.len() {
return Err(anyhow!( return Err(anyhow!(
"Bilinear patch mesh has out-of-bounds vertex index {} ({} \"P\" values were given). Discarding this mesh.", "Bilinear patch mesh has out-of-bounds vertex index {} ({} \"P\" values were given). Discarding this mesh.",

View file

@ -45,7 +45,7 @@ pub fn create_curve(
let u_max = (i + 1) as Float / n_segments as Float; let u_max = (i + 1) as Float / n_segments as Float;
let curve = CurveShape { let curve = CurveShape {
common: curve_common.clone(), common: curve_common,
u_min, u_min,
u_max, u_max,
}; };
@ -90,7 +90,7 @@ impl CreateShape for CurveShape {
if basis == "bezier" { if basis == "bezier" {
if cp.len() <= degree as usize if cp.len() <= degree as usize
|| ((cp.len() - 1 - degree as usize) % degree as usize) != 0 || !(cp.len() - 1 - degree as usize).is_multiple_of(degree as usize)
{ {
return Err(anyhow!( return Err(anyhow!(
"Invalid number of control points {}: for the degree {} Bezier basis {} + n * {} are required.", "Invalid number of control points {}: for the degree {} Bezier basis {} + n * {} are required.",

View file

@ -3,7 +3,6 @@ use ply_rs::parser::Parser;
use ply_rs::ply::{DefaultElement, Property}; use ply_rs::ply::{DefaultElement, Property};
use shared::core::geometry::{Normal3f, Point2f, Point3f, VectorLike}; use shared::core::geometry::{Normal3f, Point2f, Point3f, VectorLike};
use shared::shapes::mesh::TriangleMesh; use shared::shapes::mesh::TriangleMesh;
use shared::utils::sampling::PiecewiseConstant2D;
use shared::Transform; use shared::Transform;
use std::fs::File; use std::fs::File;
use std::path::Path; use std::path::Path;
@ -214,9 +213,9 @@ impl TriQuadMesh {
let v20 = p2 - p0; let v20 = p2 - p0;
let face_normal = v10.cross(v20); let face_normal = v10.cross(v20);
self.n[i0] = self.n[i0] + Normal3f::from(face_normal); self.n[i0] += Normal3f::from(face_normal);
self.n[i1] = self.n[i1] + Normal3f::from(face_normal); self.n[i1] += Normal3f::from(face_normal);
self.n[i2] = self.n[i2] + Normal3f::from(face_normal); self.n[i2] += Normal3f::from(face_normal);
} }
for quad in self.quad_indices.chunks_exact(4) { for quad in self.quad_indices.chunks_exact(4) {
@ -231,14 +230,14 @@ impl TriQuadMesh {
let face_normal = v10.cross(v20); let face_normal = v10.cross(v20);
for &idx in &indices { for &idx in &indices {
self.n[idx] = self.n[idx] + Normal3f::from(face_normal); self.n[idx] += Normal3f::from(face_normal);
} }
} }
for normal in &mut self.n { for normal in &mut self.n {
let len_sq = normal.norm_squared(); let len_sq = normal.norm_squared();
if len_sq > 0.0 { if len_sq > 0.0 {
*normal = *normal / len_sq.sqrt(); *normal /= len_sq.sqrt();
} }
} }
} }

View file

@ -60,7 +60,7 @@ impl CreateShape for TriangleShape {
n.clear(); n.clear();
} }
for (_, &index) in vertex_indices.iter().enumerate() { for &index in vertex_indices.iter() {
// Check for negative indices (if keeping i32) or out of bounds // Check for negative indices (if keeping i32) or out of bounds
if index < 0 || index as usize >= p.len() { if index < 0 || index as usize >= p.len() {
bail!( bail!(

View file

@ -5,7 +5,6 @@ use shared::core::spectrum::Spectrum;
use shared::spectra::{DenselySampledSpectrum, RGBColorSpace}; use shared::spectra::{DenselySampledSpectrum, RGBColorSpace};
use shared::utils::math::SquareMatrix; use shared::utils::math::SquareMatrix;
use shared::Ptr; use shared::Ptr;
use std::sync::Arc;
pub trait CreateRGBColorSpace { pub trait CreateRGBColorSpace {
fn new( fn new(

View file

@ -1,7 +1,7 @@
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;
use shared::spectra::cie::*; use shared::spectra::cie::*;
use shared::spectra::{DenselySampledSpectrum, PiecewiseLinearSpectrum}; use shared::spectra::{DenselySampledSpectrum, PiecewiseLinearSpectrum};
use shared::{gbox, gvec_from_slice, leak, Float, Ptr}; use shared::{gvec_from_slice, leak, Float};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::LazyLock; use std::sync::LazyLock;

View file

@ -107,10 +107,10 @@ impl StandardColorSpaces {
pub fn get_colorspace_context() -> StandardColorSpaces { pub fn get_colorspace_context() -> StandardColorSpaces {
StandardColorSpaces { StandardColorSpaces {
srgb: SRGB.clone().into(), srgb: SRGB.clone(),
dci_p3: DCI_P3.clone().into(), dci_p3: DCI_P3.clone(),
rec2020: REC2020.clone().into(), rec2020: REC2020.clone(),
aces2065_1: ACES.clone().into(), aces2065_1: ACES.clone(),
} }
} }

View file

@ -1,5 +1,4 @@
use parking_lot::Mutex; use parking_lot::Mutex;
use shared::core::geometry::{Bounds2i, Point2i};
use std::collections::HashSet; use std::collections::HashSet;
use std::hash::Hash; use std::hash::Hash;
use std::sync::Arc; use std::sync::Arc;
@ -8,6 +7,15 @@ pub struct InternCache<T> {
cache: Mutex<HashSet<Arc<T>>>, cache: Mutex<HashSet<Arc<T>>>,
} }
impl<T> Default for InternCache<T>
where
T: Eq + Clone + Hash,
{
fn default() -> Self {
Self::new()
}
}
impl<T> InternCache<T> impl<T> InternCache<T>
where where
T: Eq + Clone + Hash, T: Eq + Clone + Hash,

View file

@ -157,7 +157,7 @@ impl MIPMap {
} }
pub fn get_rgb_colorspace(&self) -> Option<RGBColorSpace> { pub fn get_rgb_colorspace(&self) -> Option<RGBColorSpace> {
self.color_space.clone() self.color_space
} }
pub fn get_level(&self, level: usize) -> &HostImage { pub fn get_level(&self, level: usize) -> &HostImage {

View file

@ -1,5 +1,4 @@
use crossbeam_channel::{Receiver, bounded}; use crossbeam_channel::{Receiver, bounded};
use rayon::prelude::*;
#[derive(Debug)] #[derive(Debug)]
pub struct AsyncJob<T> { pub struct AsyncJob<T> {

View file

@ -515,7 +515,7 @@ impl ParameterDictionary {
|| param.type_name == "rgb" || param.type_name == "rgb"
|| param.type_name == "blackbody" || param.type_name == "blackbody"
{ {
return Some(self.extract_spectrum_array(param, stype)[0].clone()); return Some(self.extract_spectrum_array(param, stype)[0]);
} }
} }
} }
@ -654,7 +654,7 @@ impl ParameterDictionary {
} }
fn extract_sampled_spectrum(&self, param: &ParsedParameter) -> Vec<Spectrum> { fn extract_sampled_spectrum(&self, param: &ParsedParameter) -> Vec<Spectrum> {
if param.floats.len() % 2 != 0 { if !param.floats.len().is_multiple_of(2) {
panic!( panic!(
"{}: Found odd number of values for '{}'", "{}: Found odd number of values for '{}'",
param.loc, param.name param.loc, param.name
@ -852,11 +852,11 @@ impl TextureParameterDictionary {
pub fn get_float_texture(&self, name: &str, val: Float) -> Result<Arc<FloatTexture>> { pub fn get_float_texture(&self, name: &str, val: Float) -> Result<Arc<FloatTexture>> {
if let Some(tex) = self.get_float_texture_or_null(name)? { if let Some(tex) = self.get_float_texture_or_null(name)? {
return Ok(tex); Ok(tex)
} else { } else {
return Ok(Arc::new(FloatTexture::Constant(FloatConstantTexture::new( Ok(Arc::new(FloatTexture::Constant(FloatConstantTexture::new(
val, val,
)))); ))))
} }
} }
@ -961,7 +961,7 @@ impl TextureParameterDictionary {
_ => {} _ => {}
} }
} }
return None; None
} }
pub fn get_float_texture_or_null(&self, name: &str) -> Result<Option<Arc<FloatTexture>>> { pub fn get_float_texture_or_null(&self, name: &str) -> Result<Option<Arc<FloatTexture>>> {

View file

@ -102,7 +102,6 @@ fn convert_spectrum(tex: &SpectrumTexture, arena: &Arena) -> GPUSpectrumTexture
t.base t.base
.mipmap .mipmap
.color_space .color_space
.clone()
.unwrap_or_else(crate::spectra::default_colorspace), .unwrap_or_else(crate::spectra::default_colorspace),
), ),
}) })
@ -127,14 +126,14 @@ impl Upload for Arc<SpectrumTexture> {
impl Upload for &FloatTexture { impl Upload for &FloatTexture {
type Target = Ptr<GPUFloatTexture>; type Target = Ptr<GPUFloatTexture>;
fn upload(self, arena: &Arena) -> Self::Target { fn upload(self, arena: &Arena) -> Self::Target {
arena.alloc(convert_float(&self, arena)) arena.alloc(convert_float(self, arena))
} }
} }
impl Upload for &SpectrumTexture { impl Upload for &SpectrumTexture {
type Target = Ptr<GPUSpectrumTexture>; type Target = Ptr<GPUSpectrumTexture>;
fn upload(self, arena: &Arena) -> Self::Target { fn upload(self, arena: &Arena) -> Self::Target {
arena.alloc(convert_spectrum(&self, arena)) arena.alloc(convert_spectrum(self, arena))
} }
} }
impl Upload for Option<Arc<FloatTexture>> { impl Upload for Option<Arc<FloatTexture>> {

181
src/wavefront/aggregate.rs Normal file
View file

@ -0,0 +1,181 @@
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::wavefront::WavefrontAggregate;
pub struct CpuAggregate {
pub aggregate: Primitive,
}
impl CpuAggregate {
pub fn new(aggregate: Primitive) -> Self {
Self { aggregate }
}
}
impl WavefrontAggregate for CpuAggregate {
fn bounds(&self) -> Bounds3f {
self.aggregate.bounds()
}
fn intersect_closest(
&self,
max_rays: usize,
ray_q: &RayQueue,
escaped_ray_q: &EscapedRayQueue,
hit_area_light_q: &HitAreaLightQueue,
basic_eval_mtl_q: &MaterialEvalQueue,
universal_eval_mtl_q: &MaterialEvalQueue,
next_ray_q: &RayQueue,
pixel_sample_state: &PixelSampleState,
) {
let n_rays = ray_q.size().min(max_rays as u32);
let options = get_options();
(0..n_rays as usize).into_par_iter().for_each(|i| {
let work = 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 {
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,
});
return;
};
let intr = &si.intr;
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,
});
return;
}
// Check for 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,
});
}
// 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.
let material = *intr.material.get().unwrap();
let eval_q = if material.can_evaluate_textures(&BasicTextureEvaluator) {
basic_eval_mtl_q
} else {
universal_eval_mtl_q
};
eval_q.push(MaterialEvalWorkItem {
p: intr.p(),
n: intr.n(),
ns: intr.shading.n,
dpdu: intr.shading.dpdu,
dpdv: intr.shading.dpdv,
uv: intr.common.uv,
wo: -work.ray_d,
time: work.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,
});
});
}
fn intersect_shadow(
&self,
max_rays: usize,
shadow_ray_q: &ShadowRayQueue,
pixel_sample_state: &PixelSampleState,
) {
let n_rays = shadow_ray_q.size().min(max_rays as u32);
for i in 0..n_rays as usize {
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(),
);
if !self.aggregate.intersect_p(&ray, Some(work.t_max)) {
let pi = work.pixel_index as usize;
let mut l = pixel_sample_state.l.get(pi);
l += work.l_d;
pixel_sample_state.l.set(pi, l);
}
}
}
fn intersect_shadow_tr(
&self,
max_rays: usize,
shadow_ray_q: &ShadowRayQueue,
pixel_sample_state: &PixelSampleState,
) {
self.intersect_shadow(max_rays, shadow_ray_q, pixel_sample_state);
}
}

View file

@ -1,38 +1,459 @@
// use crate::MAX_TAGS; use super::CpuAggregate;
// use shared::{Ptr, GVec}; use crate::globals::get_options;
// use shared::core::film::Film; use shared::core::bxdf::{FArgs, TransportMode};
// use shared::core::color::RGB; use shared::core::camera::{Camera, CameraTrait};
// use shared::core::filter::Filter; use shared::core::filter::{Filter, FilterTrait};
// use shared::core::light::Light; use shared::core::geometry::{
// use shared::core::sampler::Sampler; Bounds2i, Point2f, Point2i, Point3f, Point3fi, Ray, RayDifferential, Vector2f, Vector3f,
// use shared::wavefront::{WavefrontAggregate, RayQueue, MediumSampleQueue, EscapedRayQueue, HitAreaLightQueue, MaterialEvalQueue, ShadowRayQueue, GetBSSRDFAndProbeRayQueue, SubsurfaceScatterQueue}; VectorLike,
// };
// pub struct WavefrontPathIntegrator { use shared::core::interaction::InteractionTrait;
// pub init_visible_surface: bool, use shared::core::light::{Light, LightSampleContext, LightTrait};
// pub have_subsurface: bool, use shared::core::material::{MaterialEvalContext, MaterialTrait};
// pub have_media: bool, use shared::core::sampler::{CameraSample, Sampler, SamplerTrait};
// pub have_basic_eval_material: [bool; MAX_TAGS + 1], use shared::core::texture::{TextureEvalContext, UniversalTextureEvaluator};
// pub have_universal_eval_material: [bool; MAX_TAGS + 1], use shared::lights::sampler::{LightSampler, LightSamplerTrait};
// pub filter: Filter, use shared::spectra::{SampledSpectrum, SampledWavelengths};
// pub film: Film, use shared::utils::math::square;
// pub sampler: Sampler, use shared::utils::sampling::power_heuristic;
// pub camera: Camera, use shared::utils::soa::{SoA, SoAAllocator, WorkQueue};
// pub infinite_lights: GVec<Light>, use shared::wavefront::{WavefrontAggregate, WavefrontPathIntegrator, WavefrontRenderer};
// pub max_depth: usize,
// pub sampler_per_pixel: usize, impl WavefrontRenderer for WavefrontPathIntegrator<CpuAggregate> {
// pub regularize: bool, pub fn render(&mut self) {
// pub scanlines_per_pixel: usize, let film = self.camera.get_film();
// pub max_queue_size: usize, let filter = film.get_filter();
// pub medium_sample_queue: Ptr<MediumSampleQueue>, let pixel_bounds = film.pixel_bounds();
// pub medium_scatter_queue: Ptr<MediumScatterQueue>, let resolution = pixel_bounds.diagonal();
// pub escaped_ray_queue: Ptr<EscapedRayQueue>,
// pub hit_area_light_queue: Ptr<HitAreaLightQueue>, let total_work = (pixel_bounds.area() as u64) * (self.samples_per_pixel as u64);
// pub basic_eval_material_queue: Ptr<MaterialEvalQueue>, let options = get_options();
// pub universal_eval_material_queue: Ptr<MaterialEvalQueue>, let progress = PbrtProgress::new(total_work, "Rendering", options.quiet);
// pub shadow_ray_queue: Ptr<ShadowRayQueue>,
// pub bssrdf_eval_queue: PTr<GetBSSRDFAndProbeRayQueue>, for sample_index in 0..self.samples_per_pixel {
// pub subsurface_scatter_queue: Ptr<SubsurfaceScatterQueue>, // Process image in scanline batches
// pub display_rgb: Ptr<RGB>, let mut y0 = pixel_bounds.p_min.y();
// pub display_rgb_host: Ptr<RGB>, 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 output queues before intersection
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();
// Skip if no rays to trace
if self.ray_queues[current].size() == 0 {
break;
}
// Sorting of rays into output queues
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,
);
// 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);
y0 = y1;
}
}
}
/// Stage 1: Generate camera rays for scanlines [y0, y1).
fn generate_camera_rays(
&mut self,
y0: i32,
y1: i32,
sample_index: u32,
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.
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);
// 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 lambda = SampledWavelengths::sample_visible(self.sampler.get1d());
let camera_sample = crate::core::sampler::get_camera_sample(
&mut self.sampler,
p_pixel,
&self.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, 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
.filter_weight
.set(pi, camera_sample.filter_weight);
self.pixel_sample_state
.prev_intr_ctx
.set(pi, LightSampleContext::default());
// 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(),
});
}
}
}
/// Handle escaped rays — 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 mut l_contrib = SampledSpectrum::new(0.0);
// 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 le = light.le(&ray, &w.lambda);
if le.is_black() {
continue;
}
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();
}
}
if !l_contrib.is_black() {
let pi = w.pixel_index as usize;
let mut l = self.pixel_sample_state.l.get(pi);
l += l_contrib;
self.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 = w.area_light.get().unwrap();
let le = light.l(w.p, w.n, w.uv, w.wo, &w.lambda);
if le.is_black() {
continue;
}
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()
};
if !l_contrib.is_black() {
let pi = w.pixel_index as usize;
let mut l = self.pixel_sample_state.l.get(pi);
l += l_contrib;
self.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;
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 Some(material) = w.material.get() else {
continue;
};
let tex_eval = UniversalTextureEvaluator;
let ctx = MaterialEvalContext {
texture: TextureEvalContext {
p: w.p,
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.dpdu,
};
let mut bsdf = material.get_bsdf(&tex_eval, &ctx, &lambda);
if bsdf.flags().is_empty() {
continue;
}
if self.regularize && any_non_specular {
bsdf.regularize();
}
if depth >= self.max_depth {
continue;
}
// Sample a light, compute contribution,
// push shadow ray with deferred visibility
if bsdf.flags().is_non_specular() {
let light_ctx = LightSampleContext {
pi: Point3fi::new_from_point(w.p),
n: w.n,
ns: w.ns,
};
if let Some(sampled_light) = self
.light_sampler
.sample_with_context(&light_ctx, self.sampler.get1d())
{
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
} else {
let p_b = bsdf.pdf(w.wo, wi, FArgs::default());
let w_l = power_heuristic(1, p_l, 1, p_b);
beta * w_l * ls.l * f_cos / p_l
};
if !l_d.is_black() {
let ray_o = Ray::offset_origin(
&Point3fi::new_from_point(w.p),
&w.n,
&wi,
);
let t_max = (1.0 - 1e-4)
* (Point3f::from(ls.p_light.p()) - ray_o).norm()
/ wi.norm();
self.shadow_ray_queue.push(ShadowRayWorkItem {
ray_o,
ray_d: wi,
ray_time: w.time,
t_max,
lambda,
l_d,
pixel_index: w.pixel_index,
});
}
}
}
}
}
}
}
// 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) {
// 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 l = self.pixel_sample_state.l.get(pi);
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),
l,
&lambda,
Some(&crate::core::film::VisibleSurface::default()),
filter_weight,
);
pi += 1;
}
}
}
}

View file

@ -1 +1,4 @@
pub mod aggregate;
pub mod integrator; pub mod integrator;
pub use aggregate::CpuAggregate;