175 lines
5.8 KiB
Rust
175 lines
5.8 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::resolve_filename;
|
|
use crate::{Arena, DeviceRepr, 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;
|
|
use shared::core::texture::{SpectrumType, TextureEvalContext};
|
|
use shared::lights::DiffuseAreaLight;
|
|
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: &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(¶ms.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, _) = if is_constant_zero {
|
|
(LightType::DeltaPosition, None)
|
|
} else {
|
|
(LightType::Area, Some(alpha))
|
|
};
|
|
|
|
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 {
|
|
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))
|
|
}
|