201 lines
6.6 KiB
Rust
201 lines
6.6 KiB
Rust
use crate::core::image::{Image, ImageIO};
|
|
use crate::core::light::CreateLight;
|
|
use crate::core::spectrum::spectrum_to_photometric;
|
|
use crate::spectra::colorspace::new;
|
|
use crate::utils::{Arena, ParameterDictionary, Upload, resolve_filename};
|
|
use log::error;
|
|
use shared::Float;
|
|
use shared::core::geometry::{Bounds2f, VectorLike};
|
|
use shared::core::image::ImageAccess;
|
|
use shared::core::light::{Light, LightBase};
|
|
use shared::core::medium::MediumInterface;
|
|
use shared::core::spectrum::Spectrum;
|
|
use shared::lights::ProjectionLight;
|
|
use shared::spectra::RGBColorSpace;
|
|
use shared::utils::math::{radians, square};
|
|
use shared::utils::{Ptr, Transform};
|
|
|
|
pub trait CreateProjectionLight {
|
|
fn new(
|
|
render_from_light: Transform,
|
|
medium_interface: MediumInterface,
|
|
le: Spectrum,
|
|
scale: Float,
|
|
image: Ptr<Image>,
|
|
image_color_space: Ptr<RGBColorSpace>,
|
|
fov: Float,
|
|
) -> Self;
|
|
}
|
|
|
|
impl CreateProjectionLight for ProjectionLight {
|
|
fn new(
|
|
render_from_light: Transform,
|
|
medium_interface: MediumInterface,
|
|
le: Spectrum,
|
|
scale: Float,
|
|
image: Ptr<Image>,
|
|
image_color_space: Ptr<RGBColorSpace>,
|
|
fov: Float,
|
|
) -> Self {
|
|
let base = LightBase::new(
|
|
LightType::DeltaPosition,
|
|
render_from_light,
|
|
medium_interface,
|
|
);
|
|
let aspect = image.resolution().x() as Float / image.resolution().y() as Float;
|
|
let screen_bounds = if aspect > 1. {
|
|
Bounds2f::from_points(Point2f::new(-aspect, -1.), Point2f::new(aspect, 1.))
|
|
} else {
|
|
Bounds2f::from_points(
|
|
Point2f::new(-1., 1. / aspect),
|
|
Point2f::new(1., 1. / aspect),
|
|
)
|
|
};
|
|
|
|
let hither = 1e-3;
|
|
let screen_from_light = Transform::perspective(fov.unwrap(), hither, 1e30).unwrap();
|
|
let light_from_screen = screen_from_light.inverse();
|
|
let opposite = (radians(fov.unwrap()) / 2.).tan();
|
|
let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect };
|
|
let a = 4. * square(opposite) * aspect_ratio;
|
|
let dwda = |p: Point2f| {
|
|
let w =
|
|
Vector3f::from(light_from_screen.apply_to_point(Point3f::new(p.x(), p.y(), 0.)));
|
|
cos_theta(w.normalize()).powi(3)
|
|
};
|
|
|
|
let d = image.get_sampling_distribution(dwda, screen_bounds);
|
|
let distrib = PiecewiseConstant2D::new_with_bounds(&d, screen_bounds);
|
|
|
|
Self {
|
|
base,
|
|
image,
|
|
image_color_space,
|
|
distrib,
|
|
screen_bounds,
|
|
screen_from_light,
|
|
light_from_screen,
|
|
scale,
|
|
hither,
|
|
a,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl CreateLight for ProjectionLight {
|
|
fn create(
|
|
arena: &mut Arena,
|
|
render_from_light: Transform,
|
|
medium: Medium,
|
|
parameters: &ParameterDictionary,
|
|
loc: &FileLoc,
|
|
_shape: &Shape,
|
|
_alpha_text: &FloatTexture,
|
|
colorspace: Option<&RGBColorSpace>,
|
|
) -> Result<Light, Error> {
|
|
let mut scale = parameters.get_one_float("scale", 1.);
|
|
let power = parameters.get_one_float("power", -1.);
|
|
let fov = parameters.get_one_float("fov", 90.);
|
|
|
|
let filename = resolve_filename(parameters.get_one_string("filename", ""));
|
|
if filename.is_empty() {
|
|
return Err(error!(loc, "must provide filename for projection light"));
|
|
}
|
|
|
|
let im = Image::read(&filename, None)
|
|
.map_err(|e| error!(loc, "could not load image '{}': {}", filename, e))?;
|
|
|
|
if im.image.has_any_infinite_pixels() {
|
|
return Err(error!(
|
|
loc,
|
|
"image '{}' has infinite pixels, not suitable for light", filename
|
|
));
|
|
}
|
|
|
|
if im.image.has_any_nan_pixels() {
|
|
return Err(error!(
|
|
loc,
|
|
"image '{}' has NaN pixels, not suitable for light", filename
|
|
));
|
|
}
|
|
|
|
let channel_desc = im
|
|
.image
|
|
.get_channel_desc(&["R", "G", "B"])
|
|
.map_err(|_| error!(loc, "image '{}' must have R, G, B channels", filename))?;
|
|
|
|
let image = im.image.select_channels(&channel_desc);
|
|
let colorspace = im
|
|
.metadata
|
|
.colorspace
|
|
.ok_or_else(|| error!(loc, "image '{}' missing colorspace metadata", filename))?;
|
|
|
|
scale /= spectrum_to_photometric(colorspace.illuminant);
|
|
if power > 0. {
|
|
let k_e = compute_emissive_power(&image, colorspace, fov);
|
|
}
|
|
|
|
let flip = Transform::scale(1., -1., 1.);
|
|
let render_from_light_flip = render_from_light * flip;
|
|
|
|
let specific = ProjectionLight::new(
|
|
render_from_light_flip,
|
|
medium_interface,
|
|
le,
|
|
scale,
|
|
image.upload(arena),
|
|
colorspace.upload(arena),
|
|
fov,
|
|
);
|
|
|
|
Ok(Light::Projection(specific))
|
|
}
|
|
}
|
|
|
|
fn compute_screen_bounds(aspect: Float) -> Bounds2f {
|
|
if aspect > 1.0 {
|
|
Bounds2f::from_points(Point2f::new(-aspect, -1.0), Point2f::new(aspect, 1.0))
|
|
} else {
|
|
Bounds2f::from_points(
|
|
Point2f::new(-1.0, -1.0 / aspect),
|
|
Point2f::new(1.0, 1.0 / aspect),
|
|
)
|
|
}
|
|
}
|
|
|
|
fn compute_emissive_power(image: &Image, colorspace: &RGBColorSpace, fov: Float) -> Float {
|
|
let res = image.resolution();
|
|
let aspect = res.x() as Float / res.y() as Float;
|
|
let screen_bounds = compute_screen_bounds(aspect);
|
|
|
|
let hither = 1e-3;
|
|
let screen_from_light =
|
|
Transform::perspective(fov, hither, 1e30).expect("Failed to create perspective transform");
|
|
let light_from_screen = screen_from_light.inverse();
|
|
|
|
let opposite = (radians(fov) / 2.0).tan();
|
|
let aspect_factor = if aspect > 1.0 { aspect } else { 1.0 / aspect };
|
|
let a = 4.0 * square(opposite) * aspect_factor;
|
|
|
|
let luminance = colorspace.luminance_vector();
|
|
let mut sum: Float = 0.0;
|
|
|
|
for y in 0..res.y() {
|
|
for x in 0..res.x() {
|
|
let lerp_factor = Point2f::new(
|
|
(x as Float + 0.5) / res.x() as Float,
|
|
(y as Float + 0.5) / res.y() as Float,
|
|
);
|
|
let ps = screen_bounds.lerp(lerp_factor);
|
|
let w_point = light_from_screen.apply_to_point(Point3f::new(ps.x(), ps.y(), 0.0));
|
|
let w = Vector3f::from(w_point).normalize();
|
|
let dwda = w.z().powi(3);
|
|
|
|
for c in 0..3 {
|
|
sum += image.get_channel(Point2i::new(x, y), c) * luminance[c] * dwda;
|
|
}
|
|
}
|
|
}
|
|
|
|
a * sum / (res.x() * res.y()) as Float
|
|
}
|