pbrt/src/lights/diffuse.rs

157 lines
5.3 KiB
Rust

use crate::core::image::{HostImage, ImageIO};
use crate::core::light::lookup_spectrum;
use crate::core::spectrum::spectrum_to_photometric;
use crate::core::texture::FloatTexture;
use crate::utils::resolve_filename;
use crate::utils::upload::ArenaUpload;
use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::{anyhow, Result};
use shared::core::geometry::Point2i;
use shared::core::light::{Light, LightBase, LightType};
use shared::core::medium::{Medium, MediumInterface};
use shared::core::shape::{Shape, ShapeTrait};
use shared::core::spectrum::Spectrum;
use shared::core::texture::{GPUFloatTexture, SpectrumType, TextureEvalContext};
use shared::lights::DiffuseAreaLight;
use shared::spectra::RGBColorSpace;
use shared::utils::Transform;
use shared::{Float, PI};
use std::path::Path;
pub fn create(
render_from_light: Transform,
medium: Option<Medium>,
params: &ParameterDictionary,
loc: &FileLoc,
shape: &Shape,
alpha: &FloatTexture,
colorspace: Option<&RGBColorSpace>,
arena: &Arena,
) -> Result<Light> {
let mut l = params.get_one_spectrum("l", None, SpectrumType::Illuminant);
let default_cs = crate::spectra::default_colorspace();
let cs = colorspace.unwrap_or(&default_cs);
let illum_spec = Spectrum::Dense(cs.illuminant);
let mut scale = params.get_one_float("scale", 1.)?;
let two_sided = params.get_one_bool("twosided", false)?;
let filename = resolve_filename(&params.get_one_string("filename", "")?);
let (image, image_color_space): (Option<HostImage>, Option<RGBColorSpace>) =
if !filename.is_empty() {
if l.is_some() {
return Err(anyhow!("{}: both \"L\" and \"filename\" specified", loc));
}
let im = HostImage::read(Path::new(&filename), None)?;
if im.image.has_any_infinite_pixels() {
return Err(anyhow!("{}: image has infinite pixel values", loc));
}
if im.image.has_any_nan_pixels() {
return Err(anyhow!("{}: image has NaN pixel values", loc));
}
let channel_desc = im
.image
.get_channel_desc(&["R", "G", "B"])
.map_err(|_| anyhow!("{}: image must have R, G, B channels", loc))?;
let image = im.image.select_channels(&channel_desc);
let cs = im.metadata.get_colorspace();
(Some(image), cs)
} else {
if l.is_none() {
l = Some(illum_spec);
}
(None, None)
};
let l_for_scale = l.as_ref().unwrap_or(&illum_spec);
scale /= spectrum_to_photometric(*l_for_scale);
let phi_v = params.get_one_float("power", -1.0)?;
if phi_v > 0.0 {
let mut k_e: Float = 1.0;
if let Some(ref img) = image {
let lum_vec = image_color_space
.as_ref()
.expect("image present but no color space")
.luminance_vector();
let mut sum_k_e = 0.0;
let res = img.resolution();
for y in 0..res.y() {
for x in 0..res.x() {
let r = img.get_channel(Point2i::new(x, y), 0);
let g = img.get_channel(Point2i::new(x, y), 1);
let b = img.get_channel(Point2i::new(x, y), 2);
sum_k_e += r * lum_vec[0] + g * lum_vec[1] + b * lum_vec[2];
}
}
k_e = sum_k_e / (res.x() * res.y()) as Float;
}
let side_factor = if two_sided { 2.0 } else { 1.0 };
k_e *= side_factor * shape.area() * PI;
scale *= phi_v / k_e;
}
// Upload alpha texture to GPU and check for null texture
let alpha_ptr = arena.upload(alpha);
let light_type = match unsafe { alpha_ptr.as_ref() } {
GPUFloatTexture::Constant(t) if t.evaluate(&TextureEvalContext::default()) == 0.0 => {
LightType::DeltaPosition
}
_ => LightType::Area,
};
let mi = match medium {
Some(m) => {
let ptr = arena.alloc(m);
MediumInterface {
inside: ptr,
outside: ptr,
}
}
None => MediumInterface::default(),
};
let base = LightBase::new(light_type, render_from_light, mi);
if let Some(ref img) = image {
let desc = img
.get_channel_desc(&["R", "G", "B"])
.expect("Image used for DiffuseAreaLight doesn't have R, G, B channels");
assert_eq!(3, desc.size());
assert!(desc.is_identity());
assert!(
image_color_space.is_some(),
"Image provided but ColorSpace is missing"
);
}
let is_triangle_or_bilinear = matches!(*shape, Shape::Triangle(_) | Shape::BilinearPatch(_));
if render_from_light.has_scale(None) && !is_triangle_or_bilinear {
eprintln!(
"Warning: scaling detected in rendering-to-light transform; \
image may have errors."
);
}
let specific = DiffuseAreaLight {
base,
area: shape.area(),
shape: arena.alloc(*shape),
alpha: alpha_ptr,
image: arena.upload(image),
colorspace: arena.alloc_opt(image_color_space),
lemit: arena.alloc((*lookup_spectrum(l_for_scale)).clone()),
two_sided,
scale,
};
Ok(Light::DiffuseArea(specific))
}