pbrt/src/lights/diffuse.rs

164 lines
5.9 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::{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;
use shared::core::shape::{Shape, ShapeTrait};
use shared::core::spectrum::Spectrum;
use shared::core::texture::GPUFloatTexture;
use shared::core::texture::{SpectrumType, TextureEvalContext};
use shared::lights::DiffuseAreaLight;
use shared::spectra::RGBColorSpace;
use shared::utils::{Ptr, Transform};
use shared::{Float, PI};
impl CreateLight for DiffuseAreaLight {
fn create(
render_from_light: Transform,
medium: 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 illum_spec = Spectrum::Dense(colorspace.unwrap().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) = if !filename.is_empty() {
if l.is_some() {
return Err(anyhow!("{}: both \"L\" and \"filename\" specified", loc));
}
let im = Image::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 {
// k_e is the emissive power of the light as defined by the spectral
// distribution and texture and is used to normalize the emitted
// radiance such that the user-defined power will be the actual power
// emitted by the light.
let mut k_e: Float = 1.0;
if let Some(ref img) = image {
// Get the appropriate luminance vector from the image colour space
let lum_vec = image_color_space.unwrap().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;
// now multiply up scale to hit the target power
scale *= phi_v / k_e;
}
let alpha_ptr = alpha.upload(arena);
let is_constant_zero = match &*alpha_ptr {
GPUFloatTexture::Constant(tex) => tex.evaluate(&TextureEvalContext::default()) == 0.0,
_ => false,
};
let (light_type, stored_alpha) = if is_constant_zero {
(LightType::DeltaPosition, None)
} else {
(LightType::Area, Some(alpha))
};
let base = LightBase::new(light_type, render_from_light, medium.into());
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 {
println!(
"Scaling detected in rendering to light space transformation! \
Proceed at your own risk; your image may have errors."
);
}
let shape_ptr = shape.upload(arena);
let image_ptr = image
.as_ref()
.map(|img| arena.alloc(*img.device()))
.unwrap_or(Ptr::null());
let colorspace_ptr = image_color_space
.map(|cs| cs.upload(arena))
.unwrap_or(Ptr::null());
let lemit_ptr = arena.alloc(lookup_spectrum(l_for_scale).device());
let specific = DiffuseAreaLight {
base,
area: shape.area(),
shape: shape_ptr,
alpha: alpha_ptr,
image: image_ptr,
colorspace: colorspace_ptr,
lemit: lemit_ptr,
two_sided,
scale,
};
Ok(Light::DiffuseArea(specific))
}
}