Running tests on parsing
This commit is contained in:
parent
c659ea0f44
commit
c8d083df62
26 changed files with 827 additions and 663 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -12,3 +12,4 @@ tests/
|
||||||
*.spv
|
*.spv
|
||||||
*.json
|
*.json
|
||||||
*.txt
|
*.txt
|
||||||
|
scenes/
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use crate::utils::math::{next_float_down, next_float_up};
|
use crate::utils::math::{next_float_down, next_float_up};
|
||||||
use num_traits::Zero;
|
|
||||||
use core::ops::{Add, Div, Mul, Neg, Sub};
|
use core::ops::{Add, Div, Mul, Neg, Sub};
|
||||||
|
use num_traits::Zero;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -19,123 +19,118 @@ pub fn lookup_spectrum(s: &Spectrum) -> Arc<DenselySampledSpectrumBuffer> {
|
||||||
cache.lookup(dense_spectrum).into()
|
cache.lookup(dense_spectrum).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait CreateLight {
|
// Placeholders for non-area lights that never inspect these arguments.
|
||||||
fn create(
|
// TODO: refactor each light's create to only take what it actually needs,
|
||||||
render_from_light: Transform,
|
// then delete these.
|
||||||
medium: Medium,
|
fn dummy_shape() -> Shape {
|
||||||
parameters: &ParameterDictionary,
|
Shape::default()
|
||||||
loc: &FileLoc,
|
|
||||||
shape: &Shape,
|
|
||||||
alpha_text: &FloatTexture,
|
|
||||||
colorspace: Option<&RGBColorSpace>,
|
|
||||||
arena: &Arena,
|
|
||||||
) -> Result<Light>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait LightFactory {
|
fn dummy_alpha() -> FloatTexture {
|
||||||
fn create(
|
FloatTexture::default()
|
||||||
name: &str,
|
|
||||||
arena: &mut Arena,
|
|
||||||
render_from_light: Transform,
|
|
||||||
medium: Medium,
|
|
||||||
parameters: &ParameterDictionary,
|
|
||||||
loc: &FileLoc,
|
|
||||||
shape: &Shape,
|
|
||||||
alpha_tex: &FloatTexture,
|
|
||||||
colorspace: Option<&RGBColorSpace>,
|
|
||||||
camera_transform: CameraTransform,
|
|
||||||
) -> Result<Self>
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LightFactory for Light {
|
/// Create a non-area light from a scene file directive.
|
||||||
fn create(
|
pub fn create_light(
|
||||||
name: &str,
|
name: &str,
|
||||||
arena: &mut Arena,
|
render_from_light: Transform,
|
||||||
render_from_light: Transform,
|
medium: Option<Medium>,
|
||||||
medium: Medium,
|
parameters: &ParameterDictionary,
|
||||||
parameters: &ParameterDictionary,
|
loc: &FileLoc,
|
||||||
loc: &FileLoc,
|
camera_transform: CameraTransform,
|
||||||
shape: &Shape,
|
arena: &mut Arena,
|
||||||
alpha_tex: &FloatTexture,
|
) -> Result<Light> {
|
||||||
colorspace: Option<&RGBColorSpace>,
|
let shape = dummy_shape();
|
||||||
camera_transform: CameraTransform,
|
let alpha = dummy_alpha();
|
||||||
) -> Result<Self>
|
|
||||||
where
|
match name {
|
||||||
Self: Sized,
|
"point" => crate::lights::point::create(
|
||||||
{
|
render_from_light,
|
||||||
match name {
|
medium,
|
||||||
"diffuse" => DiffuseAreaLight::create(
|
parameters,
|
||||||
render_from_light,
|
loc,
|
||||||
medium,
|
&shape,
|
||||||
parameters,
|
&alpha,
|
||||||
loc,
|
None,
|
||||||
shape,
|
arena,
|
||||||
alpha_tex,
|
),
|
||||||
colorspace,
|
"spot" => crate::lights::spot::create(
|
||||||
arena,
|
render_from_light,
|
||||||
),
|
medium,
|
||||||
"point" => PointLight::create(
|
parameters,
|
||||||
render_from_light,
|
loc,
|
||||||
medium,
|
&shape,
|
||||||
parameters,
|
&alpha,
|
||||||
loc,
|
None,
|
||||||
shape,
|
arena,
|
||||||
alpha_tex,
|
),
|
||||||
colorspace,
|
"distant" => crate::lights::distant::create(
|
||||||
arena,
|
render_from_light,
|
||||||
),
|
medium,
|
||||||
"spot" => SpotLight::create(
|
parameters,
|
||||||
render_from_light,
|
loc,
|
||||||
medium,
|
&shape,
|
||||||
parameters,
|
&alpha,
|
||||||
loc,
|
None,
|
||||||
shape,
|
arena,
|
||||||
alpha_tex,
|
),
|
||||||
colorspace,
|
"goniometric" => crate::lights::goniometric::create(
|
||||||
arena,
|
render_from_light,
|
||||||
),
|
medium,
|
||||||
"goniometric" => GoniometricLight::create(
|
parameters,
|
||||||
render_from_light,
|
loc,
|
||||||
medium,
|
&shape,
|
||||||
parameters,
|
&alpha,
|
||||||
loc,
|
None,
|
||||||
shape,
|
arena,
|
||||||
alpha_tex,
|
),
|
||||||
colorspace,
|
"projection" => crate::lights::projection::create(
|
||||||
arena,
|
render_from_light,
|
||||||
),
|
medium,
|
||||||
"projection" => ProjectionLight::create(
|
parameters,
|
||||||
render_from_light,
|
loc,
|
||||||
medium,
|
&shape,
|
||||||
parameters,
|
&alpha,
|
||||||
loc,
|
None,
|
||||||
shape,
|
arena,
|
||||||
alpha_tex,
|
),
|
||||||
colorspace,
|
"infinite" => crate::lights::infinite::create(
|
||||||
arena,
|
render_from_light,
|
||||||
),
|
medium.into(),
|
||||||
"distant" => DistantLight::create(
|
camera_transform,
|
||||||
render_from_light,
|
parameters,
|
||||||
medium,
|
None,
|
||||||
parameters,
|
loc,
|
||||||
loc,
|
arena,
|
||||||
shape,
|
),
|
||||||
alpha_tex,
|
"diffuse" => Err(anyhow!(
|
||||||
colorspace,
|
"{}: \"diffuse\" is an area light; use create_area_light with a shape",
|
||||||
arena,
|
loc
|
||||||
),
|
)),
|
||||||
"infinite" => crate::lights::infinite::create(
|
_ => Err(anyhow!("{}: unknown light type \"{}\"", loc, name)),
|
||||||
render_from_light,
|
|
||||||
medium.into(),
|
|
||||||
camera_transform,
|
|
||||||
parameters,
|
|
||||||
colorspace,
|
|
||||||
loc,
|
|
||||||
arena,
|
|
||||||
),
|
|
||||||
_ => Err(anyhow!("{}: unknown light type: \"{}\"", loc, name)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a diffuse area light bound to a specific shape.
|
||||||
|
/// Called once per sub-shape (e.g. once per triangle in a mesh).
|
||||||
|
pub fn create_area_light(
|
||||||
|
render_from_light: Transform,
|
||||||
|
medium: Option<Medium>,
|
||||||
|
parameters: &ParameterDictionary,
|
||||||
|
loc: &FileLoc,
|
||||||
|
shape: &Shape,
|
||||||
|
alpha_tex: &FloatTexture,
|
||||||
|
colorspace: Option<&RGBColorSpace>,
|
||||||
|
arena: &mut Arena,
|
||||||
|
) -> Result<Light> {
|
||||||
|
crate::lights::diffuse::create(
|
||||||
|
render_from_light,
|
||||||
|
medium,
|
||||||
|
parameters,
|
||||||
|
loc,
|
||||||
|
shape,
|
||||||
|
alpha_tex,
|
||||||
|
colorspace,
|
||||||
|
arena,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,10 @@ impl MaterialFactory for Material {
|
||||||
named_materials: &HashMap<String, Material>,
|
named_materials: &HashMap<String, Material>,
|
||||||
loc: FileLoc,
|
loc: FileLoc,
|
||||||
arena: &Arena,
|
arena: &Arena,
|
||||||
) -> Result<Self> where Self: Sized {
|
) -> Result<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
match name {
|
match name {
|
||||||
"diffuse" => {
|
"diffuse" => {
|
||||||
DiffuseMaterial::create(parameters, normal_map, named_materials, &loc, arena)
|
DiffuseMaterial::create(parameters, normal_map, named_materials, &loc, arena)
|
||||||
|
|
|
||||||
|
|
@ -641,7 +641,7 @@ impl ParserTarget for BasicSceneBuilder {
|
||||||
arena: Arc<Arena>,
|
arena: Arc<Arena>,
|
||||||
) -> Result<(), ParserError> {
|
) -> Result<(), ParserError> {
|
||||||
let name = normalize_utf8(orig_name);
|
let name = normalize_utf8(orig_name);
|
||||||
self.verify_world("Texture", &loc);
|
self.verify_world("Texture", &loc)?;
|
||||||
let dict = ParameterDictionary::from_array(
|
let dict = ParameterDictionary::from_array(
|
||||||
params.clone(),
|
params.clone(),
|
||||||
&self.graphics_state.texture_attributes,
|
&self.graphics_state.texture_attributes,
|
||||||
|
|
@ -701,7 +701,7 @@ impl ParserTarget for BasicSceneBuilder {
|
||||||
params: ParsedParameterVector,
|
params: ParsedParameterVector,
|
||||||
loc: FileLoc,
|
loc: FileLoc,
|
||||||
) -> Result<(), ParserError> {
|
) -> Result<(), ParserError> {
|
||||||
self.verify_world("material", &loc);
|
self.verify_world("material", &loc)?;
|
||||||
let entity = SceneEntity {
|
let entity = SceneEntity {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
loc,
|
loc,
|
||||||
|
|
@ -733,7 +733,7 @@ impl ParserTarget for BasicSceneBuilder {
|
||||||
let dict = ParameterDictionary::from_array(
|
let dict = ParameterDictionary::from_array(
|
||||||
params.clone(),
|
params.clone(),
|
||||||
&self.graphics_state.medium_attributes,
|
&self.graphics_state.medium_attributes,
|
||||||
self.graphics_state.color_space.clone()
|
self.graphics_state.color_space.clone(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let render_from_light = AnimatedTransform::new(
|
let render_from_light = AnimatedTransform::new(
|
||||||
|
|
@ -757,7 +757,6 @@ impl ParserTarget for BasicSceneBuilder {
|
||||||
|
|
||||||
self.scene.add_light(entity);
|
self.scene.add_light(entity);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn area_light_source(
|
fn area_light_source(
|
||||||
|
|
@ -786,7 +785,7 @@ impl ParserTarget for BasicSceneBuilder {
|
||||||
let dict = ParameterDictionary::from_array(
|
let dict = ParameterDictionary::from_array(
|
||||||
params.clone(),
|
params.clone(),
|
||||||
&self.graphics_state.shape_attributes,
|
&self.graphics_state.shape_attributes,
|
||||||
self.graphics_state.color_space.clone()
|
self.graphics_state.color_space.clone(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let render_from_object = self.graphics_state.ctm[0];
|
let render_from_object = self.graphics_state.ctm[0];
|
||||||
|
|
@ -797,7 +796,7 @@ impl ParserTarget for BasicSceneBuilder {
|
||||||
let light_entity = SceneEntity {
|
let light_entity = SceneEntity {
|
||||||
name: al.name.clone(),
|
name: al.name.clone(),
|
||||||
loc: al.loc.clone(),
|
loc: al.loc.clone(),
|
||||||
parameters: al_dict
|
parameters: al_dict,
|
||||||
};
|
};
|
||||||
Some(self.scene.add_area_light(light_entity))
|
Some(self.scene.add_area_light(light_entity))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -816,7 +815,7 @@ impl ParserTarget for BasicSceneBuilder {
|
||||||
base: SceneEntity {
|
base: SceneEntity {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
loc,
|
loc,
|
||||||
parameters: dict
|
parameters: dict,
|
||||||
},
|
},
|
||||||
render_from_object: Arc::new(render_from_object),
|
render_from_object: Arc::new(render_from_object),
|
||||||
object_from_render: Arc::new(object_from_render),
|
object_from_render: Arc::new(object_from_render),
|
||||||
|
|
@ -828,7 +827,11 @@ impl ParserTarget for BasicSceneBuilder {
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.active_instance_definition.is_some() {
|
if self.active_instance_definition.is_some() {
|
||||||
self.active_instance_definition.as_mut().unwrap().shapes.push(entity)
|
self.active_instance_definition
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.shapes
|
||||||
|
.push(entity)
|
||||||
} else {
|
} else {
|
||||||
self.scene.add_shape(entity);
|
self.scene.add_shape(entity);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
mod builder;
|
pub mod builder;
|
||||||
mod entities;
|
pub mod entities;
|
||||||
mod scene;
|
pub mod scene;
|
||||||
mod state;
|
pub mod state;
|
||||||
|
|
||||||
pub use builder::BasicSceneBuilder;
|
pub use builder::BasicSceneBuilder;
|
||||||
pub use entities::*;
|
pub use entities::*;
|
||||||
pub use scene::{BasicScene, SceneLookup};
|
pub use scene::{BasicScene, SceneLookup};
|
||||||
pub use state::*;
|
pub use state::*;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ use crate::core::camera::CameraFactory;
|
||||||
use crate::core::film::FilmFactory;
|
use crate::core::film::FilmFactory;
|
||||||
use crate::core::filter::FilterFactory;
|
use crate::core::filter::FilterFactory;
|
||||||
use crate::core::image::{Image, io::ImageIO};
|
use crate::core::image::{Image, io::ImageIO};
|
||||||
use crate::core::light::LightFactory;
|
|
||||||
use crate::core::material::MaterialFactory;
|
use crate::core::material::MaterialFactory;
|
||||||
use crate::core::primitive::{CreateGeometricPrimitive, CreateSimplePrimitive};
|
use crate::core::primitive::{CreateGeometricPrimitive, CreateSimplePrimitive};
|
||||||
use crate::core::sampler::SamplerFactory;
|
use crate::core::sampler::SamplerFactory;
|
||||||
|
|
@ -17,7 +16,7 @@ use crate::{Arena, FileLoc};
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use shared::core::camera::{CameraTransform, Camera};
|
use shared::core::camera::{Camera, CameraTransform};
|
||||||
use shared::core::color::LINEAR;
|
use shared::core::color::LINEAR;
|
||||||
use shared::core::film::Film;
|
use shared::core::film::Film;
|
||||||
use shared::core::filter::Filter;
|
use shared::core::filter::Filter;
|
||||||
|
|
@ -502,26 +501,114 @@ impl BasicScene {
|
||||||
&self,
|
&self,
|
||||||
camera_transform: &CameraTransform,
|
camera_transform: &CameraTransform,
|
||||||
arena: &mut Arena,
|
arena: &mut Arena,
|
||||||
) -> Result<Vec<Light>> {
|
) -> Vec<Light> {
|
||||||
let state = self.light_state.lock();
|
let state = self.light_state.lock();
|
||||||
|
|
||||||
state.lights.par_iter().map(|entity| {
|
state
|
||||||
let render_from_light = entity.transformed_base.render_from_object.start_transform;
|
.lights
|
||||||
let medium = self.get_medium(
|
.iter()
|
||||||
&entity.medium,
|
.filter_map(|entity| {
|
||||||
&entity.transformed_base.base.loc,
|
let render_from_light = entity.transformed_base.render_from_object.start_transform;
|
||||||
|
|
||||||
|
let medium = self
|
||||||
|
.get_medium(&entity.medium, &entity.transformed_base.base.loc)
|
||||||
|
.map(|m| *m);
|
||||||
|
|
||||||
|
match crate::core::light::create_light(
|
||||||
|
&entity.transformed_base.base.name,
|
||||||
|
render_from_light,
|
||||||
|
medium,
|
||||||
|
&entity.transformed_base.base.parameters,
|
||||||
|
&entity.transformed_base.base.loc,
|
||||||
|
camera_transform.clone(),
|
||||||
|
arena,
|
||||||
|
) {
|
||||||
|
Ok(light) => Some(light),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!(
|
||||||
|
"{}: failed to create light: {}",
|
||||||
|
entity.transformed_base.base.loc,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create area lights for shapes that reference one. Produces a map from
|
||||||
|
/// shape index to a vec of lights (one per sub-shape, e.g. per triangle).
|
||||||
|
/// Must be called after shapes are loaded but before upload_shapes.
|
||||||
|
pub fn create_area_lights(
|
||||||
|
&self,
|
||||||
|
loaded_shapes: &[Vec<Shape>],
|
||||||
|
shape_entities: &[ShapeSceneEntity],
|
||||||
|
textures: &NamedTextures,
|
||||||
|
arena: &mut Arena,
|
||||||
|
) -> HashMap<usize, Vec<Light>> {
|
||||||
|
let light_state = self.light_state.lock();
|
||||||
|
let mut shape_lights: HashMap<usize, Vec<Light>> = HashMap::new();
|
||||||
|
|
||||||
|
for (i, entity) in shape_entities.iter().enumerate() {
|
||||||
|
let light_idx = match entity.light_index {
|
||||||
|
Some(idx) => idx,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let shapes = match loaded_shapes.get(i) {
|
||||||
|
Some(s) if !s.is_empty() => s,
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let al_entity = &light_state.area_lights[light_idx];
|
||||||
|
|
||||||
|
let alpha_tex = self.get_alpha_texture(
|
||||||
|
&entity.base.parameters,
|
||||||
|
&entity.base.loc,
|
||||||
|
&textures.float_textures,
|
||||||
);
|
);
|
||||||
|
|
||||||
Light::create(
|
// Use the film colorspace as fallback for area light emission
|
||||||
&entity.transformed_base.base.name,
|
let film_cs = self.film_colorspace.lock();
|
||||||
&entity.transformed_base.base.parameters,
|
let colorspace_ref = al_entity
|
||||||
render_from_light,
|
.parameters
|
||||||
camera_transform,
|
.color_space
|
||||||
medium.map(|m| *m),
|
.as_ref()
|
||||||
&entity.transformed_base.base.loc,
|
.or(film_cs.as_ref());
|
||||||
arena,
|
|
||||||
)
|
let render_from_light = *entity.render_from_object;
|
||||||
}).collect()
|
|
||||||
|
let lights: Vec<Light> = shapes
|
||||||
|
.iter()
|
||||||
|
.filter_map(|shape| {
|
||||||
|
match crate::core::light::create_area_light(
|
||||||
|
render_from_light,
|
||||||
|
None,
|
||||||
|
&al_entity.parameters,
|
||||||
|
&al_entity.loc,
|
||||||
|
shape,
|
||||||
|
alpha_tex
|
||||||
|
.as_ref()
|
||||||
|
.expect("Alpha texture required for area light"),
|
||||||
|
colorspace_ref.map(|cs| cs.as_ref()),
|
||||||
|
arena,
|
||||||
|
) {
|
||||||
|
Ok(light) => Some(light),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{}: failed to create area light: {}", al_entity.loc, e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if !lights.is_empty() {
|
||||||
|
shape_lights.insert(i, lights);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shape_lights
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_aggregate(
|
pub fn create_aggregate(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use super::{SceneEntity, TextureSceneEntity, LightSceneEntity};
|
use super::{LightSceneEntity, SceneEntity, TextureSceneEntity};
|
||||||
use crate::core::image::Image;
|
use crate::core::image::Image;
|
||||||
use crate::core::texture::{FloatTexture, SpectrumTexture};
|
use crate::core::texture::{FloatTexture, SpectrumTexture};
|
||||||
use crate::utils::parallel::AsyncJob;
|
use crate::utils::parallel::AsyncJob;
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,12 @@ pub enum FloatTexture {
|
||||||
Wrinkled(WrinkledTexture),
|
Wrinkled(WrinkledTexture),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for FloatTexture {
|
||||||
|
fn default() -> Self {
|
||||||
|
FloatTexture::Constant(FloatConstantTexture::new(1.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FloatTextureTrait for Arc<FloatTexture> {
|
impl FloatTextureTrait for Arc<FloatTexture> {
|
||||||
fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
|
fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
|
||||||
self.as_ref().evaluate(ctx)
|
self.as_ref().evaluate(ctx)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::Arena;
|
|
||||||
use crate::core::film::{CreateFilmBase, PixelSensor};
|
use crate::core::film::{CreateFilmBase, PixelSensor};
|
||||||
use crate::utils::containers::Array2D;
|
use crate::utils::containers::Array2D;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use crate::Arena;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use shared::core::camera::CameraTransform;
|
use shared::core::camera::CameraTransform;
|
||||||
use shared::core::film::{Film, FilmBase, RGBFilm, RGBPixel};
|
use shared::core::film::{Film, FilmBase, RGBFilm, RGBPixel};
|
||||||
|
|
@ -69,7 +70,10 @@ impl CreateFilm for RGBFilm {
|
||||||
loc: &FileLoc,
|
loc: &FileLoc,
|
||||||
_arena: &Arena,
|
_arena: &Arena,
|
||||||
) -> Result<Film> {
|
) -> Result<Film> {
|
||||||
let colorspace = params.color_space.as_ref().unwrap();
|
let colorspace = params.color_space.as_ref().cloned().unwrap_or_else(|| {
|
||||||
|
let stdcs = crate::spectra::get_colorspace_device();
|
||||||
|
Arc::new(*stdcs.srgb)
|
||||||
|
});
|
||||||
let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY)?;
|
let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY)?;
|
||||||
let write_fp16 = params.get_one_bool("savefp16", true)?;
|
let write_fp16 = params.get_one_bool("savefp16", true)?;
|
||||||
let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?;
|
let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?;
|
||||||
|
|
|
||||||
|
|
@ -23,20 +23,52 @@ pub static UC_RHO: [Float; N_RHO_SAMPLES] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static U_RHO: [Point2f; N_RHO_SAMPLES] = [
|
pub static U_RHO: [Point2f; N_RHO_SAMPLES] = [
|
||||||
Point2f { 0: [0.855985, 0.570367]},
|
Point2f {
|
||||||
Point2f { 0: [0.381823, 0.851844]},
|
0: [0.855985, 0.570367],
|
||||||
Point2f { 0: [0.285328, 0.764262]},
|
},
|
||||||
Point2f { 0: [0.733380, 0.114073]},
|
Point2f {
|
||||||
Point2f { 0: [0.542663, 0.344465]},
|
0: [0.381823, 0.851844],
|
||||||
Point2f { 0: [0.127274, 0.414848]},
|
},
|
||||||
Point2f { 0: [0.964700, 0.947162]},
|
Point2f {
|
||||||
Point2f { 0: [0.594089, 0.643463]},
|
0: [0.285328, 0.764262],
|
||||||
Point2f { 0: [0.095109, 0.170369]},
|
},
|
||||||
Point2f { 0: [0.825444, 0.263359]},
|
Point2f {
|
||||||
Point2f { 0: [0.429467, 0.454469]},
|
0: [0.733380, 0.114073],
|
||||||
Point2f { 0: [0.244460, 0.816459]},
|
},
|
||||||
Point2f { 0: [0.756135, 0.731258]},
|
Point2f {
|
||||||
Point2f { 0: [0.516165, 0.152852]},
|
0: [0.542663, 0.344465],
|
||||||
Point2f { 0: [0.180888, 0.214174]},
|
},
|
||||||
Point2f { 0: [0.898579, 0.503897]},
|
Point2f {
|
||||||
|
0: [0.127274, 0.414848],
|
||||||
|
},
|
||||||
|
Point2f {
|
||||||
|
0: [0.964700, 0.947162],
|
||||||
|
},
|
||||||
|
Point2f {
|
||||||
|
0: [0.594089, 0.643463],
|
||||||
|
},
|
||||||
|
Point2f {
|
||||||
|
0: [0.095109, 0.170369],
|
||||||
|
},
|
||||||
|
Point2f {
|
||||||
|
0: [0.825444, 0.263359],
|
||||||
|
},
|
||||||
|
Point2f {
|
||||||
|
0: [0.429467, 0.454469],
|
||||||
|
},
|
||||||
|
Point2f {
|
||||||
|
0: [0.244460, 0.816459],
|
||||||
|
},
|
||||||
|
Point2f {
|
||||||
|
0: [0.756135, 0.731258],
|
||||||
|
},
|
||||||
|
Point2f {
|
||||||
|
0: [0.516165, 0.152852],
|
||||||
|
},
|
||||||
|
Point2f {
|
||||||
|
0: [0.180888, 0.214174],
|
||||||
|
},
|
||||||
|
Point2f {
|
||||||
|
0: [0.898579, 0.503897],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
|
use super::RayIntegratorTrait;
|
||||||
use super::base::IntegratorBase;
|
use super::base::IntegratorBase;
|
||||||
use super::constants::*;
|
use super::constants::*;
|
||||||
use super::state::PathState;
|
use super::state::PathState;
|
||||||
use super::RayIntegratorTrait;
|
|
||||||
use crate::core::interaction::InteractionGetter;
|
|
||||||
use crate::Arena;
|
use crate::Arena;
|
||||||
use shared::core::bsdf::{BSDFSample, BSDF};
|
use crate::core::interaction::InteractionGetter;
|
||||||
|
use shared::core::bsdf::{BSDF, BSDFSample};
|
||||||
use shared::core::bxdf::{BxDFFlags, FArgs, TransportMode};
|
use shared::core::bxdf::{BxDFFlags, FArgs, TransportMode};
|
||||||
use shared::core::camera::Camera;
|
use shared::core::camera::Camera;
|
||||||
use shared::core::film::VisibleSurface;
|
use shared::core::film::VisibleSurface;
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::core::image::{Image, ImageIO};
|
use crate::core::image::{Image, ImageIO};
|
||||||
use crate::core::light::{CreateLight, lookup_spectrum};
|
use crate::core::light::lookup_spectrum;
|
||||||
use crate::core::spectrum::spectrum_to_photometric;
|
use crate::core::spectrum::spectrum_to_photometric;
|
||||||
use crate::core::texture::FloatTexture;
|
use crate::core::texture::FloatTexture;
|
||||||
use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename};
|
use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename};
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use shared::core::geometry::Point2i;
|
use shared::core::geometry::Point2i;
|
||||||
use shared::core::light::{Light, LightBase, LightType};
|
use shared::core::light::{Light, LightBase, LightType};
|
||||||
use shared::core::medium::Medium;
|
use shared::core::medium::{Medium, MediumInterface};
|
||||||
use shared::core::shape::{Shape, ShapeTrait};
|
use shared::core::shape::{Shape, ShapeTrait};
|
||||||
use shared::core::spectrum::Spectrum;
|
use shared::core::spectrum::Spectrum;
|
||||||
use shared::core::texture::GPUFloatTexture;
|
use shared::core::texture::GPUFloatTexture;
|
||||||
|
|
@ -18,147 +18,155 @@ use shared::spectra::RGBColorSpace;
|
||||||
use shared::utils::{Ptr, Transform};
|
use shared::utils::{Ptr, Transform};
|
||||||
use shared::{Float, PI};
|
use shared::{Float, PI};
|
||||||
|
|
||||||
impl CreateLight for DiffuseAreaLight {
|
pub fn create(
|
||||||
fn create(
|
render_from_light: Transform,
|
||||||
render_from_light: Transform,
|
medium: Option<Medium>,
|
||||||
medium: Medium,
|
params: &ParameterDictionary,
|
||||||
params: &ParameterDictionary,
|
loc: &FileLoc,
|
||||||
loc: &FileLoc,
|
shape: &Shape,
|
||||||
shape: &Shape,
|
alpha: &FloatTexture,
|
||||||
alpha: &FloatTexture,
|
colorspace: Option<&RGBColorSpace>,
|
||||||
colorspace: Option<&RGBColorSpace>,
|
arena: &Arena,
|
||||||
arena: &Arena,
|
) -> Result<Light> {
|
||||||
) -> Result<Light> {
|
let mut l = params.get_one_spectrum("l", None, SpectrumType::Illuminant);
|
||||||
let mut l = params.get_one_spectrum("l", None, SpectrumType::Illuminant);
|
let illum_spec = Spectrum::Dense(colorspace.unwrap().illuminant);
|
||||||
let illum_spec = Spectrum::Dense(colorspace.unwrap().illuminant);
|
let mut scale = params.get_one_float("scale", 1.)?;
|
||||||
let mut scale = params.get_one_float("scale", 1.)?;
|
let two_sided = params.get_one_bool("twosided", false)?;
|
||||||
let two_sided = params.get_one_bool("twosided", false)?;
|
|
||||||
|
|
||||||
let filename = resolve_filename(¶ms.get_one_string("filename", "")?);
|
let filename = resolve_filename(¶ms.get_one_string("filename", "")?);
|
||||||
let (image, image_color_space) = if !filename.is_empty() {
|
let (image, image_color_space) = if !filename.is_empty() {
|
||||||
if l.is_some() {
|
if l.is_some() {
|
||||||
return Err(anyhow!("{}: both \"L\" and \"filename\" specified", loc));
|
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 im = Image::read(Path::new(&filename), None)?;
|
||||||
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 {
|
if im.image.has_any_infinite_pixels() {
|
||||||
(LightType::DeltaPosition, None)
|
return Err(anyhow!("{}: image has infinite pixel values", loc));
|
||||||
} else {
|
}
|
||||||
(LightType::Area, Some(alpha))
|
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;
|
||||||
|
|
||||||
let base = LightBase::new(light_type, render_from_light, medium.into());
|
|
||||||
if let Some(ref img) = image {
|
if let Some(ref img) = image {
|
||||||
let desc = img
|
// Get the appropriate luminance vector from the image colour space
|
||||||
.get_channel_desc(&["R", "G", "B"])
|
let lum_vec = image_color_space.unwrap().luminance_vector();
|
||||||
.expect("Image used for DiffuseAreaLight doesn't have R, G, B channels");
|
|
||||||
assert_eq!(3, desc.size());
|
let mut sum_k_e = 0.0;
|
||||||
assert!(desc.is_identity());
|
let res = img.resolution();
|
||||||
assert!(
|
|
||||||
image_color_space.is_some(),
|
for y in 0..res.y() {
|
||||||
"Image provided but ColorSpace is missing"
|
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 is_triangle_or_bilinear =
|
let side_factor = if two_sided { 2.0 } else { 1.0 };
|
||||||
matches!(*shape, Shape::Triangle(_) | Shape::BilinearPatch(_));
|
k_e *= side_factor * shape.area() * PI;
|
||||||
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);
|
// now multiply up scale to hit the target power
|
||||||
let image_ptr = image
|
scale *= phi_v / k_e;
|
||||||
.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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::core::light::{CreateLight, lookup_spectrum};
|
use crate::core::light::lookup_spectrum;
|
||||||
use crate::core::spectrum::spectrum_to_photometric;
|
use crate::core::spectrum::spectrum_to_photometric;
|
||||||
use crate::core::texture::FloatTexture;
|
use crate::core::texture::FloatTexture;
|
||||||
use crate::utils::{Arena, FileLoc, ParameterDictionary};
|
use crate::utils::{Arena, FileLoc, ParameterDictionary};
|
||||||
|
|
@ -36,59 +36,57 @@ impl CreateDistantLight for DistantLight {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateLight for DistantLight {
|
pub fn create(
|
||||||
fn create(
|
render_from_light: Transform,
|
||||||
render_from_light: Transform,
|
_medium: Option<Medium>,
|
||||||
_medium: Medium,
|
parameters: &ParameterDictionary,
|
||||||
parameters: &ParameterDictionary,
|
_loc: &FileLoc,
|
||||||
_loc: &FileLoc,
|
_shape: &Shape,
|
||||||
_shape: &Shape,
|
_alpha_text: &FloatTexture,
|
||||||
_alpha_text: &FloatTexture,
|
colorspace: Option<&RGBColorSpace>,
|
||||||
colorspace: Option<&RGBColorSpace>,
|
_arena: &Arena,
|
||||||
_arena: &Arena,
|
) -> Result<Light> {
|
||||||
) -> Result<Light> {
|
let l = parameters
|
||||||
let l = parameters
|
.get_one_spectrum(
|
||||||
.get_one_spectrum(
|
"L",
|
||||||
"L",
|
Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
|
||||||
Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
|
SpectrumType::Illuminant,
|
||||||
SpectrumType::Illuminant,
|
)
|
||||||
)
|
.unwrap();
|
||||||
.unwrap();
|
let mut scale = parameters.get_one_float("scale", 1.)?;
|
||||||
let mut scale = parameters.get_one_float("scale", 1.)?;
|
|
||||||
|
|
||||||
let from = parameters.get_one_point3f("from", Point3f::new(0., 0., 0.))?;
|
let from = parameters.get_one_point3f("from", Point3f::new(0., 0., 0.))?;
|
||||||
let to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.))?;
|
let to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.))?;
|
||||||
let w = (from - to).normalize();
|
let w = (from - to).normalize();
|
||||||
let (v1, v2) = w.coordinate_system();
|
let (v1, v2) = w.coordinate_system();
|
||||||
let m: [Float; 16] = [
|
let m: [Float; 16] = [
|
||||||
v1.x(),
|
v1.x(),
|
||||||
v2.x(),
|
v2.x(),
|
||||||
w.x(),
|
w.x(),
|
||||||
0.,
|
0.,
|
||||||
v1.y(),
|
v1.y(),
|
||||||
v2.y(),
|
v2.y(),
|
||||||
w.y(),
|
w.y(),
|
||||||
0.,
|
0.,
|
||||||
v1.z(),
|
v1.z(),
|
||||||
v2.z(),
|
v2.z(),
|
||||||
w.z(),
|
w.z(),
|
||||||
0.,
|
0.,
|
||||||
0.,
|
0.,
|
||||||
0.,
|
0.,
|
||||||
0.,
|
0.,
|
||||||
1.,
|
1.,
|
||||||
];
|
];
|
||||||
let t = Transform::from_flat(&m).expect("Could not create transform for DistantLight");
|
let t = Transform::from_flat(&m).expect("Could not create transform for DistantLight");
|
||||||
let final_render = render_from_light * t;
|
let final_render = render_from_light * t;
|
||||||
scale /= spectrum_to_photometric(l);
|
scale /= spectrum_to_photometric(l);
|
||||||
// Adjust scale to meet target illuminance value
|
// Adjust scale to meet target illuminance value
|
||||||
let e_v = parameters.get_one_float("illuminance", -1.)?;
|
let e_v = parameters.get_one_float("illuminance", -1.)?;
|
||||||
if e_v > 0. {
|
if e_v > 0. {
|
||||||
scale *= e_v;
|
scale *= e_v;
|
||||||
}
|
|
||||||
|
|
||||||
let specific = DistantLight::new(final_render, l, scale);
|
|
||||||
|
|
||||||
Ok(Light::Distant(specific))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let specific = DistantLight::new(final_render, l, scale);
|
||||||
|
|
||||||
|
Ok(Light::Distant(specific))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::core::image::{Image, ImageIO};
|
use crate::core::image::{Image, ImageIO};
|
||||||
use crate::core::light::{CreateLight, lookup_spectrum};
|
use crate::core::light::lookup_spectrum;
|
||||||
use crate::core::spectrum::spectrum_to_photometric;
|
use crate::core::spectrum::spectrum_to_photometric;
|
||||||
use crate::core::texture::FloatTexture;
|
use crate::core::texture::FloatTexture;
|
||||||
use crate::utils::sampling::PiecewiseConstant2D;
|
use crate::utils::sampling::PiecewiseConstant2D;
|
||||||
|
|
@ -9,7 +9,7 @@ use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use shared::core::geometry::Point2i;
|
use shared::core::geometry::Point2i;
|
||||||
use shared::core::light::{Light, LightBase, LightType};
|
use shared::core::light::{Light, LightBase, LightType};
|
||||||
use shared::core::medium::Medium;
|
use shared::core::medium::{Medium, MediumInterface};
|
||||||
use shared::core::shape::Shape;
|
use shared::core::shape::Shape;
|
||||||
use shared::core::spectrum::Spectrum;
|
use shared::core::spectrum::Spectrum;
|
||||||
use shared::core::texture::SpectrumType;
|
use shared::core::texture::SpectrumType;
|
||||||
|
|
@ -18,95 +18,99 @@ use shared::spectra::RGBColorSpace;
|
||||||
use shared::utils::{Ptr, Transform};
|
use shared::utils::{Ptr, Transform};
|
||||||
use shared::{Float, PI};
|
use shared::{Float, PI};
|
||||||
|
|
||||||
impl CreateLight for GoniometricLight {
|
pub fn create(
|
||||||
fn create(
|
render_from_light: Transform,
|
||||||
render_from_light: Transform,
|
medium: Option<Medium>,
|
||||||
medium: Medium,
|
params: &ParameterDictionary,
|
||||||
params: &ParameterDictionary,
|
loc: &FileLoc,
|
||||||
loc: &FileLoc,
|
_shape: &Shape,
|
||||||
_shape: &Shape,
|
_alpha_text: &FloatTexture,
|
||||||
_alpha_text: &FloatTexture,
|
colorspace: Option<&RGBColorSpace>,
|
||||||
colorspace: Option<&RGBColorSpace>,
|
arena: &Arena,
|
||||||
arena: &Arena,
|
) -> Result<Light> {
|
||||||
) -> Result<Light> {
|
let i = params
|
||||||
let i = params
|
.get_one_spectrum(
|
||||||
.get_one_spectrum(
|
"I",
|
||||||
"I",
|
Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
|
||||||
Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
|
SpectrumType::Illuminant,
|
||||||
SpectrumType::Illuminant,
|
)
|
||||||
)
|
.expect("Could not retrieve spectrum");
|
||||||
.expect("Could not retrieve spectrum");
|
let mut scale = params.get_one_float("scale", 1.)?;
|
||||||
let mut scale = params.get_one_float("scale", 1.)?;
|
let filename = resolve_filename(¶ms.get_one_string("filename", "")?);
|
||||||
let filename = resolve_filename(¶ms.get_one_string("filename", "")?);
|
let image: Ptr<Image> = if filename.is_empty() {
|
||||||
let image: Ptr<Image> = if filename.is_empty() {
|
Ptr::null()
|
||||||
Ptr::null()
|
} else {
|
||||||
} else {
|
let im = Image::read(Path::new(&filename), None)
|
||||||
let im = Image::read(Path::new(&filename), None)
|
.map_err(|e| anyhow!("could not load image '{}': {}", filename, e))?;
|
||||||
.map_err(|e| anyhow!("could not load image '{}': {}", filename, e))?;
|
|
||||||
|
|
||||||
let loaded = im.image;
|
let loaded = im.image;
|
||||||
let res = loaded.resolution();
|
let res = loaded.resolution();
|
||||||
|
|
||||||
if loaded.has_any_infinite_pixels() {
|
if loaded.has_any_infinite_pixels() {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
"image '{}' has infinite pixels, not suitable for light",
|
"image '{}' has infinite pixels, not suitable for light",
|
||||||
filename
|
filename
|
||||||
));
|
));
|
||||||
}
|
|
||||||
|
|
||||||
if res.x() != res.y() {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"image resolution ({}, {}) is non-square; unlikely to be an equal-area map",
|
|
||||||
res.x(),
|
|
||||||
res.y()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr::from(&convert_to_luminance_image(&loaded, &filename, loc)?)
|
|
||||||
};
|
|
||||||
|
|
||||||
scale /= spectrum_to_photometric(i);
|
|
||||||
let phi_v = params.get_one_float("power", -1.0)?;
|
|
||||||
|
|
||||||
if phi_v > 0.0 {
|
|
||||||
let k_e = compute_emissive_power(&image);
|
|
||||||
scale *= phi_v / k_e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let swap_yz: [Float; 16] = [
|
if res.x() != res.y() {
|
||||||
1., 0., 0., 0., 0., 0., 1., 0., 0., 1., 0., 0., 0., 0., 0., 1.,
|
return Err(anyhow!(
|
||||||
];
|
"image resolution ({}, {}) is non-square; unlikely to be an equal-area map",
|
||||||
let t = Transform::from_flat(&swap_yz)
|
res.x(),
|
||||||
.expect("Could not create transform for GoniometricLight");
|
res.y()
|
||||||
let final_render_from_light = render_from_light * t;
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let base = LightBase::new(
|
Ptr::from(&convert_to_luminance_image(&loaded, &filename, loc)?)
|
||||||
LightType::DeltaPosition,
|
};
|
||||||
final_render_from_light,
|
|
||||||
medium.into(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let iemit = lookup_spectrum(&i);
|
scale /= spectrum_to_photometric(i);
|
||||||
|
let phi_v = params.get_one_float("power", -1.0)?;
|
||||||
|
|
||||||
let image_ptr = if !image.is_null() {
|
if phi_v > 0.0 {
|
||||||
let distrib = PiecewiseConstant2D::from_image(&image);
|
let k_e = compute_emissive_power(&image);
|
||||||
let distrib_ptr = distrib.upload(arena);
|
scale *= phi_v / k_e;
|
||||||
let img_ptr = image.upload(arena);
|
|
||||||
(img_ptr, distrib_ptr)
|
|
||||||
} else {
|
|
||||||
(Ptr::null(), Ptr::null())
|
|
||||||
};
|
|
||||||
|
|
||||||
let specific = GoniometricLight {
|
|
||||||
base,
|
|
||||||
iemit: arena.alloc(iemit.device()),
|
|
||||||
scale,
|
|
||||||
image: image_ptr.0,
|
|
||||||
distrib: image_ptr.1,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Light::Goniometric(specific))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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).expect("Could not create transform for GoniometricLight");
|
||||||
|
let final_render_from_light = render_from_light * t;
|
||||||
|
|
||||||
|
let mi = match medium {
|
||||||
|
Some(m) => {
|
||||||
|
let ptr = arena.alloc(m);
|
||||||
|
MediumInterface {
|
||||||
|
inside: ptr,
|
||||||
|
outside: ptr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => MediumInterface::default(),
|
||||||
|
};
|
||||||
|
let base = LightBase::new(LightType::DeltaPosition, render_from_light, mi);
|
||||||
|
|
||||||
|
let iemit = lookup_spectrum(&i);
|
||||||
|
|
||||||
|
let image_ptr = if !image.is_null() {
|
||||||
|
let distrib = PiecewiseConstant2D::from_image(&image);
|
||||||
|
let distrib_ptr = distrib.upload(arena);
|
||||||
|
let img_ptr = image.upload(arena);
|
||||||
|
(img_ptr, distrib_ptr)
|
||||||
|
} else {
|
||||||
|
(Ptr::null(), Ptr::null())
|
||||||
|
};
|
||||||
|
|
||||||
|
let specific = GoniometricLight {
|
||||||
|
base,
|
||||||
|
iemit: arena.alloc(iemit.device()),
|
||||||
|
scale,
|
||||||
|
image: image_ptr.0,
|
||||||
|
distrib: image_ptr.1,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Light::Goniometric(specific))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_to_luminance_image(image: &Image, filename: &str, loc: &FileLoc) -> Result<Image> {
|
fn convert_to_luminance_image(image: &Image, filename: &str, loc: &FileLoc) -> Result<Image> {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use shared::core::camera::CameraTransform;
|
||||||
use shared::core::geometry::{Bounds2f, Frame, Point2f, Point2i, Point3f, VectorLike, cos_theta};
|
use shared::core::geometry::{Bounds2f, Frame, Point2f, Point2i, Point3f, VectorLike, cos_theta};
|
||||||
use shared::core::image::{DeviceImage, WrapMode};
|
use shared::core::image::{DeviceImage, WrapMode};
|
||||||
use shared::core::light::{Light, LightBase, LightType};
|
use shared::core::light::{Light, LightBase, LightType};
|
||||||
use shared::core::medium::MediumInterface;
|
use shared::core::medium::{Medium, MediumInterface};
|
||||||
use shared::core::spectrum::Spectrum;
|
use shared::core::spectrum::Spectrum;
|
||||||
use shared::core::texture::SpectrumType;
|
use shared::core::texture::SpectrumType;
|
||||||
use shared::lights::{ImageInfiniteLight, PortalInfiniteLight, UniformInfiniteLight};
|
use shared::lights::{ImageInfiniteLight, PortalInfiniteLight, UniformInfiniteLight};
|
||||||
|
|
@ -124,7 +124,7 @@ impl CreateUniformInfiniteLight for UniformInfiniteLight {
|
||||||
|
|
||||||
pub fn create(
|
pub fn create(
|
||||||
render_from_light: Transform,
|
render_from_light: Transform,
|
||||||
_medium: MediumInterface,
|
_medium: Option<Medium>,
|
||||||
camera_transform: CameraTransform,
|
camera_transform: CameraTransform,
|
||||||
parameters: &ParameterDictionary,
|
parameters: &ParameterDictionary,
|
||||||
colorspace: Option<&RGBColorSpace>,
|
colorspace: Option<&RGBColorSpace>,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::core::light::{CreateLight, lookup_spectrum};
|
use crate::core::light::lookup_spectrum;
|
||||||
use crate::core::spectrum::spectrum_to_photometric;
|
use crate::core::spectrum::spectrum_to_photometric;
|
||||||
use crate::core::texture::FloatTexture;
|
use crate::core::texture::FloatTexture;
|
||||||
use crate::utils::{Arena, FileLoc, ParameterDictionary};
|
use crate::utils::{Arena, FileLoc, ParameterDictionary};
|
||||||
|
|
@ -42,36 +42,46 @@ impl CreatePointLight for PointLight {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateLight for PointLight {
|
pub fn create(
|
||||||
fn create(
|
render_from_light: Transform,
|
||||||
render_from_light: Transform,
|
medium: Option<Medium>,
|
||||||
medium: Medium,
|
parameters: &ParameterDictionary,
|
||||||
parameters: &ParameterDictionary,
|
_loc: &FileLoc,
|
||||||
_loc: &FileLoc,
|
_shape: &Shape,
|
||||||
_shape: &Shape,
|
_alpha: &FloatTexture,
|
||||||
_alpha: &FloatTexture,
|
colorspace: Option<&RGBColorSpace>,
|
||||||
colorspace: Option<&RGBColorSpace>,
|
arena: &Arena,
|
||||||
_arena: &Arena,
|
) -> Result<Light> {
|
||||||
) -> Result<Light> {
|
let l = parameters
|
||||||
let l = parameters
|
.get_one_spectrum(
|
||||||
.get_one_spectrum(
|
"L",
|
||||||
"L",
|
Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
|
||||||
Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
|
SpectrumType::Illuminant,
|
||||||
SpectrumType::Illuminant,
|
)
|
||||||
)
|
.unwrap();
|
||||||
.unwrap();
|
let mut scale = parameters.get_one_float("scale", 1.)?;
|
||||||
let mut scale = parameters.get_one_float("scale", 1.)?;
|
scale /= spectrum_to_photometric(l);
|
||||||
scale /= spectrum_to_photometric(l);
|
let phi_v = parameters.get_one_float("power", 1.)?;
|
||||||
let phi_v = parameters.get_one_float("power", 1.)?;
|
if phi_v > 0. {
|
||||||
if phi_v > 0. {
|
let k_e = 4. * PI;
|
||||||
let k_e = 4. * PI;
|
scale *= phi_v / k_e;
|
||||||
scale *= 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 specific = PointLight::new(final_render, medium.into(), l, scale);
|
|
||||||
Ok(Light::Point(specific))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let from = parameters.get_one_point3f("from", Point3f::zero())?;
|
||||||
|
let tf = Transform::translate(from.into());
|
||||||
|
let final_render = render_from_light * tf;
|
||||||
|
|
||||||
|
let mi = match medium {
|
||||||
|
Some(m) => {
|
||||||
|
let ptr = arena.alloc(m);
|
||||||
|
MediumInterface {
|
||||||
|
inside: ptr,
|
||||||
|
outside: ptr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => MediumInterface::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let specific = PointLight::new(final_render, mi, l, scale);
|
||||||
|
Ok(Light::Point(specific))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::core::image::{Image, ImageIO};
|
use crate::core::image::{Image, ImageIO};
|
||||||
use crate::core::light::CreateLight;
|
|
||||||
use crate::core::spectrum::spectrum_to_photometric;
|
use crate::core::spectrum::spectrum_to_photometric;
|
||||||
use crate::core::texture::FloatTexture;
|
use crate::core::texture::FloatTexture;
|
||||||
use crate::utils::sampling::PiecewiseConstant2D;
|
use crate::utils::sampling::PiecewiseConstant2D;
|
||||||
|
|
@ -10,7 +9,7 @@ use shared::core::geometry::{
|
||||||
Bounds2f, Point2f, Point2i, Point3f, Vector3f, VectorLike, cos_theta,
|
Bounds2f, Point2f, Point2i, Point3f, Vector3f, VectorLike, cos_theta,
|
||||||
};
|
};
|
||||||
use shared::core::light::{Light, LightBase, LightType};
|
use shared::core::light::{Light, LightBase, LightType};
|
||||||
use shared::core::medium::Medium;
|
use shared::core::medium::{Medium, MediumInterface};
|
||||||
use shared::core::shape::Shape;
|
use shared::core::shape::Shape;
|
||||||
use shared::core::spectrum::Spectrum;
|
use shared::core::spectrum::Spectrum;
|
||||||
use shared::lights::ProjectionLight;
|
use shared::lights::ProjectionLight;
|
||||||
|
|
@ -19,117 +18,124 @@ use shared::utils::Transform;
|
||||||
use shared::utils::math::{radians, square};
|
use shared::utils::math::{radians, square};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
impl CreateLight for ProjectionLight {
|
pub fn create(
|
||||||
fn create(
|
render_from_light: Transform,
|
||||||
render_from_light: Transform,
|
medium: Option<Medium>,
|
||||||
medium: Medium,
|
parameters: &ParameterDictionary,
|
||||||
parameters: &ParameterDictionary,
|
loc: &FileLoc,
|
||||||
loc: &FileLoc,
|
_shape: &Shape,
|
||||||
_shape: &Shape,
|
_alpha_text: &FloatTexture,
|
||||||
_alpha_text: &FloatTexture,
|
_colorspace: Option<&RGBColorSpace>,
|
||||||
_colorspace: Option<&RGBColorSpace>,
|
arena: &Arena,
|
||||||
arena: &Arena,
|
) -> Result<Light> {
|
||||||
) -> Result<Light> {
|
let mut scale = parameters.get_one_float("scale", 1.)?;
|
||||||
let mut scale = parameters.get_one_float("scale", 1.)?;
|
let power = parameters.get_one_float("power", -1.)?;
|
||||||
let power = parameters.get_one_float("power", -1.)?;
|
let fov = parameters.get_one_float("fov", 90.)?;
|
||||||
let fov = parameters.get_one_float("fov", 90.)?;
|
|
||||||
|
|
||||||
let filename = resolve_filename(¶meters.get_one_string("filename", "")?);
|
let filename = resolve_filename(¶meters.get_one_string("filename", "")?);
|
||||||
if filename.is_empty() {
|
if filename.is_empty() {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
"{}: must provide filename for projection light",
|
"{}: must provide filename for projection light",
|
||||||
loc
|
loc
|
||||||
));
|
));
|
||||||
}
|
|
||||||
|
|
||||||
let im = Image::read(Path::new(&filename), None)
|
|
||||||
.map_err(|e| anyhow!("{}: could not load image '{}': {}", loc, filename, e))?;
|
|
||||||
|
|
||||||
if im.image.has_any_infinite_pixels() {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"{}: image '{}' has infinite pixels, not suitable for light",
|
|
||||||
loc,
|
|
||||||
filename
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if im.image.has_any_nan_pixels() {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"{}: image '{}' has NaN pixels, not suitable for light",
|
|
||||||
loc,
|
|
||||||
filename
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let channel_desc = im
|
|
||||||
.image
|
|
||||||
.get_channel_desc(&["R", "G", "B"])
|
|
||||||
.map_err(|_| anyhow!("{}: image '{}' must have R, G, B channels", loc, filename))?;
|
|
||||||
|
|
||||||
let image = im.image.select_channels(&channel_desc);
|
|
||||||
let colorspace = im
|
|
||||||
.metadata
|
|
||||||
.colorspace
|
|
||||||
.ok_or_else(|| anyhow!("{}: image '{}' missing colorspace metadata", loc, filename))?;
|
|
||||||
|
|
||||||
scale /= spectrum_to_photometric(Spectrum::Dense(colorspace.illuminant));
|
|
||||||
if power > 0. {
|
|
||||||
let k_e = compute_emissive_power(&image, &colorspace, fov);
|
|
||||||
scale /= k_e;
|
|
||||||
}
|
|
||||||
|
|
||||||
let flip = Transform::scale(1., -1., 1.);
|
|
||||||
let render_from_light_flip = render_from_light * flip;
|
|
||||||
|
|
||||||
let base = LightBase::new(LightType::DeltaPosition, render_from_light, medium.into());
|
|
||||||
|
|
||||||
let opposite = (radians(fov) / 2.0).tan();
|
|
||||||
let res = image.resolution();
|
|
||||||
let aspect = res.x() as Float / res.y() as Float;
|
|
||||||
let aspect_ratio = if aspect > 1.0 { aspect } else { 1.0 / aspect };
|
|
||||||
let a = 4.0 * square(opposite) * aspect_ratio;
|
|
||||||
let screen_bounds = 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),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let hither = 1e-3;
|
|
||||||
let screen_from_light = Transform::perspective(fov, hither, 1e30).unwrap();
|
|
||||||
let light_from_screen = screen_from_light.inverse();
|
|
||||||
|
|
||||||
let dwda = |p: Point2f| {
|
|
||||||
let w =
|
|
||||||
Vector3f::from(light_from_screen.apply_to_point(Point3f::new(p.x(), p.y(), 0.0)));
|
|
||||||
cos_theta(w.normalize()).powi(3)
|
|
||||||
};
|
|
||||||
|
|
||||||
let d = image.get_sampling_distribution(dwda, screen_bounds);
|
|
||||||
let distrib = PiecewiseConstant2D::from_slice(
|
|
||||||
d.as_slice(),
|
|
||||||
d.x_size() as usize,
|
|
||||||
d.y_size() as usize,
|
|
||||||
screen_bounds,
|
|
||||||
);
|
|
||||||
|
|
||||||
let specific = ProjectionLight {
|
|
||||||
base,
|
|
||||||
image: image.upload(arena),
|
|
||||||
image_color_space: colorspace.upload(arena),
|
|
||||||
distrib: distrib.upload(arena),
|
|
||||||
screen_bounds,
|
|
||||||
screen_from_light,
|
|
||||||
light_from_screen,
|
|
||||||
scale,
|
|
||||||
hither,
|
|
||||||
a,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Light::Projection(specific))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let im = Image::read(Path::new(&filename), None)
|
||||||
|
.map_err(|e| anyhow!("{}: could not load image '{}': {}", loc, filename, e))?;
|
||||||
|
|
||||||
|
if im.image.has_any_infinite_pixels() {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"{}: image '{}' has infinite pixels, not suitable for light",
|
||||||
|
loc,
|
||||||
|
filename
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if im.image.has_any_nan_pixels() {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"{}: image '{}' has NaN pixels, not suitable for light",
|
||||||
|
loc,
|
||||||
|
filename
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let channel_desc = im
|
||||||
|
.image
|
||||||
|
.get_channel_desc(&["R", "G", "B"])
|
||||||
|
.map_err(|_| anyhow!("{}: image '{}' must have R, G, B channels", loc, filename))?;
|
||||||
|
|
||||||
|
let image = im.image.select_channels(&channel_desc);
|
||||||
|
let colorspace = im
|
||||||
|
.metadata
|
||||||
|
.colorspace
|
||||||
|
.ok_or_else(|| anyhow!("{}: image '{}' missing colorspace metadata", loc, filename))?;
|
||||||
|
|
||||||
|
scale /= spectrum_to_photometric(Spectrum::Dense(colorspace.illuminant));
|
||||||
|
if power > 0. {
|
||||||
|
let k_e = compute_emissive_power(&image, &colorspace, fov);
|
||||||
|
scale /= k_e;
|
||||||
|
}
|
||||||
|
|
||||||
|
let flip = Transform::scale(1., -1., 1.);
|
||||||
|
let render_from_light_flip = render_from_light * flip;
|
||||||
|
|
||||||
|
let mi = match medium {
|
||||||
|
Some(m) => {
|
||||||
|
let ptr = arena.alloc(m);
|
||||||
|
MediumInterface {
|
||||||
|
inside: ptr,
|
||||||
|
outside: ptr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => MediumInterface::default(),
|
||||||
|
};
|
||||||
|
let base = LightBase::new(LightType::DeltaPosition, render_from_light, mi);
|
||||||
|
|
||||||
|
let opposite = (radians(fov) / 2.0).tan();
|
||||||
|
let res = image.resolution();
|
||||||
|
let aspect = res.x() as Float / res.y() as Float;
|
||||||
|
let aspect_ratio = if aspect > 1.0 { aspect } else { 1.0 / aspect };
|
||||||
|
let a = 4.0 * square(opposite) * aspect_ratio;
|
||||||
|
let screen_bounds = 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),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let hither = 1e-3;
|
||||||
|
let screen_from_light = Transform::perspective(fov, hither, 1e30).unwrap();
|
||||||
|
let light_from_screen = screen_from_light.inverse();
|
||||||
|
|
||||||
|
let dwda = |p: Point2f| {
|
||||||
|
let w = Vector3f::from(light_from_screen.apply_to_point(Point3f::new(p.x(), p.y(), 0.0)));
|
||||||
|
cos_theta(w.normalize()).powi(3)
|
||||||
|
};
|
||||||
|
|
||||||
|
let d = image.get_sampling_distribution(dwda, screen_bounds);
|
||||||
|
let distrib = PiecewiseConstant2D::from_slice(
|
||||||
|
d.as_slice(),
|
||||||
|
d.x_size() as usize,
|
||||||
|
d.y_size() as usize,
|
||||||
|
screen_bounds,
|
||||||
|
);
|
||||||
|
|
||||||
|
let specific = ProjectionLight {
|
||||||
|
base,
|
||||||
|
image: image.upload(arena),
|
||||||
|
image_color_space: colorspace.upload(arena),
|
||||||
|
distrib: distrib.upload(arena),
|
||||||
|
screen_bounds,
|
||||||
|
screen_from_light,
|
||||||
|
light_from_screen,
|
||||||
|
scale,
|
||||||
|
hither,
|
||||||
|
a,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Light::Projection(specific))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_screen_bounds(aspect: Float) -> Bounds2f {
|
fn compute_screen_bounds(aspect: Float) -> Bounds2f {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::core::light::{CreateLight, lookup_spectrum};
|
use crate::core::light::lookup_spectrum;
|
||||||
use crate::core::spectrum::spectrum_to_photometric;
|
use crate::core::spectrum::spectrum_to_photometric;
|
||||||
use crate::core::texture::FloatTexture;
|
use crate::core::texture::FloatTexture;
|
||||||
use crate::utils::{Arena, FileLoc, ParameterDictionary};
|
use crate::utils::{Arena, FileLoc, ParameterDictionary};
|
||||||
|
|
@ -53,52 +53,53 @@ impl CreateSpotLight for SpotLight {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateLight for SpotLight {
|
pub fn create(
|
||||||
fn create(
|
render_from_light: Transform,
|
||||||
render_from_light: Transform,
|
medium: Option<Medium>,
|
||||||
medium: Medium,
|
parameters: &ParameterDictionary,
|
||||||
parameters: &ParameterDictionary,
|
_loc: &FileLoc,
|
||||||
_loc: &FileLoc,
|
_shape: &Shape,
|
||||||
_shape: &Shape,
|
_alpha_tex: &FloatTexture,
|
||||||
_alpha_tex: &FloatTexture,
|
colorspace: Option<&RGBColorSpace>,
|
||||||
colorspace: Option<&RGBColorSpace>,
|
arena: &Arena,
|
||||||
arena: &Arena,
|
) -> Result<Light> {
|
||||||
) -> Result<Light> {
|
let i = parameters
|
||||||
let i = parameters
|
.get_one_spectrum(
|
||||||
.get_one_spectrum(
|
"I",
|
||||||
"I",
|
Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
|
||||||
Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
|
SpectrumType::Illuminant,
|
||||||
SpectrumType::Illuminant,
|
)
|
||||||
)
|
.expect("No spectrum");
|
||||||
.expect("No spectrum");
|
let mut scale = parameters.get_one_float("scale", 1.)?;
|
||||||
let mut scale = parameters.get_one_float("scale", 1.)?;
|
let coneangle = parameters.get_one_float("coneangle", 30.)?;
|
||||||
let coneangle = parameters.get_one_float("coneangle", 30.)?;
|
let conedelta = parameters.get_one_float("conedelta", 5.)?;
|
||||||
let conedelta = parameters.get_one_float("conedelta", 5.)?;
|
let from = parameters.get_one_point3f("from", Point3f::zero())?;
|
||||||
let from = parameters.get_one_point3f("from", Point3f::zero())?;
|
let to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.))?;
|
||||||
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 dir_to_z = Transform::from(Frame::from_z((to - from).normalize()));
|
let t = Transform::translate(from.into()) * dir_to_z.inverse();
|
||||||
let t = Transform::translate(from.into()) * dir_to_z.inverse();
|
let final_render = render_from_light * t;
|
||||||
let final_render = render_from_light * t;
|
scale /= spectrum_to_photometric(i);
|
||||||
scale /= spectrum_to_photometric(i);
|
|
||||||
|
|
||||||
let phi_v = parameters.get_one_float("power", -1.)?;
|
let phi_v = parameters.get_one_float("power", -1.)?;
|
||||||
if phi_v > 0. {
|
if phi_v > 0. {
|
||||||
let cos_falloff_end = radians(coneangle).cos();
|
let cos_falloff_end = radians(coneangle).cos();
|
||||||
let cos_falloff_start = radians(coneangle - conedelta).cos();
|
let cos_falloff_start = radians(coneangle - conedelta).cos();
|
||||||
let k_e =
|
let k_e = 2. * PI * ((1. - cos_falloff_start) + (cos_falloff_start - cos_falloff_end) / 2.);
|
||||||
2. * PI * ((1. - cos_falloff_start) + (cos_falloff_start - cos_falloff_end) / 2.);
|
scale *= phi_v / k_e;
|
||||||
scale *= phi_v / k_e;
|
|
||||||
}
|
|
||||||
|
|
||||||
let specific = SpotLight::new(
|
|
||||||
final_render,
|
|
||||||
medium.into(),
|
|
||||||
i,
|
|
||||||
scale,
|
|
||||||
coneangle,
|
|
||||||
coneangle - conedelta,
|
|
||||||
);
|
|
||||||
arena.alloc(specific);
|
|
||||||
Ok(Light::Spot(specific))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mi = match medium {
|
||||||
|
Some(m) => {
|
||||||
|
let ptr = arena.alloc(m);
|
||||||
|
MediumInterface {
|
||||||
|
inside: ptr,
|
||||||
|
outside: ptr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => MediumInterface::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let specific = SpotLight::new(final_render, mi, i, scale, coneangle, coneangle - conedelta);
|
||||||
|
arena.alloc(specific);
|
||||||
|
Ok(Light::Spot(specific))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -45,11 +45,7 @@ impl CreateSpectrumTexture for SpectrumBilerpTexture {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpectrumTextureTrait for SpectrumBilerpTexture {
|
impl SpectrumTextureTrait for SpectrumBilerpTexture {
|
||||||
fn evaluate(
|
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||||
&self,
|
|
||||||
_ctx: &TextureEvalContext,
|
|
||||||
_lambda: &SampledWavelengths,
|
|
||||||
) -> SampledSpectrum {
|
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -3,8 +3,7 @@ use crate::core::texture::{FloatTexture, SpectrumTexture};
|
||||||
use crate::spectra::data::get_named_spectrum;
|
use crate::spectra::data::get_named_spectrum;
|
||||||
use crate::spectra::piecewise::PiecewiseLinearSpectrumBuffer;
|
use crate::spectra::piecewise::PiecewiseLinearSpectrumBuffer;
|
||||||
use crate::utils::FileLoc;
|
use crate::utils::FileLoc;
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{bail, Result};
|
||||||
use shared::Float;
|
|
||||||
use shared::core::color::RGB;
|
use shared::core::color::RGB;
|
||||||
use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector2f, Vector3f};
|
use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector2f, Vector3f};
|
||||||
use shared::core::spectrum::Spectrum;
|
use shared::core::spectrum::Spectrum;
|
||||||
|
|
@ -13,11 +12,12 @@ use shared::spectra::{
|
||||||
PiecewiseLinearSpectrum, RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum,
|
PiecewiseLinearSpectrum, RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum,
|
||||||
RGBUnboundedSpectrum,
|
RGBUnboundedSpectrum,
|
||||||
};
|
};
|
||||||
|
use shared::Float;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
Arc,
|
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn error_exit(loc: Option<&FileLoc>, message: &str) -> String {
|
pub fn error_exit(loc: Option<&FileLoc>, message: &str) -> String {
|
||||||
|
|
@ -350,30 +350,31 @@ impl ParameterDictionary {
|
||||||
where
|
where
|
||||||
T: PBRTParameter,
|
T: PBRTParameter,
|
||||||
{
|
{
|
||||||
let param = self.params[0].clone();
|
for param in &self.params {
|
||||||
if param.name == name && param.type_name == T::TYPE_NAME {
|
if param.name == name && param.type_name == T::TYPE_NAME {
|
||||||
let values = T::get_values(¶m);
|
let values = T::get_values(param);
|
||||||
|
|
||||||
if values.is_empty() {
|
if values.is_empty() {
|
||||||
bail!(
|
bail!(
|
||||||
"{}: No values provided for parameter \"{}\".",
|
"{}: No values provided for parameter \"{}\".",
|
||||||
¶m.loc,
|
¶m.loc,
|
||||||
name
|
name
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if values.len() != T::N_PER_ITEM {
|
||||||
|
bail!(
|
||||||
|
"{}: Expected {} values for parameter \"{}\". Found {}.",
|
||||||
|
¶m.loc,
|
||||||
|
T::N_PER_ITEM,
|
||||||
|
name,
|
||||||
|
values.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
param.looked_up.store(true, Ordering::Relaxed);
|
||||||
|
return Ok(T::convert(values));
|
||||||
}
|
}
|
||||||
|
|
||||||
if values.len() != T::N_PER_ITEM {
|
|
||||||
bail!(
|
|
||||||
"{}: Expected {} values for parameter \"{}\". Found {}.",
|
|
||||||
¶m.loc,
|
|
||||||
T::N_PER_ITEM,
|
|
||||||
name,
|
|
||||||
values.len()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
param.looked_up.store(true, Ordering::Relaxed);
|
|
||||||
return Ok(T::convert(values));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(default_val)
|
Ok(default_val)
|
||||||
|
|
|
||||||
|
|
@ -385,6 +385,7 @@ impl Tokenizer {
|
||||||
if first_char == '"' {
|
if first_char == '"' {
|
||||||
self.advance();
|
self.advance();
|
||||||
let mut val = String::new();
|
let mut val = String::new();
|
||||||
|
val.push('"');
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let ch = self.advance().ok_or(ParserError::UnexpectedEof)?;
|
let ch = self.advance().ok_or(ParserError::UnexpectedEof)?;
|
||||||
|
|
@ -406,6 +407,8 @@ impl Tokenizer {
|
||||||
val.push(ch);
|
val.push(ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val.push('"');
|
||||||
|
|
||||||
return Ok(Some(Token {
|
return Ok(Some(Token {
|
||||||
text: val,
|
text: val,
|
||||||
loc: start_loc,
|
loc: start_loc,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue