pbrt/src/lights/goniometric.rs

168 lines
5 KiB
Rust

use std::path::Path;
use crate::core::image::{Image, ImageIO};
use crate::core::light::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, Upload, 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 fn create(
render_from_light: Transform,
medium: Option<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 mi = match medium {
Some(m) => {
let ptr = arena.alloc(m);
MediumInterface {
inside: ptr,
outside: ptr,
}
}
None => MediumInterface::default(),
};
let base = LightBase::new(LightType::DeltaPosition, render_from_light, mi);
let iemit = lookup_spectrum(&i);
let image_ptr = if !image.is_null() {
let distrib = PiecewiseConstant2D::from_image(&image);
let distrib_ptr = distrib.upload(arena);
let img_ptr = image.upload(arena);
(img_ptr, distrib_ptr)
} else {
(Ptr::null(), Ptr::null())
};
let specific = GoniometricLight {
base,
iemit: arena.alloc(iemit.device()),
scale,
image: image_ptr.0,
distrib: image_ptr.1,
};
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
}