pbrt/shared/src/lights/projection.rs

169 lines
5.1 KiB
Rust

use crate::{
spectra::{RGB, RGBColorSpace},
utils::{Transform, sampling::PiecewiseConstant2D},
};
#[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 image_id: u32,
pub a: Float,
pub distrib: PiecewiseConstant2D,
pub image_color_space: *const RGBColorSpace,
}
impl ProjectionLight {
pub fn new(
render_from_light: Transform,
medium_interface: MediumInterface,
image_id: u32,
image_color_space: RGBColorSpace,
scale: Float,
fov: Float,
) -> Self {
let base = LightBase::new(
LightType::DeltaPosition,
&render_from_light,
&medium_interface,
);
let image = Image::new();
let aspect = image.resolution().x() as Float / image.resolution().y() as Float;
let screen_bounds = if aspect > 1. {
Bounds2f::from_points(Point2f::new(-aspect, -1.), Point2f::new(aspect, 1.))
} else {
Bounds2f::from_points(
Point2f::new(-1., 1. / aspect),
Point2f::new(1., 1. / aspect),
)
};
let hither = 1e-3;
let screen_from_light = TransformGeneric::perspective(fov, hither, 1e30).unwrap();
let light_from_screen = screen_from_light.inverse();
let opposite = (radians(fov) / 2.).tan();
let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect };
let a = 4. * square(opposite) * aspect_ratio;
let dwda = |p: Point2f| {
let w =
Vector3f::from(light_from_screen.apply_to_point(Point3f::new(p.x(), p.y(), 0.)));
cos_theta(w.normalize()).powi(3)
};
let d = image.get_sampling_distribution(dwda, screen_bounds);
let distrib = PiecewiseConstant2D::new_with_bounds(&d, screen_bounds);
Self {
base,
image_id,
image_color_space,
screen_bounds,
screen_from_light,
light_from_screen,
scale,
hither,
a,
distrib,
}
}
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.as_ref(), RGB::clamp_zero(rgb));
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> {
todo!()
}
fn pdf_li(
&self,
_ctx: &LightSampleContext,
_wi: Vector3f,
_allow_incomplete_pdf: bool,
) -> Float {
todo!()
}
fn l(
&self,
_p: Point3f,
_n: Normal3f,
_uv: Point2f,
_w: Vector3f,
_lambda: &SampledWavelengths,
) -> SampledSpectrum {
todo!()
}
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
}
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
let mut sum = SampledSpectrum::new(0.);
for y in 0..self.image.resolution.y() {
for x in 0..self.image.resolution.x() {
let ps = self.screen_bounds.lerp(Point2f::new(
(x as Float + 0.5) / self.image.resolution.x() as Float,
(y as Float + 0.5) / self.image.resolution.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.as_ref(),
RGB::clamp_zero(rgb),
);
sum += s.sample(&lambda) * dwda;
}
}
self.scale * self.a * sum / (self.image.resolution.x() * self.image.resolution.y()) as Float
}
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
todo!()
}
fn bounds(&self) -> Option<LightBounds> {
todo!()
}
}