157 lines
5.3 KiB
Rust
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(¶ms.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))
|
|
}
|