Some more refactoring, more error correction. When will this end?

This commit is contained in:
pingu 2026-01-22 16:18:26 +00:00
parent 93bcd465eb
commit 380b1c9f90
6 changed files with 492 additions and 1167 deletions

71
src/integrators/base.rs Normal file
View file

@ -0,0 +1,71 @@
use super::state::PathState;
use crate::core::light::Light;
use shared::core::geometry::Ray;
use shared::core::interaction::{Interaction, InteractionTrait};
use shared::core::primitive::Primitive;
use shared::core::shape::ShapeIntersection;
use shared::lights::LightSampler;
use shared::spectra::SampledWavelengths;
use shared::utils::sampling::power_heuristic;
use shared::{Float, SHADOW_EPSILON};
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct IntegratorBase {
pub aggregate: Arc<Primitive>,
pub lights: Vec<Arc<Light>>,
pub infinite_lights: Vec<Arc<Light>>,
}
impl IntegratorBase {
pub fn new(aggregate: Arc<Primitive>, lights: Vec<Arc<Light>>) -> Self {
let infinite_lights = lights
.iter()
.filter(|light| light.light_type().is_infinite())
.cloned()
.collect();
Self {
aggregate,
lights,
infinite_lights,
}
}
pub fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
self.aggregate.intersect(ray, t_max)
}
pub fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
self.aggregate.intersect_p(ray, t_max)
}
pub fn unoccluded(&self, p0: &Interaction, p1: &Interaction) -> bool {
!self.intersect_p(&p0.spawn_ray_to_interaction(p1), Some(1. - SHADOW_EPSILON))
}
pub fn add_infinite_light_contribution(
&self,
state: &mut PathState,
ray: &Ray,
lambda: &SampledWavelengths,
light_sampler: Option<&LightSampler>,
use_mis: bool,
) {
for light in &self.infinite_lights {
let le = light.le(ray, lambda);
if le.is_black() {
continue;
}
if state.depth == 0 || state.specular_bounce || !use_mis {
state.l += state.beta * le;
} else if let Some(sampler) = light_sampler {
let p_l = sampler.pmf_with_context(&state.prev_ctx, light)
* light.pdf_li(&state.prev_ctx, ray.d, true);
let w_b = power_heuristic(1, state.prev_pdf, 1, p_l);
state.l += state.beta * w_b * le;
}
}
}
}

View file

@ -0,0 +1,42 @@
use shared::core::geometry::Point2f;
use shared::Float;
pub const N_RHO_SAMPLES: usize = 16;
pub static UC_RHO: [Float; N_RHO_SAMPLES] = [
0.75741637,
0.37870818,
0.7083487,
0.18935409,
0.9149363,
0.35417435,
0.5990858,
0.09467703,
0.8578725,
0.45746812,
0.686759,
0.17708716,
0.9674518,
0.2995429,
0.5083201,
0.047338516,
];
pub static U_RHO: [Point2f; N_RHO_SAMPLES] = [
Point2f::new(0.855985, 0.570367),
Point2f::new(0.381823, 0.851844),
Point2f::new(0.285328, 0.764262),
Point2f::new(0.733380, 0.114073),
Point2f::new(0.542663, 0.344465),
Point2f::new(0.127274, 0.414848),
Point2f::new(0.964700, 0.947162),
Point2f::new(0.594089, 0.643463),
Point2f::new(0.095109, 0.170369),
Point2f::new(0.825444, 0.263359),
Point2f::new(0.429467, 0.454469),
Point2f::new(0.244460, 0.816459),
Point2f::new(0.756135, 0.731258),
Point2f::new(0.516165, 0.152852),
Point2f::new(0.180888, 0.214174),
Point2f::new(0.898579, 0.503897),
];

File diff suppressed because it is too large Load diff

320
src/integrators/path.rs Normal file
View file

