pbrt/shared/src/lights/projection.rs

162 lines
5.3 KiB
Rust

use crate::Float;
use crate::core::color::RGB;
use crate::core::geometry::{
Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f, VectorLike, cos_theta,
};
use crate::core::image::Image;
use crate::core::interaction::{Interaction, InteractionBase, SimpleInteraction};
use crate::core::light::{
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
};
use crate::core::medium::MediumInterface;
use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{radians, square};
use crate::{
spectra::{RGBColorSpace, RGBIlluminantSpectrum},
utils::{Ptr, Transform, sampling::PiecewiseConstant2D},
};
use num_traits::Float as NumFloat;
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct ProjectionLight {
pub base: LightBase,
pub scale: Float,
pub hither: Float,
pub screen_bounds: Bounds2f,
pub screen_from_light: Transform,
pub light_from_screen: Transform,
pub a: Float,
pub image: Ptr<Image>,
pub distrib: Ptr<PiecewiseConstant2D>,
pub image_color_space: Ptr<RGBColorSpace>,
}
impl ProjectionLight {
pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
if w.z() < self.hither {
return SampledSpectrum::new(0.);
}
let ps = self.screen_from_light.apply_to_point(w.into());
if !self.screen_bounds.contains(Point2f::new(ps.x(), ps.y())) {
return SampledSpectrum::new(0.);
}
let uv = Point2f::from(self.screen_bounds.offset(&Point2f::new(ps.x(), ps.y())));
let mut rgb = RGB::default();
for c in 0..3 {
rgb[c] = self.image.lookup_nearest_channel(uv, c);
}
let s = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero());
self.scale * s.sample(lambda)
}
}
impl LightTrait for ProjectionLight {
fn base(&self) -> &LightBase {
&self.base
}
fn sample_li(
&self,
ctx: &LightSampleContext,
_u: Point2f,
lambda: &SampledWavelengths,
_allow_incomplete_pdf: bool,
) -> Option<LightLiSample> {
let render_from_light = self.base().render_from_light;
let p = render_from_light.apply_to_point(Point3f::new(0., 0., 0.));
let wi = (p - ctx.p()).normalize();
let wl = render_from_light.apply_inverse_vector(-wi);
let li = self.i(wl, lambda) / p.distance_squared(ctx.p());
if li.is_black() {
return None;
}
let base = InteractionBase::new_boundary(p, 0., self.base.medium_interface);
let intr = SimpleInteraction::new(base);
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
}
fn pdf_li(
&self,
_ctx: &LightSampleContext,
_wi: Vector3f,
_allow_incomplete_pdf: bool,
) -> Float {
0.
}
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
let mut sum = SampledSpectrum::new(0.);
let res = self.image.resolution();
for y in 0..res.y() {
for x in 0..res.x() {
let ps = self.screen_bounds.lerp(Point2f::new(
(x as Float + 0.5) / res.x() as Float,
(y as Float + 0.5) / res.y() as Float,
));
let w_raw = Vector3f::from(self.light_from_screen.apply_to_point(Point3f::new(
ps.x(),
ps.y(),
0.,
)));
let w = w_raw.normalize();
let dwda = cos_theta(w).powi(3);
let mut rgb = RGB::default();
for c in 0..3 {
rgb[c] = self.image.get_channel(Point2i::new(x, y), c);
}
let s = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero());
sum += s.sample(&lambda) * dwda;
}
}
self.scale * self.a * sum / (res.x() * res.y()) as Float
}
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {}
fn bounds(&self) -> Option<LightBounds> {
let mut sum = 0.;
for v in 0..self.image.resolution().y() {
for u in 0..self.image.resolution().x() {
let uv = Point2i::new(u, v);
sum += self.image.get_channel(uv, 0).max(
self.image
.get_channel(uv, 1)
.max(self.image.get_channel(uv, 2)),
);
}
}
let phi =
self.scale * sum / (self.image.resolution().x() * self.image.resolution().y()) as f32;
let p_corner = Point3f::new(
self.screen_bounds.p_max.x(),
self.screen_bounds.p_max.y(),
0.,
);
let w_corner = Vector3f::from(self.light_from_screen.apply_to_point(p_corner)).normalize();
let cos_total_width = cos_theta(w_corner);
let p = self
.base
.render_from_light
.apply_to_point(Point3f::new(0., 0., 0.));
let w = self
.base
.render_from_light
.apply_to_vector(Vector3f::new(0., 0., 1.));
Some(LightBounds::new(
&Bounds3f::from_points(p, p),
w,
phi,
1.,
cos_total_width,
false,
))
}
}