pbrt/src/lights/goniometric.rs

178 lines
5.5 KiB
Rust

use std::path::Path;
use crate::core::image::{Image, ImageIO};
use crate::core::light::{CreateLight, lookup_spectrum};
use crate::core::spectrum::spectrum_to_photometric;
use crate::core::texture::FloatTexture;
use crate::utils::sampling::PiecewiseConstant2D;
use crate::utils::{Arena, FileLoc, ParameterDictionary, resolve_filename};
use anyhow::{Result, anyhow};
use shared::core::geometry::Point2i;
use shared::core::light::{Light, LightBase, LightType};
use shared::core::medium::{Medium, MediumInterface};
use shared::core::shape::Shape;
use shared::core::spectrum::Spectrum;
use shared::core::texture::SpectrumType;
use shared::lights::GoniometricLight;
use shared::spectra::RGBColorSpace;
use shared::utils::{Ptr, Transform};
use shared::{Float, PI};
pub trait CreateGoniometricLight {
fn new(
render_from_light: Transform,
medium_interface: MediumInterface,
le: Spectrum,
scale: Float,
image: Ptr<Image>,
) -> Self;
}
impl CreateGoniometricLight for GoniometricLight {
fn new(
render_from_light: Transform,
medium_interface: MediumInterface,
le: Spectrum,
scale: Float,
image: Ptr<Image>,
) -> Self {
let base = LightBase::new(
LightType::DeltaPosition,
render_from_light,
medium_interface,
);
let iemit = lookup_spectrum(&le);
let distrib = PiecewiseConstant2D::from_image(&image);
Self {
base,
iemit: Ptr::from(&iemit.device()),
scale,
image: Ptr::from(image.device()),
distrib: Ptr::from(&distrib.device),
}
}
}
impl CreateLight for GoniometricLight {
fn create(
render_from_light: Transform,
medium: Medium,
params: &ParameterDictionary,
loc: &FileLoc,
_shape: &Shape,
_alpha_text: &FloatTexture,
colorspace: Option<&RGBColorSpace>,
_arena: &Arena,
) -> Result<Light> {
let i = params
.get_one_spectrum(
"I",
Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
SpectrumType::Illuminant,
)
.expect("Could not retrieve spectrum");
let mut scale = params.get_one_float("scale", 1.)?;
let filename = resolve_filename(&params.get_one_string("filename", "")?);
let image: Ptr<Image> = if filename.is_empty() {
Ptr::null()
} else {
let im = Image::read(Path::new(&filename), None)
.map_err(|e| anyhow!("could not load image '{}': {}", filename, e))?;
let loaded = im.image;
let res = loaded.resolution();
if loaded.has_any_infinite_pixels() {
return Err(anyhow!(
"image '{}' has infinite pixels, not suitable for light",
filename
));
}
if res.x() != res.y() {
return Err(anyhow!(
"image resolution ({}, {}) is non-square; unlikely to be an equal-area map",
res.x(),
res.y()
));
}
Ptr::from(&convert_to_luminance_image(&loaded, &filename, loc)?)
};
scale /= spectrum_to_photometric(i);
let phi_v = params.get_one_float("power", -1.0)?;
if phi_v > 0.0 {
let k_e = compute_emissive_power(&image);
scale *= phi_v / k_e;
}
let swap_yz: [Float; 16] = [
1., 0., 0., 0., 0., 0., 1., 0., 0., 1., 0., 0., 0., 0., 0., 1.,
];
let t = Transform::from_flat(&swap_yz)
.expect("Could not create transform for GoniometricLight");
let final_render_from_light = render_from_light * t;
let specific =
GoniometricLight::new(final_render_from_light, medium.into(), i, scale, image);
Ok(Light::Goniometric(specific))
}
}
fn convert_to_luminance_image(image: &Image, filename: &str, loc: &FileLoc) -> Result<Image> {
let res = image.resolution();
let rgb_desc = image.get_channel_desc(&["R", "G", "B"]);
let y_desc = image.get_channel_desc(&["Y"]);
match (rgb_desc, y_desc) {
(Ok(_), Ok(_)) => Err(anyhow!(
"{}: Image '{}' has both RGB and Y channels; ambiguous",
loc,
filename
)),
(Ok(_), Err(_)) => {
// Convert RGB to Y (luminance)
let mut y_pixels = Vec::with_capacity((res.x() * res.y()) as usize);
for y in 0..res.y() {
for x in 0..res.x() {
let r = image.get_channel(Point2i::new(x, y), 0);
let g = image.get_channel(Point2i::new(x, y), 1);
let b = image.get_channel(Point2i::new(x, y), 2);
y_pixels.push((r + g + b) / 3.0);
}
}
Ok(Image::from_f32(y_pixels, res, &["Y"].to_vec()))
}
(Err(_), Ok(_)) => {
// Already has Y channel
Ok(image.clone())
}
(Err(_), Err(_)) => Err(anyhow!(
"{}: Image '{}' has neither RGB nor Y channels",
loc,
filename
)),
}
}
fn compute_emissive_power(image: &Image) -> Float {
let res = image.resolution();
let mut sum_y = 0.0;
for y in 0..res.y() {
for x in 0..res.x() {
sum_y += image.get_channel(Point2i::new(x, y), 0);
}
}
4.0 * PI * sum_y / (res.x() * res.y()) as Float
}