Implementing arena based memory allocation
This commit is contained in:
parent
f7c47be077
commit
0ef563d1a5
9 changed files with 1036 additions and 179 deletions
|
|
@ -1,55 +1,34 @@
|
||||||
|
use shared::Float;
|
||||||
|
use shared::core::ligh::{Light, LightBase, LightType};
|
||||||
|
use shared::core::medium::MediumInterface;
|
||||||
|
use shared::core::shape::Shape;
|
||||||
|
use shared::core::spectrum::{Spectrum, SpectrumTrait};
|
||||||
|
use shared::core::texture::SpectrumType;
|
||||||
|
use shared::lights::DiffuseAreaLight;
|
||||||
|
use shared::spectra::RGBColorSpace;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::core::image::{Image, ImageIO};
|
||||||
|
use crate::core::light::{CreateLight, lookup_spectrum};
|
||||||
|
use crate::core::spectrum::spectrum_to_photometric;
|
||||||
use crate::core::texture::{
|
use crate::core::texture::{
|
||||||
FloatTexture, FloatTextureTrait, TextureEvalContext, TextureEvaluator,
|
FloatTexture, FloatTextureTrait, TextureEvalContext, TextureEvaluator,
|
||||||
UniversalTextureEvaluator,
|
UniversalTextureEvaluator,
|
||||||
};
|
};
|
||||||
use crate::shapes::{Shape, ShapeSample, ShapeSampleContext, ShapeTrait};
|
use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename};
|
||||||
use crate::utils::hash::hash_float;
|
|
||||||
use shared::core::color::RGB;
|
|
||||||
use shared::core::geometry::{
|
|
||||||
Bounds3f, Normal3f, Point2f, Point2fi, Point2i, Point3f, Point3fi, Ray, Vector3f, VectorLike,
|
|
||||||
};
|
|
||||||
use shared::core::interaction::{
|
|
||||||
Interaction, InteractionTrait, SimpleInteraction, SurfaceInteraction,
|
|
||||||
};
|
|
||||||
use shared::core::light::{
|
|
||||||
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
|
|
||||||
};
|
|
||||||
use shared::core::medium::MediumInterface;
|
|
||||||
use shared::core::spectrum::{Spectrum, SpectrumTrait};
|
|
||||||
use shared::images::Image;
|
|
||||||
use shared::spectra::{
|
|
||||||
DenselySampledSpectrum, RGBColorSpace, RGBIlluminantSpectrum, SampledSpectrum,
|
|
||||||
SampledWavelengths,
|
|
||||||
};
|
|
||||||
use shared::{Float, PI};
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
impl CreateLight for DiffuseAreaLight {
|
||||||
pub struct DiffuseAreaLightStorage {
|
fn new(
|
||||||
shape: Arc<Shape>,
|
render_from_light: shared::utils::Transform,
|
||||||
alpha: Option<Arc<GPUFloatTexture>>,
|
|
||||||
image: Option<Arc<Image>>,
|
|
||||||
image_color_space: Option<Arc<RGBColorSpace>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct DiffuseAreaLightHost {
|
|
||||||
pub view: DiffuseAreaLight,
|
|
||||||
_storage: DiffuseAreaLightStorage,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DiffuseAreaLightHost {
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn new(
|
|
||||||
render_from_light: Transform,
|
|
||||||
medium_interface: MediumInterface,
|
medium_interface: MediumInterface,
|
||||||
le: Spectrum,
|
le: Spectrum,
|
||||||
scale: Float,
|
scale: Float,
|
||||||
shape: Shape,
|
shape: Option<shared::utils::Ptr<Shape>>,
|
||||||
alpha: FloatTexture,
|
alpha: Option<shared::utils::Ptr<FloatTexture>>,
|
||||||
image: Option<Image>,
|
image: Option<shared::utils::Ptr<Image>>,
|
||||||
image_color_space: Option<Arc<RGBColorSpace>>,
|
image_color_space: Option<shared::utils::Ptr<RGBColorSpace>>,
|
||||||
two_sided: bool,
|
two_sided: Option<bool>,
|
||||||
|
fov: Option<Float>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let is_constant_zero = match &alpha {
|
let is_constant_zero = match &alpha {
|
||||||
FloatTexture::FloatConstant(tex) => tex.evaluate(&TextureEvalContext::default()) == 0.0,
|
FloatTexture::FloatConstant(tex) => tex.evaluate(&TextureEvalContext::default()) == 0.0,
|
||||||
|
|
@ -91,22 +70,111 @@ impl DiffuseAreaLightHost {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let storage = DiffuseAreaLightStorage {
|
Self {
|
||||||
shape,
|
|
||||||
alpha: stored_alpha,
|
|
||||||
image,
|
|
||||||
image_color_space,
|
|
||||||
};
|
|
||||||
|
|
||||||
let view = DiffuseAreaLight {
|
|
||||||
base,
|
base,
|
||||||
area: shape.area(),
|
area: shape.area(),
|
||||||
|
image,
|
||||||
|
image_color_space,
|
||||||
shape: Ptr::from(&*storage.shape),
|
shape: Ptr::from(&*storage.shape),
|
||||||
};
|
alpha: stored_alpha,
|
||||||
|
lemit,
|
||||||
Self {
|
two_sided,
|
||||||
view,
|
scale,
|
||||||
_storage: storage,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create(
|
||||||
|
arena: &mut Arena,
|
||||||
|
render_from_light: Transform,
|
||||||
|
medium: Medium,
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
loc: &FileLoc,
|
||||||
|
shape: &Shape,
|
||||||
|
alpha_text: &FloatTexture,
|
||||||
|
colorspace: Option<&RGBColorSpace>,
|
||||||
|
) -> Light {
|
||||||
|
let mut l = params.get_one_spectrum("l", None, SpectrumType::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 mut image_color_space = colorspace.clone();
|
||||||
|
if !filename.is_empty() {
|
||||||
|
if l.is_some() {
|
||||||
|
panic!(
|
||||||
|
"{}: Both \"L\" and \"filename\" specified for DiffuseAreaLight.",
|
||||||
|
loc
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let im = Image::read(filename, None);
|
||||||
|
let image: Image = im.image;
|
||||||
|
if image.has_any_infinite_pixels() {
|
||||||
|
panic!(
|
||||||
|
"{:?}: Image '{}' has NaN pixels. Not suitable for light.",
|
||||||
|
loc, filename
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let channel_desc = image.get_channel_desc(&["R", "G", "B"]).expect(&format!(
|
||||||
|
"{:?}: Image '{}' must have R, G, B channels",
|
||||||
|
loc, filename
|
||||||
|
));
|
||||||
|
let selected_image = image.select_channels(channel_desc);
|
||||||
|
image_color_space = im.metadata.colorspace;
|
||||||
|
} else if l.is_none() {
|
||||||
|
l = Some(colorpace.unwrap().Illuminant.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let l_for_scale = l.as_ref().unwrap_or(&colorspace.illuminant);
|
||||||
|
let lemit_data = lookup_spectrum(l_for_scale);
|
||||||
|
scale /= spectrum_to_photometric(lemit_data);
|
||||||
|
|
||||||
|
let phi_v = parameters.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_host {
|
||||||
|
// Get the appropriate luminance vector from the image colour space
|
||||||
|
let lum_vec = image_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_data.area() * PI;
|
||||||
|
|
||||||
|
// now multiply up scale to hit the target power
|
||||||
|
scale *= phi_v / k_e;
|
||||||
|
}
|
||||||
|
|
||||||
|
let specific = DiffuseAreaLight::new(
|
||||||
|
render_from_light,
|
||||||
|
medium.into(),
|
||||||
|
l,
|
||||||
|
scale,
|
||||||
|
shape.upload(arena),
|
||||||
|
alpha.upload(arena),
|
||||||
|
image.upload(arena),
|
||||||
|
image_color_space.upload(arena),
|
||||||
|
Some(true),
|
||||||
|
shape.area(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Light::DiffuseArea(specific)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
108
src/lights/distant.rs
Normal file
108
src/lights/distant.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
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};
|
||||||
|
use shared::core::geometry::{Vector3f, VectorLike};
|
||||||
|
use shared::core::light::{Light, LightBase};
|
||||||
|
use shared::core::medium::{Medium, MediumInterface};
|
||||||
|
use shared::core::shape::Shape;
|
||||||
|
use shared::lights::DistantLight;
|
||||||
|
use shared::spectra::RGBColorSpace;
|
||||||
|
use shared::utils::Transform;
|
||||||
|
|
||||||
|
impl CreateLight for DistantLight {
|
||||||
|
fn new(
|
||||||
|
render_from_light: Transform,
|
||||||
|
medium_interface: shared::core::medium::MediumInterface,
|
||||||
|
le: shared::core::spectrum::Spectrum,
|
||||||
|
scale: shared::Float,
|
||||||
|
shape: Option<shared::utils::Ptr<Shape>>,
|
||||||
|
alpha: Option<shared::utils::Ptr<FloatTexture>>,
|
||||||
|
image: Option<shared::utils::Ptr<Image>>,
|
||||||
|
image_color_space: Option<shared::utils::Ptr<RGBColorSpace>>,
|
||||||
|
two_sided: Option<bool>,
|
||||||
|
fov: Option<shared::Float>,
|
||||||
|
) -> Self {
|
||||||
|
let base = LightBase::new(
|
||||||
|
LightType::DeltaDirection,
|
||||||
|
render_from_light,
|
||||||
|
MediumInterface::empty(),
|
||||||
|
);
|
||||||
|
let lemit = lookup_spectrum(le);
|
||||||
|
Self {
|
||||||
|
base,
|
||||||
|
lemit,
|
||||||
|
scale,
|
||||||
|
scene_center: Vector3f::default(),
|
||||||
|
scene_radius: 0.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(
|
||||||
|
arena: &mut Arena,
|
||||||
|
render_from_light: Transform,
|
||||||
|
medium: Medium,
|
||||||
|
parameters: &ParameterDictionary,
|
||||||
|
loc: &FileLoc,
|
||||||
|
shape: &Shape,
|
||||||
|
alpha_text: &FloatTexture,
|
||||||
|
colorspace: Option<&RGBColorSpace>,
|
||||||
|
) -> Light {
|
||||||
|
let l = parameters
|
||||||
|
.get_one_spectrum(
|
||||||
|
"L",
|
||||||
|
colorspace.unwrap().illuminant,
|
||||||
|
SpectrumType::Illuminant,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let lemit = lookup_spectrum(l);
|
||||||
|
let mut scale = parameters.get_one_float("scale", 1);
|
||||||
|
|
||||||
|
let from = parameters.get_one_point3f("from", Point3f::new(0., 0., 0.));
|
||||||
|
let to = parameters.get_one_point3f("to", Point3f(0., 0., 1.));
|
||||||
|
let w = (from - to).normalize();
|
||||||
|
let (v1, v2) = w.coordinate_system();
|
||||||
|
let m: [Float; 16] = [
|
||||||
|
v1.x(),
|
||||||
|
v2.x(),
|
||||||
|
w.x(),
|
||||||
|
0.,
|
||||||
|
v1.y(),
|
||||||
|
v2.y(),
|
||||||
|
w.y(),
|
||||||
|
0.,
|
||||||
|
v1.z(),
|
||||||
|
v2.z(),
|
||||||
|
w.z(),
|
||||||
|
0.,
|
||||||
|
0.,
|
||||||
|
0.,
|
||||||
|
0.,
|
||||||
|
1.,
|
||||||
|
];
|
||||||
|
let t = Transform::from_flat(m);
|
||||||
|
let final_render = render_from_light * t;
|
||||||
|
scale /= spectrum_to_photometric(l.unwrap());
|
||||||
|
// Adjust scale to meet target illuminance value
|
||||||
|
// Like for IBLs we measure illuminance as incident on an upward-facing
|
||||||
|
// patch.
|
||||||
|
let e_v = parameters.get_one_float("illuminance", -1.);
|
||||||
|
if e_v > 0. {
|
||||||
|
sc *= e_v;
|
||||||
|
}
|
||||||
|
let specific = DistantLight::new(
|
||||||
|
final_render,
|
||||||
|
medium.into(),
|
||||||
|
le,
|
||||||
|
scale,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
Light::Distant(specific)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,31 @@
|
||||||
use crate::core::light::{LightBaseTrait, LightFactory};
|
use crate::core::image::{Image, ImageIO, ImageMetadata};
|
||||||
|
use crate::core::light::{CreateLight, lookup_spectrum};
|
||||||
|
use crate::core::spectrum::spectrum_to_photometric;
|
||||||
|
use crate::core::texture::FloatTexture;
|
||||||
|
use crate::utils::sampling::PiecewiseConstant2D;
|
||||||
|
use crate::utils::{Arena, FileLoc, ParameterDictionary, resolve_filename};
|
||||||
|
use shared::core::image::ImageBase;
|
||||||
|
use shared::core::light::{Light, LightBase, LightType};
|
||||||
|
use shared::core::medium::{Medium, MediumInterface};
|
||||||
|
use shared::core::spectrum::Spectrum;
|
||||||
|
use shared::core::texture::SpectrumType;
|
||||||
|
use shared::lights::GoniometricLight;
|
||||||
|
use shared::spectra::RGBColorSpace;
|
||||||
|
use shared::utils::Transform;
|
||||||
|
use shared::utils::containers::Array2D;
|
||||||
|
|
||||||
impl LightFactory for GoniometricLight {
|
impl CreateLight for GoniometricLight {
|
||||||
fn new(
|
fn new(
|
||||||
render_from_light: Transform,
|
render_from_light: Transform,
|
||||||
medium_interface: MediumInterface,
|
medium_interface: MediumInterface,
|
||||||
scale: Float,
|
le: Spectrum,
|
||||||
iemit: &Spectrum,
|
scale: shared::Float,
|
||||||
image: &Image,
|
shape: Option<shared::utils::Ptr<Shape>>,
|
||||||
|
alpha: Option<shared::utils::Ptr<FloatTexture>>,
|
||||||
|
image: Option<shared::utils::Ptr<Image>>,
|
||||||
|
image_color_space: Option<shared::utils::Ptr<RGBColorSpace>>,
|
||||||
|
two_sided: Option<bool>,
|
||||||
|
fov: Option<shared::Float>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let base = LightBase::new(
|
let base = LightBase::new(
|
||||||
LightType::DeltaPosition,
|
LightType::DeltaPosition,
|
||||||
|
|
@ -14,15 +33,142 @@ impl LightFactory for GoniometricLight {
|
||||||
medium_interface,
|
medium_interface,
|
||||||
);
|
);
|
||||||
|
|
||||||
let i_interned = LightBase::lookup_spectrum(&iemit);
|
let iemit = lookup_spectrum(le);
|
||||||
let d = image.get_sampling_distribution_uniform();
|
let d = image.unwrap().get_sampling_distribution_uniform();
|
||||||
let distrib = PiecewiseConstant2D::new_with_data(&d);
|
let distrib = PiecewiseConstant2D::new_with_data(d);
|
||||||
Self {
|
Self {
|
||||||
base,
|
base,
|
||||||
iemit: i_interned,
|
iemit,
|
||||||
scale,
|
scale,
|
||||||
image: Ptr::from(image),
|
image,
|
||||||
distrib,
|
distrib,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create(
|
||||||
|
arena: &mut Arena,
|
||||||
|
render_from_light: Transform,
|
||||||
|
medium: Medium,
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
loc: &FileLoc,
|
||||||
|
shape: &Shape,
|
||||||
|
alpha_text: &FloatTexture,
|
||||||
|
colorspace: Option<&RGBColorSpace>,
|
||||||
|
) -> Light {
|
||||||
|
let i = params.get_one_spectrum(
|
||||||
|
"I",
|
||||||
|
colorspace.unwrap().illuminant,
|
||||||
|
SpectrumType::Illuminant,
|
||||||
|
);
|
||||||
|
let lemit_data = lookup_spectrum(&i_spectrum_def);
|
||||||
|
let sc = params.get_one_float("scale", 1.);
|
||||||
|
let filename = resolve_filename(params.get_one_string("filename", ""));
|
||||||
|
let mut image: Option<Image> = None;
|
||||||
|
|
||||||
|
if filename.is_empty() {
|
||||||
|
println!(
|
||||||
|
"{}: Both \"L\" and \"filename\" specified for DiffuseAreaLight.",
|
||||||
|
loc
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let im = Image::read(filename, None).expect("Could not load image");
|
||||||
|
let loaded_img: Image = im.image;
|
||||||
|
let res = loaded_img.resolution();
|
||||||
|
let metadata: ImageMetadata = im.metadata;
|
||||||
|
if loaded_img.has_any_infinite_pixels() {
|
||||||
|
panic!(
|
||||||
|
"{:?}: Image '{}' has NaN pixels. Not suitable for light.",
|
||||||
|
loc, filename
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if res.x() != res.y() {
|
||||||
|
panic!(
|
||||||
|
"{:?}: Image resolution ({}, {}) is non square. Unlikely that this is an equal area map.",
|
||||||
|
loc,
|
||||||
|
res.x(),
|
||||||
|
res.y(),
|
||||||
|
y()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let rgb_desc = loaded_img.get_channel_desc(&["R", "G", "B"]);
|
||||||
|
let y_desc = loaded_img.get_channel_desc(&["Y"]);
|
||||||
|
if let Ok(rgb) = rgb_desc {
|
||||||
|
if y_desc.is_ok() {
|
||||||
|
panic!(
|
||||||
|
"{:?}: Image '{}' has both RGB and Y channels. Ambiguous.",
|
||||||
|
loc, filename
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut y_pixels = Vec::with_capacity((res.x * res.y) as usize);
|
||||||
|
|
||||||
|
for y in 0..res.y {
|
||||||
|
for x in 0..res.x {
|
||||||
|
let r = loaded_img.get_channel(Point2i::new(x, y), 0);
|
||||||
|
let g = loaded_img.get_channel(Point2i::new(x, y), 1);
|
||||||
|
let b = loaded_img.get_channel(Point2i::new(x, y), 2);
|
||||||
|
|
||||||
|
y_pixels.push((r + g + b) / 3.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loaded_img = Some(Image::new(
|
||||||
|
PixelFormat::F32,
|
||||||
|
res,
|
||||||
|
&["Y"],
|
||||||
|
ColorEncoding::Linear,
|
||||||
|
));
|
||||||
|
} else if y_desc.is_ok() {
|
||||||
|
image = Some(loaded_img);
|
||||||
|
} else {
|
||||||
|
panic!(
|
||||||
|
"{:?}: Image '{}' has neither RGB nor Y channels.",
|
||||||
|
loc, filename
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scale /= spectrum_to_photometric(&lemit_data);
|
||||||
|
let phi_v = params.get_one_float("power", -1.0);
|
||||||
|
|
||||||
|
if phi_v > 0.0 {
|
||||||
|
if let Some(ref img) = image {
|
||||||
|
let mut sum_y = 0.0;
|
||||||
|
let res = img.resolution();
|
||||||
|
|
||||||
|
for y in 0..res.y {
|
||||||
|
for x in 0..res.x {
|
||||||
|
sum_y += img.get_channel(Point2i::new(x, y), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let k_e = 4.0 * PI * sum_y / (res.x * res.y) as Float;
|
||||||
|
scale *= phi_v / k_e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let swap_yz: [Float; 16] = [
|
||||||
|
1., 0., 0., 0., 0., 0., 1., 0., 0., 1., 0., 0., 0., 0., 0., 1.,
|
||||||
|
];
|
||||||
|
let t = Transform::from_flat(swap_yz);
|
||||||
|
let final_render_from_light = render_from_light * t;
|
||||||
|
|
||||||
|
let d: Array2D<Float> = image.get_sampling_distribution();
|
||||||
|
distrib = PiecewiseConstant2D::new_with_data(d);
|
||||||
|
|
||||||
|
let specific = GoniometricLight::new(
|
||||||
|
render_from_light,
|
||||||
|
medium_interface,
|
||||||
|
le,
|
||||||
|
scale,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Some(image),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
Light::Goniometric(specific)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,27 @@
|
||||||
use shared::Float;
|
use shared::Float;
|
||||||
use shared::core::geometry::{Bounds3f, Point2f};
|
use shared::core::geometry::{Bounds3f, Point2f};
|
||||||
use shared::core::light::{LightBase, LightType};
|
use shared::core::light::{CreateLight, Light, LightBase, LightType};
|
||||||
use shared::core::medium::MediumInterface;
|
use shared::core::medium::MediumInterface;
|
||||||
use shared::lights::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight};
|
use shared::lights::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight};
|
||||||
use shared::spectra::RGBColorSpace;
|
use shared::spectra::RGBColorSpace;
|
||||||
use shared::utils::Transform;
|
use shared::utils::Transform;
|
||||||
use shared::utils::sampling::PiecewiseConstant2D;
|
use shared::utils::sampling::DevicePiecewiseConstant2D;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::core::light::{LightBaseTrait, LightFactory};
|
use crate::core::light::{LightBaseTrait, lookup_spectrum};
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl CreateLight for InfiniteImageLight {
|
||||||
struct InfiniteImageLightStorage {
|
fn new(
|
||||||
image: Image,
|
|
||||||
distrib: PiecewiseConstant2D,
|
|
||||||
compensated_distrib: PiecewiseConstant2D,
|
|
||||||
image_color_space: RGBColorSpace,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct InfiniteImageLightHost {
|
|
||||||
pub view: InfiniteImageLight,
|
|
||||||
pub filename: String, // Kept on Host only
|
|
||||||
_storage: Arc<InfiniteImageLightStorage>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::Deref for InfiniteImageLightHost {
|
|
||||||
type Target = InfiniteImageLight;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.view
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InfiniteImageLightHost {
|
|
||||||
pub fn new(
|
|
||||||
render_from_light: Transform,
|
render_from_light: Transform,
|
||||||
image: Image,
|
medium_interface: MediumInterface,
|
||||||
image_color_space: RGBColorSpace,
|
le: shared::core::spectrum::Spectrum,
|
||||||
scale: Float,
|
scale: Float,
|
||||||
filename: String,
|
shape: Option<shared::utils::Ptr<Shape>>,
|
||||||
|
alpha: Option<shared::utils::Ptr<FloatTexture>>,
|
||||||
|
image: Option<shared::utils::Ptr<Image>>,
|
||||||
|
image_color_space: Option<shared::utils::Ptr<RGBColorSpace>>,
|
||||||
|
two_sided: Option<bool>,
|
||||||
|
fov: Option<Float>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let base = LightBase::new(
|
let base = LightBase::new(
|
||||||
LightType::Infinite,
|
LightType::Infinite,
|
||||||
|
|
@ -53,6 +36,7 @@ impl InfiniteImageLightHost {
|
||||||
assert_eq!(3, desc.size());
|
assert_eq!(3, desc.size());
|
||||||
assert!(desc.is_identity());
|
assert!(desc.is_identity());
|
||||||
if image.resolution().x() != image.resolution().y() {
|
if image.resolution().x() != image.resolution().y() {
|
||||||
|
hash(hashee, into);
|
||||||
panic!(
|
panic!(
|
||||||
"{}: image resolution ({}, {}) is non-square. It's unlikely this is an equal area environment map.",
|
"{}: image resolution ({}, {}) is non-square. It's unlikely this is an equal area environment map.",
|
||||||
filename,
|
filename,
|
||||||
|
|
@ -62,7 +46,7 @@ impl InfiniteImageLightHost {
|
||||||
}
|
}
|
||||||
let mut d = image.get_sampling_distribution_uniform();
|
let mut d = image.get_sampling_distribution_uniform();
|
||||||
let domain = Bounds2f::from_points(Point2f::new(0., 0.), Point2f::new(1., 1.));
|
let domain = Bounds2f::from_points(Point2f::new(0., 0.), Point2f::new(1., 1.));
|
||||||
let distrib = PiecewiseConstant2D::new_with_bounds(&d, domain);
|
let distrib = DevicePiecewiseConstant2D::new_with_bounds(&d, domain);
|
||||||
let slice = &mut d.values; // or d.as_slice_mut()
|
let slice = &mut d.values; // or d.as_slice_mut()
|
||||||
let count = slice.len() as Float;
|
let count = slice.len() as Float;
|
||||||
let sum: Float = slice.iter().sum();
|
let sum: Float = slice.iter().sum();
|
||||||
|
|
@ -79,31 +63,31 @@ impl InfiniteImageLightHost {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let compensated_distrib = PiecewiseConstant2D::new_with_bounds(&d, domain);
|
let compensated_distrib = DevicePiecewiseConstant2D::new_with_bounds(&d, domain);
|
||||||
|
|
||||||
let storage = Arc::new(InfiniteImageLightStorage {
|
InfiniteImageLight {
|
||||||
image,
|
|
||||||
distrib,
|
|
||||||
compensated_distrib,
|
|
||||||
image_color_space,
|
|
||||||
});
|
|
||||||
|
|
||||||
let view = InfiniteImageLight {
|
|
||||||
base,
|
base,
|
||||||
image: &storage.image,
|
image: &image,
|
||||||
image_color_space: &storage.image_color_space,
|
image_color_space: &storage.image_color_space,
|
||||||
scene_center: Point3f::default(),
|
scene_center: Point3f::default(),
|
||||||
scene_radius: 0.,
|
scene_radius: 0.,
|
||||||
scale,
|
scale,
|
||||||
distrib: &storage.distrib,
|
distrib: &distrib,
|
||||||
compensated_distrib: &storage.compensated_distrib,
|
compensated_distrib: &compensated_distrib,
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
|
||||||
view,
|
|
||||||
filename,
|
|
||||||
_storage: storage,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create(
|
||||||
|
arena: &mut crate::utils::Arena,
|
||||||
|
render_from_light: Transform,
|
||||||
|
medium: Medium,
|
||||||
|
parameters: &crate::utils::ParameterDictionary,
|
||||||
|
loc: &FileLoc,
|
||||||
|
shape: &Shape,
|
||||||
|
alpha_tex: &FloatTexture,
|
||||||
|
colorspace: Option<&RGBColorSpace>,
|
||||||
|
) -> shared::core::light::Light {
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,21 +105,18 @@ pub struct InfinitePortalLightHost {
|
||||||
_storage: Arc<InfinitePortalLightStorage>,
|
_storage: Arc<InfinitePortalLightStorage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Deref for InfinitePortalLightHost {
|
impl CreateLight for InfinitePortalLightHost {
|
||||||
type Target = InfinitePortalLight;
|
fn new(
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.view
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InfinitePortalLightHost {
|
|
||||||
pub fn new(
|
|
||||||
render_from_light: Transform,
|
render_from_light: Transform,
|
||||||
equal_area_image: &Image,
|
medium_interface: MediumInterface,
|
||||||
image_color_space: RGBColorSpace,
|
le: shared::core::spectrum::Spectrum,
|
||||||
scale: Float,
|
scale: Float,
|
||||||
filename: String,
|
shape: Option<shared::utils::Ptr<Shape>>,
|
||||||
points: Vec<Point3f>,
|
alpha: Option<shared::utils::Ptr<FloatTexture>>,
|
||||||
|
image: Option<shared::utils::Ptr<Image>>,
|
||||||
|
image_color_space: Option<shared::utils::Ptr<RGBColorSpace>>,
|
||||||
|
two_sided: Option<bool>,
|
||||||
|
fov: Option<Float>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let base = LightBase::new(
|
let base = LightBase::new(
|
||||||
LightType::Infinite,
|
LightType::Infinite,
|
||||||
|
|
@ -250,34 +231,54 @@ impl InfinitePortalLightHost {
|
||||||
image_color_space,
|
image_color_space,
|
||||||
});
|
});
|
||||||
|
|
||||||
let view = InfinitePortalLight {
|
InfinitePortalLight {
|
||||||
base,
|
base,
|
||||||
image: &storage.image,
|
image,
|
||||||
image_color_space: &storage.image_color_space,
|
image_color_space: &storage.image_color_space,
|
||||||
scale,
|
scale,
|
||||||
scene_center: Point3f::default(),
|
scene_center: Point3f::default(),
|
||||||
scene_radius: 0.,
|
scene_radius: 0.,
|
||||||
portal,
|
portal,
|
||||||
portal_frame,
|
portal_frame,
|
||||||
distribution: &storage.distribution,
|
distribution,
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
view,
|
|
||||||
filename,
|
|
||||||
_storage: storage,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create(
|
||||||
|
arena: &mut crate::utils::Arena,
|
||||||
|
render_from_light: Transform,
|
||||||
|
medium: Medium,
|
||||||
|
parameters: &crate::utils::ParameterDictionary,
|
||||||
|
loc: &FileLoc,
|
||||||
|
shape: &Shape,
|
||||||
|
alpha_tex: &FloatTexture,
|
||||||
|
colorspace: Option<&RGBColorSpace>,
|
||||||
|
) -> Light {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LightFactory for InfiniteUniformLight {
|
impl CreateLight for InfiniteUniformLight {
|
||||||
fn new(render_from_light: Transform, le: Spectrum, scale: Float) -> Self {
|
fn new(
|
||||||
|
render_from_light: Transform,
|
||||||
|
medium_interface: MediumInterface,
|
||||||
|
le: shared::core::spectrum::Spectrum,
|
||||||
|
scale: Float,
|
||||||
|
shape: Option<shared::utils::Ptr<Shape>>,
|
||||||
|
alpha: Option<shared::utils::Ptr<FloatTexture>>,
|
||||||
|
image: Option<shared::utils::Ptr<Image>>,
|
||||||
|
image_color_space: Option<shared::utils::Ptr<RGBColorSpace>>,
|
||||||
|
two_sided: Option<bool>,
|
||||||
|
fov: Option<Float>,
|
||||||
|
cos_fallof_start: Option<Float>,
|
||||||
|
total_width: Option<Float>,
|
||||||
|
) -> Self {
|
||||||
let base = LightBase::new(
|
let base = LightBase::new(
|
||||||
LightType::Infinite,
|
LightType::Infinite,
|
||||||
render_from_light,
|
&render_from_light,
|
||||||
MediumInterface::default(),
|
&MediumInterface::default(),
|
||||||
);
|
);
|
||||||
let lemit = LightBase::lookup_spectrum(&le);
|
let lemit = lookup_spectrum(le);
|
||||||
Self {
|
Self {
|
||||||
base,
|
base,
|
||||||
lemit,
|
lemit,
|
||||||
|
|
@ -286,4 +287,17 @@ impl LightFactory for InfiniteUniformLight {
|
||||||
scene_radius: 0.,
|
scene_radius: 0.,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create(
|
||||||
|
arena: &mut crate::utils::Arena,
|
||||||
|
render_from_light: Transform,
|
||||||
|
medium: Medium,
|
||||||
|
parameters: &crate::utils::ParameterDictionary,
|
||||||
|
loc: &FileLoc,
|
||||||
|
shape: &Shape,
|
||||||
|
alpha_tex: &FloatTexture,
|
||||||
|
colorspace: Option<&RGBColorSpace>,
|
||||||
|
) -> Light {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
pub mod diffuse;
|
pub mod diffuse;
|
||||||
|
pub mod distant;
|
||||||
pub mod goniometric;
|
pub mod goniometric;
|
||||||
pub mod infinite;
|
pub mod infinite;
|
||||||
|
pub mod point;
|
||||||
pub mod projection;
|
pub mod projection;
|
||||||
pub mod sampler;
|
pub mod sampler;
|
||||||
|
pub mod spot;
|
||||||
|
|
|
||||||
74
src/lights/point.rs
Normal file
74
src/lights/point.rs
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
use crate::core::light::{CreateLight, LightBaseTrait, lookup_spectrum};
|
||||||
|
use crate::core::spectrum::spectrum_to_photometric;
|
||||||
|
use crate::core::texture::FloatTexture;
|
||||||
|
use crate::utils::{Arena, FileLoc, ParameterDictionary};
|
||||||
|
use shared::PI;
|
||||||
|
use shared::core::geometry::VectorLike;
|
||||||
|
use shared::core::light::{Light, LightBase, LightType};
|
||||||
|
use shared::core::medium::Medium;
|
||||||
|
use shared::core::shape::Shape;
|
||||||
|
use shared::lights::PointLight;
|
||||||
|
use shared::spectra::RGBColorSpace;
|
||||||
|
use shared::utils::Transform;
|
||||||
|
|
||||||
|
impl CreateLight for PointLight {
|
||||||
|
fn new(
|
||||||
|
render_from_light: Transform,
|
||||||
|
medium_interface: shared::core::medium::MediumInterface,
|
||||||
|
le: shared::core::spectrum::Spectrum,
|
||||||
|
scale: shared::Float,
|
||||||
|
_shape: Option<shared::utils::Ptr<Shape>>,
|
||||||
|
_alpha: Option<shared::utils::Ptr<FloatTexture>>,
|
||||||
|
_image: Option<shared::utils::Ptr<Image>>,
|
||||||
|
_image_color_space: Option<shared::utils::Ptr<RGBColorSpace>>,
|
||||||
|
_two_sided: Option<bool>,
|
||||||
|
_fov: Option<shared::Float>,
|
||||||
|
) -> Self {
|
||||||
|
let base = LightBase::new(
|
||||||
|
LightType::DeltaPosition,
|
||||||
|
render_from_light,
|
||||||
|
medium_interface,
|
||||||
|
);
|
||||||
|
let i = lookup_spectrum(le);
|
||||||
|
|
||||||
|
Self { base, scale, i }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(
|
||||||
|
arena: &mut Arena,
|
||||||
|
render_from_light: Transform,
|
||||||
|
medium: Medium,
|
||||||
|
parameters: &ParameterDictionary,
|
||||||
|
loc: &FileLoc,
|
||||||
|
shape: &Shape,
|
||||||
|
alpha_text: &FloatTexture,
|
||||||
|
colorspace: Option<&RGBColorSpace>,
|
||||||
|
) -> Light {
|
||||||
|
let l = parameters
|
||||||
|
.get_one_spectrum(
|
||||||
|
"L",
|
||||||
|
colorspace.unwrap().illuminant,
|
||||||
|
SpectrumType::Illuminant,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let lemit = lookup_spectrum(l);
|
||||||
|
let mut scale = parameters.get_one_float("scale", 1);
|
||||||
|
scale /= spectrum_to_photometric(l.unwrap());
|
||||||
|
let phi_v = parameters.get_one_float("power", 1.);
|
||||||
|
if phi_v > 0. {
|
||||||
|
let k_e = 4. * PI;
|
||||||
|
sc *= phi_v / k_e;
|
||||||
|
}
|
||||||
|
|
||||||
|
let from = parameters.get_one_point3f("from", Point3f::zero());
|
||||||
|
let tf = Transform::translate(from.into());
|
||||||
|
let final_render = render_from_light * tf;
|
||||||
|
let base = LightBase::new(LightType::DeltaPosition, render_from_light, medium.into());
|
||||||
|
let specific = PointLight {
|
||||||
|
base,
|
||||||
|
scale,
|
||||||
|
i: arena.alloc(lemit),
|
||||||
|
};
|
||||||
|
Light::Point(specific)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,26 +1,30 @@
|
||||||
use crate::core::image::ImageBuffer;
|
use crate::core::image::{Image, ImageIO, ImageMetadata};
|
||||||
use shared::core::light::LightBase;
|
use crate::core::light::CreateLight;
|
||||||
|
use crate::core::spectrum::spectrum_to_photometric;
|
||||||
|
use crate::spectra::colorspace::new;
|
||||||
|
use crate::utils::{Ptr, Upload, resolve_filename};
|
||||||
|
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::lights::ProjectionLight;
|
||||||
|
use shared::spectra::RGBColorSpace;
|
||||||
|
use shared::utils::math::{radians, square};
|
||||||
|
use shared::utils::{Ptr, Transform};
|
||||||
|
|
||||||
pub struct ProjectionLightStorage {
|
impl CreateLight for ProjectionLight {
|
||||||
image: *const Image,
|
fn new(
|
||||||
distrib: *const PiecewiseConstant2D,
|
|
||||||
image_color_space: *const RGBColorSpace,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ProjectionLightHost {
|
|
||||||
pub view: ProjectionLight,
|
|
||||||
_storage: ProjectionLightStorage,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProjectionLightHost {
|
|
||||||
pub fn new(
|
|
||||||
render_from_light: Transform,
|
render_from_light: Transform,
|
||||||
medium_interface: MediumInterface,
|
medium_interface: MediumInterface,
|
||||||
image: ImageBuffer,
|
le: Spectrum,
|
||||||
image_color_space: RGBColorSpace,
|
scale: shared::Float,
|
||||||
scale: Float,
|
shape: Option<Ptr<Shape>>,
|
||||||
fov: Float,
|
alpha: Option<Ptr<FloatTexture>>,
|
||||||
|
image: Option<Ptr<Image>>,
|
||||||
|
image_color_space: Option<Ptr<RGBColorSpace>>,
|
||||||
|
two_sided: Option<bool>,
|
||||||
|
fov: Option<shared::Float>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let base = LightBase::new(
|
let base = LightBase::new(
|
||||||
LightType::DeltaPosition,
|
LightType::DeltaPosition,
|
||||||
|
|
@ -38,9 +42,9 @@ impl ProjectionLightHost {
|
||||||
};
|
};
|
||||||
|
|
||||||
let hither = 1e-3;
|
let hither = 1e-3;
|
||||||
let screen_from_light = Transform::perspective(fov, hither, 1e30).unwrap();
|
let screen_from_light = Transform::perspective(fov.unwrap(), hither, 1e30).unwrap();
|
||||||
let light_from_screen = screen_from_light.inverse();
|
let light_from_screen = screen_from_light.inverse();
|
||||||
let opposite = (radians(fov) / 2.).tan();
|
let opposite = (radians(fov.unwrap()) / 2.).tan();
|
||||||
let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect };
|
let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect };
|
||||||
let a = 4. * square(opposite) * aspect_ratio;
|
let a = 4. * square(opposite) * aspect_ratio;
|
||||||
let dwda = |p: Point2f| {
|
let dwda = |p: Point2f| {
|
||||||
|
|
@ -52,28 +56,109 @@ impl ProjectionLightHost {
|
||||||
let d = image.get_sampling_distribution(dwda, screen_bounds);
|
let d = image.get_sampling_distribution(dwda, screen_bounds);
|
||||||
let distrib = PiecewiseConstant2D::new_with_bounds(&d, screen_bounds);
|
let distrib = PiecewiseConstant2D::new_with_bounds(&d, screen_bounds);
|
||||||
|
|
||||||
let storage = ProjectionLightStorage {
|
Self {
|
||||||
|
base,
|
||||||
image,
|
image,
|
||||||
image_color_space,
|
image_color_space,
|
||||||
distrib,
|
distrib,
|
||||||
};
|
|
||||||
|
|
||||||
let view = ProjectionLight {
|
|
||||||
base,
|
|
||||||
image: storage.image,
|
|
||||||
image_color_space: storage.image_color_space,
|
|
||||||
screen_bounds,
|
screen_bounds,
|
||||||
screen_from_light,
|
screen_from_light,
|
||||||
light_from_screen,
|
light_from_screen,
|
||||||
scale,
|
scale,
|
||||||
hither,
|
hither,
|
||||||
a,
|
a,
|
||||||
distrib: storage.distrib,
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
view,
|
|
||||||
_storage: storage,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create(
|
||||||
|
arena: &mut crate::utils::Arena,
|
||||||
|
render_from_light: shared::utils::Transform,
|
||||||
|
medium: Medium,
|
||||||
|
parameters: &crate::utils::ParameterDictionary,
|
||||||
|
loc: &FileLoc,
|
||||||
|
_shape: &Shape,
|
||||||
|
_alpha_text: &FloatTexture,
|
||||||
|
_colorspace: Option<&shared::spectra::RGBColorSpace>,
|
||||||
|
) -> Light {
|
||||||
|
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() {
|
||||||
|
panic!(
|
||||||
|
"{}: Must provide filename for projection light source.",
|
||||||
|
loc
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let im = Image::read(filename, None).unwrap();
|
||||||
|
let image: Image = im.image;
|
||||||
|
let metadata: ImageMetadata = im.metadata;
|
||||||
|
if image.has_any_infinite_pixels() {
|
||||||
|
panic!(
|
||||||
|
"{:?}: Image '{}' has NaN pixels. Not suitable for light.",
|
||||||
|
loc, filename
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let colorspace: RGBColorSpace = metadata.colorspace.unwrap();
|
||||||
|
let channel_desc = image
|
||||||
|
.get_channel_desc(&["R", "G", "B"])
|
||||||
|
.unwrap_or_else(|_| {
|
||||||
|
panic!(
|
||||||
|
"{:?}: Image '{}' must have R, G and B channels.",
|
||||||
|
loc, filename
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let image = image.select_channels(channel_desc);
|
||||||
|
scale /= spectrum_to_photometric(colorspace.illuminant);
|
||||||
|
if power > 0. {
|
||||||
|
let hither = 1e-3;
|
||||||
|
let aspect = image.resolution().x() as Float / image.resolution().y() as Float;
|
||||||
|
let screen_bounds = if aspect > 1. {
|
||||||
|
Bounds2f::from_poins(Point2f::new(-aspect, -1.), Point2f::new(aspect, 1.))
|
||||||
|
} else {
|
||||||
|
Bounds2f::from_points(
|
||||||
|
Point2f::new(-1., -1. / aspect),
|
||||||
|
Point2f::new(1., 1. / aspect),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let screen_from_light = Transform::perspective(fov, hither, 1e30);
|
||||||
|
let light_from_screen = screen_from_light.inverse();
|
||||||
|
let opposite = (radians(fov) / 2.).tan();
|
||||||
|
let aspect_factor = if aspect > 1. { aspect } else { 1. / aspect };
|
||||||
|
let a = 4. * square(aspect_factor);
|
||||||
|
let mut sum = 0;
|
||||||
|
let luminance = colorspace.luminance_vector();
|
||||||
|
let res = image.resolution();
|
||||||
|
for y in 0..res.y() {
|
||||||
|
for x in 0..res.x() {
|
||||||
|
let lerp_factor = Point2f::new((x + 0.5) / res.x(), (y + 0.5) / res.y());
|
||||||
|
let ps = screen_bounds.lerp(lerp_factor);
|
||||||
|
let w_point =
|
||||||
|
light_from_screen.apply_to_point(Point3f::new(ps.x(), ps.y(), 0.));
|
||||||
|
let w = Vector3f::from(w_point).normalize();
|
||||||
|
let dwda = w.z().powi(3);
|
||||||
|
for c in 0..3 {
|
||||||
|
sum += image.get_channel(Point2f::new(x, y), c) * luminance[c] * dwda;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scale *= power / (a * sum / (res.x() + res.y()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let flip = Transform::scale(1., -1., 1.);
|
||||||
|
let render_from_light_flip = render_from_light * flip;
|
||||||
|
let specific = Self::new(
|
||||||
|
render_from_light,
|
||||||
|
medium_interface,
|
||||||
|
le,
|
||||||
|
scale,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
image.upload(arena),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
Light::Projection(specific)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
100
src/lights/spot.rs
Normal file
100
src/lights/spot.rs
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
use crate::core::image::{Image, ImageIO, ImageMetadata};
|
||||||
|
use crate::core::light::CreateLight;
|
||||||
|
use crate::core::spectrum::spectrum_to_photometric;
|
||||||
|
use crate::spectra::colorspace::new;
|
||||||
|
use crate::utils::{Ptr, Upload, resolve_filename};
|
||||||
|
use shared::PI;
|
||||||
|
use shared::core::geometry::{Bounds2f, Frame, VectorLike};
|
||||||
|
use shared::core::image::ImageAccess;
|
||||||
|
use shared::core::light::{Light, LightBase, LightType};
|
||||||
|
use shared::core::medium::MediumInterface;
|
||||||
|
use shared::core::spectrum::Spectrum;
|
||||||
|
use shared::core::texture::SpectrumType;
|
||||||
|
use shared::lights::{ProjectionLight, SpotLight};
|
||||||
|
use shared::spectra::RGBColorSpace;
|
||||||
|
use shared::utils::math::{radians, square};
|
||||||
|
use shared::utils::{Ptr, Transform};
|
||||||
|
|
||||||
|
impl CreateLight for SpotLight {
|
||||||
|
fn new(
|
||||||
|
render_from_light: Transform,
|
||||||
|
medium_interface: MediumInterface,
|
||||||
|
le: Spectrum,
|
||||||
|
scale: shared::Float,
|
||||||
|
shape: Option<Ptr<Shape>>,
|
||||||
|
alpha: Option<Ptr<FloatTexture>>,
|
||||||
|
image: Option<Ptr<Image>>,
|
||||||
|
image_color_space: Option<Ptr<RGBColorSpace>>,
|
||||||
|
two_sided: Option<bool>,
|
||||||
|
fov: Option<shared::Float>,
|
||||||
|
cos_falloff_start: Option<shared::Float>,
|
||||||
|
total_width: Option<shared::Float>,
|
||||||
|
) -> Self {
|
||||||
|
let base = LightBase::new(
|
||||||
|
LightType::DeltaPosition,
|
||||||
|
render_from_light,
|
||||||
|
MediumInterface::empty(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let iemit = lookup_spectrum(le);
|
||||||
|
Self {
|
||||||
|
base,
|
||||||
|
iemit,
|
||||||
|
scale,
|
||||||
|
cos_falloff_end: radians(total_width.unwrap().cos()),
|
||||||
|
cos_falloff_start: radians(cos_falloff_start.unwrap().cos()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(
|
||||||
|
arena: &mut crate::utils::Arena,
|
||||||
|
render_from_light: Transform,
|
||||||
|
medium: Medium,
|
||||||
|
parameters: &crate::utils::ParameterDictionary,
|
||||||
|
loc: &FileLoc,
|
||||||
|
shape: &Shape,
|
||||||
|
alpha_tex: &FloatTexture,
|
||||||
|
colorspace: Option<&RGBColorSpace>,
|
||||||
|
) -> Light {
|
||||||
|
let i = parameters
|
||||||
|
.get_one_spectrum(
|
||||||
|
"I",
|
||||||
|
colorspace.unwrap().illuminant,
|
||||||
|
SpectrumType::Illuminant,
|
||||||
|
)
|
||||||
|
.expect("No spectrum");
|
||||||
|
let mut scale = parameters.get_one_float("scale", 1.);
|
||||||
|
let coneangle = parameters.get_one_float("coneangle", def);
|
||||||
|
let conedelta = parameters.get_one_float("conedelta", def);
|
||||||
|
let from = parameters.get_one_point3f("from", Point3f::zero());
|
||||||
|
let to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.));
|
||||||
|
let dir_to_z = Transform::from(Frame::from_z((to - from).normalize()));
|
||||||
|
let t = Transform::translate(from.into()) * dir_to_z.inverse();
|
||||||
|
let final_render = render_from_light * t;
|
||||||
|
scale /= spectrum_to_photometric(i);
|
||||||
|
|
||||||
|
let phi_v = parameters.get_one_float("power", -1.);
|
||||||
|
if phi_v > 0. {
|
||||||
|
let cos_falloff_end = radians(coneangle).cos();
|
||||||
|
let cos_falloff_start = radians(coneangle - conedelta).cos();
|
||||||
|
let k_e =
|
||||||
|
2. * PI * ((1. - cos_falloff_start) + (cos_falloff_start - cos_falloff_end) / 2);
|
||||||
|
scale *= phi_v / k_e;
|
||||||
|
}
|
||||||
|
|
||||||
|
let specific = SpotLight::new(
|
||||||
|
final_render,
|
||||||
|
medium.into(),
|
||||||
|
le,
|
||||||
|
scale,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Some(coneangle),
|
||||||
|
Some(coneangle - conedelta),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
259
src/utils/arena.rs
Normal file
259
src/utils/arena.rs
Normal file
|
|
@ -0,0 +1,259 @@
|
||||||
|
use crate::core::image::Image;
|
||||||
|
use crate::core::texture::FloatTexture;
|
||||||
|
use crate::utils::sampling::PiecewiseConstant2D;
|
||||||
|
use core::alloc::Layout;
|
||||||
|
use shared::Float;
|
||||||
|
use shared::core::color::RGBToSpectrumTable;
|
||||||
|
use shared::core::image::DeviceImage;
|
||||||
|
use shared::core::shape::Shape;
|
||||||
|
use shared::core::texture::GPUFloatTexture;
|
||||||
|
use shared::spectra::{RGBColorSpace, StandardColorSpaces};
|
||||||
|
use shared::textures::*;
|
||||||
|
use shared::utils::Ptr;
|
||||||
|
use shared::utils::sampling::DevicePiecewiseConstant2D;
|
||||||
|
|
||||||
|
pub struct Arena {
|
||||||
|
buffer: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arena {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
buffer: Vec::with_capacity(64 * 1024 * 1024),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alloc<T: Copy>(&mut self, value: T) -> Ptr<T> {
|
||||||
|
let layout = Layout::new::<T>();
|
||||||
|
let offset = self.alloc_raw(layout);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let ptr = self.buffer.as_mut_ptr().add(offset) as *mut T;
|
||||||
|
std::ptr::write(ptr, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr {
|
||||||
|
offset: offset as i32,
|
||||||
|
_marker: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alloc_slice<T: Copy>(&mut self, values: &[T]) -> Ptr<T> {
|
||||||
|
let layout = Layout::array::<T>(values.len()).unwrap();
|
||||||
|
let offset = self.alloc_raw(layout);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let ptr = self.buffer.as_mut_ptr().add(offset) as *mut T;
|
||||||
|
std::ptr::copy_nonoverlapping(values.as_ptr(), ptr, values.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr {
|
||||||
|
offset: offset as i32,
|
||||||
|
_marker: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_raw(&mut self, layout: Layout) -> usize {
|
||||||
|
let len = self.buffer.len();
|
||||||
|
let align_offset = (len + layout.align() - 1) & !(layout.align() - 1);
|
||||||
|
let new_len = align_offset + layout.size();
|
||||||
|
|
||||||
|
if new_len > self.buffer.capacity() {
|
||||||
|
// Growth strategy: Double capacity to reduce frequency of resizing
|
||||||
|
let new_cap = std::cmp::max(self.buffer.capacity() * 2, new_len);
|
||||||
|
self.buffer.reserve(new_cap - self.buffer.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.buffer.resize(new_len, 0);
|
||||||
|
align_offset
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn raw_data(&self) -> &[u8] {
|
||||||
|
&self.buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Upload {
|
||||||
|
type Target: Copy;
|
||||||
|
|
||||||
|
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Upload for Shape {
|
||||||
|
type Target = Shape;
|
||||||
|
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
|
||||||
|
arena.alloc(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Upload for Image {
|
||||||
|
type Target = DeviceImage;
|
||||||
|
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
|
||||||
|
let pixels_ptr = arena.alloc_slice(&self.storage_as_slice());
|
||||||
|
let device_img = DeviceImage {
|
||||||
|
base: self.base,
|
||||||
|
pixels: pixels_ptr,
|
||||||
|
};
|
||||||
|
arena.alloc(device_img)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Upload for FloatTexture {
|
||||||
|
type Target = GPUFloatTexture;
|
||||||
|
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
|
||||||
|
let gpu_variant = match self {
|
||||||
|
FloatTexture::Constant(tex) => GPUFloatTexture::Constant(tex.clone()),
|
||||||
|
FloatTexture::Checkerboard(tex) => GPUFloatTexture::Checkerboard(tex.clone()),
|
||||||
|
FloatTexture::Dots(tex) => GPUFloatTexture::Dots(tex.clone()),
|
||||||
|
FloatTexture::FBm(tex) => GPUFloatTexture::FBm(tex.clone()),
|
||||||
|
FloatTexture::Windy(tex) => GPUFloatTexture::Windy(tex.clone()),
|
||||||
|
FloatTexture::Wrinkled(tex) => GPUFloatTexture::Wrinkled(tex.clone()),
|
||||||
|
FloatTexture::Constant(val) => GPUFloatTexture::Constant(*val),
|
||||||
|
FloatTexture::Scaled(tex) => {
|
||||||
|
let child_ptr = tex.texture.upload(arena);
|
||||||
|
|
||||||
|
let gpu_scaled = GPUFloatScaledTexture {
|
||||||
|
tex: child_ptr,
|
||||||
|
scale: tex.scale.upload(arena),
|
||||||
|
};
|
||||||
|
GPUFloatTexture::Scaled(gpu_scaled)
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatTexture::Mix(tex) => {
|
||||||
|
let tex1_ptr = tex.tex1.upload(arena);
|
||||||
|
let tex2_ptr = tex.tex2.upload(arena);
|
||||||
|
let amount_ptr = tex.amount.upload(arena);
|
||||||
|
|
||||||
|
let gpu_mix = GPUFloatMixTexture {
|
||||||
|
tex1: tex1_ptr,
|
||||||
|
tex2: tex2_ptr,
|
||||||
|
amount: amount_ptr,
|
||||||
|
};
|
||||||
|
GPUFloatTexture::Mix(gpu_mix)
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatTexture::DirectionMix(tex) => {
|
||||||
|
let tex1_ptr = tex.tex1.upload(arena);
|
||||||
|
let tex2_ptr = tex.tex2.upload(arena);
|
||||||
|
let gpu_dmix = shared::textures::GPUFloatDirectionMixTexture {
|
||||||
|
tex1: tex1_ptr,
|
||||||
|
tex2: tex2_ptr,
|
||||||
|
dir: tex.dir,
|
||||||
|
};
|
||||||
|
GPUFloatTexture::DirectionMix(gpu_dmix)
|
||||||
|
}
|
||||||
|
FloatTexture::Image(tex) => {
|
||||||
|
let image_ptr = tex.image.upload(arena);
|
||||||
|
|
||||||
|
let gpu_image_tex = GPUFloatImageTexture {
|
||||||
|
mapping: tex.mapping,
|
||||||
|
tex_obj: image_ptr.offset as u64,
|
||||||
|
scale: tex.scale,
|
||||||
|
invert: tex.invert,
|
||||||
|
mapping: tex.mapping,
|
||||||
|
};
|
||||||
|
GPUFloatTexture::Image(gpu_image_tex)
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatTexture::Ptex(tex) => {
|
||||||
|
todo!("Implement Ptex buffer upload")
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatTexture::Bilerp(tex) => GPUFloatTexture::Bilerp(tex.clone()),
|
||||||
|
};
|
||||||
|
|
||||||
|
arena.alloc(gpu_variant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Upload for RGBToSpectrumTable {
|
||||||
|
type Target = RGBToSpectrumTable;
|
||||||
|
|
||||||
|
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
|
||||||
|
let z_ptr = arena.alloc_slice(&self.z_nodes);
|
||||||
|
let c_ptr = arena.alloc_slice(&self.coeffs);
|
||||||
|
|
||||||
|
let shared_table = RGBToSpectrumTable {
|
||||||
|
z_nodes: z_ptr,
|
||||||
|
coeffs: c_ptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
arena.alloc(shared_table)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Upload for RGBColorSpace {
|
||||||
|
type Target = RGBColorSpace;
|
||||||
|
|
||||||
|
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
|
||||||
|
let table_ptr = self.rgb_to_spectrum_table.upload(arena);
|
||||||
|
|
||||||
|
let shared_space = RGBColorSpace {
|
||||||
|
r: self.r,
|
||||||
|
g: self.g,
|
||||||
|
b: self.b,
|
||||||
|
w: self.w,
|
||||||
|
illuminant: self.illuminant.clone(),
|
||||||
|
rgb_to_spectrum_table: table_ptr,
|
||||||
|
xyz_from_rgb: self.xyz_from_rgb,
|
||||||
|
rgb_from_xyz: self.rgb_from_xyz,
|
||||||
|
};
|
||||||
|
|
||||||
|
arena.alloc(shared_space)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Upload for StandardColorSpaces {
|
||||||
|
type Target = StandardColorSpaces;
|
||||||
|
|
||||||
|
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
|
||||||
|
let srgb_ptr = self.srgb.upload(arena);
|
||||||
|
let dci_ptr = self.dci_p3.upload(arena);
|
||||||
|
let rec_ptr = self.rec2020.upload(arena);
|
||||||
|
let aces_ptr = self.aces2065_1.upload(arena);
|
||||||
|
|
||||||
|
let registry = StandardColorSpaces {
|
||||||
|
srgb: srgb_ptr,
|
||||||
|
dci_p3: dci_ptr,
|
||||||
|
rec2020: rec_ptr,
|
||||||
|
aces2065_1: aces_ptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
arena.alloc(registry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Upload for PiecewiseConstant2D {
|
||||||
|
type Target = DevicePiecewiseConstant2D;
|
||||||
|
|
||||||
|
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
|
||||||
|
let marginal_shared = self.p_marginal.to_shared(arena);
|
||||||
|
|
||||||
|
let conditionals_shared: Vec<DevicePiecewiseConstant1D> = self
|
||||||
|
.p_conditionals
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.to_shared(arena))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let conditionals_ptr = arena.alloc_slice(&conditionals_shared);
|
||||||
|
|
||||||
|
let shared_2d = DevicePiecewiseConstant2D {
|
||||||
|
domain: self.domain,
|
||||||
|
p_marginal: marginal_shared,
|
||||||
|
n_conditionals: self.p_conditionals.len(),
|
||||||
|
p_conditional_v: conditionals_ptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
arena.alloc(shared_2d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Upload> Upload for Option<T> {
|
||||||
|
type Target = T::Target;
|
||||||
|
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
|
||||||
|
match self {
|
||||||
|
Some(val) => val.upload(arena),
|
||||||
|
None => Ptr::null(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue