Changing behaviour of Ray

This commit is contained in:
Wito Wiala 2026-05-21 02:15:08 +01:00
parent 82255e5046
commit 1ea327cb6c
11 changed files with 223 additions and 201 deletions

View file

@ -98,7 +98,7 @@ impl CameraTrait for PerspectiveCamera {
Point3f::new(0., 0., 0.),
p_vector.normalize(),
Some(self.sample_time(sample.time)),
&*self.base().medium,
self.base().medium,
);
// Modify ray for depth of field
if self.lens_radius > 0. {

View file

@ -4,7 +4,7 @@ use crate::core::shape::ShapeIntersection;
use crate::{Float, Ptr, GVec, gvec};
#[repr(C)]
#[derive(Debug, Clone, Copy)]
#[derive(Default, Debug, Clone, Copy)]
pub struct LinearBVHNode {
pub bounds: Bounds3f,
pub primitives_offset: usize,

View file

@ -798,7 +798,11 @@ impl DigitPermutation {
inv_base_m *= inv_base;
}
let mut permutations = gvec_with_capacity(n_digits as usize * base as usize);
let mut permutations = {
let mut v = gvec_with_capacity(n_digits as usize * base as usize);
v.resize(n_digits as usize * base as usize, 0u16);
v
};
for digit_index in 0..n_digits {
let hash_input = [base as u64, digit_index as u64, seed];
@ -829,7 +833,11 @@ impl DigitPermutation {
pub fn compute_radical_inverse_permutations(seed: u64) -> GVec<DigitPermutation> {
let mut result = gvec();
result.extend(PRIMES.iter().map(|&base| DigitPermutation::new(base as i32, seed)));
result.extend(
PRIMES
.iter()
.map(|&base| DigitPermutation::new(base as i32, seed)),
);
result
}

View file

@ -6,10 +6,9 @@ use shared::core::camera::CameraTransform;
use shared::core::light::Light;
use shared::core::medium::Medium;
use shared::core::shape::Shape;
use shared::spectra::DenselySampledSpectrum;
use shared::core::spectrum::Spectrum;
use shared::spectra::RGBColorSpace;
use shared::{Ptr, Transform};
use shared::spectra::{DenselySampledSpectrum, RGBColorSpace};
use shared::Transform;
use std::sync::Arc;
pub fn lookup_spectrum(s: &Spectrum) -> Arc<DenselySampledSpectrum> {
@ -18,9 +17,6 @@ pub fn lookup_spectrum(s: &Spectrum) -> Arc<DenselySampledSpectrum> {
cache.lookup(dense_spectrum).into()
}
// Placeholders for non-area lights that never inspect these arguments.
// TODO: refactor each light to only take what it actually needs,
// then delete this bullshit
fn dummy_shape() -> Shape {
Shape::default()
}
@ -29,6 +25,8 @@ fn dummy_alpha() -> FloatTexture {
FloatTexture::default()
}
/// Create a non-area light. Returns a Light value — the caller decides
/// whether to wrap it in Arc (host ownership) or arena.alloc (GPU).
pub fn create_light(
name: &str,
render_from_light: Transform,
@ -36,79 +34,47 @@ pub fn create_light(
parameters: &ParameterDictionary,
loc: &FileLoc,
camera_transform: CameraTransform,
arena: &mut Arena,
arena: &Arena,
) -> Result<Light> {
let shape = dummy_shape();
let alpha = dummy_alpha();
match name {
"point" => crate::lights::point::create(
render_from_light,
medium,
parameters,
loc,
&shape,
&alpha,
None,
arena,
render_from_light, medium, parameters, loc,
&shape, &alpha, None, arena,
),
"spot" => crate::lights::spot::create(
render_from_light,
medium,
parameters,
loc,
&shape,
&alpha,
None,
arena,
render_from_light, medium, parameters, loc,
&shape, &alpha, None, arena,
),
"distant" => crate::lights::distant::create(
render_from_light,
medium,
parameters,
loc,
&shape,
&alpha,
None,
arena,
render_from_light, medium, parameters, loc,
&shape, &alpha, None, arena,
),
"goniometric" => crate::lights::goniometric::create(
render_from_light,
medium,
parameters,
loc,
&shape,
&alpha,
None,
arena,
render_from_light, medium, parameters, loc,
&shape, &alpha, None, arena,
),
"projection" => crate::lights::projection::create(
render_from_light,
medium,
parameters,
loc,
&shape,
&alpha,
None,
arena,
render_from_light, medium, parameters, loc,
&shape, &alpha, None, arena,
),
"infinite" => crate::lights::infinite::create(
render_from_light,
medium.into(),
camera_transform,
parameters,
None,
loc,
arena,
render_from_light, medium.into(), camera_transform,
parameters, None, loc, arena,
),
"diffuse" => Err(anyhow!(
"{}: \"diffuse\" is an area light. Use create_area_light with a shape",
"{}: \"diffuse\" is an area light; use create_area_light with a shape",
loc
)),
_ => Err(anyhow!("{}: unknown light type \"{}\"", loc, name)),
}
}
/// Create a diffuse area light bound to a specific shape.
/// Returns a Light value. The individual light constructor still uses
/// the arena internally for sub-allocations (shape, image, spectra),
/// but the Light itself is returned as a value for the caller to place.
pub fn create_area_light(
render_from_light: Transform,
medium: Option<Medium>,
@ -118,10 +84,9 @@ pub fn create_area_light(
alpha_tex: &FloatTexture,
colorspace: Option<&RGBColorSpace>,
arena: &Arena,
) -> Result<Ptr<Light>> {
let light = crate::lights::diffuse::create(
) -> Result<Light> {
crate::lights::diffuse::create(
render_from_light, medium, parameters, loc,
shape, alpha_tex, colorspace, arena,
)?;
Ok(arena.alloc(light))
)
}

View file

@ -36,7 +36,7 @@ pub struct SceneLookup<'a> {
pub media: &'a HashMap<String, Arc<Medium>>,
pub named_materials: &'a HashMap<String, Material>,
pub materials: &'a Vec<Material>,
pub shape_lights: &'a HashMap<usize, Vec<Ptr<Light>>>,
pub shape_lights: &'a HashMap<usize, Vec<Light>>,
}
impl<'a> SceneLookup<'a> {
@ -130,8 +130,9 @@ impl BasicScene {
*self.film_colorspace.lock() = Some(Arc::clone(cs));
}
let filter = Filter::create(&filter.name, &filter.parameters, &filter.loc, &arena)
let filter = Filter::create(&filter.name, &filter.parameters, &filter.loc, arena)
.map_err(|e| anyhow!("Failed to create filter: {}", e))?;
let shutter_close = camera.base.parameters.get_one_float("shutterclose", 1.)?;
let shutter_open = camera.base.parameters.get_one_float("shutteropen", 0.)?;
let exposure_time = shutter_close - shutter_open;
@ -144,7 +145,7 @@ impl BasicScene {
filter,
Some(camera.camera_transform.clone()),
&film.loc,
&arena,
arena,
)
.map_err(|e| anyhow!("Failed to create film: {}", e))?,
);
@ -154,36 +155,33 @@ impl BasicScene {
job: None,
};
let sampler_film = Arc::clone(&film_instance);
let sampler_job = run_async(move || {
let res = sampler_film.as_ref().base().full_resolution;
Sampler::create(
&sampler.name,
&sampler.parameters,
res,
&sampler.loc,
&arena,
)
.map_err(|e| anyhow!("Failed to create sampler: {}", e))
});
self.sampler_state.lock().job = Some(sampler_job);
let res = film_instance.as_ref().base().full_resolution;
let sampler_result =
Sampler::create(&sampler.name, &sampler.parameters, res, &sampler.loc, arena)
.map_err(|e| anyhow!("Failed to create sampler: {}", e))?;
*self.sampler_state.lock() = SingletonState {
result: Some(Arc::new(sampler_result)),
job: None,
};
let medium = self.get_medium(&camera.medium, &camera.base.loc);
let camera_result = Camera::create(
&camera.base.name,
&camera.base.parameters,
&camera.camera_transform,
medium,
Arc::clone(&film_instance),
&camera.base.loc,
arena,
)
.map_err(|e| anyhow!("Failed to create camera: {}", e))?;
*self.camera_state.lock() = SingletonState {
result: Some(Arc::new(camera_result)),
job: None,
};
let camera_film = Arc::clone(&film_instance);
let scene_ptr = Arc::clone(self);
let camera_job = run_async(move || {
let medium = scene_ptr.get_medium(&camera.medium, &camera.base.loc);
Camera::create(
&camera.base.name,
&camera.base.parameters,
&camera.camera_transform,
medium,
camera_film,
&camera.base.loc,
&arena,
)
.map_err(|e| anyhow!("Failed to create camera: {}", e))
});
self.camera_state.lock().job = Some(camera_job);
Ok(())
}
@ -506,11 +504,7 @@ impl BasicScene {
Ok((named_materials, materials))
}
pub fn create_lights(
&self,
camera_transform: &CameraTransform,
arena: &mut Arena,
) -> Vec<Light> {
pub fn create_lights(&self, camera_transform: &CameraTransform, arena: &Arena) -> Vec<Light> {
let state = self.light_state.lock();
state
@ -555,9 +549,9 @@ impl BasicScene {
shape_entities: &[ShapeSceneEntity],
textures: &NamedTextures,
arena: &Arena,
) -> HashMap<usize, Vec<Ptr<Light>>> {
) -> HashMap<usize, Vec<Light>> {
let light_state = self.light_state.lock();
let mut shape_lights: HashMap<usize, Vec<Ptr<Light>>> = HashMap::new();
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 {
@ -581,7 +575,6 @@ impl BasicScene {
let default_alpha = Arc::new(FloatTexture::default());
let alpha_ref = alpha_tex.as_ref().unwrap_or(&default_alpha);
// Use the film colorspace as fallback for area light emission
let film_cs = self.film_colorspace.lock();
let colorspace_ref = al_entity
.parameters
@ -591,7 +584,7 @@ impl BasicScene {
let render_from_light = *entity.render_from_object;
let lights: Vec<Ptr<Light>> = shapes
let lights: Vec<Light> = shapes
.iter()
.filter_map(|shape| {
match crate::core::light::create_area_light(
@ -678,7 +671,7 @@ impl BasicScene {
al_params: Option<&SceneEntity>,
render_from_light: Transform,
film_cs: Option<&RGBColorSpace>,
arena: &mut Arena,
arena: &Arena,
area_lights: &mut Vec<Arc<Light>>,
) -> Vec<(Ptr<Shape>, Ptr<Light>, Ptr<GPUFloatTexture>)> {
shapes
@ -689,7 +682,7 @@ impl BasicScene {
let cs = al_entity.parameters.color_space.as_deref().or(film_cs);
let default_alpha = Arc::new(FloatTexture::default());
let alpha_ref = alpha_tex.as_ref().unwrap_or(&default_alpha);
crate::core::light::create_area_light(
match crate::core::light::create_area_light(
render_from_light,
None,
&al_entity.parameters,
@ -698,15 +691,21 @@ impl BasicScene {
alpha_ref,
cs,
arena,
)
.ok()
) {
Ok(light) => {
// Keep an Arc copy for the host-side light list
area_lights.push(Arc::new(light));
// Alloc into arena for the GPU primitive
Some(arena.alloc(light))
}
Err(e) => {
log::error!("Failed to create area light: {}", e);
None
}
}
})
.unwrap_or(Ptr::null());
if !area_light_ptr.is_null() {
area_lights.push(Arc::new(unsafe { &*area_light_ptr.as_raw() }.clone()));
}
let alpha_ptr = alpha_tex
.as_ref()
.map(|t| arena.upload(t.as_ref()))
@ -932,13 +931,16 @@ impl BasicScene {
.unwrap_or(Ptr::null()),
};
let shape_lights_opt = lookup
// Light is &Light from the HashMap — alloc into arena for the Ptr
let light_ptr = lookup
.shape_lights
.get(&shape_ctx.entity_index)
.and_then(|lights| lights.get(shape_ctx.shape_index));
.and_then(|lights| lights.get(shape_ctx.shape_index))
.map(|l| arena.alloc(*l))
.unwrap_or(Ptr::null());
let light_ptr = shape_lights_opt.copied().unwrap_or(Ptr::null());
let shape_ptr = shape_ctx.shape;
let prim = if light_ptr.is_null() && !mi.is_medium_transition() && alpha_tex.is_none() {
Primitive::Simple(SimplePrimitive::new(shape_ptr, Ptr::from(&mtl)))
} else {
@ -947,7 +949,10 @@ impl BasicScene {
arena.alloc(mtl),
light_ptr,
mi.clone(),
arena.upload(alpha_tex),
alpha_tex
.as_ref()
.map(|t| arena.upload(t.as_ref()))
.unwrap_or(Ptr::null()),
))
};

View file

@ -11,7 +11,7 @@ use shared::core::light::{Light, LightBase, LightType};
use shared::core::medium::{Medium, MediumInterface};
use shared::core::shape::{Shape, ShapeTrait};
use shared::core::spectrum::Spectrum;
use shared::core::texture::{SpectrumType, TextureEvalContext, GPUFloatTexture};
use shared::core::texture::{GPUFloatTexture, SpectrumType, TextureEvalContext};
use shared::lights::DiffuseAreaLight;
use shared::spectra::RGBColorSpace;
use shared::utils::Transform;
@ -101,7 +101,7 @@ pub fn create(
// Upload alpha texture to GPU and check for null texture
let alpha_ptr = arena.upload(alpha);
let light_type = match alpha_ptr.as_ref() {
let light_type = match unsafe { alpha_ptr.as_ref() } {
GPUFloatTexture::Constant(t) if t.evaluate(&TextureEvalContext::default()) == 0.0 => {
LightType::DeltaPosition
}

View file

@ -1,60 +1,84 @@
use crate::Arena;
use shared::core::light::{Light, LightTrait};
use shared::lights::sampler::PowerLightSampler;
use shared::lights::sampler::{
BVHLightSampler, LightSampler, PowerLightSampler, UniformLightSampler,
};
use shared::utils::sampling::AliasTable;
use shared::spectra::{SampledSpectrum, SampledWavelengths};
use std::collections::HashMap;
use shared::utils::Ptr;
use shared::Float;
use std::sync::Arc;
pub struct PowerSamplerHost {
pub lights: Vec<Arc<Light>>,
pub light_to_index: HashMap<usize, usize>,
pub alias_table: AliasTable,
}
impl PowerSamplerHost {
pub fn new(lights: &[Arc<Light>]) -> Self {
if lights.is_empty() {
return Self {
lights: Vec::new(),
light_to_index: HashMap::new(),
alias_table: AliasTable::new(&[]),
};
/// Top-level dispatcher matching the C++ LightSampler::Create.
pub fn create_light_sampler(
name: &str,
lights: &[Arc<Light>],
arena: &Arena,
) -> LightSampler {
let device_lights = lights_to_slice(lights, arena);
match name {
"uniform" => LightSampler::Uniform(create_uniform(device_lights, lights.len())),
"power" => LightSampler::Power(create_power(lights, device_lights, arena)),
"bvh" => {
log::warn!("BVH light sampler not yet implemented, falling back to power");
LightSampler::Power(create_power(lights, device_lights, arena))
}
let mut lights_vec = Vec::with_capacity(lights.len());
let mut light_to_index = HashMap::with_capacity(lights.len());
let mut light_power = Vec::with_capacity(lights.len());
let lambda = SampledWavelengths::sample_visible(0.5);
for (i, light) in lights.iter().enumerate() {
lights_vec.push(light.clone());
let ptr = Arc::as_ptr(light) as usize;
light_to_index.insert(ptr, i);
let phi = SampledSpectrum::safe_div(&light.phi(lambda), &lambda.pdf());
light_power.push(phi.average());
}
let alias_table = AliasTable::new(&light_power);
Self {
lights: lights_vec,
light_to_index,
alias_table,
_ => {
log::error!("Unknown light sampler \"{}\", using power", name);
LightSampler::Power(create_power(lights, device_lights, arena))
}
}
// pub fn to_device(&self, arena: &Arena) -> PowerLightSampler {
// let device_lights: Vec<Light> = self.lights.iter().map(|l| (**l).clone()).collect();
// let (lights_ptr, _) = arena.alloc_slice(&device_lights);
// let alias_device = self.alias_table.to_device(arena);
//
// PowerLightSampler {
// lights: lights_ptr,
// lights_len: self.lights.len() as u32,
// alias_table: alias_device,
// }
// }
}
fn lights_to_slice(lights: &[Arc<Light>], arena: &Arena) -> (Ptr<Light>, u32) {
if lights.is_empty() {
return (Ptr::null(), 0);
}
let vals: Vec<Light> = lights.iter().map(|l| **l).collect();
let (ptr, _) = arena.alloc_slice(&vals);
(ptr, lights.len() as u32)
}
fn create_uniform(
(lights, lights_len): (Ptr<Light>, u32),
_count: usize,
) -> UniformLightSampler {
UniformLightSampler::new(lights, lights_len)
}
fn create_power(
host_lights: &[Arc<Light>],
(lights, lights_len): (Ptr<Light>, u32),
arena: &Arena,
) -> PowerLightSampler {
if host_lights.is_empty() {
return PowerLightSampler {
lights: Ptr::null(),
lights_len: 0,
alias_table: Ptr::null(),
};
}
let lambda = SampledWavelengths::sample_visible(0.5);
let mut light_power: Vec<Float> = host_lights
.iter()
.map(|l| {
let phi = SampledSpectrum::safe_div(&l.phi(lambda), &lambda.pdf());
phi.average()
})
.collect();
// If all lights have zero power, treat as uniform
if light_power.iter().sum::<Float>() == 0.0 {
light_power.fill(1.0);
}
let alias_table = AliasTable::new(&light_power);
let alias_ptr = arena.alloc(alias_table);
PowerLightSampler {
lights,
lights_len,
alias_table: alias_ptr,
}
}

View file

@ -1,22 +1,42 @@
use crate::Arena;
use crate::core::image::HostImage;
use crate::core::material::CreateMaterial;
use crate::core::texture::SpectrumTexture;
use crate::utils::upload::ArenaUpload;
use crate::utils::{FileLoc, TextureParameterDictionary};
use crate::Arena;
use anyhow::Result;
use shared::core::material::Material;
use shared::core::spectrum::Spectrum;
use shared::core::texture::SpectrumType;
use shared::materials::{DiffuseMaterial, DiffuseTransmissionMaterial};
use shared::spectra::ConstantSpectrum;
use shared::textures::SpectrumConstantTexture;
use std::collections::HashMap;
use std::sync::Arc;
impl CreateMaterial for DiffuseMaterial {
fn create(
_parameters: &TextureParameterDictionary,
_normal_map: Option<Arc<HostImage>>,
parameters: &TextureParameterDictionary,
normal_map: Option<Arc<HostImage>>,
_named_materials: &HashMap<String, Material>,
_loc: &FileLoc,
_arena: &Arena,
arena: &Arena,
) -> Result<Material> {
todo!()
let reflectance = parameters
.get_spectrum_texture("reflectance", None, SpectrumType::Albedo)
.unwrap_or_else(|| {
Arc::new(SpectrumTexture::Constant(
SpectrumConstantTexture::new(Spectrum::Constant(ConstantSpectrum::new(0.5))),
))
});
let displacement = parameters.get_float_texture_or_null("displacement")?;
let specific = DiffuseMaterial {
reflectance: arena.upload(reflectance),
displacement: arena.upload(displacement),
normal_map: arena.upload(normal_map),
};
Ok(Material::Diffuse(specific))
}
}

View file

@ -12,8 +12,8 @@ pub trait CreateRGBColorSpace {
r: Point2f,
g: Point2f,
b: Point2f,
illuminant: &DenselySampledSpectrum,
rgb_to_spectrum_table: &RGBToSpectrumTable,
illuminant: Ptr<DenselySampledSpectrum>,
rgb_to_spectrum_table: Ptr<RGBToSpectrumTable>,
) -> Self;
}
@ -22,19 +22,16 @@ impl CreateRGBColorSpace for RGBColorSpace {
r: Point2f,
g: Point2f,
b: Point2f,
illuminant: &DenselySampledSpectrum,
rgb_to_spectrum_table: &RGBToSpectrumTable,
illuminant: Ptr<DenselySampledSpectrum>,
rgb_to_spectrum_table: Ptr<RGBToSpectrumTable>,
) -> Self {
let stdspec = get_spectra_context();
let illum_ptr = Ptr::from(illuminant);
let illum_spectrum = Spectrum::Dense(illum_ptr);
let illum_spectrum = Spectrum::Dense(illuminant);
let w_xyz: XYZ = illum_spectrum.to_xyz(&stdspec);
let w = w_xyz.xy();
let r_xyz = XYZ::from_xyy(r, Some(1.0));
let g_xyz = XYZ::from_xyy(g, Some(1.0));
let b_xyz = XYZ::from_xyy(b, Some(1.0));
let rgb_values = [
[r_xyz.x(), g_xyz.x(), b_xyz.x()],
[r_xyz.y(), g_xyz.y(), b_xyz.y()],
@ -44,14 +41,13 @@ impl CreateRGBColorSpace for RGBColorSpace {
let c: RGB = rgb.inverse().unwrap() * w_xyz;
let xyz_from_rgb = rgb * SquareMatrix::diag(&[c.r, c.g, c.b]);
let rgb_from_xyz = xyz_from_rgb.inverse().expect("singular");
RGBColorSpace {
r,
g,
b,
w,
illuminant: illum_ptr,
rgb_to_spectrum_table: Ptr::from(rgb_to_spectrum_table),
illuminant,
rgb_to_spectrum_table,
xyz_from_rgb,
rgb_from_xyz,
}

View file

@ -6,7 +6,7 @@ use shared::core::spectrum::{Spectrum, StandardSpectra};
use shared::spectra::cie::{CIE_D65, CIE_X, CIE_Y, CIE_Z};
use shared::spectra::{DenselySampledSpectrum, DeviceStandardColorSpaces, RGBColorSpace};
use shared::Ptr;
use std::sync::{Arc, OnceLock, LazyLock};
use std::sync::{Arc, LazyLock, OnceLock};
pub mod colorspace;
pub mod data;
@ -21,10 +21,6 @@ pub static CIE_Z_DATA: LazyLock<DenselySampledSpectrum> =
pub static CIE_D65_DATA: LazyLock<DenselySampledSpectrum> =
LazyLock::new(|| data::create_cie(&CIE_D65));
fn get_d65_illuminant_buffer() -> Arc<DenselySampledSpectrum> {
Arc::new(CIE_D65_DATA.clone())
}
pub fn cie_x() -> Spectrum {
Spectrum::Dense(Ptr::from(&*CIE_X_DATA))
}
@ -47,42 +43,46 @@ pub fn get_spectra_context() -> StandardSpectra {
}
}
static D65_ILLUMINANT: LazyLock<DenselySampledSpectrum> = LazyLock::new(|| CIE_D65_DATA.clone());
pub static SRGB: LazyLock<Arc<RGBColorSpace>> = LazyLock::new(|| {
let illum = get_d65_illuminant_buffer();
let r = Point2f::new(0.64, 0.33);
let g = Point2f::new(0.3, 0.6);
let b = Point2f::new(0.15, 0.06);
let table_ptr = Ptr::from(&*SRGB_TABLE);
let illum_ptr = Ptr::from(&*D65_ILLUMINANT);
Arc::new(RGBColorSpace::new(r, g, b, &illum, &table_ptr))
Arc::new(RGBColorSpace::new(r, g, b, illum_ptr, table_ptr))
});
pub static DCI_P3: LazyLock<Arc<RGBColorSpace>> = LazyLock::new(|| {
let illum = get_d65_illuminant_buffer();
let r = Point2f::new(0.680, 0.320);
let g = Point2f::new(0.265, 0.690);
let b = Point2f::new(0.150, 0.060);
let table_ptr = Ptr::from(&*DCI_P3_TABLE);
Arc::new(RGBColorSpace::new(r, g, b, &illum, &table_ptr))
let illum_ptr = Ptr::from(&*D65_ILLUMINANT);
Arc::new(RGBColorSpace::new(r, g, b, illum_ptr, table_ptr))
});
pub static REC2020: LazyLock<Arc<RGBColorSpace>> = LazyLock::new(|| {
let illum = get_d65_illuminant_buffer();
let r = Point2f::new(0.708, 0.292);
let g = Point2f::new(0.170, 0.797);
let b = Point2f::new(0.131, 0.046);
let table_ptr = Ptr::from(&*REC2020_TABLE);
Arc::new(RGBColorSpace::new(r, g, b, &illum, &table_ptr))
let illum_ptr = Ptr::from(&*D65_ILLUMINANT);
Arc::new(RGBColorSpace::new(r, g, b, illum_ptr, table_ptr))
});
pub static ACES: LazyLock<Arc<RGBColorSpace>> = LazyLock::new(|| {
let illum = get_d65_illuminant_buffer();
let r = Point2f::new(0.7347, 0.2653);
let g = Point2f::new(0.0000, 1.0000);
let b = Point2f::new(0.0001, -0.0770);
let table_ptr = Ptr::from(&ACES_TABLE);
Arc::new(RGBColorSpace::new(r, g, b, &illum, &table_ptr))
let table_ptr = Ptr::from(&*ACES_TABLE);
let illum_ptr = Ptr::from(&*D65_ILLUMINANT);
Arc::new(RGBColorSpace::new(r, g, b, illum_ptr, table_ptr))
});
#[derive(Debug, Clone)]
@ -141,4 +141,3 @@ pub fn default_colorspace_ref() -> &'static RGBColorSpace {
pub fn default_illuminant() -> Spectrum {
Spectrum::Dense(default_colorspace().illuminant)
}

View file

@ -915,7 +915,12 @@ impl TextureParameterDictionary {
panic!("[{:?}] Negative RGB values for '{}'", p.loc, name);
}
let cs = self.dict.color_space.as_ref().unwrap();
let cs = self
.dict
.color_space
.as_ref()
.map(|arc| arc.as_ref())
.unwrap_or_else(|| crate::spectra::default_colorspace_ref());
let s: Spectrum = match stype {
SpectrumType::Illuminant => {
Spectrum::RGBIlluminant(RGBIlluminantSpectrum::new(cs, rgb))