@ -0,0 +1,320 @@
use super::RayIntegratorTrait;
use super::base::IntegratorBase;
use super::constants::*;
use super::state::PathState;
use crate::Arena;
use shared::core::bsdf::{BSDF, BSDFSample};
use shared::core::bxdf::{BxDFFlags, FArgs, TransportMode};
use shared::core::camera::Camera;
use shared::core::film::VisibleSurface;
use shared::core::geometry::{Point2i, Ray, Vector3f, VectorLike};
use shared::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use shared::core::light::{Light, LightSampleContext};
use shared::core::primitive::Primitive;
use shared::core::sampler::{Sampler, SamplerTrait};
use shared::lights::sampler::LightSampler;
use shared::lights::sampler::LightSamplerTrait;
use shared::spectra::{SampledSpectrum, SampledWavelengths};
use shared::utils::math::square;
use shared::utils::sampling::{
power_heuristic, sample_uniform_hemisphere, sample_uniform_sphere, uniform_hemisphere_pdf,
uniform_sphere_pdf,
};
use std::sync::Arc;
#[derive(Clone, Copy)]
pub struct PathConfig {
pub max_depth: usize,
pub regularize: bool,
pub sample_lights: bool,
pub sample_bsdf: bool,
pub handle_volumes: bool,
pub use_mis: bool,
}
impl PathConfig {
pub const SIMPLE: Self = Self {
max_depth: 5,
regularize: false,
sample_lights: true,
sample_bsdf: true,
handle_volumes: false,
use_mis: false,
};
pub const FULL: Self = Self {
max_depth: 8,
regularize: true,
sample_lights: true,
sample_bsdf: true,
handle_volumes: false,
use_mis: true,
};
pub const VOLUMETRIC: Self = Self {
max_depth: 8,
regularize: true,
sample_lights: true,
sample_bsdf: true,
handle_volumes: true,
use_mis: true,
};
}
pub struct PathIntegrator {
base: IntegratorBase,
camera: Arc<Camera>,
light_sampler: LightSampler,
config: PathConfig,
}
unsafe impl Send for PathIntegrator {}
unsafe impl Sync for PathIntegrator {}
impl PathIntegrator {
pub fn new(
aggregate: Arc<Primitive>,
lights: Vec<Arc<Light>>,
camera: Arc<Camera>,
config: PathConfig,
) -> Self {
let base = IntegratorBase::new(aggregate, lights.clone());
let light_sampler = LightSampler::new(&lights);
Self {
base,
camera,
light_sampler,
config,
}
}
fn sample_direct(
&self,
intr: &SurfaceInteraction,
bsdf: &BSDF,
state: &PathState,
lambda: &SampledWavelengths,
sampler: &mut Sampler,
) -> SampledSpectrum {
let ctx = LightSampleContext::from(intr);
let Some(sampled) = self
.light_sampler
.sample_with_context(&ctx, sampler.get1d())
else {
return SampledSpectrum::ZERO;
};
let Some(ls) = sampled.light.sample_li(&ctx, sampler.get2d(), lambda, true) else {
return SampledSpectrum::ZERO;
};
if ls.l.is_black() || ls.pdf == 0.0 {
return SampledSpectrum::ZERO;
}
let wo = intr.wo();
let wi = ls.wi;
let Some(f) = bsdf.f(wo, wi, TransportMode::Radiance) else {
return SampledSpectrum::ZERO;
};
let f = f * wi.abs_dot(intr.shading.n.into());
if f.is_black() {
return SampledSpectrum::ZERO;
}
if !self
.base
.unoccluded(&Interaction::Surface(intr.clone()), &ls.p_light)
{
return SampledSpectrum::ZERO;
}
let p_l = sampled.p * ls.pdf;
if !self.config.use_mis || sampled.light.light_type().is_delta_light() {
ls.l * f / p_l
} else {
let p_b = bsdf.pdf(wo, wi, FArgs::default());
let w_l = power_heuristic(1, p_l, 1, p_b);
w_l * ls.l * f / p_l
}
}
fn sample_direction(
&self,
wo: Vector3f,
bsdf: &BSDF,
isect: &SurfaceInteraction,
sampler: &mut Sampler,
) -> Option<BSDFSample> {
if self.config.sample_bsdf {
bsdf.sample_f(wo, sampler.get1d(), sampler.get2d(), FArgs::default())
} else {
self.sample_uniform(wo, bsdf, isect, sampler)
}
}
fn sample_uniform(
&self,
wo: Vector3f,
bsdf: &BSDF,
isect: &SurfaceInteraction,
sampler: &mut Sampler,
) -> Option<BSDFSample> {
let flags = bsdf.flags();
let (wi, pdf) = if flags.is_reflective() && flags.is_transmissive() {
(sample_uniform_sphere(sampler.get2d()), uniform_sphere_pdf())
} else {
let mut wi = sample_uniform_hemisphere(sampler.get2d());
let same_hemi = wo.dot(isect.n().into()) * wi.dot(isect.n().into()) > 0.0;
if (flags.is_reflective() && !same_hemi) || (flags.is_transmissive() && same_hemi) {
wi = -wi;
}
(wi, uniform_hemisphere_pdf())
};
let f = bsdf.f(wo, wi, TransportMode::Radiance)?;
Some(BSDFSample {
f,
wi,
pdf,
flags: BxDFFlags::empty(), // or appropriate flags
eta: 1.0,
pdf_is_proportional: false,
})
}
fn compute_visible_surface(
&self,
isect: &SurfaceInteraction,
bsdf: &BSDF,
lambda: &SampledWavelengths,
) -> VisibleSurface {
let albedo = bsdf.rho_wo(isect.wo(), &UC_RHO, &U_RHO);
VisibleSurface::new(isect, &albedo, lambda)
}
}
impl RayIntegratorTrait for PathIntegrator {
fn li(
&self,
mut ray: Ray,
lambda: &SampledWavelengths,
sampler: &mut Sampler,
want_visible: bool,
_arena: &mut Arena,
) -> (SampledSpectrum, Option<VisibleSurface>) {
let mut state = PathState::new();
let mut visible = None;
loop {
let Some(mut si) = self.base.intersect(&ray, None) else {
self.base.add_infinite_light_contribution(
&mut state,
&ray,
lambda,
Some(&self.light_sampler),
self.config.use_mis,
);
break;
};
let isect = &mut si.intr;
// Emission from hit surface
let le = isect.le(-ray.d, lambda);
if !le.is_black() {
if state.depth == 0 || state.specular_bounce {
state.l += state.beta * le;
} else if self.config.use_mis {
if let Some(light) = &isect.area_light {
let p_l = self.light_sampler.pmf_with_context(&state.prev_ctx, light)
* light.pdf_li(&state.prev_ctx, ray.d, true);
let w_b = power_heuristic(1, state.prev_pdf, 1, p_l);
state.l += state.beta * w_b * le;
}
}
}
// Get BSDF
let Some(mut bsdf) = isect.get_bsdf(&ray, lambda, &self.camera, sampler) else {
state.specular_bounce = true;
isect.skip_intersection(&mut ray, si.t_hit());
continue;
};
// Visible surface at primary hit
if state.depth == 0 && want_visible {
visible = Some(self.compute_visible_surface(isect, &bsdf, lambda));
}
// Depth check
if state.depth >= self.config.max_depth {
break;
}
state.depth += 1;
// Regularization
if self.config.regularize && state.any_non_specular_bounces {
bsdf.regularize();
}
// Direct lighting
if self.config.sample_lights && bsdf.flags().is_non_specular() {
state.l += state.beta * self.sample_direct(isect, &bsdf, &state, lambda, sampler);
}
// Sample BSDF for next direction
let wo = -ray.d;
let Some(bs) = self.sample_direction(wo, &bsdf, isect, sampler) else {
break;
};
state.beta *= bs.f * bs.wi.abs_dot(isect.shading.n.into()) / bs.pdf;
state.prev_pdf = if bs.pdf_is_proportional {
bsdf.pdf(wo, bs.wi, FArgs::default())
} else {
bs.pdf
};
state.specular_bounce = bs.is_specular();
state.any_non_specular_bounces |= !bs.is_specular();
if bs.is_transmissive() {
state.eta_scale *= square(bs.eta);
}
state.prev_ctx = LightSampleContext::from(&*isect);
ray = isect.spawn_ray_with_differentials(&ray, bs.wi, bs.flags, bs.eta);
// Russian roulette
if state.russian_roulette(sampler, 1) {
break;
}
}
(state.l, visible)
}
fn evaluate_pixel_sample(
&self,
p_pixel: Point2i,
sample_ind: usize,
sampler: &mut Sampler,
arena: &mut Arena,
) {
crate::integrators::pipeline::evaluate_pixel_sample(
self,
self.camera.as_ref(),
sampler,
p_pixel,
sample_ind,
arena,
);
}
}

View file

@ -1,12 +1,19 @@
use super::RayIntegratorTrait;
use super::base::IntegratorBase;
use crate::Arena;
use crate::core::image::{Image, ImageMetadata};
use crate::spectra::get_spectra_context;
use indicatif::{ProgressBar, ProgressStyle};
use shared::Float;
use shared::core::camera::Camera;
use shared::core::geometry::{Bounds2i, Point2i};
use shared::core::options::get_options;
use shared::core::sampler::Sampler;
use shared::core::sampler::get_camera_sample;
use shared::spectra::SampledSpectrum;
use std::io::Write;
use std::path::Path;
use super::*;
struct PbrtProgress {
bar: ProgressBar,
}

45
src/integrators/state.rs Normal file
View file

@ -0,0 +1,45 @@
use shared::Float;
use shared::core::light::LightSampleContext;
use shared::core::sampler::{Sampler, SamplerTrait};
use shared::spectra::SampledSpectrum;
pub struct PathState {
pub l: SampledSpectrum,
pub beta: SampledSpectrum,
pub depth: usize,
pub specular_bounce: bool,
pub any_non_specular_bounces: bool,
pub eta_scale: Float,
pub prev_ctx: LightSampleContext,
}
impl PathState {
pub fn new() -> Self {
Self {
l: SampledSpectrum::new(0.0),
beta: SampledSpectrum::new(1.0),
depth: 0,
specular_bounce: false,
any_non_specular_bounces: false,
eta_scale: 1.0,
prev_ctx: LightSampleContext::default(),
}
}
pub fn terminated(&self) -> bool {
self.beta.is_black()
}
/// Termination check
pub fn russian_roulette(&mut self, sampler: &mut Sampler, min_depth: usize) -> bool {
let rr_beta = self.beta * self.eta_scale;
if rr_beta.max_component_value() < 1.0 && self.depth > min_depth {
let q = (1.0 - rr_beta.max_component_value()).max(0.0);
if sampler.get1d() < q {
return true; // terminate
}
self.beta /= 1.0 - q;
}
false
}
}