Very big change, preparing for gpu integration

This commit is contained in:
pingu 2025-12-20 00:11:31 +00:00
parent 392b0a6850
commit 8bcc2fb0c8
104 changed files with 7870 additions and 4533 deletions

View file

@ -3,28 +3,52 @@ name = "pbrt"
version = "0.1.0"
edition = "2024"
[features]
default = []
use_f64 = []
cuda = ["cuda_std", "cust", "cuda_builder", "shared/cuda", ]
use_nvtx = []
[dependencies]
anyhow = "1.0.100"
bitflags = "2.10.0"
bumpalo = "3.19.0"
bytemuck = { version = "1.24.0", features = ["derive"] }
enum_dispatch = "0.3.13"
exr = "1.73.0"
flate2 = "1.1.5"
gpu = "0.2.3"
half = "2.7.1"
image_rs = { package = "image", version = "0.25.8" }
indicatif = "0.18.3"
lazy_static = "1.5.0"
log = "0.4.29"
memmap2 = "0.9.9"
num = "0.4.3"
num-integer = "0.1.46"
num-traits = "0.2.19"
nvtx = "1.3.0"
once_cell = "1.21.3"
parking_lot = "0.12.5"
paste = "1.0.15"
qoi = "0.4.1"
rand = "0.9.2"
rayon = "1.11.0"
smallvec = "1.15.1"
thiserror = "2.0.17"
unicode-normalization = "0.1.25"
wgpu = "27.0.1"
shared = { path = "shared" }
kernel = { path = "kernels" }
cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true }
cust = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, features = ["glam"], optional = true }
[features]
default = []
use_f64 = []
[build-dependencies]
spirv-builder = { git = "https://github.com/rust-gpu/rust-gpu", branch = "main", optional = true }
cuda_builder = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", optional = true }
[workspace]
members = ["kernels", "shared"]
[lints.clippy]
excessive_precision = "allow"

BIN
data/aces_coeffs.dat Normal file

Binary file not shown.

BIN
data/aces_scale.dat Normal file

Binary file not shown.

BIN
data/dcip3_coeffs.dat Normal file

Binary file not shown.

BIN
data/dcip3_scale.dat Normal file

Binary file not shown.

BIN
data/rec2020_coeffs.dat Normal file

Binary file not shown.

BIN
data/rec2020_scale.dat Normal file

Binary file not shown.

BIN
data/srgb_coeffs.dat Normal file

Binary file not shown.

BIN
data/srgb_scale.dat Normal file

Binary file not shown.

10
kernels/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "kernels"
version = "0.1.0"
edition = "2024"
[dependencies]
cuda_std = { git = "https://github.com/rust-gpu/rust-cuda", rev = "7fa76f3d717038a92c90bf4a482b0b8dd3259344" }
[lib]
crate-type = ["cdylib", "rlib"]

447
kernels/src/lib.rs Normal file
View file

@ -0,0 +1,447 @@
use cuda_std::prelude::*;
pub mod wavefront;
pub mod workitem;
use cust::context::{CacheConfig, CurrentContext, ResourceLimit};
use cust::device::DeviceAttribute;
use cust::memory::{DeviceCopy, DeviceMemory};
use cust::prelude::*;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use std::error::Error;
use std::ffi::c_void;
use std::sync::Arc;
use crate::Float;
use crate::core::geometry::{Normal, Point, Vector};
use crate::core::medium::Medium;
use crate::core::options::{PBRTOptions, get_options};
use crate::impl_gpu_traits;
use crate::impl_math_gpu_traits;
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::interval::Interval;
pub use workitem::{
EscapedRayQueue, GetBSSRDFAndProbeRayQueue, HitAreaLightQueue, MaterialEvalQueue,
MediumSampleQueue, MediumScatterQueue, PixelSampleStateStorage, RayQueue, ShadowRayQueue,
SubsurfaceScatterQueue,
};
#[repr(C, align(16))]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Float4 {
pub v: [f32; 4],
}
pub type Vec4 = Vector<Float, 4>;
impl From<Vec4> for Float4 {
#[inline]
fn from(vec: Vector<f32, 4>) -> Self {
Self { v: vec.0 }
}
}
impl From<Float4> for Vec4 {
#[inline]
fn from(storage: Float4) -> Self {
Vector(storage.v)
}
}
impl_math_gpu_traits!(Vector);
impl_math_gpu_traits!(Normal);
impl_math_gpu_traits!(Point);
impl_gpu_traits!(Interval);
impl_gpu_traits!(Float4);
impl_gpu_traits!(SampledSpectrum);
impl_gpu_traits!(SampledWavelengths);
struct KernelStats {
description: String,
num_launches: usize,
sum_ms: f32,
min_ms: f32,
max_ms: f32,
}
impl KernelStats {
fn new(description: &str) -> Self {
Self {
description: description.to_string(),
num_launches: 0,
sum_ms: 0.0,
min_ms: 0.0,
max_ms: 0.0,
}
}
}
struct ProfilerEvent {
start: Event,
stop: Event,
active: bool,
stats: Option<Arc<Mutex<KernelStats>>>,
}
impl ProfilerEvent {
fn new() -> Result<Self, cust::error::CudaError> {
let start = Event::new(EventFlags::DEFAULT)?;
let stop = Event::new(EventFlags::DEFAULT)?;
Ok(Self {
start,
stop,
active: false,
stats: None,
})
}
fn sync(&mut self) {
if !self.active {
return;
}
if self.stop.synchronize().is_ok() {
// Check timing between start and stop
match self.stop.elapsed_time_f32(&self.start) {
Ok(ms) => {
if let Some(stats_arc) = &self.stats {
let mut stats = stats_arc.lock();
stats.num_launches += 1;
if stats.num_launches == 1 {
stats.sum_ms = ms;
stats.min_ms = ms;
stats.max_ms = ms;
} else {
stats.sum_ms += ms;
stats.min_ms = stats.min_ms.min(ms);
stats.max_ms = stats.max_ms.max(ms);
}
}
}
Err(e) => log::error!("Failed to get elapsed time: {:?}", e),
}
}
self.active = false;
}
}
// --- Profiler Manager ---
struct Profiler {
kernel_stats: Vec<Arc<Mutex<KernelStats>>>,
event_pool: Vec<ProfilerEvent>,
pool_offset: usize,
}
impl Profiler {
fn new() -> Self {
Self {
kernel_stats: Vec::new(),
event_pool: Vec::new(),
pool_offset: 0,
}
}
/// Prepares an event from the pool.
/// Returns a mutable reference to the event, valid as long as the borrow of self.
fn prepare<'a>(&'a mut self, description: &str) -> &'a mut ProfilerEvent {
// Grow pool if empty or needed (simple heuristic)
if self.event_pool.is_empty() {
for _ in 0..128 {
if let Ok(e) = ProfilerEvent::new() {
self.event_pool.push(e);
}
}
}
if self.pool_offset >= self.event_pool.len() {
self.pool_offset = 0;
}
let idx = self.pool_offset;
self.pool_offset += 1;
let pe = &mut self.event_pool[idx];
if pe.active {
pe.sync();
}
pe.active = true;
pe.stats = None;
// Find or create stats
let mut found = None;
for s in &self.kernel_stats {
if s.lock().description == description {
found = Some(s.clone());
break;
}
}
if found.is_none() {
let new_stats = Arc::new(Mutex::new(KernelStats::new(description)));
self.kernel_stats.push(new_stats.clone());
found = Some(new_stats);
}
pe.stats = found;
pe
}
}
pub struct GpuState {
context: Context,
stream: Stream,
profiler: Profiler,
}
impl GpuState {
fn init(device_index: u32) -> Result<Self, Box<dyn Error>> {
cust::init(CudaFlags::empty())?;
let device = Device::get_device(device_index)?;
let name = device.name().unwrap_or_else(|_| "Unknown".into());
let memory = device.total_memory().unwrap_or(0);
let memory_gb = memory as f64 / (1024.0 * 1024.0 * 1024.0);
let major = device
.get_attribute(DeviceAttribute::ComputeCapabilityMajor)
.unwrap_or(0);
let minor = device
.get_attribute(DeviceAttribute::ComputeCapabilityMinor)
.unwrap_or(0);
log::info!(
"Selected GPU: {} ({:.2} GB, SM {}.{})",
name,
memory_gb,
major,
minor
);
let has_unified = device
.get_attribute(DeviceAttribute::UnifiedAddressing)
.unwrap_or(0);
if has_unified == 0 {
panic!("Selected GPU does not support unified addressing.");
}
let context = Context::new(device)?;
CurrentContext::set_resource_limit(ResourceLimit::StackSize, 8192)?;
let stack_size = CurrentContext::get_resource_limit(ResourceLimit::StackSize)?;
log::info!("Reset stack size to {}", stack_size);
CurrentContext::set_resource_limit(ResourceLimit::PrintfFifoSize, 32 * 1024 * 1024)?;
CurrentContext::set_cache_config(CacheConfig::PreferL1)?;
let stream = Stream::new(StreamFlags::DEFAULT, None)?;
Ok(Self {
context,
stream,
profiler: Profiler::new(),
})
}
}
lazy_static! {
static ref GPU_STATE: Mutex<Option<GpuState>> = Mutex::new(None);
}
pub fn gpu_init() {
if !get_options().use_gpu {
return;
}
let device_id = get_options().gpu_device.unwrap_or(0);
log::info!("Initializing GPU Device {}", device_id);
match GpuState::init(device_id) {
Ok(state) => {
#[cfg(feature = "use_nvtx")]
nvtx::name_thread("MAIN_THREAD");
*GPU_STATE.lock() = Some(state);
}
Err(e) => {
panic!("Failed to initialize GPU: {:?}", e);
}
}
}
pub fn gpu_thread_init() {
if let Some(state) = GPU_STATE.lock().as_ref() {
if let Err(e) = CurrentContext::set_current(&state.context) {
log::error!("Failed to set CUDA context for thread: {:?}", e);
}
}
}
pub fn gpu_wait() {
let mut guard = GPU_STATE.lock();
if let Some(state) = guard.as_mut() {
if let Err(e) = state.stream.synchronize() {
log::error!("GPU Wait failed: {:?}", e);
}
}
}
/// Launches a parallel for loop on the GPU.
///
/// # Arguments
/// * `description`: Name for profiling.
/// * `n_items`: Total items (threads).
/// * `function`: Compiled kernel function handle.
/// * `params`: Kernel parameters (must be DeviceCopy).
pub fn gpu_parallel_for<T: DeviceCopy>(
description: &str,
n_items: i32,
function: &Function,
params: &T,
) {
#[cfg(feature = "use_nvtx")]
nvtx::range_push(description);
let mut guard = GPU_STATE.lock();
let state = guard.as_mut().expect("GPU not initialized");
let (_, block_size) = match function.suggested_launch_configuration(0, 0.into()) {
Ok(cfg) => cfg,
Err(e) => panic!(
"Failed to calculate launch config for {}: {:?}",
description, e
),
};
#[cfg(debug_assertions)]
log::debug!("[{}] Block size: {}", description, block_size);
let grid_size = (n_items as u32 + block_size - 1) / block_size;
let stream = &state.stream;
let profiler = &mut state.profiler;
// Save the index we are about to use so we can retrieve the STOP event later
let event_idx = profiler.pool_offset;
{
let pe = profiler.prepare(description);
if let Err(e) = pe.start.record(stream) {
log::error!("Failed to record start event: {:?}", e);
}
}
let params_ptr = params as *const T as *mut c_void;
let n_items_ptr = &n_items as *const i32 as *mut c_void;
let args = [params_ptr, n_items_ptr];
unsafe {
if let Err(e) =
state
.stream
.launch(function, (grid_size, 1, 1), (block_size, 1, 1), 0, &args)
{
panic!("CUDA Launch failed for {}: {:?}", description, e);
}
}
// Retrieve the specific event we just set up.
// Pool_offset was incremented in prepare().
// If event_idx was the one used, the event is at event_idx.
if event_idx < profiler.event_pool.len() {
let pe = &mut profiler.event_pool[event_idx];
if let Err(e) = pe.stop.record(stream) {
log::error!("Failed to record stop event: {:?}", e);
}
}
#[cfg(debug_assertions)]
let _ = state.stream.synchronize();
#[cfg(feature = "use_nvtx")]
nvtx::range_pop();
}
pub fn report_kernel_stats() {
let mut guard = GPU_STATE.lock();
if let Some(state) = guard.as_mut() {
let _ = state.stream.synchronize();
// Process all pending events
for pe in &mut state.profiler.event_pool {
if pe.active {
pe.sync();
}
}
let mut total_ms = 0.0;
for s in &state.profiler.kernel_stats {
total_ms += s.lock().sum_ms;
}
println!("Wavefront Kernel Profile:");
for s in &state.profiler.kernel_stats {
let stats = s.lock();
let percent = if total_ms > 0.0 {
100.0 * stats.sum_ms / total_ms
} else {
0.0
};
println!(
" {:<45} {:5} launches {:9.2} ms / {:5.1}% (avg {:6.3})",
stats.description,
stats.num_launches,
stats.sum_ms,
percent,
if stats.num_launches > 0 {
stats.sum_ms / stats.num_launches as f32
} else {
0.0
}
);
}
println!("\nTotal: {:.2} ms", total_ms);
}
}
pub fn gpu_memset<T: DeviceCopy>(dst: &mut DeviceSlice<T>, value: u8) {
unsafe {
let ptr = dst.as_raw_ptr(); // Returns CUdeviceptr (u64)
let len = dst.len() * std::mem::size_of::<T>();
// We need the `cust::external::cuda` or equivalent sys crate function
log::warn!("gpu_memset requested but raw memset not exposed via safe cust API yet.");
}
}
#[macro_export]
macro_rules! impl_gpu_traits {
($name:ty) => {
unsafe impl cust::memory::DeviceCopy for $name {}
unsafe impl bytemuck::Zeroable for $name {}
unsafe impl bytemuck::Pod for $name {}
};
}
#[macro_export]
macro_rules! impl_math_gpu_traits {
($Struct:ident) => {
#[cfg(feature = "use_gpu")]
unsafe impl<T, const N: usize> cust::memory::DeviceCopy for $Struct<T, N> where
T: cust::memory::DeviceCopy + Copy
{
}
unsafe impl<T, const N: usize> bytemuck::Zeroable for $Struct<T, N> where
T: bytemuck::Zeroable
{
}
unsafe impl<T, const N: usize> bytemuck::Pod for $Struct<T, N> where T: bytemuck::Pod {}
};
}

View file

@ -0,0 +1,47 @@
use image_rs::Pixel;
use crate::camera::Camera;
use crate::core::film::Film;
use crate::core::filter::Filter;
use crate::core::sampler::Sampler;
use crate::core::scene::BasicScene;
use crate::lights::Light;
use crate::lights::LightSampler;
use crate::{
EscapedRayQueue, GetBSSRDFAndProbeRayQueue, HitAreaLightQueue, MaterialEvalQueue,
MediumSampleQueue, MediumScatterQueue, PixelSampleStateStorage, RayQueue, ShadowRayQueue,
SubsurfaceScatterQueue,
};
use std::sync::Arc;
pub struct WavefrontPathIntegrator {
pub film: Film,
pub filter: Filter,
pub sampler: Sampler,
pub camera: Arc<Camera>,
pub light_sampler: LightSampler,
pub infinite_lights: Option<Vec<Arc<Light>>>,
pub max_depth: i32,
pub samples_per_pixel: i32,
pub regularize: bool,
pub scanlines_per_pixel: i32,
pub max_queue_size: i32,
pub pixel_sample_state: PixelSampleStateStorage,
pub ray_queue: [RayQueue; 2],
pub hit_area_light_queue: HitAreaLightQueue,
pub shadow_ray_queue: ShadowRayQueue,
pub escaped_ray_queue: Option<EscapedRayQueue>,
pub basic_material_queue: Option<MaterialEvalQueue>,
pub universal_material_queue: Option<MaterialEvalQueue>,
pub medium_sample_queue: Option<MediumSampleQueue>,
pub medium_scatter_queue: Option<MediumScatterQueue>,
pub bssrf_queue: Option<GetBSSRDFAndProbeRayQueue>,
pub subsurface_queue: Option<SubsurfaceScatterQueue>,
}
#[cfg(feature = "use_gpu")]
impl WavefrontPathIntegrator {
pub fn new(scene: BasicScene) -> Self {
todo!()
}
}

535
kernels/src/workitem.rs Normal file
View file

@ -0,0 +1,535 @@
#![allow(clippy::too_many_arguments)]
use super::Float4;
use crate::Float;
use crate::core::geometry::{Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray, Vector3f};
use crate::lights::LightSampleContext;
use crate::soa_struct;
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use cust::memory::{CopyDestination, DeviceMemory};
use cust::prelude::*;
#[macro_export]
macro_rules! soa_struct {
(
$(#[$outer:meta])*
pub struct $name:ident {
$(
pub $field:ident : $type:ty
),* $(,)?
}
) => {
#[cfg(feature = "use_gpu")]
$(#[$outer])*
pub struct $name {
capacity: u32,
pub count: cust::memory::DeviceBuffer<u32>,
$(
pub $field: cust::memory::DeviceBuffer<$type>,
)*
}
#[cfg(feature = "use_gpu")]
impl $name {
pub fn new(capacity: usize) -> cust::error::CudaResult<Self> {
use cust::memory::DeviceBuffer;
Ok(Self {
capacity: capacity as u32,
count: DeviceBuffer::zeroed(1)?,
$(
$field: DeviceBuffer::zeroed(capacity)?,
)*
})
}
pub fn len(&self) -> cust::error::CudaResult<u32> {
let mut host_count = [0u32; 1];
self.count.copy_to(&mut host_count)?;
Ok(host_count[0])
}
pub fn reset(&mut self) -> cust::error::CudaResult<()> {
self.count.copy_from(&[0])
}
// Generate the View name
pub fn as_view(&mut self) -> paste::paste! { [<$name View>] } {
paste::paste! {
[<$name View>] {
capacity: self.capacity,
count: self.count.as_device_ptr().as_mut_ptr(),
$(
$field: self.$field.as_device_ptr().as_raw() as *mut $type,
)*
}
}
}
}
paste::paste! {
#[repr(C)]
#[derive(Clone, Copy)]
pub struct [<$name View>] {
pub capacity: u32,
pub count: *mut u32,
$(
pub $field: *mut $type,
)*
}
unsafe impl cust::memory::DeviceCopy for [<$name View>] {}
impl [<$name View>] {
// The raw push that fills every field
#[cfg(feature = "use_gpu")]
pub unsafe fn push(&self, $( $field : $type ),* ) -> Option<u32> {
use core::sync::atomic::{AtomicU32, Ordering};
let index = unsafe {
let counter_ptr = self.count as *mut AtomicU32;
(*counter_ptr).fetch_add(1, Ordering::Relaxed)
};
if index >= self.capacity {
return None;
}
unsafe {
$(
*self.$field.add(index as usize) = $field;
)*
}
Some(index)
}
#[cfg(feature = "use_gpu")]
pub unsafe fn size(&self) -> u32 {
use core::sync::atomic::{AtomicU32, Ordering};
unsafe {
(*(self.count as *const AtomicU32)).load(Ordering::Relaxed)
}
}
$(
#[cfg(feature = "use_gpu")]
pub fn [<$field _ptr>](&self) -> *mut $type {
self.$field
}
)*
}
}
};
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct RaySamplesDirect {
pub u: Point2f,
pub uc: Float,
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct RaySamplesIndirect {
pub uc: Float,
pub rr: Float,
pub u: Point2f,
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct RaySamplesSubsurface {
pub uc: Float,
pub u: Point2f,
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct RaySamples {
pub direct: RaySamplesDirect,
pub indirect: RaySamplesIndirect,
pub have_subsurface: bool,
pub subsurface: RaySamplesSubsurface,
}
soa_struct! {
pub struct RayQueue {
pub ray_o: Point3f,
pub ray_d: Vector3f,
pub depth: i32,
pub lambda: SampledWavelengths,
pub pixel_index: u32,
pub beta: SampledSpectrum,
pub r_u: SampledSpectrum,
pub r_l: SampledSpectrum,
pub ctx_pi: Point3f,
pub ctx_n: Normal3f,
pub ctx_ns: Normal3f,
pub eta_scale: Float,
pub specular_bounce: u32,
pub any_non_specular_bounces: u32,
}
}
soa_struct! {
pub struct PixelSampleStateStorage {
pub p_pixel: Point2i,
pub l: SampledSpectrum,
pub lambda: SampledWavelengths,
pub filter_weight: Float,
pub visible_surface: u32,
pub camera_ray_weight: SampledSpectrum,
pub rs_direct_packed: Float4,
pub rs_indirect_packed: Float4,
pub rs_subsurface_packed: Float4,
}
}
soa_struct! {
pub struct EscapedRayQueue {
pub ray_o: Point3f,
pub ray_d: Vector3f,
pub depth: i32,
pub lambda: SampledWavelengths,
pub pixel_index: u32,
pub beta: SampledSpectrum,
pub specular_bounce: u32,
pub r_u: SampledSpectrum,
pub r_l: SampledSpectrum,
pub ctx_pi: Point3f,
pub ctx_n: Normal3f,
pub ctx_ns: Normal3f,
}
}
soa_struct! {
pub struct HitAreaLightQueue {
pub area_light_id: u32, // Light ID
pub p: Point3f,
pub n: Normal3f,
pub uv: Point2f,
pub wo: Vector3f,
pub lambda: SampledWavelengths,
pub depth: i32,
pub beta: SampledSpectrum,
pub r_u: SampledSpectrum,
pub r_l: SampledSpectrum,
pub ctx_pi: Point3f,
pub ctx_n: Normal3f,
pub ctx_ns: Normal3f,
pub specular_bounce: u32,
pub pixel_index: u32,
}
}
soa_struct! {
pub struct ShadowRayQueue {
pub ray_o: Point3f,
pub ray_d: Vector3f,
pub t_max: Float,
pub lambda: SampledWavelengths,
pub ld: SampledSpectrum,
pub r_u: SampledSpectrum,
pub r_l: SampledSpectrum,
pub pixel_index: u32,
}
}
soa_struct! {
pub struct GetBSSRDFAndProbeRayQueue {
pub material_id: u32,
pub lambda: SampledWavelengths,
pub beta: SampledSpectrum,
pub r_u: SampledSpectrum,
pub p: Point3f,
pub wo: Vector3f,
pub n: Normal3f,
pub ns: Normal3f,
pub dpdus: Vector3f,
pub uv: Point2f,
pub depth: i32,
pub mi_inside: u32,
pub mi_outside: u32,
pub eta_scale: Float,
pub pixel_index: u32,
}
}
soa_struct! {
pub struct SubsurfaceScatterQueue {
pub p0: Point3f,
pub p1: Point3f,
pub depth: i32,
pub material_id: u32,
pub lambda: SampledWavelengths,
pub beta: SampledSpectrum,
pub r_u: SampledSpectrum,
pub mi_inside: u32,
pub mi_outside: u32,
pub eta_scale: Float,
pub pixel_index: u32,
}
}
soa_struct! {
pub struct MediumSampleQueue {
pub ray_o: Point3f,
pub ray_d: Vector3f,
pub t_max: Float,
pub lambda: SampledWavelengths,
pub beta: SampledSpectrum,
pub r_u: SampledSpectrum,
pub r_l: SampledSpectrum,
pub pixel_index: u32,
pub ctx_pi: Point3f,
pub ctx_n: Normal3f,
pub ctx_ns: Normal3f,
pub specular_bounce: u32,
pub any_non_specular_bounces: u32,
pub eta_scale: Float,
pub area_light_id: u32,
pub pi: Point3fi,
pub n: Normal3f,
pub dpdu: Vector3f,
pub dpdv: Vector3f,
pub wo: Vector3f,
pub uv: Point2f,
pub material_id: u32,
pub ns: Normal3f,
pub dpdus: Vector3f,
pub dpdvs: Vector3f,
pub dndus: Normal3f,
pub dndvs: Normal3f,
pub face_index: i32,
pub mi_inside: u32,
pub mi_outside: u32,
}
}
soa_struct! {
pub struct MaterialEvalQueue {
pub material_id: u32,
pub pi: Point3fi,
pub n: Normal3f,
pub dpdu: Vector3f,
pub dpdv: Vector3f,
pub time: Float,
pub depth: i32,
pub ns: Normal3f,
pub dpdus: Vector3f,
pub dpdvs: Vector3f,
pub dndus: Normal3f,
pub dndvs: Normal3f,
pub uv: Point2f,
pub face_index: i32,
pub lambda: SampledWavelengths,
pub pixel_index: u32,
pub any_non_specular_bounces: u32,
pub wo: Vector3f,
pub beta: SampledSpectrum,
pub r_u: SampledSpectrum,
pub eta_scale: Float,
pub mi_inside: u32,
pub mi_outside: u32,
}
}
soa_struct! {
pub struct MediumScatterQueue {
pub p: Point3f,
pub depth: usize,
pub lambda: SampledWavelengths,
pub beta: SampledSpectrum,
pub r_u: SampledSpectrum,
pub wo: Vector3f,
pub time: Float,
pub eta_scale: Float,
pub pixel_index: usize,
// ID
pub phase_function: u32,
pub medium: u32,
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct RayWorkItem {
pub ray: Ray,
pub depth: i32,
pub lambda: SampledWavelengths,
pub pixel_index: u32,
pub beta: SampledSpectrum,
pub r_u: SampledSpectrum,
pub r_l: SampledSpectrum,
pub prev_intr_ctx: LightSampleContext,
pub eta_scale: Float,
pub specular_bounce: bool,
pub any_non_specular_bounces: bool,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct EscapedRayWorkItem {
pub ray_o: Point3f,
pub ray_d: Vector3f,
pub depth: i32,
pub lambda: SampledWavelengths,
pub pixel_index: u32,
pub beta: SampledSpectrum,
pub specular_bounce: bool,
pub r_u: SampledSpectrum,
pub r_l: SampledSpectrum,
pub prev_intr_ctx: LightSampleContext,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct ShadowRayWorkItem {
pub ray: Ray,
pub t_max: Float,
pub lambda: SampledWavelengths,
pub ld: SampledSpectrum,
pub r_u: SampledSpectrum,
pub r_l: SampledSpectrum,
pub pixel_index: u32,
}
impl RayQueueView {
#[cfg(feature = "use_gpu")]
pub unsafe fn push_work_item(&self, item: RayWorkItem) -> Option<u32> {
unsafe {
self.push(
item.ray.o,
item.ray.d,
item.depth,
item.lambda,
item.pixel_index,
item.beta,
item.r_u,
item.r_l,
item.prev_intr_ctx.pi.into(),
item.prev_intr_ctx.n,
item.prev_intr_ctx.ns,
item.eta_scale,
if item.specular_bounce { 1 } else { 0 },
if item.any_non_specular_bounces { 1 } else { 0 },
)
}
}
}
impl EscapedRayQueueView {
#[cfg(feature = "use_gpu")]
pub unsafe fn push_work_item(&self, r: &RayWorkItem) -> Option<u32> {
unsafe {
self.push(
r.ray.o,
r.ray.d,
r.depth,
r.lambda,
r.pixel_index,
r.beta,
if r.specular_bounce { 1 } else { 0 },
r.r_u,
r.r_l,
r.prev_intr_ctx.pi.into(),
r.prev_intr_ctx.n,
r.prev_intr_ctx.ns,
)
}
}
}
impl PixelSampleStateStorageView {
#[cfg(feature = "use_gpu")]
pub unsafe fn get_samples(&self, index: u32) -> RaySamples {
let i = index as usize;
let (dir, ind, ss) = unsafe {
(
*self.rs_direct_packed.add(i),
*self.rs_indirect_packed.add(i),
*self.rs_subsurface_packed.add(i),
)
};
let direct_u = Point2f::new(dir.v[0], dir.v[1]);
let direct_uc = dir.v[2];
let flags = dir.v[3] as i32;
let have_subsurface = (flags & 1) != 0;
let indirect_uc = ind.v[0];
let indirect_rr = ind.v[1];
let indirect_u = Point2f::new(ind.v[2], ind.v[3]);
let subsurface_uc = ss.v[0];
let subsurface_u = Point2f::new(ss.v[1], ss.v[2]);
RaySamples {
direct: RaySamplesDirect {
u: direct_u,
uc: direct_uc,
},
indirect: RaySamplesIndirect {
uc: indirect_uc,
rr: indirect_rr,
u: indirect_u,
},
have_subsurface,
subsurface: RaySamplesSubsurface {
uc: subsurface_uc,
u: subsurface_u,
},
}
}
#[cfg(feature = "use_gpu")]
pub unsafe fn set_samples(&self, index: u32, rs: RaySamples) {
if index >= self.capacity {
return;
}
let i = index as usize;
let flags = if rs.have_subsurface { 1.0 } else { 0.0 };
let dir = Float4 {
v: [rs.direct.u.0[0], rs.direct.u.0[1], rs.direct.uc, flags],
};
let ind = Float4 {
v: [
rs.indirect.uc,
rs.indirect.rr,
rs.indirect.u.0[0],
rs.indirect.u.0[1],
],
};
unsafe {
*self.rs_direct_packed.add(i) = dir;
*self.rs_indirect_packed.add(i) = ind;
}
if rs.have_subsurface {
let ss = Float4 {
v: [
rs.subsurface.uc,
rs.subsurface.u.0[0],
rs.subsurface.u.0[1],
0.0,
],
};
unsafe {
*self.rs_subsurface_packed.add(i) = ss;
}
}
}
}

6
shared/Cargo.toml Normal file
View file

@ -0,0 +1,6 @@
[package]
name = "shared"
version = "0.1.0"
edition = "2024"
[dependencies]

View file

@ -9,27 +9,33 @@ pub use realistic::RealisticCamera;
pub use spherical::SphericalCamera;
use crate::core::film::{Film, FilmTrait};
use crate::core::interaction::Interaction;
use crate::core::medium::Medium;
use crate::core::pbrt::{Float, RenderingCoordinateSystem, lerp};
use crate::core::sampler::CameraSample;
use crate::geometry::{
use crate::core::geometry::{
Normal3f, Point2f, Point2i, Point3f, Ray, RayDifferential, Vector3f, VectorLike,
};
use crate::core::interaction::Interaction;
use crate::core::medium::Medium;
use crate::core::options::RenderingCoordinateSystem;
use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample;
use crate::image::ImageMetadata;
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::error::FileLoc;
use crate::utils::math::lerp;
use crate::utils::parameters::ParameterDictionary;
use crate::utils::transform::{AnimatedTransform, Transform};
use enum_dispatch::enum_dispatch;
use std::sync::Arc;
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CameraRay {
pub ray: Ray,
pub weight: SampledSpectrum,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CameraWiSample {
wi_spec: SampledSpectrum,
@ -40,10 +46,10 @@ pub struct CameraWiSample {
p_lens: Interaction,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct CameraTransform {
render_from_camera: AnimatedTransform,
world_from_render: Transform<Float>,
pub render_from_camera: AnimatedTransform,
pub world_from_render: Transform,
}
impl CameraTransform {
@ -80,7 +86,7 @@ impl CameraTransform {
}
}
pub fn camera_from_world(&self, time: Float) -> Transform<Float> {
pub fn camera_from_world(&self, time: Float) -> Transform {
(self.world_from_render * self.render_from_camera.interpolate(time)).inverse()
}
@ -109,12 +115,49 @@ impl CameraTransform {
}
}
#[repr(C)]
#[derive(Clone, Debug)]
pub struct CameraBaseParameters {
pub camera_transform: CameraTransform,
pub shutter_open: Float,
pub shutter_close: Float,
pub film: Arc<Film>,
pub medium_id: i32,
}
impl CameraBaseParameters {
pub fn new(
camera_transform: &CameraTransform,
film: Arc<Film>,
medium: Arc<Medium>,
params: &ParameterDictionary,
loc: &FileLoc,
) -> Self {
let mut shutter_open = params.get_one_float("shutteropen", 0.);
let mut shutter_close = params.get_one_float("shutterclose", 1.);
if shutter_close < shutter_open {
eprint!(
"{}: Shutter close time {} < shutter open {}. Swapping",
loc, shutter_close, shutter_open
);
std::mem::swap(&mut shutter_open, &mut shutter_close);
}
CameraBaseParameters {
camera_transform: Arc::new(camera_transform.clone()),
shutter_open,
shutter_close,
film,
medium: Some(medium),
}
}
}
#[derive(Debug)]
pub struct CameraBase {
pub camera_transform: CameraTransform,
pub shutter_open: Float,
pub shutter_close: Float,
pub film: Film,
pub film: Arc<Film>,
pub medium: Option<Arc<Medium>>,
pub min_pos_differential_x: Vector3f,
pub min_pos_differential_y: Vector3f,
@ -124,18 +167,32 @@ pub struct CameraBase {
impl CameraBase {
pub fn init_metadata(&self, metadata: &mut ImageMetadata) {
let camera_from_world: Transform<Float> =
let camera_from_world: Transform =
self.camera_transform.camera_from_world(self.shutter_open);
metadata.camera_from_world = Some(camera_from_world.get_matrix());
}
pub fn new(p: CameraBaseParameters) -> Self {
Self {
camera_transform: p.camera_transform.as_ref().clone(),
shutter_open: p.shutter_open,
shutter_close: p.shutter_close,
film: p.film.clone(),
medium: p.medium,
min_pos_differential_x: Vector3f::default(),
min_pos_differential_y: Vector3f::default(),
min_dir_differential_x: Vector3f::default(),
min_dir_differential_y: Vector3f::default(),
}
}
}
#[enum_dispatch]
pub trait CameraTrait {
fn base(&self) -> &CameraBase;
fn get_film(&self) -> Film {
self.base().film.clone()
fn get_film(&self) -> &Film {
&self.base().film
}
fn resolution(&self) -> Point2i {
@ -260,6 +317,39 @@ pub enum Camera {
Realistic(RealisticCamera),
}
impl Camera {
pub fn create(
name: &str,
params: &ParameterDictionary,
medium: Medium,
camera_transform: &CameraTransform,
film: Arc<Film>,
loc: &FileLoc,
) -> Result<Self, String> {
match name {
"perspective" => {
let camera =
PerspectiveCamera::create(params, camera_transform, film, medium, loc)?;
Ok(Camera::Perspective(camera))
}
"orthographic" => {
let camera =
OrthographicCamera::create(params, camera_transform, film, medium, loc)?;
Ok(Camera::Orthographic(camera))
}
"realistic" => {
let camera = RealisticCamera::create(params, camera_transform, film, medium, loc)?;
Ok(Camera::Realistic(camera))
}
"spherical" => {
let camera = SphericalCamera::create(params, camera_transform, film, medium, loc)?;
Ok(Camera::Spherical(camera))
}
_ => Err(format!("Camera type '{}' unknown at {}", name, loc)),
}
}
}
#[derive(Debug)]
pub struct LensElementInterface {
pub curvature_radius: Float,

View file

@ -1,21 +1,26 @@
use super::{CameraBase, CameraRay, CameraTrait};
use crate::core::film::FilmTrait;
use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample;
use crate::geometry::{
use super::{CameraBase, CameraBaseParameters, CameraRay, CameraTrait, CameraTransform};
use crate::core::film::{Film, FilmTrait};
use crate::core::geometry::{
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
};
use crate::core::medium::Medium;
use crate::core::options::get_options;
use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample;
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::error::FileLoc;
use crate::utils::parameters::ParameterDictionary;
use crate::utils::sampling::sample_uniform_disk_concentric;
use crate::utils::transform::Transform;
use crate::utils::transform::TransformGeneric;
use std::sync::Arc;
#[derive(Debug)]
pub struct OrthographicCamera {
pub base: CameraBase,
pub screen_from_camera: Transform<Float>,
pub camera_from_raster: Transform<Float>,
pub raster_from_screen: Transform<Float>,
pub screen_from_raster: Transform<Float>,
pub screen_from_camera: TransformGeneric<Float>,
pub camera_from_raster: TransformGeneric<Float>,
pub raster_from_screen: TransformGeneric<Float>,
pub screen_from_raster: TransformGeneric<Float>,
pub lens_radius: Float,
pub focal_distance: Float,
pub dx_camera: Vector3f,
@ -29,23 +34,21 @@ impl OrthographicCamera {
lens_radius: Float,
focal_distance: Float,
) -> Self {
let ndc_from_screen: Transform<Float> = Transform::scale(
let ndc_from_screen: TransformGeneric<Float> = TransformGeneric::scale(
1. / (screen_window.p_max.x() - screen_window.p_min.x()),
1. / (screen_window.p_max.y() - screen_window.p_min.y()),
1.,
) * Transform::translate(Vector3f::new(
-screen_window.p_min.x(),
-screen_window.p_max.y(),
0.,
));
let raster_from_ndc = Transform::scale(
) * TransformGeneric::translate(
Vector3f::new(-screen_window.p_min.x(), -screen_window.p_max.y(), 0.),
);
let raster_from_ndc = TransformGeneric::scale(
base.film.full_resolution().x() as Float,
-base.film.full_resolution().y() as Float,
1.,
);
let raster_from_screen = raster_from_ndc * ndc_from_screen;
let screen_from_raster = raster_from_screen.inverse();
let screen_from_camera = Transform::orthographic(0., 1.);
let screen_from_camera = TransformGeneric::orthographic(0., 1.);
let camera_from_raster = screen_from_camera.inverse() * screen_from_raster;
let dx_camera = camera_from_raster.apply_to_vector(Vector3f::new(1., 0., 0.));
let dy_camera = camera_from_raster.apply_to_vector(Vector3f::new(0., 1., 0.));
@ -66,6 +69,49 @@ impl OrthographicCamera {
dy_camera,
}
}
pub fn create(
params: &ParameterDictionary,
camera_transform: &CameraTransform,
film: Arc<Film>,
medium: Medium,
loc: &FileLoc,
) -> Result<Self, String> {
let full_res = film.full_resolution();
let camera_params =
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
let base = CameraBase::new(camera_params);
let lens_radius = params.get_one_float("lensradius", 0.);
let focal_distance = params.get_one_float("focaldistance", 1e6);
let frame = params.get_one_float(
"frameaspectratio",
full_res.x() as Float / full_res.y() as Float,
);
let mut screen = if frame > 1. {
Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.))
} else {
Bounds2f::from_points(Point2f::new(-1., -1. / frame), Point2f::new(1., 1. / frame))
};
let sw = params.get_float_array("screenwindow");
if !sw.is_empty() {
if get_options().fullscreen {
eprint!("Screenwindow is ignored in fullscreen mode");
} else {
if sw.len() == 4 {
screen = Bounds2f::from_points(
Point2f::new(sw[0], sw[2]),
Point2f::new(sw[1], sw[3]),
);
} else {
return Err(format!("{}: screenwindow param must have four values", loc));
}
}
}
Ok(Self::new(base, screen, lens_radius, focal_distance))
}
}
impl CameraTrait for OrthographicCamera {

View file

@ -1,23 +1,28 @@
use super::{CameraBase, CameraRay, CameraTrait};
use crate::camera;
use crate::core::film::FilmTrait;
use super::{CameraBase, CameraRay, CameraTrait, CameraTransform};
use crate::camera::CameraBaseParameters;
use crate::core::film::{Film, FilmTrait};
use crate::core::filter::FilterTrait;
use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample;
use crate::geometry::{
use crate::core::geometry::{
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
};
use crate::core::medium::Medium;
use crate::core::options::get_options;
use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample;
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::error::FileLoc;
use crate::utils::parameters::ParameterDictionary;
use crate::utils::sampling::sample_uniform_disk_concentric;
use crate::utils::transform::Transform;
use std::sync::Arc;
#[derive(Debug)]
pub struct PerspectiveCamera {
pub base: CameraBase,
pub screen_from_camera: Transform<Float>,
pub camera_from_raster: Transform<Float>,
pub raster_from_screen: Transform<Float>,
pub screen_from_raster: Transform<Float>,
pub screen_from_camera: Transform,
pub camera_from_raster: Transform,
pub raster_from_screen: Transform,
pub screen_from_raster: Transform,
pub lens_radius: Float,
pub focal_distance: Float,
pub dx_camera: Vector3f,
@ -28,12 +33,14 @@ pub struct PerspectiveCamera {
impl PerspectiveCamera {
pub fn new(
base: CameraBase,
screen_from_camera: &Transform<Float>,
fov: Float,
screen_window: Bounds2f,
lens_radius: Float,
focal_distance: Float,
) -> Self {
let ndc_from_screen: Transform<Float> = Transform::scale(
let screen_from_camera =
Transform::perspective(fov, 1e-2, 1000.).expect("Could not create perspective");
let ndc_from_screen = Transform::scale(
1. / (screen_window.p_max.x() - screen_window.p_min.x()),
1. / (screen_window.p_max.y() - screen_window.p_min.y()),
1.,
@ -59,9 +66,10 @@ impl PerspectiveCamera {
let w_corner_camera =
(camera_from_raster.apply_to_point(p_corner) - Point3f::new(0., 0., 0.)).normalize();
let cos_total_width = w_corner_camera.z();
Self {
base,
screen_from_camera: *screen_from_camera,
screen_from_camera,
camera_from_raster,
raster_from_screen,
screen_from_raster,
@ -72,6 +80,50 @@ impl PerspectiveCamera {
cos_total_width,
}
}
pub fn create(
params: &ParameterDictionary,
camera_transform: &CameraTransform,
film: Arc<Film>,
medium: Medium,
loc: &FileLoc,
) -> Result<Self, String> {
let full_res = film.full_resolution();
let camera_params =
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
let base = CameraBase::new(camera_params);
let lens_radius = params.get_one_float("lensradius", 0.);
let focal_distance = params.get_one_float("focaldistance", 1e6);
let frame = params.get_one_float(
"frameaspectratio",
full_res.x() as Float / full_res.y() as Float,
);
let mut screen = if frame > 1. {
Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.))
} else {
Bounds2f::from_points(Point2f::new(-1., -1. / frame), Point2f::new(1., 1. / frame))
};
let sw = params.get_float_array("screenwindow");
if !sw.is_empty() {
if get_options().fullscreen {
eprint!("Screenwindow is ignored in fullscreen mode");
} else {
if sw.len() == 4 {
screen = Bounds2f::from_points(
Point2f::new(sw[0], sw[2]),
Point2f::new(sw[1], sw[3]),
);
} else {
return Err(format!("{}: screenwindow param must have four values", loc));
}
}
}
let fov = params.get_one_float("fov", 90.);
Ok(Self::new(base, fov, screen, lens_radius, focal_distance))
}
}
impl CameraTrait for PerspectiveCamera {

View file

@ -1,21 +1,32 @@
use super::{CameraBase, CameraRay, CameraTrait, ExitPupilSample, LensElementInterface};
use crate::core::film::FilmTrait;
use crate::core::pbrt::{Float, lerp};
use crate::core::sampler::CameraSample;
use crate::geometry::{
Bounds2f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2i, Vector3f, VectorLike,
use super::{
CameraBase, CameraBaseParameters, CameraRay, CameraTrait, CameraTransform, ExitPupilSample,
LensElementInterface,
};
use crate::image::Image;
use crate::PI;
use crate::core::film::{Film, FilmTrait};
use crate::core::geometry::{
Bounds2f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector2i, Vector3f, VectorLike,
};
use crate::core::medium::Medium;
use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample;
use crate::core::scattering::refract;
use crate::image::{Image, PixelFormat};
use crate::spectra::color::SRGB;
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{quadratic, square};
use crate::utils::scattering::refract;
use crate::utils::error::FileLoc;
use crate::utils::file::read_float_file;
use crate::utils::math::{lerp, quadratic, square};
use crate::utils::parameters::ParameterDictionary;
use std::path::Path;
use std::sync::Arc;
#[derive(Debug)]
pub struct RealisticCamera {
base: CameraBase,
focus_distance: Float,
set_aperture_diameter: Float,
aperture_image: Image,
aperture_image: Option<Image>,
element_interface: Vec<LensElementInterface>,
physical_extent: Bounds2f,
exit_pupil_bounds: Vec<Bounds2f>,
@ -23,12 +34,11 @@ pub struct RealisticCamera {
impl RealisticCamera {
pub fn new(
&self,
base: CameraBase,
lens_params: Vec<Float>,
focus_distance: Float,
set_aperture_diameter: Float,
aperture_image: Image,
aperture_image: Option<Image>,
) -> Self {
let aspect =
base.film.full_resolution().x() as Float / base.film.full_resolution().y() as Float;
@ -68,7 +78,7 @@ impl RealisticCamera {
.map(|i| {
let r0 = (i as Float / n_samples as Float) * half_diag;
let r1 = ((i + 1) as Float / n_samples as Float) * half_diag;
self.bound_exit_pupil(r0, r1)
Self::compute_exit_pupil_bounds(&element_interface, r0, r1)
})
.collect();
@ -99,10 +109,14 @@ impl RealisticCamera {
self.element_interface.last().unwrap().aperture_radius
}
pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f {
fn compute_exit_pupil_bounds(
elements: &[LensElementInterface],
film_x_0: Float,
film_x_1: Float,
) -> Bounds2f {
let mut pupil_bounds = Bounds2f::default();
let n_samples = 1024 * 1024;
let rear_radius = self.rear_element_radius();
let rear_radius = elements.last().unwrap().aperture_radius;
let proj_rear_bounds = Bounds2f::from_points(
Point2f::new(-1.5 * rear_radius, -1.5 * rear_radius),
Point2f::new(1.5 * rear_radius, 1.5 * rear_radius),
@ -134,7 +148,6 @@ impl RealisticCamera {
}
}
// Return degenerate bounds if no rays made it through the lens system
if pupil_bounds.is_degenerate() {
print!(
"Unable to find exit pupil in x = {},{} on film.",
@ -143,7 +156,12 @@ impl RealisticCamera {
return pupil_bounds;
}
pupil_bounds.expand(2. * proj_rear_bounds.diagonal().norm() / (n_samples as Float).sqrt())
pupil_bounds.expand(2. * proj_rear_bounds.diagonal().norm() / (n_samples as Float).sqrt());
pupil_bounds
}
pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f {
Self::compute_exit_pupil_bounds(&self.element_interface, film_x_0, film_x_1)
}
pub fn intersect_spherical_element(
@ -291,6 +309,185 @@ impl RealisticCamera {
Some(ExitPupilSample { p_pupil, pdf })
}
pub fn create(
params: &ParameterDictionary,
camera_transform: &CameraTransform,
film: Arc<Film>,
medium: Medium,
loc: &FileLoc,
) -> Result<Self, String> {
let camera_params =
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
let base = CameraBase::new(camera_params);
let aperture_diameter = params.get_one_float("aperturediameter", 1.);
let focal_distance = params.get_one_float("focaldistance", 10.);
let lens_file = params.get_one_string("lensfile", "");
if lens_file.is_empty() {
return Err(format!("{}: No lens file supplied", loc));
}
let lens_params = read_float_file(lens_file.as_str()).map_err(|e| e.to_string())?;
if lens_params.len() % 4 != 0 {
return Err(format!(
"{}: excess values in lens specification file; must be multiple-of-four values, read {}",
loc,
lens_params.len()
));
}
let builtin_res = 256;
let rasterize = |vert: &[Point2f]| -> Image {
let mut image = Image::new(
PixelFormat::F32,
Point2i::new(builtin_res, builtin_res),
&["Y"],
SRGB,
);
let res = image.resolution();
for y in 0..res.y() {
for x in 0..res.x() {
// Map pixel to [-1, 1] range
let p = Point2f::new(
-1.0 + 2.0 * (x as Float + 0.5) / res.x() as Float,
-1.0 + 2.0 * (y as Float + 0.5) / res.y() as Float,
);
let mut winding_number = 0;
// Winding number test against edges
for i in 0..vert.len() {
let i1 = (i + 1) % vert.len();
let v_i = vert[i];
let v_i1 = vert[i1];
let e = (p.x() - v_i.x()) * (v_i1.y() - v_i.y())
- (p.y() - v_i.y()) * (v_i1.x() - v_i.x());
if v_i.y() <= p.y() {
if v_i1.y() > p.y() && e > 0.0 {
winding_number += 1;
}
} else if v_i1.y() <= p.y() && e < 0.0 {
winding_number -= 1;
}
}
image.set_channel(
Point2i::new(x, y),
0,
if winding_number == 0 { 0.0 } else { 1.0 },
);
}
}
image
};
let aperture_name = params.get_one_string("aperture", "");
let mut aperture_image: Option<Image> = None;
if !aperture_name.is_empty() {
match aperture_name.as_str() {
"gaussian" => {
let mut img = Image::new(
PixelFormat::F32,
Point2i::new(builtin_res, builtin_res),
&["Y"],
SRGB,
);
let res = img.resolution();
for y in 0..res.y() {
for x in 0..res.x() {
let uv = Point2f::new(
-1.0 + 2.0 * (x as Float + 0.5) / res.x() as Float,
-1.0 + 2.0 * (y as Float + 0.5) / res.y() as Float,
);
let r2 = square(uv.x()) + square(uv.y());
let sigma2 = 1.0;
let v = ((-r2 / sigma2).exp() - (-1.0 / sigma2).exp()).max(0.0);
img.set_channel(Point2i::new(x, y), 0, v);
}
}
aperture_image = Some(img);
}
"square" => {
let mut img = Image::new(
PixelFormat::F32,
Point2i::new(builtin_res, builtin_res),
&["Y"],
SRGB,
);
let low = (0.25 * builtin_res as Float) as i32;
let high = (0.75 * builtin_res as Float) as i32;
for y in low..high {
for x in low..high {
img.set_channel(Point2i::new(x, y), 0, 4.0);
}
}
aperture_image = Some(img);
}
"pentagon" => {
let c1 = (5.0f32.sqrt() - 1.0) / 4.0;
let c2 = (5.0f32.sqrt() + 1.0) / 4.0;
let s1 = (10.0 + 2.0 * 5.0f32.sqrt()).sqrt() / 4.0;
let s2 = (10.0 - 2.0 * 5.0f32.sqrt()).sqrt() / 4.0;
let mut vert = [
Point2f::new(0.0, 1.0),
Point2f::new(s1, c1),
Point2f::new(s2, -c2),
Point2f::new(-s2, -c2),
Point2f::new(-s1, c1),
];
for v in vert.iter_mut() {
*v = Point2f::from(Vector2f::from(*v) * 0.8);
}
aperture_image = Some(rasterize(&vert));
}
"star" => {
let mut vert = Vec::with_capacity(10);
for i in 0..10 {
let r = if i % 2 == 1 {
1.0
} else {
(72.0f32.to_radians().cos()) / (36.0f32.to_radians().cos())
};
let angle = PI * i as Float / 5.0;
vert.push(Point2f::new(r * angle.cos(), r * angle.sin()));
}
vert.reverse();
aperture_image = Some(rasterize(&vert));
}
_ => {
if let Ok(im) = Image::read(Path::new(&aperture_name), None) {
if im.image.n_channels() > 1 {
let mut mono =
Image::new(PixelFormat::F32, im.image.resolution(), &["Y"], SRGB);
let res = mono.resolution();
for y in 0..res.y() {
for x in 0..res.x() {
let avg =
im.image.get_channels_default(Point2i::new(x, y)).average();
mono.set_channel(Point2i::new(x, y), 0, avg);
}
}
aperture_image = Some(mono);
} else {
aperture_image = Some(im.image);
}
}
}
}
}
Ok(Self::new(
base,
lens_params,
focal_distance,
aperture_diameter,
aperture_image,
))
}
}
impl CameraTrait for RealisticCamera {

View file

@ -0,0 +1,135 @@
use super::{CameraBase, CameraBaseParameters, CameraRay, CameraTrait, CameraTransform};
use crate::core::film::{Film, FilmTrait};
use crate::core::geometry::{Bounds2f, Point2f, Point3f, Ray, Vector3f, spherical_direction};
use crate::core::medium::Medium;
use crate::core::options::get_options;
use crate::core::pbrt::{Float, PI};
use crate::core::sampler::CameraSample;
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::error::FileLoc;
use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square};
use crate::utils::parameters::ParameterDictionary;
use std::sync::Arc;
#[derive(Debug, PartialEq)]
pub enum Mapping {
EquiRectangular,
EqualArea,
}
#[derive(Debug)]
pub struct SphericalCamera {
pub base: CameraBase,
pub screen: Bounds2f,
pub lens_radius: Float,
pub focal_distance: Float,
pub mapping: Mapping,
}
impl SphericalCamera {
pub fn create(
params: &ParameterDictionary,
camera_transform: &CameraTransform,
film: Arc<Film>,
medium: Medium,
loc: &FileLoc,
) -> Result<Self, String> {
let full_res = film.full_resolution();
let camera_params =
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
let base = CameraBase::new(camera_params);
let lens_radius = params.get_one_float("lensradius", 0.);
let focal_distance = params.get_one_float("focaldistance", 1e30);
let frame = params.get_one_float(
"frameaspectratio",
full_res.x() as Float / full_res.y() as Float,
);
let mut screen = if frame > 1. {
Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.))
} else {
Bounds2f::from_points(Point2f::new(-1., -1. / frame), Point2f::new(1., 1. / frame))
};
let sw = params.get_float_array("screenwindow");
if !sw.is_empty() {
if get_options().fullscreen {
eprint!("Screenwindow is ignored in fullscreen mode");
} else {
if sw.len() == 4 {
screen = Bounds2f::from_points(
Point2f::new(sw[0], sw[2]),
Point2f::new(sw[1], sw[3]),
);
} else {
return Err(format!("{}: screenwindow param must have four values", loc));
}
}
}
let m = params.get_one_string("mapping", "equalarea");
let mapping = match m.as_str() {
"equal_area" => Mapping::EqualArea,
"equirectangular" => Mapping::EquiRectangular,
_ => {
return Err(format!(
"{}: unknown mapping for spherical camera at {}",
m, loc
));
}
};
Ok(Self {
mapping,
base,
screen,
lens_radius,
focal_distance,
})
}
}
impl CameraTrait for SphericalCamera {
fn base(&self) -> &CameraBase {
&self.base
}
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
self.base.init_metadata(metadata)
}
fn generate_ray(
&self,
sample: CameraSample,
_lamdba: &SampledWavelengths,
) -> Option<CameraRay> {
// Compute spherical camera ray direction
let mut uv = Point2f::new(
sample.p_film.x() / self.base().film.full_resolution().x() as Float,
sample.p_film.y() / self.base().film.full_resolution().y() as Float,
);
let dir: Vector3f;
if self.mapping == Mapping::EquiRectangular {
// Compute ray direction using equirectangular mapping
let theta = PI * uv[1];
let phi = 2. * PI * uv[0];
dir = spherical_direction(theta.sin(), theta.cos(), phi);
} else {
// Compute ray direction using equal area mapping
uv = wrap_equal_area_square(&mut uv);
dir = equal_area_square_to_sphere(uv);
}
std::mem::swap(&mut dir.y(), &mut dir.z());
let ray = Ray::new(
Point3f::new(0., 0., 0.),
dir,
Some(self.sample_time(sample.time)),
self.base().medium.clone(),
);
Some(CameraRay {
ray: self.render_from_camera(&ray, &mut None),
weight: SampledSpectrum::default(),
})
}
}

View file

@ -1,6 +1,6 @@
use crate::core::geometry::{Bounds3f, Point3f, Ray, Vector3f};
use crate::core::pbrt::{Float, find_interval};
use crate::core::primitive::PrimitiveTrait;
use crate::geometry::{Bounds3f, Point3f, Ray, Vector3f};
use crate::shapes::ShapeIntersection;
use crate::utils::math::encode_morton_3;
use crate::utils::math::next_float_down;

View file

@ -1,7 +1,7 @@
use crate::core::bxdf::BSDF;
use crate::core::interaction::{InteractionData, ShadingGeometry, SurfaceInteraction};
use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Point3fi, Vector3f};
use crate::core::interaction::{InteractionData, Shadinggeom, SurfaceInteraction};
use crate::core::pbrt::{Float, PI};
use crate::geometry::{Frame, Normal3f, Point2f, Point3f, Point3fi, Vector3f};
use crate::shapes::Shape;
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum};
use crate::utils::math::{catmull_rom_weights, square};
@ -10,10 +10,10 @@ use enum_dispatch::enum_dispatch;
use std::sync::Arc;
#[derive(Debug)]
pub struct BSSRDFSample<'a> {
pub struct BSSRDFSample {
pub sp: SampledSpectrum,
pub pdf: SampledSpectrum,
pub sw: BSDF<'a>,
pub sw: BSDF,
pub wo: Vector3f,
}
@ -76,7 +76,7 @@ impl From<&SubsurfaceInteraction> for SurfaceInteraction {
dpdv: ssi.dpdv,
dndu: Normal3f::zero(),
dndv: Normal3f::zero(),
shading: ShadingGeometry {
shading: Shadinggeom {
n: ssi.ns,
dpdu: ssi.dpdus,
dpdv: ssi.dpdvs,
@ -138,27 +138,27 @@ pub struct BSSRDFProbeSegment {
#[enum_dispatch]
pub trait BSSRDFTrait: Send + Sync + std::fmt::Debug {
fn sample_sp(&self, u1: Float, u2: Point2f) -> Option<BSSRDFProbeSegment>;
fn probe_intersection_to_sample(&self, si: &SubsurfaceInteraction) -> BSSRDFSample<'_>;
fn probe_intersection_to_sample(&self, si: &SubsurfaceInteraction) -> BSSRDFSample;
}
#[enum_dispatch(BSSRDFTrait)]
#[derive(Debug, Clone)]
pub enum BSSRDF<'a> {
Tabulated(TabulatedBSSRDF<'a>),
pub enum BSSRDF {
Tabulated(TabulatedBSSRDF),
}
#[derive(Clone, Debug)]
pub struct TabulatedBSSRDF<'a> {
pub struct TabulatedBSSRDF {
po: Point3f,
wo: Vector3f,
ns: Normal3f,
eta: Float,
sigma_t: SampledSpectrum,
rho: SampledSpectrum,
table: &'a BSSRDFTable,
table: Arc<BSSRDFTable>,
}
impl<'a> TabulatedBSSRDF<'a> {
impl TabulatedBSSRDF {
pub fn new(
po: Point3f,
wo: Vector3f,
@ -166,7 +166,7 @@ impl<'a> TabulatedBSSRDF<'a> {
eta: Float,
sigma_a: &SampledSpectrum,
sigma_s: &SampledSpectrum,
table: &'a BSSRDFTable,
table: Arc<BSSRDFTable>,
) -> Self {
let sigma_t = *sigma_a + *sigma_s;
let rho = SampledSpectrum::safe_div(sigma_s, &sigma_t);
@ -299,7 +299,7 @@ impl<'a> TabulatedBSSRDF<'a> {
}
}
impl<'a> BSSRDFTrait for TabulatedBSSRDF<'a> {
impl BSSRDFTrait for TabulatedBSSRDF {
fn sample_sp(&self, u1: Float, u2: Point2f) -> Option<BSSRDFProbeSegment> {
let f = if u1 < 0.25 {
Frame::from_x(self.ns.into())
@ -321,7 +321,7 @@ impl<'a> BSSRDFTrait for TabulatedBSSRDF<'a> {
})
}
fn probe_intersection_to_sample(&self, _si: &SubsurfaceInteraction) -> BSSRDFSample<'_> {
fn probe_intersection_to_sample(&self, _si: &SubsurfaceInteraction) -> BSSRDFSample {
todo!()
}
}

View file

@ -7,32 +7,31 @@ use std::sync::{Arc, RwLock};
use enum_dispatch::enum_dispatch;
use crate::core::medium::{HGPhaseFunction, PhaseFunctionTrait};
use crate::core::options::get_options;
use crate::core::pbrt::{Float, INV_2_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2, clamp_t};
use crate::geometry::{
use crate::core::geometry::{
Frame, Normal3f, Point2f, Vector3f, VectorLike, abs_cos_theta, cos_theta, same_hemisphere,
spherical_direction, spherical_theta,
};
use crate::spectra::{
N_SPECTRUM_SAMPLES, RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths,
use crate::core::medium::{HGPhaseFunction, PhaseFunctionTrait};
use crate::core::options::get_options;
use crate::core::pbrt::{Float, INV_2_PI, INV_4_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2};
use crate::core::scattering::{
TrowbridgeReitzDistribution, fr_complex, fr_complex_from_spectrum, fr_dielectric, reflect,
refract,
};
use crate::spectra::{
N_SPECTRUM_SAMPLES, RGB, RGBColorSpace, RGBUnboundedSpectrum, SampledSpectrum,
SampledWavelengths,
};
use crate::utils::color::RGB;
use crate::utils::colorspace::RGBColorSpace;
use crate::utils::hash::hash_buffer;
use crate::utils::math::{
fast_exp, i0, log_i0, radians, safe_acos, safe_asin, safe_sqrt, sample_discrete, square,
trimmed_logistic,
clamp, fast_exp, i0, lerp, log_i0, radians, safe_acos, safe_asin, safe_sqrt, sample_discrete,
square, trimmed_logistic,
};
use crate::utils::rng::Rng;
use crate::utils::sampling::{
PiecewiseLinear2D, cosine_hemisphere_pdf, power_heuristic, sample_cosine_hemisphere,
sample_exponential, sample_trimmed_logistic, sample_uniform_hemisphere, uniform_hemisphere_pdf,
};
use crate::utils::scattering::{
TrowbridgeReitzDistribution, fr_complex, fr_complex_from_spectrum, fr_dielectric, reflect,
refract,
};
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -209,12 +208,6 @@ impl ThinDielectricBxDF {
}
}
#[derive(Debug, Clone)]
pub struct CoatedDiffuseBxDF;
#[derive(Debug, Clone)]
pub struct CoatedConductorBxDF;
static P_MAX: usize = 3;
#[derive(Debug, Clone)]
pub struct HairBxDF {
@ -494,17 +487,19 @@ pub enum BxDF {
Conductor(ConductorBxDF),
Measured(MeasuredBxDF),
Hair(HairBxDF),
CoatedDiffuse(CoatedDiffuseBxDF),
CoatedConductor(CoatedConductorBxDF),
// DiffuseTransmission(DiffuseTransmissionBxDF),
}
#[derive(Debug, Default)]
pub struct BSDF<'a> {
bxdf: Option<&'a mut dyn BxDFTrait>,
pub struct BSDF {
bxdf: Option<BxDF>,
shading_frame: Frame,
}
impl<'a> BSDF<'a> {
pub fn new(ns: Normal3f, dpdus: Vector3f, bxdf: Option<&'a mut dyn BxDFTrait>) -> Self {
impl BSDF {
pub fn new(ns: Normal3f, dpdus: Vector3f, bxdf: Option<BxDF>) -> Self {
Self {
bxdf,
shading_frame: Frame::new(dpdus.normalize(), Vector3f::from(ns)),
@ -1724,7 +1719,7 @@ where
}
continue;
}
z = clamp_t(zp, 0.0, self.thickness);
z = clamp(zp, 0.0, self.thickness);
}
if z == exit_z {
@ -1861,158 +1856,10 @@ where
let hash0 = hash_buffer(&[get_options().seed as Float, wo.x(), wo.y(), wo.z()], 0);
let hash1 = hash_buffer(&[wi.x(), wi.y(), wi.z()], 0);
let mut rng = Rng::new_with_offset(hash0, hash1);
let mut r = || -> Float { rng.uniform::<Float>().min(ONE_MINUS_EPSILON) };
let f_args = FArgs {
mode,
sample_flags: BxDFReflTransFlags::TRANSMISSION,
};
let refl_args = FArgs {
mode,
sample_flags: BxDFReflTransFlags::REFLECTION,
};
let inters = (enter_interface, exit_interface, non_exit_interface);
for _ in 0..self.n_samples {
// Sample random walk through layers to estimate BSDF value
let mut uc = r();
let Some(wos) = enter_interface
.sample_f(wo, uc, Point2f::new(r(), r()), f_args)
.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
else {
continue;
};
uc = r();
let Some(wis) = exit_interface
.sample_f(wo, uc, Point2f::new(r(), r()), f_args)
.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
else {
continue;
};
// Declare state for random walk through BSDF layers
let mut beta = wos.f * abs_cos_theta(wos.wi) / wos.pdf;
let mut z = if entered_top { self.thickness } else { 0. };
let mut w = wos.wi;
let phase = HGPhaseFunction::new(self.g);
for depth in 0..self.max_depth {
if depth > 3 && beta.max_component_value() < 0.25 {
let q = (1. - beta.max_component_value()).max(0.);
if r() < q {
break;
}
beta /= 1. - q;
}
// Account for media between layers and possibly scatter
if !self.albedo.is_black() {
z = if z == self.thickness {
0.
} else {
self.thickness
};
beta *= self.tr(self.thickness, w);
} else {
let sigma_t = 1.;
let dz = sample_exponential(r(), sigma_t / w.z().abs());
let zp = if w.z() > 0. { z + dz } else { z - dz };
if 0. < zp && zp < self.thickness {
// Handle scattering event in layered BSDF medium
let mut wt = 1.;
if exit_interface.flags().is_specular() {
wt = power_heuristic(1, wis.pdf, 1, phase.pdf(-w, wis.wi));
}
f += beta
* self.albedo
* phase.p(-wi, -wis.wi)
* wt
* self.tr(zp - exit_z, wis.wi)
* wis.f
/ wis.pdf;
// Sample phase function and update layered path state
let u = Point2f::new(r(), r());
let Some(ps) = phase
.sample_p(-w, u)
.filter(|s| s.pdf > 0.0 && s.wi.z() != 0.0)
else {
continue;
};
beta *= self.albedo * ps.p / ps.pdf;
w = ps.wi;
z = zp;
if z < exit_z && w.z() > 0.
|| z > exit_z && w.z() < 0. && exit_interface.flags().is_specular()
{
let f_exit = exit_interface.f(-w, -wi, mode);
if !f_exit.is_black() {
let exit_pdf = exit_interface.pdf(-w, wi, f_args);
let wt = power_heuristic(1, ps.pdf, 1, exit_pdf);
f += beta * self.tr(zp - exit_z, ps.wi) * f_exit * wt;
}
}
continue;
}
z = clamp_t(zp, 0., self.thickness);
}
// Account for scattering at appropriate interface
if z == exit_z {
let uc = r();
let u = Point2f::new(r(), r());
let Some(bs) = exit_interface
.sample_f(-w, uc, u, refl_args)
.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
else {
continue;
};
beta *= bs.f * abs_cos_theta(bs.wi) / bs.pdf;
w = bs.wi;
} else {
if !non_exit_interface.flags().is_specular() {
let mut wt = 1.;
if exit_interface.flags().is_specular() {
wt = power_heuristic(
1,
wis.pdf,
1,
non_exit_interface.pdf(-w, -wis.wi, refl_args),
);
}
f += beta
* non_exit_interface.f(-w, -wis.wi, mode)
* abs_cos_theta(wis.wi)
* wt
* self.tr(self.thickness, wis.wi)
* wis.f
/ wis.pdf;
}
// Sample new direction using BSDF at _nonExitInterface_
let uc = r();
let u = Point2f::new(r(), r());
let Some(bs) = non_exit_interface
.sample_f(-w, uc, u, refl_args)
.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
else {
continue;
};
beta *= bs.f * abs_cos_theta(bs.wi) / bs.pdf;
w = bs.wi;
if !exit_interface.flags().is_specular() {
let f_exit = exit_interface.f(-w, wi, mode);
if !f_exit.is_black() {
let mut wt = 1.;
if non_exit_interface.flags().is_specular() {
let exit_pdf = exit_interface.pdf(-w, wi, f_args);
wt = power_heuristic(1, bs.pdf, 1, exit_pdf);
}
f += beta * self.tr(self.thickness, bs.wi) * f_exit * wt;
}
}
}
}
f += self.evaluate_sample(wo, wi, mode, entered_top, exit_z, inters.clone(), &mut rng)
}
f / self.n_samples as Float
@ -2020,16 +1867,251 @@ where
fn sample_f(
&self,
_wo: Vector3f,
_uc: Float,
_u: Point2f,
_f_args: FArgs,
mut wo: Vector3f,
uc: Float,
u: Point2f,
f_args: FArgs,
) -> Option<BSDFSample> {
todo!()
let mut flip_wi = false;
if TWO_SIDED && wo.z() < 0. {
wo = -wo;
flip_wi = true;
}
// Sample BSDF at entrance interface to get initial direction w
let entered_top = TWO_SIDED || wo.z() > 0.;
let bs_raw = if entered_top {
self.top.sample_f(wo, uc, u, f_args)
} else {
self.bottom.sample_f(wo, uc, u, f_args)
};
let mut bs = bs_raw.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)?;
if bs.is_reflective() {
if flip_wi {
bs.wi = -bs.wi;
}
bs.pdf_is_proportional = true;
return Some(bs);
}
let mut w = bs.wi;
let mut specular_path = bs.is_specular();
// Declare RNG for layered BSDF sampling
let hash0 = hash_buffer(&[get_options().seed as Float, wo.x(), wo.y(), wo.z()], 0);
let hash1 = hash_buffer(&[uc, u.x(), u.y()], 0);
let mut rng = Rng::new_with_offset(hash0, hash1);
let mut r = || rng.uniform::<Float>().min(ONE_MINUS_EPSILON);
// Declare common variables for layered BSDF sampling
let mut f = bs.f * abs_cos_theta(bs.wi);
let mut pdf = bs.pdf;
let mut z = if entered_top { self.thickness } else { 0. };
let phase = HGPhaseFunction::new(self.g);
for depth in 0..self.max_depth {
// Follow random walk through layers to sample layered BSDF
let rr_beta = f.max_component_value() / pdf;
if depth > 3 && rr_beta < 0.25 {
let q = (1. - rr_beta).max(0.);
if r() < q {
return None;
}
pdf *= 1. - q;
}
if w.z() < 0. {
return None;
}
if !self.albedo.is_black() {
let sigma_t = 1.;
let dz = sample_exponential(r(), sigma_t / abs_cos_theta(w));
let zp = if w.z() > 0. { z + dz } else { z - dz };
if zp > 0. && zp < self.thickness {
let Some(ps) = phase
.sample_p(-wo, Point2f::new(r(), r()))
.filter(|s| s.pdf == 0. && s.wi.z() == 0.)
else {
continue;
};
f *= self.albedo * ps.p;
pdf *= ps.pdf;
specular_path = false;
w = ps.wi;
z = zp;
continue;
}
z = clamp(zp, 0., self.thickness);
} else {
// Advance to the other layer interface
z = if z == self.thickness {
0.
} else {
self.thickness
};
f *= self.tr(self.thickness, w);
}
let interface = if z == 0. {
TopOrBottom::Bottom(&self.bottom)
} else {
TopOrBottom::Top(&self.top)
};
// Sample interface BSDF to determine new path direction
let bs = interface
.sample_f(-w, r(), Point2f::new(r(), r()), f_args)
.filter(|s| s.f.is_black() && s.pdf == 0. && s.wi.z() == 0.)?;
f *= bs.f;
pdf *= bs.pdf;
specular_path &= bs.is_specular();
w = bs.wi;
// Return BSDFSample if path has left the layers
if bs.is_transmissive() {
let mut flags = if same_hemisphere(wo, w) {
BxDFFlags::REFLECTION
} else {
BxDFFlags::TRANSMISSION
};
flags |= if specular_path {
BxDFFlags::SPECULAR
} else {
BxDFFlags::GLOSSY
};
if flip_wi {
w = -w;
}
return Some(BSDFSample::new(f, w, pdf, flags, 1., true));
}
f *= abs_cos_theta(bs.wi);
}
None
}
fn pdf(&self, _wo: Vector3f, _wi: Vector3f, _f_args: FArgs) -> Float {
todo!()
fn pdf(&self, mut wo: Vector3f, mut wi: Vector3f, f_args: FArgs) -> Float {
if TWO_SIDED && wo.z() < 0. {
wo = -wo;
wi = -wi;
}
let hash0 = hash_buffer(&[get_options().seed as Float, wi.x(), wi.y(), wi.z()], 0);
let hash1 = hash_buffer(&[wo.x(), wo.y(), wo.z()], 0);
let mut rng = Rng::new_with_offset(hash0, hash1);
let mut r = || rng.uniform::<Float>().min(ONE_MINUS_EPSILON);
let entered_top = TWO_SIDED || wo.z() > 0.;
let refl_args = FArgs {
mode: f_args.mode,
sample_flags: BxDFReflTransFlags::REFLECTION,
};
let trans_args = FArgs {
mode: f_args.mode,
sample_flags: BxDFReflTransFlags::TRANSMISSION,
};
let mut pdf_sum = 0.;
if same_hemisphere(wo, wi) {
pdf_sum += if entered_top {
self.n_samples as Float * self.top.pdf(wo, wi, refl_args)
} else {
self.n_samples as Float * self.bottom.pdf(wo, wi, refl_args)
};
}
for _ in 0..self.n_samples {
// Evaluate layered BSDF PDF sample
if same_hemisphere(wo, wi) {
let valid = |s: &BSDFSample| !s.f.is_black() && s.pdf > 0.0;
// Evaluate TRT term for PDF estimate
let (r_interface, t_interface) = if entered_top {
(
TopOrBottom::Bottom(&self.bottom),
TopOrBottom::Top(&self.top),
)
} else {
(
TopOrBottom::Top(&self.top),
TopOrBottom::Bottom(&self.bottom),
)
};
if let (Some(wos), Some(wis)) = (
t_interface
.sample_f(wo, r(), Point2f::new(r(), r()), trans_args)
.filter(valid),
t_interface
.sample_f(wi, r(), Point2f::new(r(), r()), trans_args)
.filter(valid),
) {
if !t_interface.flags().is_non_specular() {
pdf_sum += r_interface.pdf(-wos.wi, -wis.wi, f_args);
} else if let Some(rs) = r_interface
.sample_f(-wos.wi, r(), Point2f::new(r(), r()), f_args)
.filter(valid)
{
if !r_interface.flags().is_non_specular() {
pdf_sum += t_interface.pdf(-rs.wi, wi, trans_args);
} else {
let r_pdf = r_interface.pdf(-wos.wi, -wis.wi, f_args);
let t_pdf = t_interface.pdf(-rs.wi, wi, f_args);
pdf_sum += power_heuristic(1, wis.pdf, 1, r_pdf) * r_pdf;
pdf_sum += power_heuristic(1, rs.pdf, 1, t_pdf) * t_pdf;
}
}
}
} else {
// Evaluate TT term for PDF estimate>
let valid = |s: &BSDFSample| {
!s.f.is_black() && s.pdf > 0.0 && s.wi.z() > 0. || s.is_reflective()
};
let (to_interface, ti_interface) = if entered_top {
(
TopOrBottom::Top(&self.top),
TopOrBottom::Bottom(&self.bottom),
)
} else {
(
TopOrBottom::Bottom(&self.bottom),
TopOrBottom::Top(&self.top),
)
};
let Some(wos) = to_interface
.sample_f(wi, r(), Point2f::new(r(), r()), trans_args)
.filter(valid)
else {
continue;
};
let Some(wis) = to_interface
.sample_f(wi, r(), Point2f::new(r(), r()), trans_args)
.filter(valid)
else {
continue;
};
if to_interface.flags().is_specular() {
pdf_sum += ti_interface.pdf(-wos.wi, wi, f_args);
} else if ti_interface.flags().is_specular() {
pdf_sum += to_interface.pdf(wo, -wis.wi, f_args);
} else {
pdf_sum += (to_interface.pdf(wo, -wis.wi, f_args)
+ ti_interface.pdf(-wos.wi, wi, f_args))
/ 2.;
}
}
}
lerp(0.9, INV_4_PI, pdf_sum / self.n_samples as Float)
}
fn regularize(&mut self) {
@ -2041,3 +2123,6 @@ where
todo!()
}
}
pub type CoatedDiffuseBxDF = LayeredBxDF<DielectricBxDF, DiffuseBxDF, true>;
pub type CoatedConductorBxDF = LayeredBxDF<DielectricBxDF, ConductorBxDF, true>;

View file

@ -1,26 +1,31 @@
use crate::camera::CameraTransform;
use crate::core::filter::{Filter, FilterTrait};
use crate::core::interaction::SurfaceInteraction;
use crate::core::pbrt::Float;
use crate::geometry::{
Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Vector2f, Vector2fi,
use crate::core::geometry::{
Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Tuple, Vector2f, Vector2fi,
Vector2i, Vector3f,
};
use crate::core::interaction::SurfaceInteraction;
use crate::core::pbrt::Float;
use crate::image::{Image, ImageChannelDesc, ImageChannelValues, ImageMetadata, PixelFormat};
use crate::spectra::sampled::{LAMBDA_MAX, LAMBDA_MIN};
use crate::spectra::color::{MatrixMulColor, SRGB, Triplet, white_balance};
use crate::spectra::data::generate_cie_d;
use crate::spectra::{
ConstantSpectrum, DenselySampledSpectrum, N_SPECTRUM_SAMPLES, SampledSpectrum,
SampledWavelengths, Spectrum, SpectrumTrait, cie_x, cie_y, cie_z,
ConstantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES,
PiecewiseLinearSpectrum, RGB, RGBColorSpace, SampledSpectrum, SampledWavelengths, Spectrum,
SpectrumProvider, XYZ, cie_x, cie_y, cie_z, colorspace, get_named_spectrum,
};
use crate::utils::AtomicFloat;
use crate::utils::color::{MatrixMulColor, RGB, SRGB, Triplet, XYZ, white_balance};
use crate::utils::colorspace::RGBColorSpace;
use crate::utils::containers::Array2D;
use crate::utils::math::SquareMatrix;
use crate::utils::error::FileLoc;
use crate::utils::math::linear_least_squares;
use crate::utils::math::{SquareMatrix, wrap_equal_area_square};
use crate::utils::parameters::ParameterDictionary;
use crate::utils::sampling::VarianceEstimator;
use crate::utils::transform::AnimatedTransform;
use enum_dispatch::enum_dispatch;
use rayon::prelude::*;
use std::sync::{Arc, atomic::AtomicUsize, atomic::Ordering};
use std::path::Path;
use std::sync::{Arc, OnceLock, atomic::AtomicUsize, atomic::Ordering};
use once_cell::sync::Lazy;
use std::error::Error;
@ -96,19 +101,26 @@ impl RGBFilm {
pixels: Arc::new(pixels_array),
}
}
}
#[derive(Clone, Debug)]
pub struct GBufferBFilm {
pub base: FilmBase,
output_from_render: AnimatedTransform,
apply_inverse: bool,
pixels: Arc<Array2D<GBufferPixel>>,
colorspace: RGBColorSpace,
max_component_value: Float,
write_fp16: bool,
filter_integral: Float,
output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
pub fn create(
params: &ParameterDictionary,
exposure_time: Float,
filter: Filter,
_camera_transform: Option<CameraTransform>,
loc: &FileLoc,
) -> Result<Self, String> {
let colorspace = params.color_space.as_ref().unwrap();
let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY);
let write_fp16 = params.get_one_bool("savefp16", true);
let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?;
let film_base = FilmBase::new(params, filter, Some(sensor), loc);
Ok(RGBFilm::new(
film_base,
&colorspace,
max_component_value,
write_fp16,
))
}
}
#[derive(Debug, Default)]
@ -128,19 +140,229 @@ struct GBufferPixel {
}
#[derive(Clone, Debug)]
pub struct SpectralFilm {
pub struct GBufferFilm {
pub base: FilmBase,
output_from_render: AnimatedTransform,
pixels: Array2D<SpectralPixel>,
apply_inverse: bool,
pixels: Arc<Array2D<GBufferPixel>>,
colorspace: RGBColorSpace,
max_component_value: Float,
write_fp16: bool,
filter_integral: Float,
output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
}
#[derive(Clone, Debug)]
struct SpectralPixel;
impl GBufferFilm {
pub fn new(
base: &FilmBase,
output_from_render: &AnimatedTransform,
apply_inverse: bool,
colorspace: &RGBColorSpace,
max_component_value: Float,
write_fp16: bool,
) -> Self {
assert!(!base.pixel_bounds.is_empty());
let output_rgbf_from_sensor_rgb =
colorspace.rgb_from_xyz * base.sensor.as_ref().unwrap().xyz_from_sensor_rgb;
let filter_integral = base.filter.integral();
let pixels = Array2D::new(base.pixel_bounds);
Self {
base: base.clone(),
output_from_render: output_from_render.clone(),
apply_inverse,
pixels: pixels.into(),
colorspace: colorspace.clone(),
max_component_value,
write_fp16,
filter_integral,
output_rgbf_from_sensor_rgb,
}
}
pub fn create(
params: &ParameterDictionary,
exposure_time: Float,
filter: Filter,
camera_transform: Option<CameraTransform>,
loc: &FileLoc,
) -> Result<Self, String> {
let colorspace = params.color_space.as_ref().unwrap();
let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY);
let write_fp16 = params.get_one_bool("savefp16", true);
let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?;
let film_base = FilmBase::new(params, filter, Some(sensor), loc);
if Path::new(&film_base.filename).extension() != Some("exr".as_ref()) {
return Err(format!("{}: EXR is the only format supported by GBufferFilm", loc).into());
}
let coords_system = params.get_one_string("coordinatesystem", "camera");
let mut apply_inverse = false;
let camera_transform = camera_transform
.ok_or_else(|| "GBufferFilm requires a camera_transform".to_string())?;
let output_from_render = if coords_system == "camera" {
apply_inverse = true;
camera_transform.render_from_camera
} else if coords_system == "world" {
AnimatedTransform::from_transform(&camera_transform.world_from_render)
} else {
return Err(format!(
"{}: unknown coordinate system for GBufferFilm. (Expecting camera
or world",
loc
)
.into());
};
Ok(GBufferFilm::new(
&film_base,
&output_from_render,
apply_inverse,
colorspace,
max_component_value,
write_fp16,
))
}
}
#[derive(Default, Debug)]
pub struct SpectralPixel {
pub rgb_sum: [AtomicFloat; 3],
pub rgb_weigh_sum: AtomicFloat,
pub rgb_splat: [AtomicFloat; 3],
pub bucket_offset: usize,
}
pub struct SpectralPixelView<'a> {
pub metadata: &'a SpectralPixel,
pub bucket_sums: &'a [f64],
pub weight_sums: &'a [f64],
pub bucket_splats: &'a [AtomicFloat],
}
#[derive(Debug)]
pub struct SpectralFilm {
pub base: FilmBase,
pub colorspace: RGBColorSpace,
pub lambda_min: Float,
pub lambda_max: Float,
pub n_buckets: usize,
pub max_component_value: Float,
pub write_fp16: bool,
pub filter_integral: Float,
pub pixels: Array2D<SpectralPixel>,
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
pub bucket_sums: Vec<f64>,
pub weight_sums: Vec<f64>,
pub bucket_splats: Vec<AtomicFloat>,
}
impl SpectralFilm {
pub fn new(
base: &FilmBase,
lambda_min: Float,
lambda_max: Float,
n_buckets: usize,
colorspace: &RGBColorSpace,
max_component_value: Float,
write_fp16: bool,
) -> Self {
assert!(!base.pixel_bounds.is_empty());
let sensor = &base.sensor;
let output_rgbf_from_sensor_rgb =
colorspace.rgb_from_xyz * sensor.as_ref().unwrap().xyz_from_sensor_rgb;
let n_pixels = base.pixel_bounds.area() as usize;
let total_bucket_count = n_pixels * n_buckets;
let bucket_sums = vec![0.0; total_bucket_count];
let weight_sums = vec![0.0; total_bucket_count];
let filter_integral = base.filter.integral();
let bucket_splats: Vec<AtomicFloat> = (0..total_bucket_count)
.map(|_| AtomicFloat::new(0.0))
.collect();
let mut pixels = Array2D::<SpectralPixel>::new(base.pixel_bounds);
for i in 0..n_pixels {
pixels.get_linear_mut(i).bucket_offset = i * n_buckets;
}
Self {
base: base.clone(),
lambda_min,
lambda_max,
n_buckets,
pixels,
bucket_sums,
weight_sums,
bucket_splats,
colorspace: colorspace.clone(),
max_component_value,
write_fp16,
filter_integral,
output_rgbf_from_sensor_rgb,
}
}
pub fn create(
params: &ParameterDictionary,
exposure_time: Float,
filter: Filter,
_camera_transform: Option<CameraTransform>,
loc: &FileLoc,
) -> Result<Self, String> {
let colorspace = params.color_space.as_ref().unwrap();
let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY);
let write_fp16 = params.get_one_bool("savefp16", true);
let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?;
let film_base = FilmBase::new(params, filter, Some(sensor), loc);
if Path::new(&film_base.filename).extension() != Some("exr".as_ref()) {
return Err(format!("{}: EXR is the only format supported by GBufferFilm", loc).into());
}
let n_buckets = params.get_one_int("nbuckets", 16) as usize;
let lambda_min = params.get_one_float("lambdamin", LAMBDA_MIN as Float);
let lambda_max = params.get_one_float("lambdamin", LAMBDA_MAX as Float);
if lambda_min < LAMBDA_MIN as Float && lambda_max > LAMBDA_MAX as Float {
return Err(format!(
"{}: PBRT must be recompiled with different values of LAMBDA_MIN and LAMBDA_MAX",
loc
));
}
Ok(SpectralFilm::new(
&film_base,
lambda_min,
lambda_max,
n_buckets,
colorspace,
max_component_value,
write_fp16,
))
}
pub fn get_pixel_view(&self, p: Point2i) -> SpectralPixelView {
let metadata = &self.pixels[p];
let start = metadata.bucket_offset;
let end = start + self.n_buckets;
SpectralPixelView {
metadata,
bucket_sums: &self.bucket_sums[start..end],
weight_sums: &self.weight_sums[start..end],
bucket_splats: &self.bucket_splats[start..end],
}
}
}
const N_SWATCH_REFLECTANCES: usize = 24;
static SWATCH_REFLECTANCES: Lazy<[Spectrum; N_SWATCH_REFLECTANCES]> =
Lazy::new(|| std::array::from_fn(|_| Spectrum::Constant(ConstantSpectrum::new(0.8))));
static SWATCH_REFLECTANCES: Lazy<[Spectrum; N_SWATCH_REFLECTANCES]> = Lazy::new(|| {
std::array::from_fn(|i| {
let raw_data = crate::core::cie::SWATCHES_RAW[i];
let pls = PiecewiseLinearSpectrum::from_interleaved(raw_data, false);
Spectrum::PiecewiseLinear(pls)
})
});
#[derive(Debug, Clone)]
pub struct PixelSensor {
@ -153,22 +375,30 @@ pub struct PixelSensor {
impl PixelSensor {
pub fn new(
r: &Spectrum,
g: &Spectrum,
b: &Spectrum,
output_colorspace: RGBColorSpace,
sensor_illum: &Spectrum,
r: Arc<Spectrum>,
g: Arc<Spectrum>,
b: Arc<Spectrum>,
output_colorspace: Arc<RGBColorSpace>,
sensor_illum: Option<Arc<Spectrum>>,
imaging_ratio: Float,
) -> Result<Self, Box<dyn Error>> {
let r_bar = DenselySampledSpectrum::from_spectrum(r, LAMBDA_MIN, LAMBDA_MAX);
let g_bar = DenselySampledSpectrum::from_spectrum(g, LAMBDA_MIN, LAMBDA_MAX);
let b_bar = DenselySampledSpectrum::from_spectrum(b, LAMBDA_MIN, LAMBDA_MAX);
// As seen in usages of this constructos, sensor_illum can be null
// Going with the colorspace's own illuminant, but this might not be the right choice
// TODO: Test this
let illum: &Spectrum = match &sensor_illum {
Some(arc_illum) => &**arc_illum,
None => &output_colorspace.illuminant,
};
let r_bar = DenselySampledSpectrum::from_spectrum(&r);
let g_bar = DenselySampledSpectrum::from_spectrum(&g);
let b_bar = DenselySampledSpectrum::from_spectrum(&b);
let mut rgb_camera = [[0.; 3]; N_SWATCH_REFLECTANCES];
for i in 0..N_SWATCH_REFLECTANCES {
let rgb = Self::project_reflectance::<RGB>(
&SWATCH_REFLECTANCES[i],
sensor_illum,
illum,
&Spectrum::DenselySampled(r_bar.clone()),
&Spectrum::DenselySampled(g_bar.clone()),
&Spectrum::DenselySampled(b_bar.clone()),
@ -179,8 +409,8 @@ impl PixelSensor {
}
let mut xyz_output = [[0.; 3]; N_SWATCH_REFLECTANCES];
let sensor_white_g = sensor_illum.inner_product(&Spectrum::DenselySampled(g_bar.clone()));
let sensor_white_y = sensor_illum.inner_product(cie_y());
let sensor_white_g = illum.inner_product(&Spectrum::DenselySampled(g_bar.clone()));
let sensor_white_y = illum.inner_product(cie_y());
for i in 0..N_SWATCH_REFLECTANCES {
let s = SWATCH_REFLECTANCES[i].clone();
let xyz = Self::project_reflectance::<XYZ>(
@ -207,13 +437,13 @@ impl PixelSensor {
}
pub fn new_with_white_balance(
output_colorspace: RGBColorSpace,
sensor_illum: Option<Spectrum>,
output_colorspace: Arc<RGBColorSpace>,
sensor_illum: Option<Arc<Spectrum>>,
imaging_ratio: Float,
) -> Self {
let r_bar = DenselySampledSpectrum::from_spectrum(cie_x(), LAMBDA_MIN, LAMBDA_MAX);
let g_bar = DenselySampledSpectrum::from_spectrum(cie_y(), LAMBDA_MIN, LAMBDA_MAX);
let b_bar = DenselySampledSpectrum::from_spectrum(cie_z(), LAMBDA_MIN, LAMBDA_MAX);
let r_bar = DenselySampledSpectrum::from_spectrum(cie_x());
let g_bar = DenselySampledSpectrum::from_spectrum(cie_y());
let b_bar = DenselySampledSpectrum::from_spectrum(cie_z());
let xyz_from_sensor_rgb: SquareMatrix<Float, 3>;
if let Some(illum) = sensor_illum {
@ -233,6 +463,66 @@ impl PixelSensor {
}
}
pub fn create(
params: &ParameterDictionary,
output_colorspace: Arc<RGBColorSpace>,
exposure_time: Float,
loc: &FileLoc,
) -> Result<Self, String> {
let iso = params.get_one_float("iso", 100.);
let mut white_balance_temp = params.get_one_float("whitebalance", 0.);
let sensor_name = params.get_one_string("sensor", "cie1931");
if sensor_name != "cie1931" && white_balance_temp == 0. {
white_balance_temp = 6500.;
}
let imaging_ratio = exposure_time * iso / 100.;
let d_illum = if white_balance_temp == 0. {
generate_cie_d(6500.)
} else {
generate_cie_d(white_balance_temp)
};
let sensor_illum: Option<Arc<Spectrum>> = if white_balance_temp != 0. {
Some(Arc::new(Spectrum::DenselySampled(d_illum)))
} else {
None
};
if sensor_name == "cie1931" {
return Ok(PixelSensor::new_with_white_balance(
output_colorspace,
sensor_illum,
imaging_ratio,
));
} else {
let r_opt = get_named_spectrum(&format!("{}_r", sensor_name));
let g_opt = get_named_spectrum(&format!("{}_g", sensor_name));
let b_opt = get_named_spectrum(&format!("{}_b", sensor_name));
if r_opt.is_none() || g_opt.is_none() || b_opt.is_none() {
return Err(format!(
"{}: unknown sensor type '{}' (missing RGB spectral data)",
loc, sensor_name
)
.into());
}
let r = Arc::new(r_opt.unwrap());
let g = Arc::new(g_opt.unwrap());
let b = Arc::new(b_opt.unwrap());
return PixelSensor::new(
r,
g,
b,
output_colorspace.clone(),
sensor_illum,
imaging_ratio,
)
.map_err(|e| e.to_string());
}
}
pub fn project_reflectance<T: Triplet>(
refl: &Spectrum,
illum: &Spectrum,
@ -328,29 +618,71 @@ pub struct FilmBase {
impl FilmBase {
pub fn new(
full_resolution: Point2i,
pixel_bounds: Bounds2i,
params: &ParameterDictionary,
filter: Filter,
diagonal: Float,
sensor: Option<PixelSensor>,
filename: String,
loc: &FileLoc,
) -> Self {
let x_res = params.get_one_int("xresolution", 1280);
let y_res = params.get_one_int("yresolution", 720);
if x_res <= 0 || y_res <= 0 {
eprintln!(
"{}: Film resolution must be > 0. Defaulting to 1280x720.",
loc
);
}
let full_resolution = Point2i::new(x_res.max(1), y_res.max(1));
let crop_data = params.get_float_array("cropwindow");
let crop = if crop_data.len() == 4 {
Bounds2f::from_points(
Point2f::new(crop_data[0], crop_data[2]),
Point2f::new(crop_data[1], crop_data[3]),
)
} else {
Bounds2f::from_points(Point2f::zero(), Point2f::new(1.0, 1.0))
};
let p_min = Point2i::new(
(full_resolution.x() as Float * crop.p_min.x()).ceil() as i32,
(full_resolution.y() as Float * crop.p_min.y()).ceil() as i32,
);
let p_max = Point2i::new(
(full_resolution.x() as Float * crop.p_max.x()).ceil() as i32,
(full_resolution.y() as Float * crop.p_max.y()).ceil() as i32,
);
let mut pixel_bounds = Bounds2i::from_points(p_min, p_max);
if pixel_bounds.is_empty() {
eprintln!("{}: Film crop window results in empty pixel bounds.", loc);
}
let rad = filter.radius();
let expansion = Point2i::new(rad.x().ceil() as i32, rad.y().ceil() as i32);
pixel_bounds = pixel_bounds.expand(expansion);
let diagonal_mm = params.get_one_float("diagonal", 35.0);
let filename = params.get_one_string("filename", "pbrt.exr");
Self {
full_resolution,
pixel_bounds,
filter,
diagonal: 0.001 * diagonal,
diagonal: diagonal_mm * 0.001,
sensor,
filename,
}
}
}
#[enum_dispatch]
pub trait FilmTrait: Sync {
fn base(&self) -> &FilmBase;
fn base_mut(&mut self) -> &mut FilmBase;
fn add_sample(
&mut self,
&self,
_p_filme: Point2i,
_l: SampledSpectrum,
_lambda: &SampledWavelengths,
@ -487,13 +819,46 @@ pub trait FilmTrait: Sync {
}
}
#[derive(Clone, Debug)]
#[enum_dispatch(FilmTrait)]
#[derive(Debug)]
pub enum Film {
RGB(RGBFilm),
GBuffer(GBufferBFilm),
GBuffer(GBufferFilm),
Spectral(SpectralFilm),
}
impl Film {
pub fn create(
name: &str,
params: &ParameterDictionary,
exposure_time: Float,
filter: Filter,
camera_transform: Option<CameraTransform>,
loc: &FileLoc,
) -> Result<Self, String> {
match name {
"rgb" => {
let film = RGBFilm::create(params, exposure_time, filter, camera_transform, loc)?;
Ok(Film::RGB(film))
}
"gbuffer" => {
let film =
GBufferFilm::create(params, exposure_time, filter, camera_transform, loc)?;
Ok(Film::GBuffer(film))
}
"spectral" => {
let film =
SpectralFilm::create(params, exposure_time, filter, camera_transform, loc)?;
Ok(Film::Spectral(film))
}
_ => Err(format!("Film type '{}' unknown at {}", name, loc)),
}
}
}
impl FilmTrait for RGBFilm {
fn base(&self) -> &FilmBase {
&self.base
@ -504,7 +869,7 @@ impl FilmTrait for RGBFilm {
}
fn add_sample(
&mut self,
&self,
p_film: Point2i,
l: SampledSpectrum,
lambda: &SampledWavelengths,
@ -606,7 +971,7 @@ impl FilmTrait for RGBFilm {
}
}
impl FilmTrait for GBufferBFilm {
impl FilmTrait for GBufferFilm {
fn base(&self) -> &FilmBase {
&self.base
}
@ -700,29 +1065,3 @@ impl FilmTrait for SpectralFilm {
true
}
}
impl FilmTrait for Film {
fn base(&self) -> &FilmBase {
match self {
Film::RGB(film) => film.base(),
Film::GBuffer(film) => film.base(),
Film::Spectral(film) => film.base(),
}
}
fn base_mut(&mut self) -> &mut FilmBase {
match self {
Film::RGB(film) => film.base_mut(),
Film::GBuffer(film) => film.base_mut(),
Film::Spectral(film) => film.base_mut(),
}
}
fn uses_visible_surface(&self) -> bool {
match self {
Film::RGB(film) => film.uses_visible_surface(),
Film::GBuffer(film) => film.uses_visible_surface(),
Film::Spectral(film) => film.uses_visible_surface(),
}
}
}

View file

@ -1,7 +1,9 @@
use crate::core::pbrt::{Float, lerp};
use crate::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f};
use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f};
use crate::core::pbrt::Float;
use crate::utils::containers::Array2D;
use crate::utils::math::{gaussian, gaussian_integral, sample_tent, windowed_sinc};
use crate::utils::error::FileLoc;
use crate::utils::math::{gaussian, gaussian_integral, lerp, sample_tent, windowed_sinc};
use crate::utils::parameters::ParameterDictionary;
use crate::utils::sampling::PiecewiseConstant2D;
use enum_dispatch::enum_dispatch;
@ -76,6 +78,34 @@ pub enum Filter {
Triangle(TriangleFilter),
}
impl Filter {
pub fn create(name: &str, params: ParameterDictionary, loc: &FileLoc) -> Result<Self, String> {
match name {
"box" => {
let filter = BoxFilter::create(&params, loc);
Ok(Filter::Box(filter))
}
"gaussian" => {
let filter = GaussianFilter::create(&params, loc);
Ok(Filter::Gaussian(filter))
}
"mitchell" => {
let filter = MitchellFilter::create(&params, loc);
Ok(Filter::Mitchell(filter))
}
"sinc" => {
let filter = LanczosSincFilter::create(&params, loc);
Ok(Filter::LanczosSinc(filter))
}
"triangle" => {
let filter = TriangleFilter::create(&params, loc);
Ok(Filter::Triangle(filter))
}
_ => Err(format!("Film type '{}' unknown at {}", name, loc)),
}
}
}
#[derive(Clone, Debug)]
pub struct BoxFilter {
pub radius: Vector2f,
@ -85,6 +115,12 @@ impl BoxFilter {
pub fn new(radius: Vector2f) -> Self {
Self { radius }
}
pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self {
let xw = params.get_one_float("xradius", 0.5);
let yw = params.get_one_float("yradius", 0.5);
Self::new(Vector2f::new(xw, yw))
}
}
impl FilterTrait for BoxFilter {
@ -141,6 +177,13 @@ impl GaussianFilter {
sampler,
}
}
pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self {
let xw = params.get_one_float("xradius", 1.5);
let yw = params.get_one_float("yradius", 1.5);
let sigma = params.get_one_float("sigma", 0.5);
Self::new(Vector2f::new(xw, yw), sigma)
}
}
impl FilterTrait for GaussianFilter {
@ -189,6 +232,14 @@ impl MitchellFilter {
}
}
pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self {
let xw = params.get_one_float("xradius", 2.);
let yw = params.get_one_float("yradius", 2.);
let b = params.get_one_float("B", 1. / 3.);
let c = params.get_one_float("C", 1. / 3.);
Self::new(Vector2f::new(xw, yw), b, c)
}
fn mitchell_1d_eval(b: Float, c: Float, x: Float) -> Float {
let x = x.abs();
if x <= 1.0 {
@ -250,6 +301,13 @@ impl LanczosSincFilter {
sampler,
}
}
pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self {
let xw = params.get_one_float("xradius", 4.);
let yw = params.get_one_float("yradius", 4.);
let tau = params.get_one_float("tau", 3.);
Self::new(Vector2f::new(xw, yw), tau)
}
}
impl FilterTrait for LanczosSincFilter {
@ -299,6 +357,12 @@ impl TriangleFilter {
pub fn new(radius: Vector2f) -> Self {
Self { radius }
}
pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self {
let xw = params.get_one_float("xradius", 2.);
let yw = params.get_one_float("yradius", 2.);
Self::new(Vector2f::new(xw, yw))
}
}
impl FilterTrait for TriangleFilter {

View file

@ -87,12 +87,12 @@ where
d.0.iter().fold(T::one(), |acc, &val| acc * val)
}
pub fn expand(&self, delta: T) -> Self {
let mut p_min = self.p_min;
let mut p_max = self.p_max;
p_min = p_min - Vector::fill(delta);
p_max = p_max + Vector::fill(delta);
Self { p_min, p_max }
pub fn expand(&self, delta: impl Into<Vector<T, N>>) -> Self {
let v = delta.into();
Self {
p_min: self.p_min - v,
p_max: self.p_max + v,
}
}
pub fn lerp(&self, t: Point<T, N>) -> Point<T, N> {

View file

@ -1,6 +1,6 @@
use super::{Bounds3f, Float, PI, Point3f, Vector3f, VectorLike};
use crate::utils::math::{degrees, safe_acos, safe_asin, safe_sqrt, square};
use crate::utils::transform::Transform;
use crate::utils::transform::TransformGeneric;
#[derive(Debug, Clone)]
pub struct DirectionCone {
@ -110,7 +110,7 @@ impl DirectionCone {
return DirectionCone::entire_sphere();
}
let w = Transform::rotate_around_axis(degrees(theta_r), wr).apply_to_vector(a.w);
let w = TransformGeneric::rotate_around_axis(degrees(theta_r), wr).apply_to_vector(a.w);
DirectionCone::new(w, theta_o.cos())
}
}

View file

@ -13,8 +13,8 @@ pub use self::primitives::{
pub use self::ray::{Ray, RayDifferential};
pub use self::traits::{Lerp, Sqrt, Tuple, VectorLike};
use crate::core::pbrt::{Float, PI, clamp_t};
use crate::utils::math::square;
use crate::core::pbrt::{Float, PI};
use crate::utils::math::{clamp, square};
use num_traits::Float as NumFloat;
#[inline]
@ -61,7 +61,7 @@ pub fn cos_phi(w: Vector3f) -> Float {
if sin_theta == 0. {
1.
} else {
clamp_t(w.x() / sin_theta, -1., 1.)
clamp(w.x() / sin_theta, -1., 1.)
}
}
@ -71,7 +71,7 @@ pub fn sin_phi(w: Vector3f) -> Float {
if sin_theta == 0. {
0.
} else {
clamp_t(w.y() / sin_theta, -1., 1.)
clamp(w.y() / sin_theta, -1., 1.)
}
}
@ -113,7 +113,7 @@ pub fn spherical_quad_area(a: Vector3f, b: Vector3f, c: Vector3f, d: Vector3f) -
}
pub fn spherical_theta(v: Vector3f) -> Float {
clamp_t(v.z(), -1.0, 1.0).acos()
clamp(v.z(), -1.0, 1.0).acos()
}
pub fn spherical_phi(v: Vector3f) -> Float {

View file

@ -1,7 +1,7 @@
use super::traits::{Sqrt, Tuple, VectorLike};
use super::{Float, NumFloat, PI, clamp_t};
use super::{Float, NumFloat, PI};
use crate::utils::interval::Interval;
use crate::utils::math::{difference_of_products, quadratic, safe_asin};
use crate::utils::math::{clamp, difference_of_products, quadratic, safe_asin};
use num_traits::{AsPrimitive, FloatConst, Num, Signed, Zero};
use std::hash::{Hash, Hasher};
use std::iter::Sum;
@ -10,12 +10,15 @@ use std::ops::{
};
// N-dimensional displacement
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Vector<T, const N: usize>(pub [T; N]);
// N-dimensional location
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Point<T, const N: usize>(pub [T; N]);
// N-dimensional surface normal
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Normal<T, const N: usize>(pub [T; N]);
@ -55,6 +58,16 @@ macro_rules! impl_tuple_core {
}
}
impl<T, const N: usize> From<T> for $Struct<T, N>
where
T: Copy,
{
#[inline]
fn from(scalar: T) -> Self {
Self([scalar; N])
}
}
impl<const N: usize> $Struct<f32, N> {
#[inline]
pub fn floor(&self) -> $Struct<i32, N> {
@ -272,6 +285,23 @@ macro_rules! impl_float_vector_ops {
};
}
macro_rules! impl_tuple_conversions {
($Struct:ident) => {
impl<T: NumFloat> From<(T, T, T)> for $Struct<T, 3> {
fn from(tuple: (T, T, T)) -> Self {
Self([tuple.0, tuple.1, tuple.2])
}
}
// Allow converting (x, y) -> Vector<T, 2>
impl<T: NumFloat> From<(T, T)> for $Struct<T, 2> {
fn from(tuple: (T, T)) -> Self {
Self([tuple.0, tuple.1])
}
}
};
}
macro_rules! impl_abs {
($Struct:ident) => {
impl<T, const N: usize> $Struct<T, N>
@ -351,6 +381,11 @@ impl_accessors!(Vector);
impl_accessors!(Point);
impl_accessors!(Normal);
// Convert from tuple of Floats, for parsing issues
impl_tuple_conversions!(Vector);
impl_tuple_conversions!(Point);
impl_tuple_conversions!(Normal);
impl<T: Copy, const N: usize> From<Vector<T, N>> for Normal<T, N> {
fn from(v: Vector<T, N>) -> Self {
Self(v.0)
@ -448,9 +483,11 @@ pub type Vector3<T> = Vector<T, 3>;
pub type Vector3f = Vector3<Float>;
pub type Vector3i = Vector3<i32>;
pub type Vector3fi = Vector3<Interval>;
pub type Normal2<T> = Normal<T, 2>;
pub type Normal3<T> = Normal<T, 3>;
pub type Normal3f = Normal3<Float>;
pub type Normal3i = Normal3<i32>;
pub type Vector4<T> = Vector<T, 4>;
impl<T: Copy> Vector2<T> {
pub fn new(x: T, y: T) -> Self {
@ -462,6 +499,12 @@ impl<T: Copy> Point2<T> {
Self([x, y])
}
}
impl<T: Copy> Normal2<T> {
pub fn new(x: T, y: T) -> Self {
Self([x, y])
}
}
impl<T: Copy> Vector3<T> {
pub fn new(x: T, y: T, z: T) -> Self {
Self([x, y, z])
@ -477,6 +520,11 @@ impl<T: Copy> Normal3<T> {
Self([x, y, z])
}
}
impl<T: Copy> Vector4<T> {
pub fn new(x: T, y: T, z: T, w: T) -> Self {
Self([x, y, z, w])
}
}
// Vector operations
impl<T> Vector3<T>
@ -786,7 +834,7 @@ impl OctahedralVector {
#[inline]
pub fn encode(f: Float) -> u16 {
(clamp_t((f + 1.0) / 2.0, 0.0, 1.0) * 65535.0).round() as u16
(clamp((f + 1.0) / 2.0, 0.0, 1.0) * 65535.0).round() as u16
}
}

View file

@ -1,22 +1,22 @@
use crate::camera::{Camera, CameraTrait};
use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::{BSDF, BxDFFlags, DiffuseBxDF};
use crate::core::bxdf::{BSDF, BxDF, BxDFFlags, DiffuseBxDF};
use crate::core::geometry::{
Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike,
};
use crate::core::material::{
Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map,
};
use crate::core::medium::{Medium, MediumInterface, PhaseFunction};
use crate::core::options::get_options;
use crate::core::pbrt::{Float, clamp_t};
use crate::core::pbrt::Float;
use crate::core::sampler::{Sampler, SamplerTrait};
use crate::core::texture::{FloatTexture, UniversalTextureEvaluator};
use crate::geometry::{
Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike,
};
use crate::image::Image;
use crate::lights::{Light, LightTrait};
use crate::shapes::Shape;
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{difference_of_products, square};
use crate::utils::math::{clamp, difference_of_products, square};
use bumpalo::Bump;
use enum_dispatch::enum_dispatch;
@ -172,7 +172,7 @@ impl InteractionTrait for SimpleInteraction {
}
#[derive(Default, Clone, Debug)]
pub struct ShadingGeometry {
pub struct Shadinggeom {
pub n: Normal3f,
pub dpdu: Vector3f,
pub dpdv: Vector3f,
@ -188,7 +188,7 @@ pub struct SurfaceInteraction {
pub dpdv: Vector3f,
pub dndu: Normal3f,
pub dndv: Normal3f,
pub shading: ShadingGeometry,
pub shading: Shadinggeom,
pub face_index: usize,
pub area_light: Option<Arc<Light>>,
pub material: Option<Arc<Material>>,
@ -273,22 +273,22 @@ impl SurfaceInteraction {
self.dvdy = difference_of_products(ata00, atb1y, ata01, atb0y) * inv_det;
// Clamp derivatives
self.dudx = if self.dudx.is_finite() {
clamp_t(self.dudx, -1e8, 1e8)
clamp(self.dudx, -1e8, 1e8)
} else {
0.
};
self.dvdx = if self.dvdx.is_finite() {
clamp_t(self.dvdx, -1e8, 1e8)
clamp(self.dvdx, -1e8, 1e8)
} else {
0.
};
self.dudy = if self.dudy.is_finite() {
clamp_t(self.dudy, -1e8, 1e8)
clamp(self.dudy, -1e8, 1e8)
} else {
0.
};
self.dvdy = if self.dvdy.is_finite() {
clamp_t(self.dvdy, -1e8, 1e8)
clamp(self.dvdy, -1e8, 1e8)
} else {
0.
};
@ -304,14 +304,13 @@ impl SurfaceInteraction {
}
}
pub fn get_bsdf<'a>(
pub fn get_bsdf(
&mut self,
r: &Ray,
lambda: &SampledWavelengths,
camera: &Camera,
scratch: &'a Bump,
sampler: &mut Sampler,
) -> Option<BSDF<'a>> {
) -> Option<BSDF> {
self.compute_differentials(r, camera, sampler.samples_per_pixel() as i32);
let material = {
@ -332,13 +331,13 @@ impl SurfaceInteraction {
let normal_map = material.get_normal_map();
if displacement.is_some() || normal_map.is_some() {
// This calls the function defined above
self.compute_bump_geometry(&tex_eval, displacement, normal_map);
self.compute_bump_geom(&tex_eval, displacement, normal_map);
}
let mut bsdf = material.get_bxdf(&tex_eval, &ctx, lambda, scratch);
let mut bsdf = material.get_bsdf(&tex_eval, &ctx, lambda);
if get_options().force_diffuse {
let r = bsdf.rho_wo(self.common.wo, &[sampler.get1d()], &[sampler.get2d()]);
let diff_bxdf = scratch.alloc(DiffuseBxDF::new(r));
let diff_bxdf = BxDF::Diffuse(DiffuseBxDF::new(r));
bsdf = BSDF::new(self.shading.n, self.shading.dpdu, Some(diff_bxdf));
}
Some(bsdf)
@ -350,7 +349,7 @@ impl SurfaceInteraction {
lambda: &SampledWavelengths,
_camera: &Camera,
_scratch: &Bump,
) -> Option<BSSRDF<'_>> {
) -> Option<BSSRDF> {
let material = {
let root_mat = self.material.as_deref()?;
let mut active_mat: &Material = root_mat;
@ -368,17 +367,17 @@ impl SurfaceInteraction {
material.get_bssrdf(&tex_eval, &ctx, lambda)
}
fn compute_bump_geometry(
fn compute_bump_geom(
&mut self,
tex_eval: &UniversalTextureEvaluator,
displacement: Option<FloatTexture>,
normal_image: Option<&Image>,
normal_image: Option<Arc<Image>>,
) {
let ctx = NormalBumpEvalContext::from(&*self);
let (dpdu, dpdv) = if let Some(disp) = displacement {
bump_map(tex_eval, &disp, &ctx)
} else if let Some(map) = normal_image {
normal_map(map, &ctx)
normal_map(map.as_ref(), &ctx)
} else {
(self.shading.dpdu, self.shading.dpdv)
};
@ -388,7 +387,7 @@ impl SurfaceInteraction {
ns = -ns;
}
self.set_shading_geometry(ns, dpdu, dpdv, self.shading.dndu, self.shading.dndv, false);
self.set_shading_geom(ns, dpdu, dpdv, self.shading.dndu, self.shading.dndv, false);
}
pub fn spawn_ray_with_differentials(
@ -544,7 +543,7 @@ impl SurfaceInteraction {
dpdv,
dndu,
dndv,
shading: ShadingGeometry {
shading: Shadinggeom {
n: shading_n,
dpdu,
dpdv,
@ -581,7 +580,7 @@ impl SurfaceInteraction {
si
}
pub fn set_shading_geometry(
pub fn set_shading_geom(
&mut self,
ns: Normal3f,
dpdus: Vector3f,

View file

@ -4,16 +4,21 @@ use std::ops::Deref;
use std::sync::Arc;
use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::{BSDF, DielectricBxDF, DiffuseBxDF};
use crate::core::bxdf::{
BSDF, BxDF, CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF,
};
use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike};
use crate::core::interaction::InteractionTrait;
use crate::core::interaction::{Interaction, ShadingGeometry, SurfaceInteraction};
use crate::core::interaction::{Interaction, Shadinggeom, SurfaceInteraction};
use crate::core::pbrt::Float;
use crate::core::texture::{FloatTexture, SpectrumTexture, TextureEvalContext, TextureEvaluator};
use crate::geometry::{Frame, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike};
use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::texture::{
FloatTexture, FloatTextureTrait, SpectrumTexture, TextureEvalContext, TextureEvaluator,
};
use crate::image::{Image, WrapMode, WrapMode2D};
use crate::spectra::{SampledWavelengths, Spectrum, SpectrumTrait};
use crate::spectra::{SampledSpectrum, SampledWavelengths, Spectrum, SpectrumProvider};
use crate::utils::hash::hash_float;
use crate::utils::scattering::TrowbridgeReitzDistribution;
use crate::utils::math::clamp;
#[derive(Clone, Debug)]
pub struct MaterialEvalContext {
@ -47,7 +52,7 @@ pub struct NormalBumpEvalContext {
p: Point3f,
uv: Point2f,
n: Normal3f,
shading: ShadingGeometry,
shading: Shadinggeom,
dpdx: Vector3f,
dpdy: Vector3f,
// All 0
@ -148,23 +153,22 @@ pub fn bump_map<T: TextureEvaluator>(
#[enum_dispatch]
pub trait MaterialTrait: Send + Sync + std::fmt::Debug {
fn get_bxdf<'a, T: TextureEvaluator>(
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
scratch: &'a Bump,
) -> BSDF<'a>;
) -> BSDF;
fn get_bssrdf<'a, T: TextureEvaluator>(
fn get_bssrdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> Option<BSSRDF<'a>>;
) -> Option<BSSRDF>;
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool;
fn get_normal_map(&self) -> Option<&Image>;
fn get_normal_map(&self) -> Option<Arc<Image>>;
fn get_displacement(&self) -> Option<FloatTexture>;
fn has_surface_scattering(&self) -> bool;
}
@ -186,100 +190,354 @@ pub enum Material {
}
#[derive(Clone, Debug)]
pub struct CoatedDiffuseMaterial;
pub struct CoatedDiffuseMaterial {
displacement: FloatTexture,
normal_map: Option<Arc<Image>>,
reflectance: SpectrumTexture,
albedo: SpectrumTexture,
u_roughness: FloatTexture,
v_roughness: FloatTexture,
thickness: FloatTexture,
g: FloatTexture,
eta: Spectrum,
remap_roughness: bool,
max_depth: usize,
n_samples: usize,
}
impl CoatedDiffuseMaterial {
#[allow(clippy::too_many_arguments)]
pub fn new(
reflectance: SpectrumTexture,
u_roughness: FloatTexture,
v_roughness: FloatTexture,
thickness: FloatTexture,
albedo: SpectrumTexture,
g: FloatTexture,
eta: Spectrum,
displacement: FloatTexture,
normal_map: Option<Arc<Image>>,
remap_roughness: bool,
max_depth: usize,
n_samples: usize,
) -> Self {
Self {
displacement,
normal_map,
reflectance,
albedo,
u_roughness,
v_roughness,
thickness,
g,
eta,
remap_roughness,
max_depth,
n_samples,
}
}
}
impl MaterialTrait for CoatedDiffuseMaterial {
fn get_bxdf<'a, T: TextureEvaluator>(
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSDF {
let r = SampledSpectrum::clamp(
&tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda),
0.,
1.,
);
let mut u_rough = tex_eval.evaluate_float(&self.u_roughness, ctx);
let mut v_rough = tex_eval.evaluate_float(&self.v_roughness, ctx);
if self.remap_roughness {
u_rough = TrowbridgeReitzDistribution::roughness_to_alpha(u_rough);
v_rough = TrowbridgeReitzDistribution::roughness_to_alpha(v_rough);
}
let distrib = TrowbridgeReitzDistribution::new(u_rough, v_rough);
let thick = tex_eval.evaluate_float(&self.thickness, ctx);
let mut sampled_eta = self.eta.evaluate(lambda[0]);
if self.eta.is_constant() {
let mut lambda = *lambda;
lambda.terminate_secondary_inplace();
}
if sampled_eta == 0. {
sampled_eta = 1.
}
let a = SampledSpectrum::clamp(
&tex_eval.evaluate_spectrum(&self.albedo, ctx, lambda),
0.,
1.,
);
let gg = clamp(tex_eval.evaluate_float(&self.g, ctx), -1., 1.);
let bxdf = BxDF::CoatedDiffuse(CoatedDiffuseBxDF::new(
DielectricBxDF::new(sampled_eta, distrib),
DiffuseBxDF::new(r),
thick,
a,
gg,
self.max_depth,
self.n_samples,
));
BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf))
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
_scratch: &'a Bump,
) -> BSDF<'a> {
todo!()
}
fn get_bssrdf<'a, T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF<'a>> {
todo!()
) -> Option<BSSRDF> {
None
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(
&[
&self.u_roughness,
&self.v_roughness,
&self.thickness,
&self.g,
],
&[&self.reflectance, &self.albedo],
)
}
fn get_normal_map(&self) -> Option<&Image> {
todo!()
fn get_normal_map(&self) -> Option<Arc<Image>> {
self.normal_map.clone()
}
fn get_displacement(&self) -> Option<FloatTexture> {
todo!()
Some(self.displacement.clone())
}
fn has_surface_scattering(&self) -> bool {
todo!()
false
}
}
#[derive(Clone, Debug)]
pub struct CoatedConductorMaterial;
pub struct CoatedConductorMaterial {
displacement: FloatTexture,
normal_map: Option<Arc<Image>>,
interface_uroughness: FloatTexture,
interface_vroughness: FloatTexture,
thickness: FloatTexture,
interface_eta: Spectrum,
g: FloatTexture,
albedo: SpectrumTexture,
conductor_uroughness: FloatTexture,
conductor_vroughness: FloatTexture,
conductor_eta: Option<SpectrumTexture>,
k: Option<SpectrumTexture>,
reflectance: SpectrumTexture,
remap_roughness: bool,
max_depth: usize,
n_samples: usize,
}
impl CoatedConductorMaterial {
#[allow(clippy::too_many_arguments)]
pub fn new(
displacement: FloatTexture,
normal_map: Option<Arc<Image>>,
interface_uroughness: FloatTexture,
interface_vroughness: FloatTexture,
thickness: FloatTexture,
interface_eta: Spectrum,
g: FloatTexture,
albedo: SpectrumTexture,
conductor_uroughness: FloatTexture,
conductor_vroughness: FloatTexture,
conductor_eta: Option<SpectrumTexture>,
k: Option<SpectrumTexture>,
reflectance: SpectrumTexture,
remap_roughness: bool,
max_depth: usize,
n_samples: usize,
) -> Self {
Self {
displacement,
normal_map,
interface_uroughness,
interface_vroughness,
thickness,
interface_eta,
g,
albedo,
conductor_uroughness,
conductor_vroughness,
conductor_eta,
k,
reflectance,
remap_roughness,
max_depth,
n_samples,
}
}
}
impl MaterialTrait for CoatedConductorMaterial {
fn get_bxdf<'a, T: TextureEvaluator>(
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSDF {
let mut iurough = tex_eval.evaluate_float(&self.interface_uroughness, ctx);
let mut ivrough = tex_eval.evaluate_float(&self.interface_vroughness, ctx);
if self.remap_roughness {
iurough = TrowbridgeReitzDistribution::roughness_to_alpha(iurough);
ivrough = TrowbridgeReitzDistribution::roughness_to_alpha(ivrough);
}
let interface_distrib = TrowbridgeReitzDistribution::new(iurough, ivrough);
let thick = tex_eval.evaluate_float(&self.thickness, ctx);
let mut ieta = self.interface_eta.evaluate(lambda[0]);
if self.interface_eta.is_constant() {
let mut lambda = *lambda;
lambda.terminate_secondary_inplace();
}
if ieta == 0. {
ieta = 1.;
}
let (mut ce, mut ck) = if let Some(eta_tex) = &self.conductor_eta {
let k_tex = self
.k
.as_ref()
.expect("CoatedConductor: 'k' must be provided if 'conductor_eta' is present");
let ce = tex_eval.evaluate_spectrum(eta_tex, ctx, lambda);
let ck = tex_eval.evaluate_spectrum(k_tex, ctx, lambda);
(ce, ck)
} else {
let r = SampledSpectrum::clamp(
&tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda),
0.,
0.9999,
);
let ce = SampledSpectrum::new(1.0);
let one_minus_r = SampledSpectrum::new(1.) - r;
let ck = 2. * r.sqrt() / SampledSpectrum::clamp_zero(&one_minus_r).sqrt();
(ce, ck)
};
ce /= ieta;
ck /= ieta;
let mut curough = tex_eval.evaluate_float(&self.conductor_uroughness, ctx);
let mut cvrough = tex_eval.evaluate_float(&self.conductor_vroughness, ctx);
if self.remap_roughness {
curough = TrowbridgeReitzDistribution::roughness_to_alpha(curough);
cvrough = TrowbridgeReitzDistribution::roughness_to_alpha(cvrough);
}
let conductor_distrib = TrowbridgeReitzDistribution::new(curough, cvrough);
let a = SampledSpectrum::clamp(
&tex_eval.evaluate_spectrum(&self.albedo, ctx, lambda),
0.,
1.,
);
let gg = clamp(tex_eval.evaluate_float(&self.g, ctx), -1., 1.);
let bxdf = BxDF::CoatedConductor(CoatedConductorBxDF::new(
DielectricBxDF::new(ieta, interface_distrib),
ConductorBxDF::new(&conductor_distrib, ce, ck),
thick,
a,
gg,
self.max_depth,
self.n_samples,
));
BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf))
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
_scratch: &'a Bump,
) -> BSDF<'a> {
todo!()
) -> Option<BSSRDF> {
None
}
fn get_bssrdf<'a, T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF<'a>> {
todo!()
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
let float_textures = [
&self.interface_uroughness,
&self.interface_vroughness,
&self.thickness,
&self.g,
&self.conductor_uroughness,
&self.conductor_vroughness,
];
let mut spectrum_textures = Vec::with_capacity(4);
spectrum_textures.push(&self.albedo);
if let Some(eta) = &self.conductor_eta {
spectrum_textures.push(eta);
}
if let Some(k) = &self.k {
spectrum_textures.push(k);
}
if self.conductor_eta.is_none() {
spectrum_textures.push(&self.reflectance);
}
tex_eval.can_evaluate(&float_textures, &spectrum_textures)
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<&Image> {
todo!()
fn get_normal_map(&self) -> Option<Arc<Image>> {
self.normal_map.clone()
}
fn get_displacement(&self) -> Option<FloatTexture> {
todo!()
Some(self.displacement.clone())
}
fn has_surface_scattering(&self) -> bool {
todo!()
false
}
}
#[derive(Clone, Debug)]
pub struct ConductorMaterial;
impl MaterialTrait for ConductorMaterial {
fn get_bxdf<'a, T: TextureEvaluator>(
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
_scratch: &'a Bump,
) -> BSDF<'a> {
) -> BSDF {
todo!()
}
fn get_bssrdf<'a, T>(
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF<'a>> {
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<&Image> {
fn get_normal_map(&self) -> Option<Arc<Image>> {
todo!()
}
fn get_displacement(&self) -> Option<FloatTexture> {
@ -300,13 +558,12 @@ pub struct DielectricMaterial {
}
impl MaterialTrait for DielectricMaterial {
fn get_bxdf<'a, T: TextureEvaluator>(
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
scratch: &'a Bump,
) -> BSDF<'a> {
) -> BSDF {
let mut sampled_eta = self.eta.evaluate(lambda[0]);
if !self.eta.is_constant() {
lambda.terminate_secondary();
@ -325,17 +582,17 @@ impl MaterialTrait for DielectricMaterial {
}
let distrib = TrowbridgeReitzDistribution::new(u_rough, v_rough);
let bxdf = scratch.alloc(DielectricBxDF::new(sampled_eta, distrib));
let bxdf = BxDF::Dielectric(DielectricBxDF::new(sampled_eta, distrib));
BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf))
}
fn get_bssrdf<'a, T>(
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF<'a>> {
) -> Option<BSSRDF> {
None
}
@ -343,8 +600,8 @@ impl MaterialTrait for DielectricMaterial {
tex_eval.can_evaluate(&[&self.u_roughness, &self.v_roughness], &[])
}
fn get_normal_map(&self) -> Option<&Image> {
self.normal_map.as_deref()
fn get_normal_map(&self) -> Option<Arc<Image>> {
self.normal_map.clone()
}
fn get_displacement(&self) -> Option<FloatTexture> {
@ -363,24 +620,23 @@ pub struct DiffuseMaterial {
}
impl MaterialTrait for DiffuseMaterial {
fn get_bxdf<'a, T: TextureEvaluator>(
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
scratch: &'a Bump,
) -> BSDF<'a> {
) -> BSDF {
let r = tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda);
let bxdf = scratch.alloc(DiffuseBxDF::new(r));
let bxdf = BxDF::Diffuse(DiffuseBxDF::new(r));
BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf))
}
fn get_bssrdf<'a, T>(
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF<'a>> {
) -> Option<BSSRDF> {
todo!()
}
@ -388,12 +644,14 @@ impl MaterialTrait for DiffuseMaterial {
tex_eval.can_evaluate(&[], &[&self.reflectance])
}
fn get_normal_map(&self) -> Option<&Image> {
self.normal_map.as_deref()
fn get_normal_map(&self) -> Option<Arc<Image>> {
self.normal_map.clone()
}
fn get_displacement(&self) -> Option<FloatTexture> {
Some(self.displacement.clone())
}
fn has_surface_scattering(&self) -> bool {
false
}
@ -401,33 +659,35 @@ impl MaterialTrait for DiffuseMaterial {
#[derive(Clone, Debug)]
pub struct DiffuseTransmissionMaterial;
impl MaterialTrait for DiffuseTransmissionMaterial {
fn get_bxdf<'a, T: TextureEvaluator>(
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
_scratch: &'a Bump,
) -> BSDF<'a> {
) -> BSDF {
todo!()
}
fn get_bssrdf<'a, T>(
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF<'a>> {
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<&Image> {
fn get_normal_map(&self) -> Option<Arc<Image>> {
todo!()
}
fn get_displacement(&self) -> Option<FloatTexture> {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
@ -435,28 +695,27 @@ impl MaterialTrait for DiffuseTransmissionMaterial {
#[derive(Clone, Debug)]
pub struct HairMaterial;
impl MaterialTrait for HairMaterial {
fn get_bxdf<'a, T: TextureEvaluator>(
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
_scratch: &'a Bump,
) -> BSDF<'a> {
) -> BSDF {
todo!()
}
fn get_bssrdf<'a, T>(
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF<'a>> {
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<&Image> {
fn get_normal_map(&self) -> Option<Arc<Image>> {
todo!()
}
fn get_displacement(&self) -> Option<FloatTexture> {
@ -470,28 +729,27 @@ impl MaterialTrait for HairMaterial {
#[derive(Clone, Debug)]
pub struct MeasuredMaterial;
impl MaterialTrait for MeasuredMaterial {
fn get_bxdf<'a, T: TextureEvaluator>(
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
_scratch: &'a Bump,
) -> BSDF<'a> {
) -> BSDF {
todo!()
}
fn get_bssrdf<'a, T>(
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF<'a>> {
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<&Image> {
fn get_normal_map(&self) -> Option<Arc<Image>> {
todo!()
}
fn get_displacement(&self) -> Option<FloatTexture> {
@ -504,28 +762,27 @@ impl MaterialTrait for MeasuredMaterial {
#[derive(Clone, Debug)]
pub struct SubsurfaceMaterial;
impl MaterialTrait for SubsurfaceMaterial {
fn get_bxdf<'a, T: TextureEvaluator>(
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
_scratch: &'a Bump,
) -> BSDF<'a> {
) -> BSDF {
todo!()
}
fn get_bssrdf<'a, T>(
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF<'a>> {
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<&Image> {
fn get_normal_map(&self) -> Option<Arc<Image>> {
todo!()
}
fn get_displacement(&self) -> Option<FloatTexture> {
@ -538,27 +795,26 @@ impl MaterialTrait for SubsurfaceMaterial {
#[derive(Clone, Debug)]
pub struct ThinDielectricMaterial;
impl MaterialTrait for ThinDielectricMaterial {
fn get_bxdf<'a, T: TextureEvaluator>(
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
_scratch: &'a Bump,
) -> BSDF<'a> {
) -> BSDF {
todo!()
}
fn get_bssrdf<'a, T>(
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF<'a>> {
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<&Image> {
fn get_normal_map(&self) -> Option<Arc<Image>> {
todo!()
}
fn get_displacement(&self) -> Option<FloatTexture> {
@ -600,23 +856,22 @@ impl MixMaterial {
}
impl MaterialTrait for MixMaterial {
fn get_bxdf<'a, T: TextureEvaluator>(
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
scratch: &'a Bump,
) -> BSDF<'a> {
) -> BSDF {
let chosen_mat = self.choose_material(tex_eval, ctx);
chosen_mat.get_bxdf(tex_eval, ctx, lambda, scratch)
chosen_mat.get_bsdf(tex_eval, ctx, lambda)
}
fn get_bssrdf<'a, T>(
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF<'a>> {
) -> Option<BSSRDF> {
None
}
@ -624,7 +879,7 @@ impl MaterialTrait for MixMaterial {
tex_eval.can_evaluate(&[&self.amount], &[])
}
fn get_normal_map(&self) -> Option<&Image> {
fn get_normal_map(&self) -> Option<Arc<Image>> {
None
}

View file

@ -2,18 +2,18 @@ use bumpalo::Bump;
use enum_dispatch::enum_dispatch;
use std::sync::Arc;
use crate::core::pbrt::{Float, INV_4_PI, PI, clamp_t};
use crate::geometry::{
use crate::core::geometry::{
Bounds3f, Frame, Point2f, Point3f, Point3i, Ray, Vector3f, VectorLike, spherical_direction,
};
use crate::core::pbrt::{Float, INV_4_PI, PI};
use crate::spectra::{
BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum,
RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait,
RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumProvider,
};
use crate::utils::containers::SampledGrid;
use crate::utils::math::square;
use crate::utils::math::{clamp, square};
use crate::utils::rng::Rng;
use crate::utils::transform::Transform;
use crate::utils::transform::TransformGeneric;
#[derive(Debug, Clone, Copy)]
pub struct PhaseFunctionSample {
@ -229,7 +229,7 @@ impl DDAMajorantIterator {
let grid_intersect = Vector3f::from(p_grid_start);
for axis in 0..3 {
iter.voxel[axis] = clamp_t(
iter.voxel[axis] = clamp(
(grid_intersect[axis] * grid.res[axis] as Float) as i32,
0,
grid.res[axis] - 1,
@ -424,11 +424,9 @@ impl HomogeneousMedium {
le_scale: Float,
g: Float,
) -> Self {
let mut sigma_a_spec =
DenselySampledSpectrum::from_spectrum(&sigma_a, LAMBDA_MIN, LAMBDA_MAX);
let mut sigma_s_spec =
DenselySampledSpectrum::from_spectrum(&sigma_s, LAMBDA_MIN, LAMBDA_MAX);
let mut le_spec = DenselySampledSpectrum::from_spectrum(&le, LAMBDA_MIN, LAMBDA_MAX);
let mut sigma_a_spec = DenselySampledSpectrum::from_spectrum(&sigma_a);
let mut sigma_s_spec = DenselySampledSpectrum::from_spectrum(&sigma_s);
let mut le_spec = DenselySampledSpectrum::from_spectrum(&le);
sigma_a_spec.scale(sigma_scale);
sigma_s_spec.scale(sigma_scale);
@ -480,7 +478,7 @@ impl MediumTrait for HomogeneousMedium {
#[derive(Debug, Clone)]
pub struct GridMedium {
bounds: Bounds3f,
render_from_medium: Transform<Float>,
render_from_medium: TransformGeneric<Float>,
sigma_a_spec: DenselySampledSpectrum,
sigma_s_spec: DenselySampledSpectrum,
density_grid: SampledGrid<Float>,
@ -496,7 +494,7 @@ impl GridMedium {
#[allow(clippy::too_many_arguments)]
pub fn new(
bounds: &Bounds3f,
render_from_medium: &Transform<Float>,
render_from_medium: &TransformGeneric<Float>,
sigma_a: &Spectrum,
sigma_s: &Spectrum,
sigma_scale: Float,
@ -506,11 +504,9 @@ impl GridMedium {
le: &Spectrum,
le_scale: SampledGrid<Float>,
) -> Self {
let mut sigma_a_spec =
DenselySampledSpectrum::from_spectrum(sigma_a, LAMBDA_MIN, LAMBDA_MAX);
let mut sigma_s_spec =
DenselySampledSpectrum::from_spectrum(sigma_s, LAMBDA_MIN, LAMBDA_MAX);
let le_spec = DenselySampledSpectrum::from_spectrum(le, LAMBDA_MIN, LAMBDA_MAX);
let mut sigma_a_spec = DenselySampledSpectrum::from_spectrum(sigma_a);
let mut sigma_s_spec = DenselySampledSpectrum::from_spectrum(sigma_s);
let le_spec = DenselySampledSpectrum::from_spectrum(le);
sigma_a_spec.scale(sigma_scale);
sigma_s_spec.scale(sigma_scale);
@ -628,7 +624,7 @@ impl MediumTrait for GridMedium {
#[derive(Debug, Clone)]
pub struct RGBGridMedium {
bounds: Bounds3f,
render_from_medium: Transform<Float>,
render_from_medium: TransformGeneric<Float>,
le_grid: Option<SampledGrid<RGBIlluminantSpectrum>>,
le_scale: Float,
phase: HGPhaseFunction,
@ -642,7 +638,7 @@ impl RGBGridMedium {
#[allow(clippy::too_many_arguments)]
pub fn new(
bounds: &Bounds3f,
render_from_medium: &Transform<Float>,
render_from_medium: &TransformGeneric<Float>,
g: Float,
sigma_a_grid: Option<SampledGrid<RGBUnboundedSpectrum>>,
sigma_s_grid: Option<SampledGrid<RGBUnboundedSpectrum>>,

View file

@ -1,9 +1,9 @@
pub mod aggregates;
pub mod bssrdf;
pub mod bxdf;
pub mod cie;
pub mod film;
pub mod filter;
pub mod geometry;
pub mod interaction;
pub mod material;
pub mod medium;
@ -11,4 +11,5 @@ pub mod options;
pub mod pbrt;
pub mod primitive;
pub mod sampler;
pub mod scattering;
pub mod texture;

View file

@ -1,4 +1,5 @@
use crate::geometry::Point2i;
use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i};
use crate::Float;
use std::ops::Deref;
use std::sync::OnceLock;
@ -19,11 +20,9 @@ pub struct BasicPBRTOptions {
pub force_diffuse: bool,
pub use_gpu: bool,
pub wavefront: bool,
pub interactive: bool,
pub fullscreen: bool,
pub rendering_space: RenderingCoordinateSystem,
pub debug_start: Option<(Point2i, i32)>,
pub write_partial_images: bool,
pub mse_reference_image: Option<String>,
pub mse_reference_output: Option<String>,
}
impl Default for BasicPBRTOptions {
@ -37,11 +36,9 @@ impl Default for BasicPBRTOptions {
force_diffuse: false,
use_gpu: false,
wavefront: false,
interactive: false,
fullscreen: false,
rendering_space: RenderingCoordinateSystem::CameraWorld,
debug_start: Some((Point2i::default(), 0)),
write_partial_images: false,
mse_reference_image: None,
mse_reference_output: None,
}
}
}
@ -52,7 +49,20 @@ pub struct PBRTOptions {
pub n_threads: usize,
pub log_level: String,
pub write_partial_images: bool,
pub image_file: String,
pub pixel_samples: Option<i32>,
pub gpu_device: Option<u32>,
pub mse_reference_image: Option<String>,
pub mse_reference_output: Option<String>,
pub debug_start: Option<(Point2i, i32)>,
pub quick_render: bool,
pub upgrade: bool,
pub display_server: String,
pub crop_window: Option<Bounds2f>,
pub pixel_bounds: Option<Bounds2i>,
pub pixel_material: Option<Point2i>,
pub displacement_edge_scale: Float,
}
impl Default for PBRTOptions {
@ -61,7 +71,20 @@ impl Default for PBRTOptions {
basic: BasicPBRTOptions::default(),
n_threads: 0,
log_level: "info".to_string(),
write_partial_images: false,
pixel_samples: None,
gpu_device: None,
quick_render: false,
upgrade: false,
image_file: "output.exr".to_string(),
mse_reference_image: None,
mse_reference_output: None,
debug_start: Some((Point2i::default(), 0)),
display_server: "".to_string(),
crop_window: None,
pixel_bounds: None,
pixel_material: None,
displacement_edge_scale: 1.,
}
}
}

View file

@ -1,6 +1,5 @@
use crate::geometry::{Lerp, Point2f, Vector2f, Vector3f};
use crate::core::geometry::Lerp;
use num_traits::{Num, PrimInt};
use std::collections::HashSet;
use std::hash::Hash;
use std::ops::{Add, Mul};
use std::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
@ -101,64 +100,6 @@ pub const PI_OVER_2: Float = 1.570_796_326_794_896_619_23;
pub const PI_OVER_4: Float = 0.785_398_163_397_448_309_61;
pub const SQRT_2: Float = 1.414_213_562_373_095_048_80;
#[inline]
pub fn lerp<T, F>(t: F, a: T, b: T) -> T
where
T: Lerp<F>,
F: Copy,
{
T::lerp(t, a, b)
}
pub fn linear_pdf<T>(x: T, a: T, b: T) -> T
where
T: Num + Copy + PartialOrd,
{
if x < T::zero() || x > T::one() {
return T::zero();
}
(T::one() + T::one()) * lerp(x, a, b) / (a + b)
}
pub fn sample_linear<T>(u: T, a: T, b: T) -> T
where
T: Num,
{
if u == T::zero() && a == T::zero() {
return T::zero();
}
u * (a + b)
}
pub fn clamp_t<T>(val: T, low: T, high: T) -> T
where
T: PartialOrd,
{
let r: T;
if val < low {
r = low;
} else if val > high {
r = high;
} else {
r = val;
}
r
}
#[inline]
pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Option<Float> {
if coeffs.is_empty() {
return None;
}
let mut result = coeffs[0];
for &c in &coeffs[1..] {
result = t.mul_add(result, c);
}
Some(result)
}
#[inline]
pub fn find_interval<T, P>(sz: T, pred: P) -> T
where
@ -196,13 +137,6 @@ pub fn gamma(n: i32) -> Float {
n as Float * MACHINE_EPSILON / (1. - n as Float * MACHINE_EPSILON)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RenderingCoordinateSystem {
Camera,
CameraWorld,
World,
}
// Define the static counters. These are thread-safe.
pub static RARE_EVENT_TOTAL_CALLS: AtomicU64 = AtomicU64::new(0);
pub static RARE_EVENT_CONDITION_MET: AtomicU64 = AtomicU64::new(0);
@ -240,30 +174,3 @@ macro_rules! check_rare {
}
};
}
pub struct InternCache<T> {
cache: Mutex<HashSet<Arc<T>>>,
}
impl<T> InternCache<T>
where
T: Eq + Hash + Clone,
{
pub fn new() -> Self {
Self {
cache: Mutex::new(HashSet::new()),
}
}
pub fn lookup(&self, value: T) -> Arc<T> {
let mut lock = self.cache.lock().unwrap();
if let Some(existing) = lock.get(&value) {
return existing.clone(); // Returns a cheap Arc copy
}
let new_item = Arc::new(value);
lock.insert(new_item.clone());
new_item
}
}

View file

@ -1,14 +1,14 @@
use crate::core::aggregates::LinearBVHNode;
use crate::core::geometry::{Bounds3f, Ray};
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::material::Material;
use crate::core::medium::{Medium, MediumInterface};
use crate::core::pbrt::Float;
use crate::core::texture::{FloatTextureTrait, TextureEvalContext};
use crate::geometry::{Bounds3f, Ray};
use crate::lights::Light;
use crate::shapes::{Shape, ShapeIntersection, ShapeTrait};
use crate::utils::hash::hash_float;
use crate::utils::transform::{AnimatedTransform, Transform};
use crate::utils::transform::{AnimatedTransform, TransformGeneric};
use enum_dispatch::enum_dispatch;
use std::sync::Arc;
@ -89,7 +89,7 @@ pub struct SimplePrimitiv {
#[derive(Debug, Clone)]
pub struct TransformedPrimitive {
primitive: Arc<dyn PrimitiveTrait>,
render_from_primitive: Transform<Float>,
render_from_primitive: TransformGeneric<Float>,
}
impl PrimitiveTrait for TransformedPrimitive {

View file

@ -4,18 +4,19 @@ use enum_dispatch::enum_dispatch;
use rand::seq::index::sample;
use crate::core::filter::FilterTrait;
use crate::core::pbrt::{
Float, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4, clamp_t, find_interval, lerp,
};
use crate::geometry::{Bounds2f, Point2f, Point2i, Vector2f};
use crate::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f};
use crate::core::options::{PBRTOptions, get_options};
use crate::core::pbrt::{Float, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4, find_interval};
use crate::utils::containers::Array2D;
use crate::utils::error::FileLoc;
use crate::utils::math::{
BinaryPermuteScrambler, DigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler,
PRIME_TABLE_SIZE, Scrambler, compute_radical_inverse_permutations, encode_morton_2,
inverse_radical_inverse, log2_int, owen_scrambled_radical_inverse, permutation_element,
PRIME_TABLE_SIZE, Scrambler, clamp, compute_radical_inverse_permutations, encode_morton_2,
inverse_radical_inverse, lerp, log2_int, owen_scrambled_radical_inverse, permutation_element,
radical_inverse, round_up_pow2, scrambled_radical_inverse, sobol_interval_to_index,
sobol_sample,
};
use crate::utils::parameters::ParameterDictionary;
use crate::utils::rng::Rng;
use crate::utils::sobol::N_SOBOL_DIMENSIONS;
use crate::utils::{hash::*, sobol};
@ -68,6 +69,21 @@ impl IndependentSampler {
rng: Rng::default(),
}
}
pub fn create(
params: &ParameterDictionary,
_full_res: Point2i,
_loc: &FileLoc,
) -> Result<Self, String> {
let options = get_options();
let nsamp = options
.quick_render
.then_some(1)
.or(options.pixel_samples)
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
let seed = params.get_one_int("seed", options.seed);
Ok(Self::new(nsamp as usize, seed as u64))
}
}
impl SamplerTrait for IndependentSampler {
@ -198,6 +214,37 @@ impl HaltonSampler {
(yp, xp - d * yp)
}
pub fn create(
params: &ParameterDictionary,
full_res: Point2i,
loc: &FileLoc,
) -> Result<Self, String> {
let options = get_options();
let nsamp = options
.quick_render
.then_some(1)
.or(options.pixel_samples)
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
let seed = params.get_one_int("seed", options.seed);
let s = match params
.get_one_string("randomization", "permutedigits")
.as_str()
{
"none" => RandomizeStrategy::None,
"permutedigits" => RandomizeStrategy::PermuteDigits,
"fastowen" => RandomizeStrategy::FastOwen,
"owen" => RandomizeStrategy::Owen,
_ => {
return Err(format!(
"{}: Unknown randomization strategy for Halton",
loc
));
}
};
Ok(HaltonSampler::new(nsamp as usize, full_res, s, seed as u64))
}
}
impl SamplerTrait for HaltonSampler {
@ -293,6 +340,35 @@ impl StratifiedSampler {
dim: 0,
}
}
pub fn create(
params: &ParameterDictionary,
_full_res: Point2i,
_loc: &FileLoc,
) -> Result<Self, String> {
let options = get_options();
let jitter = params.get_one_bool("jitter", true);
let (x_samples, y_samples) = if options.quick_render {
(1, 1)
} else if let Some(n) = options.pixel_samples {
let div = (n as f64).sqrt() as i32;
let y = (1..=div).rev().find(|d| n % d == 0).unwrap();
(n / y, y)
} else {
(
params.get_one_int("xsamples", 4),
params.get_one_int("ysamples", 4),
)
};
let seed = params.get_one_int("seed", options.seed);
Ok(Self::new(
x_samples as usize,
y_samples as usize,
Some(seed as u64),
jitter,
))
}
}
impl SamplerTrait for StratifiedSampler {
@ -407,6 +483,34 @@ impl PaddedSobolSampler {
RandomizeStrategy::None => unreachable!(),
}
}
pub fn create(
params: &ParameterDictionary,
_full_res: Point2i,
loc: &FileLoc,
) -> Result<Self, String> {
let options = get_options();
let nsamp = options
.quick_render
.then_some(1)
.or(options.pixel_samples)
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
let seed = params.get_one_int("seed", options.seed);
let s = match params.get_one_string("randomization", "fastowen").as_str() {
"none" => RandomizeStrategy::None,
"permutedigits" => RandomizeStrategy::PermuteDigits,
"fastowen" => RandomizeStrategy::FastOwen,
"owen" => RandomizeStrategy::Owen,
_ => {
return Err(format!(
"{}: Unknown randomization strategy for ZSobol",
loc
));
}
};
Ok(Self::new(nsamp as usize, s, Some(seed as u64)))
}
}
impl SamplerTrait for PaddedSobolSampler {
@ -510,6 +614,34 @@ impl SobolSampler {
RandomizeStrategy::None => unreachable!(),
}
}
pub fn create(
params: &ParameterDictionary,
full_res: Point2i,
loc: &FileLoc,
) -> Result<Self, String> {
let options = get_options();
let nsamp = options
.quick_render
.then_some(1)
.or(options.pixel_samples)
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
let seed = params.get_one_int("seed", options.seed);
let s = match params.get_one_string("randomization", "fastowen").as_str() {
"none" => RandomizeStrategy::None,
"permutedigits" => RandomizeStrategy::PermuteDigits,
"fastowen" => RandomizeStrategy::FastOwen,
"owen" => RandomizeStrategy::Owen,
_ => {
return Err(format!(
"{}: Unknown randomization strategy for ZSobol",
loc
));
}
};
Ok(Self::new(nsamp as usize, full_res, s, Some(seed as u64)))
}
}
impl SamplerTrait for SobolSampler {
@ -550,12 +682,12 @@ impl SamplerTrait for SobolSampler {
sobol_sample(self.sobol_index, 0, NoRandomizer),
sobol_sample(self.sobol_index, 1, NoRandomizer),
);
u[0] = clamp_t(
u[0] = clamp(
u[0] * self.scale as Float - self.pixel[0] as Float,
0.,
ONE_MINUS_EPSILON,
) as Float;
u[1] = clamp_t(
u[1] = clamp(
u[1] * self.scale as Float - self.pixel[1] as Float,
1.,
ONE_MINUS_EPSILON,
@ -649,6 +781,39 @@ impl ZSobolSampler {
sample_index
}
pub fn create(
params: &ParameterDictionary,
full_res: Point2i,
loc: &FileLoc,
) -> Result<Self, String> {
let options = get_options();
let nsamp = options
.quick_render
.then_some(1)
.or(options.pixel_samples)
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
let seed = params.get_one_int("seed", options.seed);
let s = match params.get_one_string("randomization", "fastowen").as_str() {
"none" => RandomizeStrategy::None,
"permutedigits" => RandomizeStrategy::PermuteDigits,
"fastowen" => RandomizeStrategy::FastOwen,
"owen" => RandomizeStrategy::Owen,
_ => {
return Err(format!(
"{}: Unknown randomization strategy for ZSobol",
loc
));
}
};
Ok(ZSobolSampler::new(
nsamp as u32,
full_res,
s,
Some(seed as u64),
))
}
}
impl SamplerTrait for ZSobolSampler {
@ -758,3 +923,40 @@ pub enum Sampler {
ZSobol(ZSobolSampler),
MLT(MLTSampler),
}
impl Sampler {
pub fn create(
name: &str,
params: &ParameterDictionary,
full_res: Point2i,
loc: &FileLoc,
) -> Result<Self, String> {
match name {
"zsobol" => {
let sampler = ZSobolSampler::create(params, full_res, loc)?;
Ok(Sampler::ZSobol(sampler))
}
"paddedsobol" => {
let sampler = PaddedSobolSampler::create(params, full_res, loc)?;
Ok(Sampler::PaddedSobol(sampler))
}
"halton" => {
let sampler = HaltonSampler::create(params, full_res, loc)?;
Ok(Sampler::Halton(sampler))
}
"sobol" => {
let sampler = SobolSampler::create(params, full_res, loc)?;
Ok(Sampler::Sobol(sampler))
}
"Independent" => {
let sampler = IndependentSampler::create(params, full_res, loc)?;
Ok(Sampler::Independent(sampler))
}
"stratified" => {
let sampler = StratifiedSampler::create(params, full_res, loc)?;
Ok(Sampler::Stratified(sampler))
}
_ => Err(format!("Film type '{}' unknown at {}", name, loc)),
}
}
}

View file

@ -2,10 +2,9 @@ use crate::core::geometry::{
Normal3f, Point2f, Vector2f, Vector3f, VectorLike, abs_cos_theta, cos_phi, cos2_theta, sin_phi,
tan2_theta,
};
use crate::core::pbrt::{Float, PI, clamp_t};
use crate::core::pbrt::{Float, PI};
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum};
use crate::utils::math::safe_sqrt;
use crate::utils::math::{lerp, square};
use crate::utils::math::{clamp, lerp, safe_sqrt, square};
use crate::utils::sampling::sample_uniform_disk_polar;
use num::complex::Complex;
@ -91,10 +90,10 @@ impl TrowbridgeReitzDistribution {
pub fn regularize(&mut self) {
if self.alpha_x < 0.3 {
self.alpha_x = clamp_t(2. * self.alpha_x, 0.1, 0.3);
self.alpha_x = clamp(2. * self.alpha_x, 0.1, 0.3);
}
if self.alpha_y < 0.3 {
self.alpha_y = clamp_t(2. * self.alpha_y, 0.1, 0.3);
self.alpha_y = clamp(2. * self.alpha_y, 0.1, 0.3);
}
}
}
@ -130,7 +129,7 @@ pub fn reflect(wo: Vector3f, n: Normal3f) -> Vector3f {
}
pub fn fr_dielectric(cos_theta_i: Float, eta: Float) -> Float {
let mut cos_safe = clamp_t(cos_theta_i, -1., 1.);
let mut cos_safe = clamp(cos_theta_i, -1., 1.);
let mut eta_corr = eta;
if cos_theta_i < 0. {
eta_corr = 1. / eta_corr;
@ -149,7 +148,7 @@ pub fn fr_dielectric(cos_theta_i: Float, eta: Float) -> Float {
}
pub fn fr_complex(cos_theta_i: Float, eta: Complex<Float>) -> Float {
let cos_corr = clamp_t(cos_theta_i, 0., 1.);
let cos_corr = clamp(cos_theta_i, 0., 1.);
let sin2_theta_i = 1. - square(cos_corr);
let sin2_theta_t: Complex<Float> = sin2_theta_i / square(eta);
let cos2_theta_t: Complex<Float> = (1. - sin2_theta_t).sqrt();

View file

@ -1,18 +1,22 @@
use crate::core::geometry::{
Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike, spherical_phi, spherical_theta,
};
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::pbrt::Float;
use crate::core::pbrt::{INV_2_PI, INV_PI, PI};
use crate::geometry::{Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike};
use crate::geometry::{spherical_phi, spherical_theta};
use crate::image::WrapMode;
use crate::spectra::color::ColorEncoding;
use crate::spectra::{
RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, Spectrum,
RGB, RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
SampledWavelengths, Spectrum, SpectrumProvider,
};
use crate::spectra::{SampledWavelengths, SpectrumTrait};
use crate::utils::color::{ColorEncoding, RGB};
use crate::utils::Transform;
use crate::utils::error::FileLoc;
use crate::utils::math::square;
use crate::utils::mipmap::MIPMap;
use crate::utils::mipmap::{MIPMapFilterOptions, MIPMapSample};
use crate::utils::transform::Transform;
use crate::utils::parameters::TextureParameterDictionary;
use crate::utils::transform::TransformGeneric;
use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};
@ -42,6 +46,46 @@ pub enum TextureMapping2D {
Planar(PlanarMapping),
}
impl TextureMapping2D {
pub fn create(
params: &TextureParameterDictionary,
render_from_texture: &Transform,
loc: &FileLoc,
) -> Self {
let mtype = params.get_one_string("mapping", "uv");
match mtype.as_str() {
"uv" => {
let su = params.get_one_float("uscale", 1.);
let sv = params.get_one_float("vscale", 1.);
let du = params.get_one_float("udelta", 0.);
let dv = params.get_one_float("vdelta", 0.);
let mapping = UVMapping::new(su, sv, du, dv);
TextureMapping2D::UV(mapping)
}
"spherical" => {
let mapping = SphericalMapping::new(&render_from_texture.inverse());
TextureMapping2D::Spherical(mapping)
}
"cylindrical" => {
let mapping = CylindricalMapping::new(&render_from_texture.inverse());
TextureMapping2D::Cylindrical(mapping)
}
"planar" => {
let vs = params.get_one_vector3f("v1", Vector3f::new(1., 0., 0.));
let vt = params.get_one_vector3f("v2", Vector3f::new(0., 1., 0.));
let ds = params.get_one_float("udelta", 0.);
let dt = params.get_one_float("vdelta", 0.);
let mapping = PlanarMapping::new(&render_from_texture.inverse(), vs, vt, ds, dt);
TextureMapping2D::Planar(mapping)
}
_ => {
log::error!("{}: 2D texture mapping unknown {}", loc, mtype);
TextureMapping2D::UV(UVMapping::default())
}
}
}
}
#[derive(Clone, Debug)]
pub struct UVMapping {
su: Float,
@ -50,6 +94,12 @@ pub struct UVMapping {
dv: Float,
}
impl UVMapping {
pub fn new(su: Float, sv: Float, du: Float, dv: Float) -> Self {
Self { su, sv, du, dv }
}
}
impl Default for UVMapping {
fn default() -> Self {
Self {
@ -80,11 +130,11 @@ impl TextureMapping2DTrait for UVMapping {
#[derive(Clone, Debug)]
pub struct SphericalMapping {
texture_from_render: Transform<Float>,
texture_from_render: TransformGeneric<Float>,
}
impl SphericalMapping {
pub fn new(texture_from_render: &Transform<Float>) -> Self {
pub fn new(texture_from_render: &TransformGeneric<Float>) -> Self {
Self {
texture_from_render: *texture_from_render,
}
@ -123,11 +173,11 @@ impl TextureMapping2DTrait for SphericalMapping {
#[derive(Clone, Debug)]
pub struct CylindricalMapping {
texture_from_render: Transform<Float>,
texture_from_render: TransformGeneric<Float>,
}
impl CylindricalMapping {
pub fn new(texture_from_render: &Transform<Float>) -> Self {
pub fn new(texture_from_render: &TransformGeneric<Float>) -> Self {
Self {
texture_from_render: *texture_from_render,
}
@ -159,7 +209,7 @@ impl TextureMapping2DTrait for CylindricalMapping {
#[derive(Clone, Debug)]
pub struct PlanarMapping {
texture_from_render: Transform<Float>,
texture_from_render: TransformGeneric<Float>,
vs: Vector3f,
vt: Vector3f,
ds: Float,
@ -168,7 +218,7 @@ pub struct PlanarMapping {
impl PlanarMapping {
pub fn new(
texture_from_render: &Transform<Float>,
texture_from_render: &TransformGeneric<Float>,
vs: Vector3f,
vt: Vector3f,
ds: Float,
@ -222,11 +272,11 @@ pub enum TextureMapping3D {
#[derive(Clone, Debug)]
pub struct PointTransformMapping {
texture_from_render: Transform<Float>,
texture_from_render: TransformGeneric<Float>,
}
impl PointTransformMapping {
pub fn new(texture_from_render: &Transform<Float>) -> Self {
pub fn new(texture_from_render: &TransformGeneric<Float>) -> Self {
Self {
texture_from_render: *texture_from_render,
}
@ -332,6 +382,7 @@ pub trait FloatTextureTrait: Send + Sync + std::fmt::Debug {
pub enum FloatTexture {
FloatConstant(FloatConstantTexture),
GPUFloatImage(GPUFloatImageTexture),
FloatImage(FloatImageTexture),
FloatMix(FloatMixTexture),
FloatDirectionMix(FloatDirectionMixTexture),
FloatScaled(FloatScaledTexture),
@ -345,6 +396,75 @@ pub enum FloatTexture {
Wrinkled(WrinkledTexture),
}
impl FloatTexture {
pub fn create(
name: &str,
render_from_texture: &Transform,
params: &TextureParameterDictionary,
loc: &FileLoc,
gpu: bool,
) -> Result<Self, String> {
match name {
"constant" => {
let tex = FloatConstantTexture::create(render_from_texture, params, loc);
Ok(FloatTexture::FloatConstant(tex))
}
"scale" => Ok(FloatScaledTexture::create(render_from_texture, params, loc)),
"mix" => {
let tex = FloatMixTexture::create(render_from_texture, params, loc);
Ok(FloatTexture::FloatMix(tex))
}
"directionmix" => {
let tex = FloatDirectionMixTexture::create(render_from_texture, params, loc);
Ok(FloatTexture::FloatDirectionMix(tex))
}
"bilerp" => {
let tex = FloatBilerpTexture::create(render_from_texture, params, loc);
Ok(FloatTexture::FloatBilerp(tex))
}
"imagemap" => {
if gpu {
let tex = GPUFloatImageTexture::create(render_from_texture, params, loc);
Ok(FloatTexture::GPUFloatImage(tex))
} else {
let tex = FloatImageTexture::create(render_from_texture, params, loc);
Ok(FloatTexture::FloatImage(tex))
}
}
"checkerboard" => {
let tex = FloatCheckerboardTexture::create(render_from_texture, params, loc);
Ok(FloatTexture::FloatCheckerboard(tex))
}
"dots" => {
let tex = FloatDotsTexture::create(render_from_texture, params, loc);
Ok(FloatTexture::FloatDots(tex))
}
"fbm" => {
let tex = FBmTexture::create(render_from_texture, params, loc);
Ok(FloatTexture::FBm(tex))
}
"wrinkled" => {
let tex = WrinkledTexture::create(render_from_texture, params, loc);
Ok(FloatTexture::Wrinkled(tex))
}
"windy" => {
let tex = WindyTexture::create(render_from_texture, params, loc);
Ok(FloatTexture::Windy(tex))
}
"ptex" => {
if gpu {
let tex = GPUFloatPtexTexture::create(render_from_texture, params, loc);
Ok(FloatTexture::GPUFLoatPtex(tex))
} else {
let tex = FloatPtexTexture::create(render_from_texture, params, loc);
Ok(FloatTexture::FloatPtex(tex))
};
}
_ => Err(format!("Float texture type '{}' unknown at {}", name, loc)),
}
}
}
#[derive(Debug, Clone)]
pub struct FloatConstantTexture {
value: Float,
@ -354,6 +474,14 @@ impl FloatConstantTexture {
pub fn new(value: Float) -> Self {
Self { value }
}
pub fn create(
_render_from_texture: &Transform,
params: &TextureParameterDictionary,
_loc: &FileLoc,
) -> Self {
Self::new(params.get_one_float("value", 1.0))
}
}
impl FloatTextureTrait for FloatConstantTexture {
@ -363,24 +491,72 @@ impl FloatTextureTrait for FloatConstantTexture {
}
#[derive(Debug, Clone)]
pub struct GPUFloatImageTexture;
pub struct GPUFloatImageTexture {
mapping: TextureMapping2D,
filename: String,
scale: Float,
invert: bool,
}
impl FloatTextureTrait for GPUFloatImageTexture {}
#[derive(Debug, Clone)]
pub struct FloatImageTexture {
base: ImageTextureBase,
}
impl FloatImageTexture {
pub fn new(
mapping: TextureMapping2D,
filename: String,
filter_options: MIPMapFilterOptions,
wrap_mode: WrapMode,
scale: Float,
invert: bool,
encoding: ColorEncoding,
) -> Self {
Self {
base: ImageTextureBase::new(
mapping,
filename,
filter_options,
wrap_mode,
scale,
invert,
encoding,
),
}
}
}
impl FloatTextureTrait for FloatImageTexture {}
#[derive(Debug, Clone)]
pub struct FloatMixTexture {
tex1: Box<FloatTexture>,
tex2: Box<FloatTexture>,
amount: Box<FloatTexture>,
tex1: Arc<FloatTexture>,
tex2: Arc<FloatTexture>,
amount: Arc<FloatTexture>,
}
impl FloatMixTexture {
pub fn new(
tex1: Box<FloatTexture>,
tex2: Box<FloatTexture>,
amount: Box<FloatTexture>,
tex1: Arc<FloatTexture>,
tex2: Arc<FloatTexture>,
amount: Arc<FloatTexture>,
) -> Self {
Self { tex1, tex2, amount }
}
pub fn create(
_render_from_texture: &Transform,
params: &TextureParameterDictionary,
_loc: &FileLoc,
) -> Self {
let tex1 = params.get_float_texture("tex1", 0.);
let tex2 = params.get_float_texture("tex2", 1.);
let amount = params.get_float_texture("amount", 0.5);
Self::new(tex1, tex2, amount)
}
}
impl FloatTextureTrait for FloatMixTexture {
@ -399,13 +575,68 @@ impl FloatTextureTrait for FloatMixTexture {
}
#[derive(Debug, Clone)]
pub struct FloatDirectionMixTexture;
pub struct FloatDirectionMixTexture {
tex1: Arc<FloatTexture>,
tex2: Arc<FloatTexture>,
dir: Vector3f,
}
impl FloatDirectionMixTexture {
pub fn new(tex1: Arc<FloatTexture>, tex2: Arc<FloatTexture>, dir: Vector3f) -> Self {
Self { tex1, tex2, dir }
}
pub fn create(
render_from_texture: &Transform,
params: &TextureParameterDictionary,
_loc: &FileLoc,
) -> Self {
let dir_raw = params.get_one_vector3f("dir", Vector3f::new(0., 1., 0.));
let dir = render_from_texture.apply_to_vector(dir_raw).normalize();
let tex1 = params.get_float_texture("tex1", 0.);
let tex2 = params.get_float_texture("tex2", 1.);
Self::new(tex1, tex2, dir)
}
}
impl FloatTextureTrait for FloatDirectionMixTexture {}
#[derive(Debug, Clone)]
pub struct FloatScaledTexture {
tex: Box<FloatTexture>,
scale: Box<FloatTexture>,
tex: Arc<FloatTexture>,
scale: Arc<FloatTexture>,
}
impl FloatScaledTexture {
pub fn new(tex: Arc<FloatTexture>, scale: Arc<FloatTexture>) -> Self {
Self { tex, scale }
}
pub fn create(
_render_from_texture: &Transform,
params: &TextureParameterDictionary,
_loc: &FileLoc,
) -> FloatTexture {
let mut tex = params.get_float_texture("tex", 1.);
let mut scale = params.get_float_texture("scale", 1.);
for _ in 0..2 {
if let FloatTexture::FloatConstant(c_tex) = &*scale {
let cs = c_tex.value;
if cs == 1.0 {
return (*tex).clone();
} else if let FloatTexture::FloatImage(img_tex) = &*tex {
let mut image_copy = img_tex.clone();
image_copy.base.multiply_scale(cs);
return FloatTexture::FloatImage(image_copy).into();
}
}
std::mem::swap(&mut tex, &mut scale);
}
std::mem::swap(&mut tex, &mut scale);
FloatTexture::FloatScaled(FloatScaledTexture::new(tex, scale))
}
}
impl FloatTextureTrait for FloatScaledTexture {
@ -419,7 +650,39 @@ impl FloatTextureTrait for FloatScaledTexture {
}
#[derive(Debug, Clone)]
pub struct FloatBilerpTexture;
pub struct FloatBilerpTexture {
mapping: TextureMapping2D,
v00: Float,
v01: Float,
v10: Float,
v11: Float,
}
impl FloatBilerpTexture {
pub fn new(mapping: TextureMapping2D, v00: Float, v01: Float, v10: Float, v11: Float) -> Self {
Self {
mapping,
v00,
v01,
v10,
v11,
}
}
pub fn create(
render_from_texture: &Transform,
params: &TextureParameterDictionary,
loc: &FileLoc,
) -> Self {
let mapping = TextureMapping2D::create(params, render_from_texture, loc);
let v00 = params.get_one_float("v00", 0.);
let v01 = params.get_one_float("v01", 1.);
let v10 = params.get_one_float("v10", 0.);
let v11 = params.get_one_float("v11", 1.);
Self::new(mapping, v00, v01, v10, v11)
}
}
impl FloatTextureTrait for FloatBilerpTexture {}
#[derive(Debug, Clone)]
@ -496,6 +759,13 @@ impl SpectrumTextureTrait for RGBReflectanceConstantTexture {
pub struct SpectrumConstantTexture {
value: Spectrum,
}
impl SpectrumConstantTexture {
pub fn new(value: Spectrum) -> Self {
Self { value }
}
}
impl SpectrumTextureTrait for SpectrumConstantTexture {
fn evaluate(&self, _ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum {
self.value.sample(lambda)
@ -518,7 +788,7 @@ impl SpectrumTextureTrait for SpectrumCheckerboardTexture {
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Copy, Debug)]
pub enum SpectrumType {
Illuminant,
Albedo,

56
shared/src/data.rs Normal file
View file

@ -0,0 +1,56 @@
use crate::Float;
use bytemuck::cast_slice;
use once_cell::sync::Lazy;
static SRGB_SCALE_BYTES: &[u8] = include_bytes!("../../data/srgb_scale.dat");
static SRGB_COEFFS_BYTES: &[u8] = include_bytes!("../../data/srgb_coeffs.dat");
pub static SRGB_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(SRGB_SCALE_BYTES));
pub static SRGB_COEFFS: Lazy<&[Float]> =
Lazy::new(|| match bytemuck::try_cast_slice(SRGB_COEFFS_BYTES) {
Ok(s) => s,
Err(_) => {
let v: Vec<Float> = bytemuck::pod_collect_to_vec(SRGB_COEFFS_BYTES);
Box::leak(v.into_boxed_slice())
}
});
static DCI_P3_SCALE_BYTES: &[u8] = include_bytes!("../../data/dcip3_scale.dat");
static DCI_P3_COEFFS_BYTES: &[u8] = include_bytes!("../../data/dcip3_coeffs.dat");
pub static DCI_P3_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(DCI_P3_SCALE_BYTES));
pub static DCI_P3_COEFFS: Lazy<&[Float]> =
Lazy::new(|| match bytemuck::try_cast_slice(DCI_P3_COEFFS_BYTES) {
Ok(s) => s,
Err(_) => {
let v: Vec<Float> = bytemuck::pod_collect_to_vec(DCI_P3_COEFFS_BYTES);
Box::leak(v.into_boxed_slice())
}
});
static ACES_SCALE_BYTES: &[u8] = include_bytes!("../../data/aces_scale.dat");
static ACES_COEFFS_BYTES: &[u8] = include_bytes!("../../data/aces_coeffs.dat");
pub static ACES_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(ACES_SCALE_BYTES));
pub static ACES_COEFFS: Lazy<&[Float]> =
Lazy::new(|| match bytemuck::try_cast_slice(ACES_COEFFS_BYTES) {
Ok(s) => s,
Err(_) => {
let v: Vec<Float> = bytemuck::pod_collect_to_vec(ACES_COEFFS_BYTES);
Box::leak(v.into_boxed_slice())
}
});
static REC2020_SCALE_BYTES: &[u8] = include_bytes!("../../data/rec2020_scale.dat");
static REC2020_COEFFS_BYTES: &[u8] = include_bytes!("../../data/rec2020_coeffs.dat");
pub static REC2020_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(REC2020_SCALE_BYTES));
pub static REC2020_COEFFS: Lazy<&[Float]> =
Lazy::new(|| match bytemuck::try_cast_slice(REC2020_COEFFS_BYTES) {
Ok(s) => s,
Err(_) => {
let v: Vec<Float> = bytemuck::pod_collect_to_vec(REC2020_COEFFS_BYTES);
Box::leak(v.into_boxed_slice())
}
});

View file

@ -1,6 +1,6 @@
use crate::core::geometry::{Bounds2i, Point2i};
use crate::core::pbrt::Float;
use crate::geometry::{Bounds2i, Point2i};
use crate::utils::colorspace::RGBColorSpace;
use crate::spectra::colorspace::RGBColorSpace;
use crate::utils::math::SquareMatrix;
use smallvec::SmallVec;
use std::collections::HashMap;

View file

@ -1,13 +1,12 @@
pub mod io;
pub mod metadata;
pub mod ops;
pub mod pixel;
use crate::core::pbrt::{Float, lerp};
use crate::geometry::{Bounds2f, Point2f, Point2fi, Point2i};
use crate::utils::color::{ColorEncoding, ColorEncodingTrait, LINEAR};
use crate::core::geometry::{Bounds2f, Point2f, Point2fi, Point2i};
use crate::core::pbrt::Float;
use crate::spectra::color::{ColorEncoding, ColorEncodingTrait, LINEAR};
use crate::utils::containers::Array2D;
use crate::utils::math::square;
use crate::utils::math::{lerp, square};
use core::hash;
use half::f16;
use pixel::PixelStorage;

View file

@ -1,6 +1,6 @@
// use rayon::prelude::*;
use crate::core::geometry::{Bounds2i, Point2i};
use crate::core::pbrt::Float;
use crate::geometry::{Bounds2i, Point2i};
use crate::image::pixel::PixelStorage;
use crate::image::{Image, PixelData, PixelFormat, WrapMode, WrapMode2D};
use crate::utils::math::windowed_sinc;
@ -242,7 +242,7 @@ fn copy_rect_in_kernel<T: PixelStorage>(
dst: &mut [T],
res: Point2i,
channels: usize,
enc: crate::utils::color::ColorEncoding,
enc: crate::spectra::color::ColorEncoding,
extent: Bounds2i,
buf: &[Float],
) {

View file

@ -1,8 +1,8 @@
use crate::core::pbrt::Float;
use crate::utils::color::{ColorEncoding, ColorEncodingTrait};
use crate::spectra::color::{ColorEncoding, ColorEncodingTrait};
use half::f16;
/// Allows writing generic algorithms that work on any image format.
// Allows writing generic algorithms that work on any image format.
pub trait PixelStorage: Copy + Send + Sync + 'static + PartialEq {
fn from_linear(val: Float, encoding: ColorEncoding) -> Self;
fn to_linear(self, encoding: ColorEncoding) -> Float;

View file

@ -6,6 +6,7 @@ use crate::camera::{Camera, CameraTrait};
use crate::core::bssrdf::{BSSRDFTrait, SubsurfaceInteraction};
use crate::core::bxdf::{BSDF, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode};
use crate::core::film::{FilmTrait, VisibleSurface};
use crate::core::geometry::{Bounds2i, Point2f, Point2i, Point3fi, Ray, Vector3f, VectorLike};
use crate::core::interaction::{
self, Interaction, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction,
};
@ -14,7 +15,6 @@ use crate::core::options::get_options;
use crate::core::pbrt::{Float, SHADOW_EPSILON};
use crate::core::primitive::{Primitive, PrimitiveTrait};
use crate::core::sampler::{CameraSample, Sampler, SamplerTrait};
use crate::geometry::{Bounds2i, Point2f, Point2i, Point3fi, Ray, Vector3f, VectorLike};
use crate::lights::sampler::{LightSampler, UniformLightSampler};
use crate::lights::{Light, LightSampleContext, LightTrait, sampler::LightSamplerTrait};
use crate::shapes::ShapeIntersection;
@ -228,7 +228,7 @@ impl RayIntegratorTrait for SimplePathIntegrator {
mut ray: Ray,
lambda: &SampledWavelengths,
sampler: &mut Sampler,
scratch: &Bump,
_scratch: &Bump,
_visible_surface: bool,
) -> (SampledSpectrum, Option<VisibleSurface>) {
let mut l = SampledSpectrum::new(0.0);
@ -255,8 +255,7 @@ impl RayIntegratorTrait for SimplePathIntegrator {
}
depth += 1;
let Some(bsdf) = isect.get_bsdf(&ray, lambda, self.camera.as_ref(), scratch, sampler)
else {
let Some(bsdf) = isect.get_bsdf(&ray, lambda, self.camera.as_ref(), sampler) else {
specular_bounce = false;
isect.skip_intersection(&mut ray, t_hit);
continue;
@ -367,7 +366,7 @@ impl RayIntegratorTrait for PathIntegrator {
mut ray: Ray,
lambda: &SampledWavelengths,
sampler: &mut Sampler,
scratch: &Bump,
_scratch: &Bump,
visible_surface: bool,
) -> (SampledSpectrum, Option<VisibleSurface>) {
let mut l = SampledSpectrum::new(0.0);
@ -410,9 +409,7 @@ impl RayIntegratorTrait for PathIntegrator {
l += beta * wl * le;
}
let Some(mut bsdf) =
isect.get_bsdf(&ray, lambda, self.camera.as_ref(), scratch, sampler)
else {
let Some(mut bsdf) = isect.get_bsdf(&ray, lambda, self.camera.as_ref(), sampler) else {
specular_bounce = true;
isect.skip_intersection(&mut ray, t_hit);
continue;
@ -544,7 +541,7 @@ impl RayIntegratorTrait for SimpleVolPathIntegrator {
mut ray: Ray,
lambda: &SampledWavelengths,
sampler: &mut Sampler,
scratch: &Bump,
_scratch: &Bump,
_visible_surface: bool,
) -> (SampledSpectrum, Option<VisibleSurface>) {
let mut l = SampledSpectrum::new(0.);
@ -632,10 +629,7 @@ impl RayIntegratorTrait for SimpleVolPathIntegrator {
};
// Handle surface intersection along ray path
if let Some(bsdf) = &hit
.intr
.get_bsdf(&ray, &lambda, &self.camera, scratch, sampler)
{
if let Some(bsdf) = &hit.intr.get_bsdf(&ray, &lambda, &self.camera, sampler) {
let uc = sampler.get1d();
let u = sampler.get2d();
if bsdf.sample_f(-ray.d, uc, u, FArgs::default()).is_some() {
@ -993,8 +987,7 @@ impl RayIntegratorTrait for VolPathIntegrator {
}
let isect = &mut hit.intr;
let Some(mut bsdf) = isect.get_bsdf(&ray, lambda, &self.camera, scratch, sampler)
else {
let Some(mut bsdf) = isect.get_bsdf(&ray, lambda, &self.camera, sampler) else {
hit.intr.skip_intersection(&mut ray, hit.t_hit());
continue;
};

View file

@ -228,7 +228,7 @@ pub fn evaluate_pixel_sample<T: RayIntegratorTrait>(
lu = 0.5;
}
let lambda = camera.get_film().sample_wavelengths(lu);
let mut film = camera.get_film();
let film = camera.get_film();
let filter = film.get_filter();
let camera_sample = get_camera_sample(sampler, pixel, filter);
if let Some(mut camera_ray) = camera.generate_ray_differential(camera_sample, &lambda) {

15
shared/src/lib.rs Normal file
View file

@ -0,0 +1,15 @@
#![allow(unused_imports, dead_code)]
#![feature(float_erf)]
#![feature(f16)]
mod camera;
mod core;
mod data;
mod image;
mod integrators;
mod lights;
mod shapes;
mod spectra;
mod utils;
pub use core::pbrt::*;

View file

@ -0,0 +1,26 @@
use crate::core::geometry::*;
use crate::core::pbrt::Float;
use crate::core::spectra::*;
use crate::shapes::GpuShapeHandle;
use crate::textures::GpuFloatTextureHandle;
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct DiffuseAreaLight {
pub lemit_coeffs: [Float; 32],
pub scale: Float,
pub area: Float,
pub two_sided: bool,
pub image_id: i32,
pub shape_id: u32,
}
impl LightTrait for DiffuseAreaLight {
fn l(&self, n: Normal3f, wo: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
if !self.two_sided && n.dot(wo) <= 0.0 {
return SampledSpectrum::new(0.0);
}
let spec = DenselySampledSpectrum::from_array(&self.lemit_coeffs);
spec.sample(lambda) * self.scale
}
}

View file

@ -1,11 +1,9 @@
use crate::{
core::geometry::Frame,
core::medium::Medium,
geometry::Frame,
spectra::RGBIlluminantSpectrum,
spectra::{RGB, RGBColorSpace, RGBIlluminantSpectrum},
utils::{
color::RGB,
colorspace::RGBColorSpace,
math::equal_area_square_to_sphere,
math::{clamp, equal_area_square_to_sphere},
sampling::{
AliasTable, PiecewiseConstant2D, WindowedPiecewiseConstant2D, sample_uniform_sphere,
uniform_sphere_pdf,
@ -15,7 +13,6 @@ use crate::{
use rayon::prelude::*;
use crate::core::pbrt::clamp_t;
use crate::image::{PixelFormat, WrapMode};
use std::path::Path;
@ -24,8 +21,8 @@ use super::{
Arc, Bounds2f, Bounds3f, DenselySampledSpectrum, Float, Image, Interaction, LightBase,
LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, MediumInterface,
Normal3f, PI, Point2f, Point2i, Point3f, Ray, SampledSpectrum, SampledWavelengths,
SimpleInteraction, Spectrum, Transform, Vector3f, VectorLike, equal_area_sphere_to_square,
square,
SimpleInteraction, Spectrum, TransformGeneric, Vector3f, VectorLike,
equal_area_sphere_to_square, square,
};
#[derive(Debug, Clone)]
@ -38,7 +35,7 @@ pub struct InfiniteUniformLight {
}
impl InfiniteUniformLight {
pub fn new(render_from_light: Transform<Float>, le: Spectrum, scale: Float) -> Self {
pub fn new(render_from_light: TransformGeneric<Float>, le: Spectrum, scale: Float) -> Self {
let base = LightBase::new(
LightType::Infinite,
&render_from_light,
@ -137,7 +134,7 @@ pub struct InfiniteImageLight {
impl InfiniteImageLight {
pub fn new(
render_from_light: Transform<Float>,
render_from_light: TransformGeneric<Float>,
image: Image,
image_color_space: Arc<RGBColorSpace>,
scale: Float,
@ -333,7 +330,7 @@ impl InfinitePortalLight {
&self.base
}
pub fn new(
render_from_light: Transform<Float>,
render_from_light: TransformGeneric<Float>,
equal_area_image: &Image,
image_color_space: Arc<RGBColorSpace>,
scale: Float,
@ -485,8 +482,8 @@ impl InfinitePortalLight {
Some((
Point2f::new(
clamp_t((alpha + PI / 2.0) / PI, 0.0, 1.0),
clamp_t((beta + PI / 2.0) / PI, 0.0, 1.0),
clamp((alpha + PI / 2.0) / PI, 0.0, 1.0),
clamp((beta + PI / 2.0) / PI, 0.0, 1.0),
),
duv_dw,
))

View file

@ -2,25 +2,24 @@ pub mod diffuse;
pub mod infinite;
pub mod sampler;
use crate::core::geometry::{
Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray,
Vector3f, VectorLike, cos_theta,
};
use crate::core::interaction::{
Interaction, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction,
};
use crate::core::medium::MediumInterface;
use crate::core::pbrt::{Float, InternCache, PI};
use crate::geometry::{
Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray,
Vector3f, VectorLike, cos_theta,
};
use crate::core::pbrt::{Float, PI};
use crate::image::Image;
use crate::spectra::{
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum, SampledSpectrum,
SampledWavelengths, Spectrum, SpectrumTrait,
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGB, RGBColorSpace, RGBIlluminantSpectrum,
SampledSpectrum, SampledWavelengths, Spectrum, SpectrumProvider,
};
use crate::utils::color::RGB;
use crate::utils::colorspace::RGBColorSpace;
use crate::utils::containers::InternCache;
use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square};
use crate::utils::sampling::PiecewiseConstant2D;
use crate::utils::transform::Transform;
use crate::utils::transform::TransformGeneric;
use bitflags::bitflags;
use enum_dispatch::enum_dispatch;
@ -29,6 +28,8 @@ use std::sync::{Arc, OnceLock};
use diffuse::DiffuseAreaLight;
use infinite::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight};
pub use sampler::{LightSampler, LightSamplerTrait};
static SPECTRUM_CACHE: OnceLock<InternCache<DenselySampledSpectrum>> = OnceLock::new();
fn get_spectrum_cache() -> &'static InternCache<DenselySampledSpectrum> {
@ -64,7 +65,7 @@ pub struct LightLeSample {
pdf_dir: Float,
}
#[derive(Debug, Default, Clone)]
#[derive(Debug, Copy, Default, Clone)]
pub struct LightSampleContext {
pub pi: Point3fi,
pub n: Normal3f,
@ -147,14 +148,14 @@ impl LightLiSample {
#[derive(Debug, Clone)]
pub struct LightBase {
pub light_type: LightType,
pub render_from_light: Transform<Float>,
pub render_from_light: TransformGeneric<Float>,
pub medium_interface: MediumInterface,
}
impl LightBase {
pub fn new(
light_type: LightType,
render_from_light: &Transform<Float>,
render_from_light: &TransformGeneric<Float>,
medium_interface: &MediumInterface,
) -> Self {
Self {
@ -185,7 +186,7 @@ impl LightBase {
pub fn lookup_spectrum(s: &Spectrum) -> Arc<DenselySampledSpectrum> {
let cache = SPECTRUM_CACHE.get_or_init(InternCache::new);
let dense_spectrum = DenselySampledSpectrum::from_spectrum(s, LAMBDA_MIN, LAMBDA_MAX);
let dense_spectrum = DenselySampledSpectrum::from_spectrum(s);
cache.lookup(dense_spectrum)
}
}
@ -354,7 +355,7 @@ pub struct DistantLight {
}
impl DistantLight {
pub fn new(render_from_light: &Transform<Float>, lemit: Spectrum, scale: Float) -> Self {
pub fn new(render_from_light: &TransformGeneric<Float>, lemit: Spectrum, scale: Float) -> Self {
let base = LightBase::new(
LightType::DeltaDirection,
render_from_light,
@ -450,7 +451,7 @@ pub struct GoniometricLight {
impl GoniometricLight {
pub fn new(
render_from_light: &Transform<Float>,
render_from_light: &TransformGeneric<Float>,
medium_interface: &MediumInterface,
iemit: Spectrum,
scale: Float,
@ -545,7 +546,7 @@ pub struct PointLight {
impl PointLight {
pub fn new(
render_from_light: Transform<Float>,
render_from_light: TransformGeneric<Float>,
medium_interface: MediumInterface,
i: &Spectrum,
scale: Float,
@ -643,15 +644,15 @@ pub struct ProjectionLight {
scale: Float,
screen_bounds: Bounds2f,
hither: Float,
screen_from_light: Transform<Float>,
light_from_screen: Transform<Float>,
screen_from_light: TransformGeneric<Float>,
light_from_screen: TransformGeneric<Float>,
a: Float,
distrib: PiecewiseConstant2D,
}
impl ProjectionLight {
pub fn new(
render_from_light: Transform<Float>,
render_from_light: TransformGeneric<Float>,
medium_interface: MediumInterface,
image: Image,
image_color_space: Arc<RGBColorSpace>,
@ -674,7 +675,7 @@ impl ProjectionLight {
};
let hither = 1e-3;
let screen_from_light = Transform::perspective(fov, hither, 1e30).unwrap();
let screen_from_light = TransformGeneric::perspective(fov, hither, 1e30).unwrap();
let light_from_screen = screen_from_light.inverse();
let opposite = (radians(fov) / 2.).tan();
let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect };
@ -803,7 +804,7 @@ pub struct SpotLight {
impl SpotLight {
pub fn new(
render_from_light: &Transform<Float>,
render_from_light: &TransformGeneric<Float>,
medium_interface: &MediumInterface,
iemit: Spectrum,
scale: Float,

View file

@ -2,13 +2,13 @@ use super::{
Bounds3f, Float, Light, LightBounds, LightSampleContext, LightTrait, Normal3f, PI, Point3f,
SampledSpectrum, SampledWavelengths, Vector3f, VectorLike, safe_sqrt, square,
};
use crate::geometry::primitives::OctahedralVector;
use crate::geometry::{DirectionCone, Normal};
use crate::utils::math::sample_discrete;
use crate::core::geometry::primitives::OctahedralVector;
use crate::core::geometry::{DirectionCone, Normal};
use crate::utils::math::{clamp, lerp, sample_discrete};
use std::collections::HashMap;
use std::sync::Arc;
use crate::core::pbrt::{ONE_MINUS_EPSILON, clamp_t, lerp};
use crate::core::pbrt::ONE_MINUS_EPSILON;
use crate::utils::sampling::AliasTable;
use enum_dispatch::enum_dispatch;
@ -149,7 +149,7 @@ impl CompactLightBounds {
if min == max {
return 0.0;
}
65535.0 * clamp_t((c - min) / (max - min), 0.0, 1.0)
65535.0 * clamp((c - min) / (max - min), 0.0, 1.0)
}
}

View file

@ -1,17 +1,16 @@
use super::{
BilinearIntersection, BilinearPatchShape, Bounds3f, DirectionCone, Interaction, Normal3f,
Point2f, Point3f, Point3fi, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext,
ShapeTrait, SurfaceInteraction, Vector3f,
ShapeTrait, SurfaceInteraction, Transform, Vector3f,
};
use crate::core::geometry::{Tuple, VectorLike, spherical_quad_area};
use crate::core::interaction::InteractionTrait;
use crate::core::pbrt::{Float, clamp_t, gamma, lerp};
use crate::geometry::{Tuple, VectorLike, spherical_quad_area};
use crate::utils::math::{SquareMatrix, difference_of_products, quadratic};
use crate::core::pbrt::{Float, gamma};
use crate::utils::math::{SquareMatrix, clamp, difference_of_products, lerp, quadratic};
use crate::utils::mesh::BilinearPatchMesh;
use crate::utils::sampling::{
bilinear_pdf, invert_spherical_rectangle_sample, sample_bilinear, sample_spherical_rectangle,
};
use crate::utils::transform::Transform;
use std::sync::Arc;
use std::sync::OnceLock;
@ -228,7 +227,7 @@ impl BilinearPatchShape {
time: Float,
wo: Vector3f,
) -> SurfaceInteraction {
// Base geometry and derivatives
// Base geom and derivatives
let p = lerp(
uv[0],
lerp(uv[1], data.p00, data.p01),
@ -379,7 +378,7 @@ impl BilinearPatchShape {
}
let r = Transform::rotate_from_to(isect.n().normalize().into(), ns.into());
isect.set_shading_geometry(
isect.set_shading_geom(
ns,
r.apply_to_vector(isect.dpdu),
r.apply_to_vector(isect.dpdv),
@ -505,10 +504,12 @@ impl BilinearPatchShape {
}
impl ShapeTrait for BilinearPatchShape {
#[inline]
fn area(&self) -> Float {
self.area
}
#[inline]
fn normal_bounds(&self) -> DirectionCone {
let data = self.get_data();
if data.p00 == data.p10
@ -553,14 +554,16 @@ impl ShapeTrait for BilinearPatchShape {
.min(n_avg.dot(n10))
.min(n_avg.dot(n01))
.min(n_avg.dot(n11));
DirectionCone::new(n_avg.into(), clamp_t(cos_theta, -1., 1.))
DirectionCone::new(n_avg.into(), clamp(cos_theta, -1., 1.))
}
#[inline]
fn bounds(&self) -> Bounds3f {
let data = self.get_data();
Bounds3f::from_points(data.p00, data.p01).union(Bounds3f::from_points(data.p10, data.p11))
}
#[inline]
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
let t_max_val = t_max?;
let data = self.get_data();
@ -576,6 +579,7 @@ impl ShapeTrait for BilinearPatchShape {
}
}
#[inline]
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
let t_max_val = t_max.unwrap_or(Float::INFINITY);
let data = self.get_data();
@ -583,6 +587,7 @@ impl ShapeTrait for BilinearPatchShape {
.is_some()
}
#[inline]
fn sample(&self, u: Point2f) -> Option<ShapeSample> {
let data = self.get_data();
// Sample bilinear patch parametric coordinate (u, v)
@ -615,6 +620,7 @@ impl ShapeTrait for BilinearPatchShape {
})
}
#[inline]
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
let data = self.get_data();
let v00 = (data.p00 - ctx.p()).normalize();
@ -632,6 +638,7 @@ impl ShapeTrait for BilinearPatchShape {
}
}
#[inline]
fn pdf(&self, intr: &Interaction) -> Float {
let Interaction::Surface(si) = intr else {
return 0.0;
@ -663,6 +670,7 @@ impl ShapeTrait for BilinearPatchShape {
if cross == 0. { 0. } else { param_pdf / cross }
}
#[inline]
fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float {
let ray = ctx.spawn_ray(wi);
let Some(isect) = self.intersect(&ray, None) else {

View file

@ -1,6 +1,5 @@
use crate::core::interaction::InteractionTrait;
use crate::core::pbrt::{clamp_t, lerp};
use crate::utils::math::square;
use crate::utils::math::{clamp, lerp, square};
use crate::utils::splines::{
bound_cubic_bezier, cubic_bezier_control_points, evaluate_cubic_bezier, subdivide_cubic_bezier,
};
@ -15,7 +14,7 @@ use std::sync::Arc;
struct IntersectionContext {
ray: Ray,
object_from_ray: Arc<Transform<Float>>,
object_from_ray: Arc<Transform>,
common: CurveCommon,
}
@ -41,7 +40,7 @@ impl CurveShape {
(dx, _) = ray.d.coordinate_system();
}
let ray_from_object = look_at(ray.o, ray.o + ray.d, dx.into()).expect("Inversion error");
let ray_from_object = look_at(ray.o, ray.o + ray.d, dx).expect("Inversion error");
let cp = [0; 4].map(|i| ray_from_object.apply_to_point(cp_obj[i]));
// Test ray against bound of projected control points
@ -71,7 +70,7 @@ impl CurveShape {
let max_depth = if l0 > 0. {
let eps = self.common.width[0].max(self.common.width[1]) * 0.05;
let r0: i32 = (1.41421356237 * 6. * l0 / (8. * eps)).log2() as i32 / 2;
clamp_t(r0, 0, 10)
clamp(r0, 0, 10)
} else {
0
};
@ -152,7 +151,7 @@ impl CurveShape {
return None;
}
let w = Vector2f::new(cp[0].x(), cp[0].y()).dot(-segment_dir) / denom;
let u = clamp_t(lerp(w, u0, u1), u0, u1);
let u = clamp(lerp(w, u0, u1), u0, u1);
let ray_length = context.ray.d.norm();
let mut hit_width = lerp(u, self.common.width[0], self.common.width[1]);
@ -169,7 +168,7 @@ impl CurveShape {
hit_width = hit_width * n_hit.dot(context.ray.d.into()).abs() / ray_length;
}
let (pc, dpcdw) = evaluate_cubic_bezier(cp, clamp_t(w, 0., 1.));
let (pc, dpcdw) = evaluate_cubic_bezier(cp, clamp(w, 0., 1.));
let ray_length = context.ray.d.norm();
if !self.valid_hit(pc, hit_width, t_max, ray_length) {

View file

@ -3,18 +3,18 @@ use super::{
Point3fi, QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext,
ShapeTrait, SurfaceInteraction, Transform, Vector3f, Vector3fi,
};
use crate::core::geometry::{Sqrt, Tuple, VectorLike};
use crate::core::interaction::InteractionTrait;
use crate::core::pbrt::{gamma, lerp};
use crate::geometry::{Sqrt, Tuple, VectorLike};
use crate::core::pbrt::gamma;
use crate::utils::interval::Interval;
use crate::utils::math::{difference_of_products, square};
use crate::utils::math::{difference_of_products, lerp, square};
use std::mem;
use std::sync::Arc;
impl CylinderShape {
pub fn new(
render_from_object: Arc<Transform<Float>>,
object_from_render: Arc<Transform<Float>>,
render_from_object: Arc<Transform>,
object_from_render: Arc<Transform>,
reverse_orientation: bool,
radius: Float,
z_min: Float,

View file

@ -3,8 +3,8 @@ use super::{
Point3fi, QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext,
ShapeTrait, SurfaceInteraction, Transform, Vector3f,
};
use crate::core::geometry::VectorLike;
use crate::core::interaction::InteractionTrait;
use crate::geometry::VectorLike;
use crate::utils::math::square;
use crate::utils::sampling::sample_uniform_disk_concentric;
use std::sync::Arc;
@ -15,8 +15,8 @@ impl DiskShape {
inner_radius: Float,
height: Float,
phi_max: Float,
render_from_object: Arc<Transform<Float>>,
object_from_render: Arc<Transform<Float>>,
render_from_object: Arc<Transform>,
object_from_render: Arc<Transform>,
reverse_orientation: bool,
) -> Self {
Self {

View file

@ -5,16 +5,16 @@ pub mod disk;
pub mod sphere;
pub mod triangle;
use crate::core::geometry::{
Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
Vector3fi, VectorLike,
};
use crate::core::interaction::{
Interaction, InteractionTrait, MediumInteraction, SurfaceInteraction,
};
use crate::core::material::Material;
use crate::core::medium::{Medium, MediumInterface};
use crate::core::pbrt::{Float, PI};
use crate::geometry::{
Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
Vector3fi, VectorLike,
};
use crate::lights::Light;
use crate::utils::math::{next_float_down, next_float_up};
use crate::utils::transform::Transform;
@ -29,8 +29,8 @@ pub struct SphereShape {
theta_z_min: Float,
theta_z_max: Float,
phi_max: Float,
render_from_object: Arc<Transform<Float>>,
object_from_render: Arc<Transform<Float>>,
render_from_object: Arc<Transform>,
object_from_render: Arc<Transform>,
reverse_orientation: bool,
transform_swap_handedness: bool,
}
@ -55,8 +55,8 @@ pub struct CylinderShape {
z_min: Float,
z_max: Float,
phi_max: Float,
render_from_object: Arc<Transform<Float>>,
object_from_render: Arc<Transform<Float>>,
render_from_object: Arc<Transform>,
object_from_render: Arc<Transform>,
reverse_orientation: bool,
transform_swap_handedness: bool,
}
@ -67,8 +67,8 @@ pub struct DiskShape {
inner_radius: Float,
height: Float,
phi_max: Float,
render_from_object: Arc<Transform<Float>>,
object_from_render: Arc<Transform<Float>>,
render_from_object: Arc<Transform>,
object_from_render: Arc<Transform>,
reverse_orientation: bool,
transform_swap_handedness: bool,
}
@ -102,8 +102,8 @@ pub struct CurveCommon {
n: [Normal3f; 2],
normal_angle: Float,
inv_sin_normal_angle: Float,
render_from_object: Arc<Transform<Float>>,
object_from_render: Arc<Transform<Float>>,
render_from_object: Arc<Transform>,
object_from_render: Arc<Transform>,
reverse_orientation: bool,
transform_swap_handedness: bool,
}
@ -116,8 +116,8 @@ impl CurveCommon {
w1: Float,
curve_type: CurveType,
norm: &[Vector3f],
render_from_object: Arc<Transform<Float>>,
object_from_render: Arc<Transform<Float>>,
render_from_object: Arc<Transform>,
object_from_render: Arc<Transform>,
reverse_orientation: bool,
) -> Self {
let transform_swap_handedness = render_from_object.swaps_handedness();

View file

@ -3,11 +3,11 @@ use super::{
QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait,
SphereShape, SurfaceInteraction, Transform, Vector3f, Vector3fi,
};
use crate::core::geometry::{Frame, Sqrt, VectorLike, spherical_direction};
use crate::core::interaction::InteractionTrait;
use crate::core::pbrt::{clamp_t, gamma};
use crate::geometry::{Frame, Sqrt, VectorLike, spherical_direction};
use crate::core::pbrt::gamma;
use crate::utils::interval::Interval;
use crate::utils::math::{difference_of_products, radians, safe_acos, safe_sqrt, square};
use crate::utils::math::{clamp, difference_of_products, radians, safe_acos, safe_sqrt, square};
use crate::utils::sampling::sample_uniform_sphere;
use std::mem;
@ -15,23 +15,23 @@ use std::sync::Arc;
impl SphereShape {
pub fn new(
render_from_object: Arc<Transform<Float>>,
object_from_render: Arc<Transform<Float>>,
render_from_object: Arc<Transform>,
object_from_render: Arc<Transform>,
reverse_orientation: bool,
radius: Float,
z_min: Float,
z_max: Float,
phi_max: Float,
) -> Self {
let theta_z_min = clamp_t(z_min.min(z_max) / radius, -1., 1.).acos();
let theta_z_max = clamp_t(z_max.min(z_max) / radius, -1., 1.).acos();
let phi_max = radians(clamp_t(phi_max, 0., 360.0));
let theta_z_min = clamp(z_min.min(z_max) / radius, -1., 1.).acos();
let theta_z_max = clamp(z_max.min(z_max) / radius, -1., 1.).acos();
let phi_max = radians(clamp(phi_max, 0., 360.0));
Self {
render_from_object: render_from_object.clone(),
object_from_render: object_from_render.clone(),
radius,
z_min: clamp_t(z_min.min(z_max), -radius, radius),
z_max: clamp_t(z_min.max(z_max), -radius, radius),
z_min: clamp(z_min.min(z_max), -radius, radius),
z_max: clamp(z_min.max(z_max), -radius, radius),
theta_z_max,
theta_z_min,
phi_max,

View file

@ -3,9 +3,9 @@ use super::{
ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait, SurfaceInteraction,
TriangleIntersection, TriangleShape, Vector2f, Vector3f,
};
use crate::core::geometry::{Sqrt, Tuple, VectorLike, spherical_triangle_area};
use crate::core::interaction::InteractionTrait;
use crate::core::pbrt::gamma;
use crate::geometry::{Sqrt, Tuple, VectorLike, spherical_triangle_area};
use crate::utils::math::{difference_of_products, square};
use crate::utils::mesh::TriangleMesh;
use crate::utils::sampling::{

View file

@ -1,4 +1,4 @@
use crate::core::pbrt::Float;
use crate::Float;
pub const CIE_SAMPLES: usize = 471;
pub const CIE_X: [Float; CIE_SAMPLES] = [
@ -1484,7 +1484,7 @@ pub const CIE_ILLUM_A: [Float; 214] = [
];
// CIE Illuminant D S basis functions
const N_CIES: usize = 107;
pub const N_CIES: usize = 107;
pub const CIE_S_LAMBDA: [Float; N_CIES] = [
300.000000, 305.000000, 310.000000, 315.000000, 320.000000, 325.000000, 330.000000, 335.000000,
@ -1687,6 +1687,30 @@ pub const CIE_ILLUM_F2: [Float; 162] = [
780.000000, 0.210000,
];
pub const CIE_ILLUM_F3: [Float; 162] = [
380.000000, 0.820000, 385.000000, 1.020000, 390.000000, 1.260000, 395.000000, 1.440000,
400.000000, 2.570000, 405.000000, 14.360000, 410.000000, 2.700000, 415.000000, 2.450000,
420.000000, 2.730000, 425.000000, 3.000000, 430.000000, 3.280000, 435.000000, 31.850000,
440.000000, 9.470000, 445.000000, 4.020000, 450.000000, 4.250000, 455.000000, 4.440000,
460.000000, 4.590000, 465.000000, 4.720000, 470.000000, 4.800000, 475.000000, 4.860000,
480.000000, 4.870000, 485.000000, 4.850000, 490.000000, 4.880000, 495.000000, 4.770000,
500.000000, 4.670000, 505.000000, 4.620000, 510.000000, 4.620000, 515.000000, 4.730000,
520.000000, 4.990000, 525.000000, 5.480000, 530.000000, 6.250000, 535.000000, 7.340000,
540.000000, 8.780000, 545.000000, 23.820000, 550.000000, 16.139999, 555.000000, 14.590000,
560.000000, 16.629999, 565.000000, 18.490000, 570.000000, 19.950001, 575.000000, 23.110001,
580.000000, 24.690001, 585.000000, 21.410000, 590.000000, 20.850000, 595.000000, 19.930000,
600.000000, 18.670000, 605.000000, 17.219999, 610.000000, 15.650000, 615.000000, 14.040000,
620.000000, 12.450000, 625.000000, 10.950000, 630.000000, 9.510000, 635.000000, 8.270000,
640.000000, 7.110000, 645.000000, 6.090000, 650.000000, 5.220000, 655.000000, 4.450000,
660.000000, 3.800000, 665.000000, 3.230000, 670.000000, 2.750000, 675.000000, 2.330000,
680.000000, 1.990000, 685.000000, 1.700000, 690.000000, 1.550000, 695.000000, 1.270000,
700.000000, 1.090000, 705.000000, 0.960000, 710.000000, 0.830000, 715.000000, 0.710000,
720.000000, 0.620000, 725.000000, 0.540000, 730.000000, 0.490000, 735.000000, 0.460000,
740.000000, 0.430000, 745.000000, 0.390000, 750.000000, 0.390000, 755.000000, 0.350000,
760.000000, 0.380000, 765.000000, 0.390000, 770.000000, 0.330000, 775.000000, 0.280000,
780.000000, 0.210000,
];
pub const CIE_ILLUM_F4: [Float; 162] = [
380.000000, 0.570000, 385.000000, 0.700000, 390.000000, 0.870000, 395.000000, 0.980000,
400.000000, 2.010000, 405.000000, 13.750000, 410.000000, 1.950000, 415.000000, 1.590000,
@ -1920,7 +1944,7 @@ pub const AG_ETA: [Float; 112] = [
799.897949, 0.142563, 826.561157, 0.145000, 855.063293, 0.151938, 885.601257, 0.163000,
];
const AG_K: [Float; 112] = [
pub const AG_K: [Float; 112] = [
298.757050, 1.080000, 302.400421, 0.882000, 306.133759, 0.761063, 309.960449, 0.647000,
313.884003, 0.550875, 317.908142, 0.504000, 322.036835, 0.554375, 326.274139, 0.663000,
330.624481, 0.818563, 335.092377, 0.986000, 339.682678, 1.120687, 344.400482, 1.240000,
@ -2116,7 +2140,7 @@ pub const TIO2_K: [Float; 68] = [
// SPDX-License-Identifier: Apache-2.0
// Data for various types of glass (refractive index eta vs. wavelength in nm).
pub const GLASSBK7_ETA: [f32; 58] = [
pub const GLASS_BK7_ETA: [f32; 58] = [
300.0,
1.5527702635739,
322.0,
@ -6184,3 +6208,199 @@ pub const SONY_ILCE_9_B: [f32; 70] = [
720.0,
0.0005996684000000017,
];
pub static N_SWATCHES_REFLECTANCES: usize = 24;
pub static SWATCHES_RAW: &[&[Float]] = &[
&[
380.0, 0.055, 390.0, 0.058, 400.0, 0.061, 410.0, 0.062, 420.0, 0.062, 430.0, 0.062, 440.0,
0.062, 450.0, 0.062, 460.0, 0.062, 470.0, 0.062, 480.0, 0.062, 490.0, 0.063, 500.0, 0.065,
510.0, 0.070, 520.0, 0.076, 530.0, 0.079, 540.0, 0.081, 550.0, 0.084, 560.0, 0.091, 570.0,
0.103, 580.0, 0.119, 590.0, 0.134, 600.0, 0.143, 610.0, 0.147, 620.0, 0.151, 630.0, 0.158,
640.0, 0.168, 650.0, 0.179, 660.0, 0.188, 670.0, 0.190, 680.0, 0.186, 690.0, 0.181, 700.0,
0.182, 710.0, 0.187, 720.0, 0.196, 730.0, 0.209,
],
&[
380.0, 0.117, 390.0, 0.143, 400.0, 0.175, 410.0, 0.191, 420.0, 0.196, 430.0, 0.199, 440.0,
0.204, 450.0, 0.213, 460.0, 0.228, 470.0, 0.251, 480.0, 0.280, 490.0, 0.309, 500.0, 0.329,
510.0, 0.333, 520.0, 0.315, 530.0, 0.286, 540.0, 0.273, 550.0, 0.276, 560.0, 0.277, 570.0,
0.289, 580.0, 0.339, 590.0, 0.420, 600.0, 0.488, 610.0, 0.525, 620.0, 0.546, 630.0, 0.562,
640.0, 0.578, 650.0, 0.595, 660.0, 0.612, 670.0, 0.625, 680.0, 0.638, 690.0, 0.656, 700.0,
0.678, 710.0, 0.700, 720.0, 0.717, 730.0, 0.734,
],
&[
380.0, 0.130, 390.0, 0.177, 400.0, 0.251, 410.0, 0.306, 420.0, 0.324, 430.0, 0.330, 440.0,
0.333, 450.0, 0.331, 460.0, 0.323, 470.0, 0.311, 480.0, 0.298, 490.0, 0.285, 500.0, 0.269,
510.0, 0.250, 520.0, 0.231, 530.0, 0.214, 540.0, 0.199, 550.0, 0.185, 560.0, 0.169, 570.0,
0.157, 580.0, 0.149, 590.0, 0.145, 600.0, 0.142, 610.0, 0.141, 620.0, 0.141, 630.0, 0.141,
640.0, 0.143, 650.0, 0.147, 660.0, 0.152, 670.0, 0.154, 680.0, 0.150, 690.0, 0.144, 700.0,
0.136, 710.0, 0.132, 720.0, 0.135, 730.0, 0.147,
],
&[
380.0, 0.051, 390.0, 0.054, 400.0, 0.056, 410.0, 0.057, 420.0, 0.058, 430.0, 0.059, 440.0,
0.060, 450.0, 0.061, 460.0, 0.062, 470.0, 0.063, 480.0, 0.065, 490.0, 0.067, 500.0, 0.075,
510.0, 0.101, 520.0, 0.145, 530.0, 0.178, 540.0, 0.184, 550.0, 0.170, 560.0, 0.149, 570.0,
0.133, 580.0, 0.122, 590.0, 0.115, 600.0, 0.109, 610.0, 0.105, 620.0, 0.104, 630.0, 0.106,
640.0, 0.109, 650.0, 0.112, 660.0, 0.114, 670.0, 0.114, 680.0, 0.112, 690.0, 0.112, 700.0,
0.115, 710.0, 0.120, 720.0, 0.125, 730.0, 0.130,
],
&[
380.0, 0.144, 390.0, 0.198, 400.0, 0.294, 410.0, 0.375, 420.0, 0.408, 430.0, 0.421, 440.0,
0.426, 450.0, 0.426, 460.0, 0.419, 470.0, 0.403, 480.0, 0.379, 490.0, 0.346, 500.0, 0.311,
510.0, 0.281, 520.0, 0.254, 530.0, 0.229, 540.0, 0.214, 550.0, 0.208, 560.0, 0.202, 570.0,
0.194, 580.0, 0.193, 590.0, 0.200, 600.0, 0.214, 610.0, 0.230, 620.0, 0.241, 630.0, 0.254,
640.0, 0.279, 650.0, 0.313, 660.0, 0.348, 670.0, 0.366, 680.0, 0.366, 690.0, 0.359, 700.0,
0.358, 710.0, 0.365, 720.0, 0.377, 730.0, 0.398,
],
&[
380.0, 0.136, 390.0, 0.179, 400.0, 0.247, 410.0, 0.297, 420.0, 0.320, 430.0, 0.337, 440.0,
0.355, 450.0, 0.381, 460.0, 0.419, 470.0, 0.466, 480.0, 0.510, 490.0, 0.546, 500.0, 0.567,
510.0, 0.574, 520.0, 0.569, 530.0, 0.551, 540.0, 0.524, 550.0, 0.488, 560.0, 0.445, 570.0,
0.400, 580.0, 0.350, 590.0, 0.299, 600.0, 0.252, 610.0, 0.221, 620.0, 0.204, 630.0, 0.196,
640.0, 0.191, 650.0, 0.188, 660.0, 0.191, 670.0, 0.199, 680.0, 0.212, 690.0, 0.223, 700.0,
0.232, 710.0, 0.233, 720.0, 0.229, 730.0, 0.229,
],
&[
380.0, 0.054, 390.0, 0.054, 400.0, 0.053, 410.0, 0.054, 420.0, 0.054, 430.0, 0.055, 440.0,
0.055, 450.0, 0.055, 460.0, 0.056, 470.0, 0.057, 480.0, 0.058, 490.0, 0.061, 500.0, 0.068,
510.0, 0.089, 520.0, 0.125, 530.0, 0.154, 540.0, 0.174, 550.0, 0.199, 560.0, 0.248, 570.0,
0.335, 580.0, 0.444, 590.0, 0.538, 600.0, 0.587, 610.0, 0.595, 620.0, 0.591, 630.0, 0.587,
640.0, 0.584, 650.0, 0.584, 660.0, 0.590, 670.0, 0.603, 680.0, 0.620, 690.0, 0.639, 700.0,
0.655, 710.0, 0.663, 720.0, 0.663, 730.0, 0.667,
],
&[
380.0, 0.122, 390.0, 0.164, 400.0, 0.229, 410.0, 0.286, 420.0, 0.327, 430.0, 0.361, 440.0,
0.388, 450.0, 0.400, 460.0, 0.392, 470.0, 0.362, 480.0, 0.316, 490.0, 0.260, 500.0, 0.209,
510.0, 0.168, 520.0, 0.138, 530.0, 0.117, 540.0, 0.104, 550.0, 0.096, 560.0, 0.090, 570.0,
0.086, 580.0, 0.084, 590.0, 0.084, 600.0, 0.084, 610.0, 0.084, 620.0, 0.084, 630.0, 0.085,
640.0, 0.090, 650.0, 0.098, 660.0, 0.109, 670.0, 0.123, 680.0, 0.143, 690.0, 0.169, 700.0,
0.205, 710.0, 0.244, 720.0, 0.287, 730.0, 0.332,
],
&[
380.0, 0.096, 390.0, 0.115, 400.0, 0.131, 410.0, 0.135, 420.0, 0.133, 430.0, 0.132, 440.0,
0.130, 450.0, 0.128, 460.0, 0.125, 470.0, 0.120, 480.0, 0.115, 490.0, 0.110, 500.0, 0.105,
510.0, 0.100, 520.0, 0.095, 530.0, 0.093, 540.0, 0.092, 550.0, 0.093, 560.0, 0.096, 570.0,
0.108, 580.0, 0.156, 590.0, 0.265, 600.0, 0.399, 610.0, 0.500, 620.0, 0.556, 630.0, 0.579,
640.0, 0.588, 650.0, 0.591, 660.0, 0.593, 670.0, 0.594, 680.0, 0.598, 690.0, 0.602, 700.0,
0.607, 710.0, 0.609, 720.0, 0.609, 730.0, 0.610,
],
&[
380.0, 0.092, 390.0, 0.116, 400.0, 0.146, 410.0, 0.169, 420.0, 0.178, 430.0, 0.173, 440.0,
0.158, 450.0, 0.139, 460.0, 0.119, 470.0, 0.101, 480.0, 0.087, 490.0, 0.075, 500.0, 0.066,
510.0, 0.060, 520.0, 0.056, 530.0, 0.053, 540.0, 0.051, 550.0, 0.051, 560.0, 0.052, 570.0,
0.052, 580.0, 0.051, 590.0, 0.052, 600.0, 0.058, 610.0, 0.073, 620.0, 0.096, 630.0, 0.119,
640.0, 0.141, 650.0, 0.166, 660.0, 0.194, 670.0, 0.227, 680.0, 0.265, 690.0, 0.309, 700.0,
0.355, 710.0, 0.396, 720.0, 0.436, 730.0, 0.478,
],
&[
380.0, 0.061, 390.0, 0.061, 400.0, 0.062, 410.0, 0.063, 420.0, 0.064, 430.0, 0.066, 440.0,
0.069, 450.0, 0.075, 460.0, 0.085, 470.0, 0.105, 480.0, 0.139, 490.0, 0.192, 500.0, 0.271,
510.0, 0.376, 520.0, 0.476, 530.0, 0.531, 540.0, 0.549, 550.0, 0.546, 560.0, 0.528, 570.0,
0.504, 580.0, 0.471, 590.0, 0.428, 600.0, 0.381, 610.0, 0.347, 620.0, 0.327, 630.0, 0.318,
640.0, 0.312, 650.0, 0.310, 660.0, 0.314, 670.0, 0.327, 680.0, 0.345, 690.0, 0.363, 700.0,
0.376, 710.0, 0.381, 720.0, 0.378, 730.0, 0.379,
],
&[
380.0, 0.063, 390.0, 0.063, 400.0, 0.063, 410.0, 0.064, 420.0, 0.064, 430.0, 0.064, 440.0,
0.065, 450.0, 0.066, 460.0, 0.067, 470.0, 0.068, 480.0, 0.071, 490.0, 0.076, 500.0, 0.087,
510.0, 0.125, 520.0, 0.206, 530.0, 0.305, 540.0, 0.383, 550.0, 0.431, 560.0, 0.469, 570.0,
0.518, 580.0, 0.568, 590.0, 0.607, 600.0, 0.628, 610.0, 0.637, 620.0, 0.640, 630.0, 0.642,
640.0, 0.645, 650.0, 0.648, 660.0, 0.651, 670.0, 0.653, 680.0, 0.657, 690.0, 0.664, 700.0,
0.673, 710.0, 0.680, 720.0, 0.684, 730.0, 0.688,
],
&[
380.0, 0.066, 390.0, 0.079, 400.0, 0.102, 410.0, 0.146, 420.0, 0.200, 430.0, 0.244, 440.0,
0.282, 450.0, 0.309, 460.0, 0.308, 470.0, 0.278, 480.0, 0.231, 490.0, 0.178, 500.0, 0.130,
510.0, 0.094, 520.0, 0.070, 530.0, 0.054, 540.0, 0.046, 550.0, 0.042, 560.0, 0.039, 570.0,
0.038, 580.0, 0.038, 590.0, 0.038, 600.0, 0.038, 610.0, 0.039, 620.0, 0.039, 630.0, 0.040,
640.0, 0.041, 650.0, 0.042, 660.0, 0.044, 670.0, 0.045, 680.0, 0.046, 690.0, 0.046, 700.0,
0.048, 710.0, 0.052, 720.0, 0.057, 730.0, 0.065,
],
&[
380.0, 0.052, 390.0, 0.053, 400.0, 0.054, 410.0, 0.055, 420.0, 0.057, 430.0, 0.059, 440.0,
0.061, 450.0, 0.066, 460.0, 0.075, 470.0, 0.093, 480.0, 0.125, 490.0, 0.178, 500.0, 0.246,
510.0, 0.307, 520.0, 0.337, 530.0, 0.334, 540.0, 0.317, 550.0, 0.293, 560.0, 0.262, 570.0,
0.230, 580.0, 0.198, 590.0, 0.165, 600.0, 0.135, 610.0, 0.115, 620.0, 0.104, 630.0, 0.098,
640.0, 0.094, 650.0, 0.092, 660.0, 0.093, 670.0, 0.097, 680.0, 0.102, 690.0, 0.108, 700.0,
0.113, 710.0, 0.115, 720.0, 0.114, 730.0, 0.114,
],
&[
380.0, 0.050, 390.0, 0.049, 400.0, 0.048, 410.0, 0.047, 420.0, 0.047, 430.0, 0.047, 440.0,
0.047, 450.0, 0.047, 460.0, 0.046, 470.0, 0.045, 480.0, 0.044, 490.0, 0.044, 500.0, 0.045,
510.0, 0.046, 520.0, 0.047, 530.0, 0.048, 540.0, 0.049, 550.0, 0.050, 560.0, 0.054, 570.0,
0.060, 580.0, 0.072, 590.0, 0.104, 600.0, 0.178, 610.0, 0.312, 620.0, 0.467, 630.0, 0.581,
640.0, 0.644, 650.0, 0.675, 660.0, 0.690, 670.0, 0.698, 680.0, 0.706, 690.0, 0.715, 700.0,
0.724, 710.0, 0.730, 720.0, 0.734, 730.0, 0.738,
],
&[
380.0, 0.058, 390.0, 0.054, 400.0, 0.052, 410.0, 0.052, 420.0, 0.053, 430.0, 0.054, 440.0,
0.056, 450.0, 0.059, 460.0, 0.067, 470.0, 0.081, 480.0, 0.107, 490.0, 0.152, 500.0, 0.225,
510.0, 0.336, 520.0, 0.462, 530.0, 0.559, 540.0, 0.616, 550.0, 0.650, 560.0, 0.672, 570.0,
0.694, 580.0, 0.710, 590.0, 0.723, 600.0, 0.731, 610.0, 0.739, 620.0, 0.746, 630.0, 0.752,
640.0, 0.758, 650.0, 0.764, 660.0, 0.769, 670.0, 0.771, 680.0, 0.776, 690.0, 0.782, 700.0,
0.790, 710.0, 0.796, 720.0, 0.799, 730.0, 0.804,
],
&[
380.0, 0.145, 390.0, 0.195, 400.0, 0.283, 410.0, 0.346, 420.0, 0.362, 430.0, 0.354, 440.0,
0.334, 450.0, 0.306, 460.0, 0.276, 470.0, 0.248, 480.0, 0.218, 490.0, 0.190, 500.0, 0.168,
510.0, 0.149, 520.0, 0.127, 530.0, 0.107, 540.0, 0.100, 550.0, 0.102, 560.0, 0.104, 570.0,
0.109, 580.0, 0.137, 590.0, 0.200, 600.0, 0.290, 610.0, 0.400, 620.0, 0.516, 630.0, 0.615,
640.0, 0.687, 650.0, 0.732, 660.0, 0.760, 670.0, 0.774, 680.0, 0.783, 690.0, 0.793, 700.0,
0.803, 710.0, 0.812, 720.0, 0.817, 730.0, 0.825,
],
&[
380.0, 0.108, 390.0, 0.141, 400.0, 0.192, 410.0, 0.236, 420.0, 0.261, 430.0, 0.286, 440.0,
0.317, 450.0, 0.353, 460.0, 0.390, 470.0, 0.426, 480.0, 0.446, 490.0, 0.444, 500.0, 0.423,
510.0, 0.385, 520.0, 0.337, 530.0, 0.283, 540.0, 0.231, 550.0, 0.185, 560.0, 0.146, 570.0,
0.118, 580.0, 0.101, 590.0, 0.090, 600.0, 0.082, 610.0, 0.076, 620.0, 0.074, 630.0, 0.073,
640.0, 0.073, 650.0, 0.074, 660.0, 0.076, 670.0, 0.077, 680.0, 0.076, 690.0, 0.075, 700.0,
0.073, 710.0, 0.072, 720.0, 0.074, 730.0, 0.079,
],
&[
380.0, 0.189, 390.0, 0.255, 400.0, 0.423, 410.0, 0.660, 420.0, 0.811, 430.0, 0.862, 440.0,
0.877, 450.0, 0.884, 460.0, 0.891, 470.0, 0.896, 480.0, 0.899, 490.0, 0.904, 500.0, 0.907,
510.0, 0.909, 520.0, 0.911, 530.0, 0.910, 540.0, 0.911, 550.0, 0.914, 560.0, 0.913, 570.0,
0.916, 580.0, 0.915, 590.0, 0.916, 600.0, 0.914, 610.0, 0.915, 620.0, 0.918, 630.0, 0.919,
640.0, 0.921, 650.0, 0.923, 660.0, 0.924, 670.0, 0.922, 680.0, 0.922, 690.0, 0.925, 700.0,
0.927, 710.0, 0.930, 720.0, 0.930, 730.0, 0.933,
],
&[
380.0, 0.171, 390.0, 0.232, 400.0, 0.365, 410.0, 0.507, 420.0, 0.567, 430.0, 0.583, 440.0,
0.588, 450.0, 0.590, 460.0, 0.591, 470.0, 0.590, 480.0, 0.588, 490.0, 0.588, 500.0, 0.589,
510.0, 0.589, 520.0, 0.591, 530.0, 0.590, 540.0, 0.590, 550.0, 0.590, 560.0, 0.589, 570.0,
0.591, 580.0, 0.590, 590.0, 0.590, 600.0, 0.587, 610.0, 0.585, 620.0, 0.583, 630.0, 0.580,
640.0, 0.578, 650.0, 0.576, 660.0, 0.574, 670.0, 0.572, 680.0, 0.571, 690.0, 0.569, 700.0,
0.568, 710.0, 0.568, 720.0, 0.566, 730.0, 0.566,
],
&[
380.0, 0.144, 390.0, 0.192, 400.0, 0.272, 410.0, 0.331, 420.0, 0.350, 430.0, 0.357, 440.0,
0.361, 450.0, 0.363, 460.0, 0.363, 470.0, 0.361, 480.0, 0.359, 490.0, 0.358, 500.0, 0.358,
510.0, 0.359, 520.0, 0.360, 530.0, 0.360, 540.0, 0.361, 550.0, 0.361, 560.0, 0.360, 570.0,
0.362, 580.0, 0.362, 590.0, 0.361, 600.0, 0.359, 610.0, 0.358, 620.0, 0.355, 630.0, 0.352,
640.0, 0.350, 650.0, 0.348, 660.0, 0.345, 670.0, 0.343, 680.0, 0.340, 690.0, 0.338, 700.0,
0.335, 710.0, 0.334, 720.0, 0.332, 730.0, 0.331,
],
&[
380.0, 0.105, 390.0, 0.131, 400.0, 0.163, 410.0, 0.180, 420.0, 0.186, 430.0, 0.190, 440.0,
0.193, 450.0, 0.194, 460.0, 0.194, 470.0, 0.192, 480.0, 0.191, 490.0, 0.191, 500.0, 0.191,
510.0, 0.192, 520.0, 0.192, 530.0, 0.192, 540.0, 0.192, 550.0, 0.192, 560.0, 0.192, 570.0,
0.193, 580.0, 0.192, 590.0, 0.192, 600.0, 0.191, 610.0, 0.189, 620.0, 0.188, 630.0, 0.186,
640.0, 0.184, 650.0, 0.182, 660.0, 0.181, 670.0, 0.179, 680.0, 0.178, 690.0, 0.176, 700.0,
0.174, 710.0, 0.173, 720.0, 0.172, 730.0, 0.171,
],
&[
380.0, 0.068, 390.0, 0.077, 400.0, 0.084, 410.0, 0.087, 420.0, 0.089, 430.0, 0.090, 440.0,
0.092, 450.0, 0.092, 460.0, 0.091, 470.0, 0.090, 480.0, 0.090, 490.0, 0.090, 500.0, 0.090,
510.0, 0.090, 520.0, 0.090, 530.0, 0.090, 540.0, 0.090, 550.0, 0.090, 560.0, 0.090, 570.0,
0.090, 580.0, 0.090, 590.0, 0.089, 600.0, 0.089, 610.0, 0.088, 620.0, 0.087, 630.0, 0.086,
640.0, 0.086, 650.0, 0.085, 660.0, 0.084, 670.0, 0.084, 680.0, 0.083, 690.0, 0.083, 700.0,
0.082, 710.0, 0.081, 720.0, 0.081, 730.0, 0.081,
],
&[
380.0, 0.031, 390.0, 0.032, 400.0, 0.032, 410.0, 0.033, 420.0, 0.033, 430.0, 0.033, 440.0,
0.033, 450.0, 0.033, 460.0, 0.032, 470.0, 0.032, 480.0, 0.032, 490.0, 0.032, 500.0, 0.032,
510.0, 0.032, 520.0, 0.032, 530.0, 0.032, 540.0, 0.032, 550.0, 0.032, 560.0, 0.032, 570.0,
0.032, 580.0, 0.032, 590.0, 0.032, 600.0, 0.032, 610.0, 0.032, 620.0, 0.032, 630.0, 0.032,
640.0, 0.032, 650.0, 0.032, 660.0, 0.032, 670.0, 0.032, 680.0, 0.032, 690.0, 0.032, 700.0,
0.032, 710.0, 0.032, 720.0, 0.032, 730.0, 0.033,
],
];

View file

@ -283,12 +283,20 @@ impl RGB {
(self.r + self.g + self.b) / 3.0
}
pub fn max(&self) -> Float {
pub fn max_component_value(&self) -> Float {
self.r.max(self.g).max(self.b)
}
pub fn max_component_index(&self) -> usize {
if self.r > self.g {
if self.r > self.b { 0 } else { 2 }
} else {
if self.g > self.b { 1 } else { 2 }
}
}
pub fn clamp_zero(rgb: Self) -> Self {
RGB::new(rgb.r.max(0.), rgb.b.max(0.), rgb.b.max(0.))
RGB::new(rgb.r.max(0.), rgb.b.max(0.), rgb.g.max(0.))
}
}
@ -510,15 +518,37 @@ pub type CoefficientArray = [[[[[Float; 3]; RES]; RES]; RES]; 3];
#[derive(Debug)]
pub struct RGBToSpectrumTable {
z_nodes: &'static [f32],
coeffs: &'static CoefficientArray,
coeffs: &'static [Float],
scale: &'static [Float],
}
impl RGBToSpectrumTable {
pub fn srgb() -> Self {
// use crate::core::constants::{RGB_TO_SPECTRUM_Z_NODES, RGB_TO_SPECTRUM_COEFFS};
// Self::new(&RGB_TO_SPECTRUM_Z_NODES, &RGB_TO_SPECTRUM_COEFFS)
todo!("Link the static constant arrays for sRGB coefficients here")
Self {
coeffs: *crate::data::SRGB_COEFFS,
scale: *crate::data::SRGB_SCALE,
}
}
pub fn dci_p3() -> Self {
Self {
coeffs: *crate::data::DCI_P3_COEFFS,
scale: *crate::data::DCI_P3_SCALE,
}
}
pub fn rec2020() -> Self {
Self {
coeffs: *crate::data::REC2020_COEFFS,
scale: *crate::data::REC2020_SCALE,
}
}
pub fn aces2065_1() -> Self {
Self {
coeffs: *crate::data::ACES_COEFFS,
scale: *crate::data::ACES_SCALE,
}
}
}
@ -563,11 +593,12 @@ impl RGBSigmoidPolynomial {
}
impl RGBToSpectrumTable {
pub fn new(z_nodes: &'static [f32], coeffs: &'static CoefficientArray) -> Self {
Self { z_nodes, coeffs }
pub fn new(scale: &'static [Float], coeffs: &'static [Float]) -> Self {
Self { scale, coeffs }
}
pub fn to_polynomial(&self, rgb: RGB) -> RGBSigmoidPolynomial {
//TODO: Check all of this logic, this is important
if rgb[0] == rgb[1] && rgb[1] == rgb[2] {
return RGBSigmoidPolynomial::new(
0.0,
@ -575,53 +606,68 @@ impl RGBToSpectrumTable {
(rgb[0] - 0.5) / (rgb[0] * (1.0 - rgb[0])).sqrt(),
);
}
let maxc;
if rgb[0] > rgb[1] {
if rgb[0] > rgb[2] {
maxc = 0;
} else {
maxc = 2;
let max_c = rgb.max_component_value();
let c = [rgb.r, rgb.g, rgb.b];
let (min_c, mid_c) = if c[0] < c[1] {
if c[1] < c[2] {
(c[0], c[1])
}
} else if rgb[1] > rgb[2] {
maxc = 1;
// 0 < 1 < 2
else if c[0] < c[2] {
(c[0], c[2])
}
// 0 < 2 < 1
else {
(c[2], c[0])
} // 2 < 0 < 1
} else {
maxc = 2;
}
if c[1] > c[2] {
(c[2], c[1])
}
// 2 < 1 < 0
else if c[0] > c[2] {
(c[1], c[2])
}
// 1 < 2 < 0
else {
(c[1], c[0])
} // 1 < 0 < 2
};
let z = rgb[maxc];
let x = rgb[(maxc + 1) % 3] * (RES - 1) as Float / z;
let y = rgb[(maxc + 2) % 3] * (RES - 1) as Float / z;
let z = min_c / max_c;
let x = mid_c / max_c;
let xi = x.min(RES as Float - 2.0);
let yi = y.min(RES as Float - 2.0);
let zi = crate::core::pbrt::find_interval(RES, |i: usize| self.z_nodes[i] < z);
let dx = (x - xi) as usize;
let dy = (y - yi) as usize;
let dz = (z - self.z_nodes[zi]) / (self.z_nodes[zi + 1] - self.z_nodes[zi]);
let mut c = [0.0; 3];
let z_float = z * (RES - 1) as Float;
let zi = (z_float as usize).min(RES - 2);
let z_t = z_float - zi as Float;
let x_float = x * (RES - 1) as Float;
let xi = (x_float as usize).min(RES - 2);
let x_t = x_float - xi as Float;
let mut coeffs = [0.0; 3];
#[allow(clippy::needless_range_loop)]
for i in 0..3 {
let co = |dx: usize, dy: usize, dz: usize| {
self.coeffs[maxc][zi as usize + dz][yi as usize + dy][xi as usize + dx][i]
};
c[i] = lerp(
dz,
lerp(
dy as Float,
lerp(dx as Float, co(0, 0, 0) as Float, co(1, 0, 0)) as Float,
lerp(dx as Float, co(0, 1, 0) as Float, co(1, 1, 0) as Float),
),
lerp(
dy as Float,
lerp(dx as Float, co(0, 0, 1) as Float, co(1, 0, 1)) as Float,
lerp(dx as Float, co(0, 1, 1) as Float, co(1, 1, 1) as Float),
),
);
let offset = |dz, dx| (((zi + dz) * RES + (xi + dx)) * RES + (RES - 1)) * 3 + i;
let c00 = self.coeffs[offset(0, 0)];
let c01 = self.coeffs[offset(0, 1)];
let c10 = self.coeffs[offset(1, 0)];
let c11 = self.coeffs[offset(1, 1)];
let v0 = lerp(x_t, c00, c01);
let v1 = lerp(x_t, c10, c11);
coeffs[i] = lerp(z_t, v0, v1);
}
let scale_float = max_c * (RES - 1) as Float;
let si = (scale_float as usize).min(RES - 2);
let s_t = scale_float - si as Float;
let scale = lerp(s_t, self.scale[si], self.scale[si + 1]);
RGBSigmoidPolynomial {
c0: c[0],
c1: c[1],
c2: c[2],
c0: coeffs[0],
c1: coeffs[1],
c2: coeffs[2],
}
}
}

View file

@ -38,7 +38,7 @@ impl RGBColorSpace {
let rgb_values = [
[r_xyz.x(), g_xyz.x(), b_xyz.x()],
[r_xyz.y(), g_xyz.y(), b_xyz.y()],
[r_xyz.z(), g_xyz.z(), g_xyz.z()],
[r_xyz.z(), g_xyz.z(), b_xyz.z()],
];
let rgb = SquareMatrix::new(rgb_values);
let c: RGB = rgb.inverse()? * w_xyz;
@ -59,6 +59,16 @@ impl RGBColorSpace {
})
}
pub fn get_named(name: &str) -> Result<Arc<RGBColorSpace>, String> {
match name.to_lowercase().as_str() {
"aces2065-1" => Ok(Self::aces2065_1().clone()),
"rec2020" => Ok(Self::rec2020().clone()),
"dci-p3" => Ok(Self::dci_p3().clone()),
"srgb" => Ok(Self::srgb().clone()),
_ => Err(format!("Color space '{}' not found", name)),
}
}
pub fn to_xyz(&self, rgb: RGB) -> XYZ {
self.xyz_from_rgb * rgb
}
@ -79,8 +89,8 @@ impl RGBColorSpace {
self.rgb_from_xyz * other.xyz_from_rgb
}
pub fn srgb() -> &'static Self {
static SRGB_SPACE: Lazy<RGBColorSpace> = Lazy::new(|| {
pub fn srgb() -> &'static Arc<Self> {
static SRGB_SPACE: Lazy<Arc<RGBColorSpace>> = Lazy::new(|| {
let r = Point2f::new(0.64, 0.33);
let g = Point2f::new(0.30, 0.60);
let b = Point2f::new(0.15, 0.06);
@ -88,12 +98,54 @@ impl RGBColorSpace {
let illuminant = Spectrum::std_illuminant_d65();
let table = RGBToSpectrumTable::srgb();
RGBColorSpace::new(r, g, b, illuminant, table)
.expect("Failed to initialize standard sRGB color space")
Arc::new(RGBColorSpace::new(r, g, b, illuminant, table).unwrap())
});
&SRGB_SPACE
}
pub fn dci_p3() -> &'static Arc<Self> {
static DCI_P3: Lazy<Arc<RGBColorSpace>> = Lazy::new(|| {
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 illuminant = Spectrum::std_illuminant_d65();
let table = RGBToSpectrumTable::dci_p3();
Arc::new(RGBColorSpace::new(r, g, b, illuminant, table).unwrap())
});
&DCI_P3
}
pub fn rec2020() -> &'static Arc<Self> {
static REC2020: Lazy<Arc<RGBColorSpace>> = Lazy::new(|| {
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 illuminant = Spectrum::std_illuminant_d65();
let table = RGBToSpectrumTable::rec2020();
Arc::new(RGBColorSpace::new(r, g, b, illuminant, table).unwrap())
});
&REC2020
}
pub fn aces2065_1() -> &'static Arc<Self> {
static ACES: Lazy<Arc<RGBColorSpace>> = Lazy::new(|| {
let r = Point2f::new(0.7347, 0.2653);
let g = Point2f::new(0.0000, 1.0000);
let b = Point2f::new(0.0001, -0.0770);
// ACES uses D60
let illuminant = Spectrum::std_illuminant_d65();
let table = RGBToSpectrumTable::aces2065_1();
Arc::new(RGBColorSpace::new(r, g, b, illuminant, table).unwrap())
});
&ACES
}
}
impl PartialEq for RGBColorSpace {

View file

@ -0,0 +1,65 @@
use super::Spectrum;
use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
use super::simple::{DenselySampledSpectrum, PiecewiseLinearSpectrum};
use crate::core::cie::{CIE_S_LAMBDA, CIE_S0, CIE_S1, CIE_S2, CIE_X, CIE_Y, CIE_Z, N_CIES};
use crate::core::pbrt::Float;
use crate::spectra::{BlackbodySpectrum, SpectrumProvider};
use crate::utils::math::square;
use once_cell::sync::Lazy;
fn create_cie_spectrum(data: &[Float]) -> Spectrum {
let pls = PiecewiseLinearSpectrum::from_interleaved(data, false);
let dss = DenselySampledSpectrum::from_spectrum(&Spectrum::PiecewiseLinear(pls));
Spectrum::DenselySampled(dss)
}
pub fn generate_cie_d(temperature: Float) -> DenselySampledSpectrum {
let cct = temperature * 1.4388 / 1.4380;
if cct < 4000.0 {
let bb = BlackbodySpectrum::new(cct);
return DenselySampledSpectrum::from_function(
|lambda| bb.evaluate(lambda),
LAMBDA_MIN,
LAMBDA_MAX,
);
}
let x = if cct < 7000. {
-4.607 * 1e9 / cct.powi(3) + 2.9678 * 1e6 / square(cct) + 0.09911 * 1e3 / cct + 0.244063
} else {
-2.0064 * 1e9 / cct.powi(3) + 1.9018 * 1e6 / square(cct) + 0.24748 * 1e3 / cct + 0.23704
};
let y = -3. * x + 2.87 * x - 0.275;
let m = 0.0241 + 0.2562 * x - 0.7341 * y;
let m1 = (-1.3515 - 1.7703 * x + 5.9114 * y) / m;
let m2 = (0.0300 - 31.4424 * x + 30.0717 * y) / m;
let values: Vec<Float> = (0..N_CIES)
.map(|i| (CIE_S0[i] + CIE_S1[i] * m1 + CIE_S2[i] * m2) * 0.01)
.collect();
let dpls = PiecewiseLinearSpectrum {
lambdas: CIE_S_LAMBDA.to_vec(),
values,
};
DenselySampledSpectrum::from_spectrum(&Spectrum::PiecewiseLinear(dpls))
}
pub(crate) fn cie_x() -> &'static Spectrum {
static X: Lazy<Spectrum> = Lazy::new(|| create_cie_spectrum(&CIE_X));
&X
}
pub(crate) fn cie_y() -> &'static Spectrum {
static Y: Lazy<Spectrum> = Lazy::new(|| create_cie_spectrum(&CIE_Y));
&Y
}
pub(crate) fn cie_z() -> &'static Spectrum {
static Z: Lazy<Spectrum> = Lazy::new(|| create_cie_spectrum(&CIE_Z));
&Z
}

View file

@ -1,25 +1,51 @@
pub mod cie;
pub mod color;
pub mod colorspace;
pub mod data;
pub mod rgb;
pub mod sampled;
pub mod simple;
use crate::core::pbrt::Float;
use crate::utils::color::{RGB, XYZ};
use crate::utils::colorspace::RGBColorSpace;
use crate::utils::file::read_float_file;
use enum_dispatch::enum_dispatch;
pub use color::{RGB, XYZ};
pub use colorspace::RGBColorSpace;
pub use data::*;
pub use rgb::*;
pub use sampled::{CIE_Y_INTEGRAL, LAMBDA_MAX, LAMBDA_MIN};
pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};
pub use simple::*; // CIE_X, etc
pub use simple::*;
//
#[enum_dispatch]
pub trait SpectrumTrait {
pub trait SpectrumProvider: Copy {
fn evaluate(&self, lambda: Float) -> Float;
fn max_value(&self) -> Float;
}
#[cfg(not(target_arch = "spirv"))]
impl SpectrumProvider for std::sync::Arc<DenselySampledSpectrum> {
fn evaluate(&self, lambda: Float) -> Float {
(**self).evaluate(lambda)
}
fn max_value(&self) -> Float {
(**self).max_value()
}
}
#[cfg(target_arch = "spirv")] // or target_os = "cuda"
impl SpectrumProvider for u32 {
fn evaluate(&self, lambda: Float) -> Float {
// Here you would call a global function that accesses
// a static buffer of spectra data
crate::gpu::lookup_global_spectrum(*self, lambda)
}
fn max_value(&self) -> Float {
crate::gpu::lookup_global_spectrum_max(*self)
}
}
#[enum_dispatch(SpectrumTrait)]
#[derive(Debug, Clone)]
pub enum Spectrum {
@ -28,6 +54,8 @@ pub enum Spectrum {
PiecewiseLinear(PiecewiseLinearSpectrum),
Blackbody(BlackbodySpectrum),
RGBAlbedo(RGBAlbedoSpectrum),
RGBIlluminant(RGBIlluminantSpectrum),
RGBUnbounded(RGBUnboundedSpectrum),
}
impl Spectrum {

View file

@ -1,11 +1,9 @@
use super::sampled::{
LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths,
use super::{
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, RGB, RGBColorSpace,
RGBSigmoidPolynomial, SampledSpectrum, SampledWavelengths, SpectrumProvider, XYZ,
};
use crate::core::pbrt::Float;
use crate::spectra::{DenselySampledSpectrum, SpectrumTrait};
use crate::utils::color::{RGB, RGBSigmoidPolynomial, XYZ};
use crate::utils::colorspace::RGBColorSpace;
use std::sync::Arc;
use crate::Float;
#[derive(Debug, Clone, Copy)]
pub struct RGBAlbedoSpectrum {
@ -28,7 +26,7 @@ impl RGBAlbedoSpectrum {
}
}
impl SpectrumTrait for RGBAlbedoSpectrum {
impl SpectrumProvider for RGBAlbedoSpectrum {
fn evaluate(&self, lambda: Float) -> Float {
self.rsp.evaluate(lambda)
}
@ -60,7 +58,7 @@ impl UnboundedRGBSpectrum {
}
}
impl SpectrumTrait for UnboundedRGBSpectrum {
impl SpectrumProvider for UnboundedRGBSpectrum {
fn evaluate(&self, lambda: Float) -> Float {
self.scale * self.rsp.evaluate(lambda)
}
@ -71,40 +69,39 @@ impl SpectrumTrait for UnboundedRGBSpectrum {
}
#[derive(Debug, Clone, Default)]
pub struct RGBIlluminantSpectrum {
pub struct RGBIlluminantSpectrum<P: SpectrumProvider> {
scale: Float,
rsp: RGBSigmoidPolynomial,
illuminant: Option<Arc<DenselySampledSpectrum>>,
illuminant: P,
}
impl RGBIlluminantSpectrum {
pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self {
let illuminant = &cs.illuminant;
let densely_sampled =
DenselySampledSpectrum::from_spectrum(illuminant, LAMBDA_MIN, LAMBDA_MAX);
let m = rgb.max();
let scale = 2. * m;
let rsp = cs.to_rgb_coeffs(if scale == 1. {
rgb / scale
} else {
RGB::new(0., 0., 0.)
});
Self {
scale,
rsp,
illuminant: Some(Arc::new(densely_sampled)),
}
}
// impl RGBIlluminantSpectrum {
// pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self {
// let illuminant = &cs.illuminant;
// let densely_sampled = DenselySampledSpectrum::from_spectrum(illuminant);
// let m = rgb.max_component_value();
// let scale = 2. * m;
// let rsp = cs.to_rgb_coeffs(if scale == 1. {
// rgb / scale
// } else {
// RGB::new(0., 0., 0.)
// });
// Self {
// scale,
// rsp,
// illuminant: Some(Arc::new(densely_sampled)),
// }
// }
//
// pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
// if self.illuminant.is_none() {
// return SampledSpectrum::new(0.);
// }
// SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i]))
// }
// }
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
if self.illuminant.is_none() {
return SampledSpectrum::new(0.);
}
SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i]))
}
}
impl SpectrumTrait for RGBIlluminantSpectrum {
impl SpectrumProvider for RGBIlluminantSpectrum {
fn evaluate(&self, lambda: Float) -> Float {
match &self.illuminant {
Some(illuminant) => {
@ -144,7 +141,7 @@ impl Default for RGBUnboundedSpectrum {
impl RGBUnboundedSpectrum {
pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self {
let m = rgb.max();
let m = rgb.max_component_value();
let scale = 2.0 * m;
@ -164,7 +161,7 @@ impl RGBUnboundedSpectrum {
}
}
impl SpectrumTrait for RGBUnboundedSpectrum {
impl SpectrumProvider for RGBUnboundedSpectrum {
fn evaluate(&self, lambda: Float) -> Float {
self.scale * self.rsp.evaluate(lambda)
}

View file

@ -1,4 +1,5 @@
use crate::core::pbrt::Float;
use crate::utils::math::{clamp, lerp};
use std::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
};
@ -12,6 +13,7 @@ pub const LAMBDA_MIN: i32 = 360;
pub const LAMBDA_MAX: i32 = 830;
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(C)]
pub struct SampledSpectrum {
pub values: [Float; N_SPECTRUM_SAMPLES],
}
@ -92,7 +94,20 @@ impl SampledSpectrum {
pub fn clamp_zero(s: &SampledSpectrum) -> Self {
let ret = SampledSpectrum::from_fn(|i| s[i].max(0.));
assert!(!ret.has_nans());
debug_assert!(!ret.has_nans());
ret
}
pub fn sqrt(&self) -> Self {
let values = self.values.map(|v| v.sqrt());
let ret = SampledSpectrum { values };
debug_assert!(!ret.has_nans());
ret
}
pub fn clamp(s: &SampledSpectrum, low: Float, high: Float) -> Self {
let ret = SampledSpectrum::from_fn(|i| clamp(s[i], low, high));
debug_assert!(!ret.has_nans());
ret
}
@ -280,6 +295,7 @@ impl Neg for SampledSpectrum {
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(C)]
pub struct SampledWavelengths {
pub lambda: [Float; N_SPECTRUM_SAMPLES],
pub pdf: [Float; N_SPECTRUM_SAMPLES],
@ -318,7 +334,7 @@ impl SampledWavelengths {
pub fn sample_uniform(u: Float, lambda_min: Float, lambda_max: Float) -> Self {
let mut lambda = [0.0; N_SPECTRUM_SAMPLES];
lambda[0] = crate::core::pbrt::lerp(u, lambda_min, lambda_min);
lambda[0] = lerp(u, lambda_min, lambda_min);
let delta = (lambda_max - lambda_min) / N_SPECTRUM_SAMPLES as Float;
for i in 1..N_SPECTRUM_SAMPLES {
lambda[i] = lambda[i - 1] + delta;

View file

@ -0,0 +1,480 @@
use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
use crate::core::cie::*;
use crate::core::pbrt::Float;
use crate::spectra::{
N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumProvider,
};
use crate::utils::file::read_float_file;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::sync::LazyLock;
#[derive(Debug, Clone, Copy)]
pub struct ConstantSpectrum {
c: Float,
}
impl ConstantSpectrum {
pub fn new(c: Float) -> Self {
Self { c }
}
}
impl SpectrumProvider for ConstantSpectrum {
fn evaluate(&self, _lambda: Float) -> Float {
self.c
}
fn max_value(&self) -> Float {
self.c
}
}
#[derive(Debug, Clone)]
pub struct DenselySampledSpectrum {
lambda_min: i32,
lambda_max: i32,
values: Vec<Float>,
}
impl DenselySampledSpectrum {
pub fn new(lambda_min: i32, lambda_max: i32) -> Self {
let n_values = (lambda_max - lambda_min + 1).max(0) as usize;
Self {
lambda_min,
lambda_max,
values: vec![0.0; n_values],
}
}
pub fn from_spectrum(spec: &Spectrum) -> Self {
let lambda_min = LAMBDA_MIN;
let lambda_max = LAMBDA_MAX;
let mut s = Self::new(lambda_min, lambda_max);
if s.values.is_empty() {
return s;
}
for lambda in lambda_min..=lambda_max {
let index = (lambda - lambda_min) as usize;
s.values[index] = spec.evaluate(lambda as Float);
}
s
}
pub fn from_spectrum_with_range(spec: &Spectrum, lambda_min: i32, lambda_max: i32) -> Self {
let mut s = Self::new(lambda_min, lambda_max);
if s.values.is_empty() {
return s;
}
for lambda in lambda_min..=lambda_max {
let index = (lambda - lambda_min) as usize;
s.values[index] = spec.evaluate(lambda as Float);
}
s
}
pub fn from_function<F>(f: F, lambda_min: i32, lambda_max: i32) -> Self
where
F: Fn(Float) -> Float,
{
let mut s = Self::new(lambda_min, lambda_max);
if s.values.is_empty() {
return s;
}
for lambda in lambda_min..=lambda_max {
let index = (lambda - lambda_min) as usize;
s.values[index] = f(lambda as Float);
}
s
}
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut s = SampledSpectrum::default();
for i in 0..N_SPECTRUM_SAMPLES {
let offset = lambda[i].round() as usize - LAMBDA_MIN as usize;
if offset >= self.values.len() {
s[i] = 0.;
} else {
s[i] = self.values[offset];
}
}
s
}
pub fn min_component_value(&self) -> Float {
self.values.iter().fold(Float::INFINITY, |a, &b| a.min(b))
}
pub fn max_component_value(&self) -> Float {
self.values
.iter()
.fold(Float::NEG_INFINITY, |a, &b| a.max(b))
}
pub fn average(&self) -> Float {
self.values.iter().sum::<Float>() / (N_SPECTRUM_SAMPLES as Float)
}
pub fn safe_div(&self, rhs: SampledSpectrum) -> Self {
let mut r = Self::new(1, 1);
for i in 0..N_SPECTRUM_SAMPLES {
r.values[i] = if rhs[i] != 0.0 {
self.values[i] / rhs.values[i]
} else {
0.0
}
}
r
}
pub fn scale(&mut self, factor: Float) {
for v in &mut self.values {
*v *= factor;
}
}
}
impl PartialEq for DenselySampledSpectrum {
fn eq(&self, other: &Self) -> bool {
if self.lambda_min != other.lambda_min
|| self.lambda_max != other.lambda_max
|| self.values.len() != other.values.len()
{
return false;
}
self.values
.iter()
.zip(&other.values)
.all(|(a, b)| a.to_bits() == b.to_bits())
}
}
impl Eq for DenselySampledSpectrum {}
impl Hash for DenselySampledSpectrum {
fn hash<H: Hasher>(&self, state: &mut H) {
self.lambda_min.hash(state);
self.lambda_max.hash(state);
for v in &self.values {
v.to_bits().hash(state);
}
}
}
impl SpectrumProvider for DenselySampledSpectrum {
fn evaluate(&self, lambda: Float) -> Float {
let offset = (lambda.round() as i32) - self.lambda_min;
if offset < 0 || offset as usize >= self.values.len() {
0.0
} else {
self.values[offset as usize]
}
}
fn max_value(&self) -> Float {
self.values.iter().fold(Float::MIN, |a, b| a.max(*b))
}
}
#[derive(Debug, Clone)]
pub struct PiecewiseLinearSpectrum {
pub lambdas: Vec<Float>,
pub values: Vec<Float>,
}
impl PiecewiseLinearSpectrum {
pub fn from_interleaved(data: &[Float], _normalize: bool) -> Self {
if data.len() % 2 != 0 {
panic!("Interleaved data must have an even number of elements");
}
let mut temp: Vec<(Float, Float)> =
data.chunks(2).map(|chunk| (chunk[0], chunk[1])).collect();
temp.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
let (lambdas, values): (Vec<Float>, Vec<Float>) = temp.into_iter().unzip();
// Normalization left to the reader
// This usually involves integrating the curve and scaling 'values'
Self { lambdas, values }
}
pub fn read(filepath: &str) -> Option<Self> {
let vals = match read_float_file(filepath) {
Ok(v) => v,
Err(_) => Vec::new(),
};
if vals.is_empty() {
return None;
}
if vals.len() % 2 == 0 {
return None;
}
let count = vals.len() / 2;
let mut lambdas = Vec::with_capacity(count);
let mut values = Vec::with_capacity(count);
for (_, pair) in vals.chunks(2).enumerate() {
let curr_lambda = pair[0];
let curr_val = pair[1];
if let Some(&prev_lambda) = lambdas.last() {
if curr_lambda <= prev_lambda {
return None;
}
}
lambdas.push(curr_lambda);
values.push(curr_val);
}
Some(PiecewiseLinearSpectrum { lambdas, values })
}
}
impl SpectrumProvider for PiecewiseLinearSpectrum {
fn evaluate(&self, lambda: Float) -> Float {
if self.lambdas.is_empty() {
return 0.0;
}
if lambda <= self.lambdas[0] {
return self.values[0];
}
if lambda >= *self.lambdas.last().unwrap() {
return *self.values.last().unwrap();
}
let i = self.lambdas.partition_point(|&l| l < lambda);
let l0 = self.lambdas[i - 1];
let l1 = self.lambdas[i];
let v0 = self.values[i - 1];
let v1 = self.values[i];
let t = (lambda - l0) / (l1 - l0);
v0 + t * (v1 - v0)
}
fn max_value(&self) -> Float {
if self.values.is_empty() {
return 0.0;
}
self.values.iter().fold(0.0, |acc, &v| acc.max(v))
}
}
#[derive(Debug, Clone, Copy)]
pub struct BlackbodySpectrum {
temperature: Float,
normalization_factor: Float,
}
// Planck's Law
impl BlackbodySpectrum {
const C: Float = 299792458.0;
const H: Float = 6.62606957e-34;
const KB: Float = 1.3806488e-23;
pub fn new(temperature: Float) -> Self {
// Physical constants
let lambda_max = 2.8977721e-3 / temperature * 1e9;
let max_val = Self::planck_law(lambda_max, temperature);
Self {
temperature,
normalization_factor: if max_val > 0.0 { 1.0 / max_val } else { 0.0 },
}
}
fn planck_law(lambda_nm: Float, temp: Float) -> Float {
if temp <= 0.0 {
return 0.0;
}
let lambda_m = lambda_nm * 1e-9;
let c1 = 2.0 * Self::H * Self::C * Self::C;
let c2 = (Self::H * Self::C) / Self::KB;
let numerator = c1 / lambda_m.powi(5);
let denominator = (c2 / (lambda_m * temp)).exp() - 1.0;
if denominator.is_infinite() {
0.0
} else {
numerator / denominator
}
}
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
SampledSpectrum::from_fn(|i| {
Self::planck_law(lambda[i], self.temperature) * self.normalization_factor
})
}
}
impl SpectrumProvider for BlackbodySpectrum {
fn evaluate(&self, lambda: Float) -> Float {
Self::planck_law(lambda, self.temperature) * self.normalization_factor
}
fn max_value(&self) -> Float {
let lambda_max = 2.8977721e-3 / self.temperature * 1e9;
Self::planck_law(lambda_max, self.temperature)
}
}
pub static NAMED_SPECTRA: LazyLock<HashMap<String, Spectrum>> = LazyLock::new(|| {
let mut m = HashMap::new();
// A macro to reduce boilerplate and make the list readable
macro_rules! add {
($name:expr, $data:expr, $norm:expr) => {
m.insert(
$name.to_string(),
Spectrum::PiecewiseLinear(PiecewiseLinearSpectrum::from_interleaved($data, $norm)),
);
};
}
add!("stdillum-A", &CIE_ILLUM_A, true);
add!("stdillum-D50", &CIE_ILLUM_D5000, true);
add!("stdillum-D65", &CIE_ILLUM_D6500, true);
add!("stdillum-F1", &CIE_ILLUM_F1, true);
add!("stdillum-F2", &CIE_ILLUM_F2, true);
add!("stdillum-F3", &CIE_ILLUM_F3, true);
add!("stdillum-F4", &CIE_ILLUM_F4, true);
add!("stdillum-F5", &CIE_ILLUM_F5, true);
add!("stdillum-F6", &CIE_ILLUM_F6, true);
add!("stdillum-F7", &CIE_ILLUM_F7, true);
add!("stdillum-F8", &CIE_ILLUM_F8, true);
add!("stdillum-F9", &CIE_ILLUM_F9, true);
add!("stdillum-F10", &CIE_ILLUM_F10, true);
add!("stdillum-F11", &CIE_ILLUM_F11, true);
add!("stdillum-F12", &CIE_ILLUM_F12, true);
add!("illum-acesD60", &ACES_ILLUM_D60, true);
// --- Glasses ---
add!("glass-BK7", &GLASS_BK7_ETA, false);
add!("glass-BAF10", &GLASS_BAF10_ETA, false);
add!("glass-FK51A", &GLASS_FK51A_ETA, false);
add!("glass-LASF9", &GLASS_LASF9_ETA, false);
add!("glass-F5", &GLASS_SF5_ETA, false);
add!("glass-F10", &GLASS_SF10_ETA, false);
add!("glass-F11", &GLASS_SF11_ETA, false);
// --- Metals ---
add!("metal-Ag-eta", &AG_ETA, false);
add!("metal-Ag-k", &AG_K, false);
add!("metal-Al-eta", &AL_ETA, false);
add!("metal-Al-k", &AL_K, false);
add!("metal-Au-eta", &AU_ETA, false);
add!("metal-Au-k", &AU_K, false);
add!("metal-Cu-eta", &CU_ETA, false);
add!("metal-Cu-k", &CU_K, false);
add!("metal-CuZn-eta", &CUZN_ETA, false);
add!("metal-CuZn-k", &CUZN_K, false);
add!("metal-MgO-eta", &MGO_ETA, false);
add!("metal-MgO-k", &MGO_K, false);
add!("metal-TiO2-eta", &TIO2_ETA, false);
add!("metal-TiO2-k", &TIO2_K, false);
// --- Canon EOS 100D ---
add!("canon_eos_100d_r", &CANON_EOS_100D_R, false);
add!("canon_eos_100d_g", &CANON_EOS_100D_G, false);
add!("canon_eos_100d_b", &CANON_EOS_100D_B, false);
// --- Canon EOS 1DX MkII ---
add!("canon_eos_1dx_mkii_r", &CANON_EOS_1DX_MKII_R, false);
add!("canon_eos_1dx_mkii_g", &CANON_EOS_1DX_MKII_G, false);
add!("canon_eos_1dx_mkii_b", &CANON_EOS_1DX_MKII_B, false);
// --- Canon EOS 200D ---
add!("canon_eos_200d_r", &CANON_EOS_200D_R, false);
add!("canon_eos_200d_g", &CANON_EOS_200D_G, false);
add!("canon_eos_200d_b", &CANON_EOS_200D_B, false);
// --- Canon EOS 200D MkII ---
add!("canon_eos_200d_mkii_r", &CANON_EOS_200D_MKII_R, false);
add!("canon_eos_200d_mkii_g", &CANON_EOS_200D_MKII_G, false);
add!("canon_eos_200d_mkii_b", &CANON_EOS_200D_MKII_B, false);
// --- Canon EOS 5D ---
add!("canon_eos_5d_r", &CANON_EOS_5D_R, false);
add!("canon_eos_5d_g", &CANON_EOS_5D_G, false);
add!("canon_eos_5d_b", &CANON_EOS_5D_B, false);
// --- Canon EOS 5D MkII ---
add!("canon_eos_5d_mkii_r", &CANON_EOS_5D_MKII_R, false);
add!("canon_eos_5d_mkii_g", &CANON_EOS_5D_MKII_G, false);
add!("canon_eos_5d_mkii_b", &CANON_EOS_5D_MKII_B, false);
// --- Canon EOS 5D MkIII ---
add!("canon_eos_5d_mkiii_r", &CANON_EOS_5D_MKIII_R, false);
add!("canon_eos_5d_mkiii_g", &CANON_EOS_5D_MKIII_G, false);
add!("canon_eos_5d_mkiii_b", &CANON_EOS_5D_MKIII_B, false);
// --- Canon EOS 5D MkIV ---
add!("canon_eos_5d_mkiv_r", &CANON_EOS_5D_MKIV_R, false);
add!("canon_eos_5d_mkiv_g", &CANON_EOS_5D_MKIV_G, false);
add!("canon_eos_5d_mkiv_b", &CANON_EOS_5D_MKIV_B, false);
// --- Canon EOS 5DS ---
add!("canon_eos_5ds_r", &CANON_EOS_5DS_R, false);
add!("canon_eos_5ds_g", &CANON_EOS_5DS_G, false);
add!("canon_eos_5ds_b", &CANON_EOS_5DS_B, false);
// --- Canon EOS M ---
add!("canon_eos_m_r", &CANON_EOS_M_R, false);
add!("canon_eos_m_g", &CANON_EOS_M_G, false);
add!("canon_eos_m_b", &CANON_EOS_M_B, false);
// --- Hasselblad L1D 20C ---
add!("hasselblad_l1d_20c_r", &HASSELBLAD_L1D_20C_R, false);
add!("hasselblad_l1d_20c_g", &HASSELBLAD_L1D_20C_G, false);
add!("hasselblad_l1d_20c_b", &HASSELBLAD_L1D_20C_B, false);
// --- Nikon D810 ---
add!("nikon_d810_r", &NIKON_D810_R, false);
add!("nikon_d810_g", &NIKON_D810_G, false);
add!("nikon_d810_b", &NIKON_D810_B, false);
// --- Nikon D850 ---
add!("nikon_d850_r", &NIKON_D850_R, false);
add!("nikon_d850_g", &NIKON_D850_G, false);
add!("nikon_d850_b", &NIKON_D850_B, false);
// --- Sony ILCE 6400 ---
add!("sony_ilce_6400_r", &SONY_ILCE_6400_R, false);
add!("sony_ilce_6400_g", &SONY_ILCE_6400_G, false);
add!("sony_ilce_6400_b", &SONY_ILCE_6400_B, false);
// --- Sony ILCE 7M3 ---
add!("sony_ilce_7m3_r", &SONY_ILCE_7M3_R, false);
add!("sony_ilce_7m3_g", &SONY_ILCE_7M3_G, false);
add!("sony_ilce_7m3_b", &SONY_ILCE_7M3_B, false);
// --- Sony ILCE 7RM3 ---
add!("sony_ilce_7rm3_r", &SONY_ILCE_7RM3_R, false);
add!("sony_ilce_7rm3_g", &SONY_ILCE_7RM3_G, false);
add!("sony_ilce_7rm3_b", &SONY_ILCE_7RM3_B, false);
// --- Sony ILCE 9 ---
add!("sony_ilce_9_r", &SONY_ILCE_9_R, false);
add!("sony_ilce_9_g", &SONY_ILCE_9_G, false);
add!("sony_ilce_9_b", &SONY_ILCE_9_B, false);
m
});
pub fn get_named_spectrum(name: &str) -> Option<Spectrum> {
NAMED_SPECTRA.get(name).cloned()
}

View file

@ -1,10 +1,12 @@
use crate::core::pbrt::{Float, lerp};
use crate::core::pbrt::Float;
use crate::utils::math::lerp;
use std::collections::HashSet;
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hash, Hasher};
use std::ops::{Add, Index, IndexMut, Mul, Sub};
use std::sync::RwLock;
use std::sync::{Arc, Mutex, RwLock};
use crate::geometry::{
use crate::core::geometry::{
Bounds2i, Bounds3f, Bounds3i, Point2i, Point3f, Point3i, Vector2i, Vector3f, Vector3i,
};
@ -92,6 +94,14 @@ impl<T> Array2D<T> {
let pp = Point2i::new(p.x() - self.extent.p_min.x(), p.y() - self.extent.p_min.y());
(pp.y() * width as i32 + pp.x()) as usize
}
pub fn get_linear(&self, index: usize) -> &T {
&self.values[index]
}
pub fn get_linear_mut(&mut self, index: usize) -> &mut T {
&mut self.values[index]
}
}
impl<T> Index<Point2i> for Array2D<T> {
@ -305,3 +315,30 @@ impl<T> SampledGrid<T> {
self.max_value_convert(bounds, |v| v.clone())
}
}
pub struct InternCache<T> {
cache: Mutex<HashSet<Arc<T>>>,
}
impl<T> InternCache<T>
where
T: Eq + Hash + Clone,
{
pub fn new() -> Self {
Self {
cache: Mutex::new(HashSet::new()),
}
}
pub fn lookup(&self, value: T) -> Arc<T> {
let mut lock = self.cache.lock().unwrap();
if let Some(existing) = lock.get(&value) {
return existing.clone(); // Returns a cheap Arc copy
}
let new_item = Arc::new(value);
lock.insert(new_item.clone());
new_item
}
}

36
shared/src/utils/error.rs Normal file
View file

@ -0,0 +1,36 @@
use image_rs::{ImageError as IError, error};
use std::fmt;
use std::sync::Arc;
use thiserror::Error;
use crate::image::PixelFormat;
#[derive(Error, Debug)]
pub enum LlsError {
SingularMatrix,
}
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum InversionError {
SingularMatrix,
EmptyMatrix,
}
impl fmt::Display for LlsError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
LlsError::SingularMatrix => write!(f, "Matrix is singular and cannot be inverted."),
}
}
}
impl fmt::Display for InversionError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
InversionError::SingularMatrix => {
write!(f, "Matrix is singular and cannot be inverted.")
}
InversionError::EmptyMatrix => write!(f, "Matrix is empty and cannot be inverted."),
}
}
}

View file

@ -3,6 +3,7 @@ use crate::utils::math::{next_float_down, next_float_up};
use num_traits::Zero;
use std::ops::{Add, Div, Mul, Neg, Sub};
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Interval {
pub low: Float,

View file

@ -1,10 +1,7 @@
use super::color::{RGB, XYZ};
use super::error::{InversionError, LlsError};
use crate::core::pbrt::{
Float, FloatBitOps, FloatBits, ONE_MINUS_EPSILON, PI, PI_OVER_4, clamp_t, evaluate_polynomial,
lerp,
};
use crate::geometry::{Point, Point2f, Point2i, Vector, Vector3f, VectorLike};
use crate::core::geometry::{Lerp, Point, Point2f, Point2i, Vector, Vector3f, VectorLike};
use crate::core::pbrt::{Float, FloatBitOps, FloatBits, ONE_MINUS_EPSILON, PI, PI_OVER_4};
use crate::spectra::color::{RGB, XYZ};
use crate::utils::hash::{hash_buffer, mix_bits};
use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV};
@ -42,6 +39,43 @@ where
a * b + c
}
pub fn clamp<T>(val: T, low: T, high: T) -> T
where
T: PartialOrd,
{
let r: T;
if val < low {
r = low;
} else if val > high {
r = high;
} else {
r = val;
}
r
}
#[inline]
pub fn lerp<T, F>(t: F, a: T, b: T) -> T
where
T: Lerp<F>,
F: Copy,
{
T::lerp(t, a, b)
}
#[inline]
pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Option<Float> {
if coeffs.is_empty() {
return None;
}
let mut result = coeffs[0];
for &c in &coeffs[1..] {
result = t.mul_add(result, c);
}
Some(result)
}
#[inline]
pub fn difference_of_products<T>(a: T, b: T, c: T, d: T) -> T
where
@ -75,7 +109,7 @@ pub fn safe_asin<T: NumFloat>(x: T) -> T {
let epsilon = T::from(0.0001).unwrap();
let one = T::one();
if x >= -(one + epsilon) && x <= one + epsilon {
clamp_t(x, -one, one).asin()
clamp(x, -one, one).asin()
} else {
panic!("Not valid value for asin")
}
@ -84,7 +118,7 @@ pub fn safe_asin<T: NumFloat>(x: T) -> T {
#[inline]
pub fn safe_acos(x: Float) -> Float {
if (-1.001..1.001).contains(&x) {
clamp_t(x, -1., 1.).asin()
clamp(x, -1., 1.).asin()
} else {
panic!("Not valid value for acos")
}
@ -236,7 +270,7 @@ pub fn smooth_step(x: Float, a: Float, b: Float) -> Float {
if x < a { return 0. } else { return 1. }
}
let t = clamp_t((x - a) / (b - a), 0., 1.);
let t = clamp((x - a) / (b - a), 0., 1.);
t * t * (3. - 2. * t)
}
@ -1281,6 +1315,19 @@ impl<T, const N: usize> SquareMatrix<T, N> {
}
}
impl<T: Copy> From<&[T; 16]> for SquareMatrix<T, 4> {
fn from(flat: &[T; 16]) -> Self {
Self {
m: [
[flat[0], flat[1], flat[2], flat[3]],
[flat[4], flat[5], flat[6], flat[7]],
[flat[8], flat[9], flat[10], flat[11]],
[flat[12], flat[13], flat[14], flat[15]],
],
}
}
}
impl<T, const N: usize> SquareMatrix<T, N>
where
T: NumFloat + Sum + Product + Copy,

View file

@ -1,7 +1,7 @@
use crate::core::geometry::{Normal3f, Point2f, Point3f, Vector3f};
use crate::core::pbrt::Float;
use crate::geometry::{Normal3f, Point2f, Point3f, Vector3f};
use crate::utils::sampling::PiecewiseConstant2D;
use crate::utils::transform::Transform;
use crate::utils::transform::TransformGeneric;
use std::sync::Arc;
#[derive(Debug, Clone)]
@ -21,7 +21,7 @@ pub struct TriangleMesh {
impl TriangleMesh {
#[allow(clippy::too_many_arguments)]
pub fn new(
render_from_object: &Transform<Float>,
render_from_object: &TransformGeneric<Float>,
reverse_orientation: bool,
indices: Vec<usize>,
mut p: Vec<Point3f>,
@ -108,7 +108,7 @@ pub struct BilinearPatchMesh {
impl BilinearPatchMesh {
pub fn new(
render_from_object: &Transform<Float>,
render_from_object: &TransformGeneric<Float>,
reverse_orientation: bool,
indices: Vec<usize>,
mut p: Vec<Point3f>,

84
shared/src/utils/mod.rs Normal file
View file

@ -0,0 +1,84 @@
use std::sync::atomic::{AtomicU64, Ordering};
pub mod containers;
pub mod error;
pub mod file;
pub mod hash;
pub mod interval;
pub mod math;
pub mod mesh;
pub mod mipmap;
pub mod parameters;
pub mod parser;
pub mod quaternion;
pub mod rng;
pub mod sampling;
pub mod sobol;
pub mod splines;
pub mod strings;
pub mod transform;
pub use file::{read_float_file, resolve_filename};
pub use strings::*;
pub use transform::{AnimatedTransform, Transform, TransformGeneric};
#[inline]
pub fn partition_slice<T, F>(data: &mut [T], predicate: F) -> usize
where
F: Fn(&T) -> bool,
{
let mut i = 0;
for j in 0..data.len() {
if predicate(&data[j]) {
data.swap(i, j);
i += 1;
}
}
i
}
#[derive(Debug)]
pub struct AtomicFloat {
bits: AtomicU64,
}
impl AtomicFloat {
pub fn new(value: f64) -> Self {
Self {
bits: AtomicU64::new(value.to_bits()),
}
}
pub fn load(&self) -> f64 {
f64::from_bits(self.bits.load(Ordering::Relaxed))
}
pub fn store(&self, value: f64) {
self.bits.store(value.to_bits(), Ordering::Relaxed);
}
pub fn add(&self, value: f64) {
let mut current_bits = self.bits.load(Ordering::Relaxed);
loop {
let current_val = f64::from_bits(current_bits);
let new_val = current_val + value;
let new_bits = new_val.to_bits();
match self.bits.compare_exchange_weak(
current_bits,
new_bits,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(x) => current_bits = x,
}
}
}
}
impl Default for AtomicFloat {
fn default() -> Self {
Self::new(0.0)
}
}

View file

@ -2,8 +2,8 @@ use std::f32::consts::PI;
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use std::ops::{Index, IndexMut};
use crate::core::geometry::{Vector3f, VectorLike};
use crate::core::pbrt::Float;
use crate::geometry::{Vector3f, VectorLike};
use crate::utils::math::{safe_asin, sinx_over_x};
#[derive(Copy, Clone, Debug, PartialEq)]

View file

@ -1,21 +1,41 @@
use super::math::safe_sqrt;
use crate::check_rare;
use crate::core::pbrt::{
Float, INV_2_PI, INV_4_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4, clamp_t,
evaluate_polynomial, find_interval, lerp,
};
use crate::core::pbrt::{RARE_EVENT_CONDITION_MET, RARE_EVENT_TOTAL_CALLS};
use crate::geometry::{
use crate::core::geometry::{
Bounds2f, Frame, Point2f, Point2i, Point3f, Vector2f, Vector2i, Vector3f, VectorLike,
};
use crate::core::pbrt::{
Float, INV_2_PI, INV_4_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4, find_interval,
};
use crate::core::pbrt::{RARE_EVENT_CONDITION_MET, RARE_EVENT_TOTAL_CALLS};
use crate::utils::containers::Array2D;
use crate::utils::math::{
catmull_rom_weights, difference_of_products, logistic, newton_bisection, square,
sum_of_products,
catmull_rom_weights, clamp, difference_of_products, evaluate_polynomial, lerp, logistic,
newton_bisection, safe_sqrt, square, sum_of_products,
};
use crate::utils::rng::Rng;
use num_traits::Num;
use std::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
pub fn linear_pdf<T>(x: T, a: T, b: T) -> T
where
T: Num + Copy + PartialOrd,
{
if x < T::zero() || x > T::one() {
return T::zero();
}
(T::one() + T::one()) * lerp(x, a, b) / (a + b)
}
// pub fn sample_linear<T>(u: T, a: T, b: T) -> T
// where
// T: Num,
// {
// if u == T::zero() && a == T::zero() {
// return T::zero();
// }
//
// u * (a + b)
// }
pub fn sample_linear(u: Float, a: Float, b: Float) -> Float {
if u == 0. && a == 0. {
return 0.;
@ -181,10 +201,10 @@ pub fn sample_spherical_rectangle(
let au = u[0] * (g0 + g1 - 2. * PI) + (u[0] - 1.) * (g2 + g3);
let fu = (au.cos() * b0 - b1) / au.sin();
let mut cu = (1. / (square(fu) + square(b0)).sqrt()).copysign(fu);
cu = clamp_t(cu, -ONE_MINUS_EPSILON, ONE_MINUS_EPSILON);
cu = clamp(cu, -ONE_MINUS_EPSILON, ONE_MINUS_EPSILON);
let mut xu = -(cu * z0) / safe_sqrt(1. - square(cu));
xu = clamp_t(xu, x0, x1);
xu = clamp(xu, x0, x1);
// Find _xv_ along $y$ edge for spherical rectangle sample
let dd = (square(xu) + square(z0)).sqrt();
@ -262,7 +282,7 @@ pub fn invert_spherical_rectangle_sample(
let v = r.to_local(p_rect - p_ref);
let mut xu = v.x();
let yv = v.y();
xu = clamp_t(xu, x0, x1);
xu = clamp(xu, x0, x1);
if xu == 0. {
xu = 1e-10;
}
@ -304,9 +324,9 @@ pub fn invert_spherical_rectangle_sample(
let hvsq = [square(hv[0]), square(hv[1])];
let yz = [(hv[0] * dd) / (1. - hvsq[0]), (hv[1] * dd) / (1. - hvsq[1])];
if (yz[0] - yv).abs() < (yz[1] - yv).abs() {
Point2f::new(clamp_t(u0, 0., 1.), u1[0])
Point2f::new(clamp(u0, 0., 1.), u1[0])
} else {
Point2f::new(clamp_t(u0, 0., 1.), u1[1])
Point2f::new(clamp(u0, 0., 1.), u1[1])
}
}
@ -358,7 +378,7 @@ pub fn sample_spherical_triangle(
let k2 = sin_phi - sin_alpha * a.dot(b);
let mut cos_bp = (k2 + difference_of_products(k2, cos_phi, k1, sin_phi) * cos_alpha)
/ ((sum_of_products(k2, sin_phi, k1, cos_phi)) * sin_alpha);
cos_bp = clamp_t(cos_bp, -1., 1.);
cos_bp = clamp(cos_bp, -1., 1.);
// Sample c' along the arc between a and c
let sin_bp = safe_sqrt(1. - square(cos_bp));
let cp = cos_bp * a + sin_bp * c.gram_schmidt(a).normalize();
@ -376,8 +396,8 @@ pub fn sample_spherical_triangle(
let mut b1 = s.dot(s1) * inv_divisor;
let mut b2 = w.dot(s.cross(e1)) * inv_divisor;
b1 = clamp_t(b1, 0., 1.);
b2 = clamp_t(b2, 0., 1.);
b1 = clamp(b1, 0., 1.);
b2 = clamp(b2, 0., 1.);
if b1 + b2 > 1. {
b1 /= b1 + b2;
b2 /= b1 + b2;
@ -443,7 +463,7 @@ pub fn invert_spherical_triangle_sample(
let u1 = (1.0 - w.dot(b)) / (1.0 - cp.dot(b));
Some(Point2f::new(clamp_t(u0, 0.0, 1.0), clamp_t(u1, 0.0, 1.0)))
Some(Point2f::new(clamp(u0, 0.0, 1.0), clamp(u1, 0.0, 1.0)))
}
pub fn sample_catmull_rom(
@ -611,7 +631,7 @@ pub fn sample_trimmed_logistic(u: Float, s: Float, a: Float, b: Float) -> Float
let p = |val: Float| invert_logistic_sample(val, s);
let u = lerp(u, p(a), p(b));
let x = sample_logistic(u, s);
clamp_t(x, a, b)
clamp(x, a, b)
}
pub fn uniform_hemisphere_pdf() -> Float {
@ -1027,7 +1047,7 @@ impl WindowedPiecewiseConstant2D {
numerator / denominator
};
let res = crate::core::pbrt::lerp(t, min, max);
let res = lerp(t, min, max);
res.clamp(min, max)
}
}

View file

@ -1,5 +1,6 @@
use crate::core::pbrt::{Float, lerp};
use crate::geometry::{Bounds3f, Lerp, Point3f, Vector3f, VectorLike};
use crate::core::geometry::{Bounds3f, Lerp, Point3f, Vector3f, VectorLike};
use crate::core::pbrt::Float;
use crate::utils::math::lerp;
use num_traits::Num;
fn bounds_cubic_bezier(cp: &[Point3f]) -> Bounds3f {

View file

@ -0,0 +1,5 @@
use unicode_normalization::UnicodeNormalization;
pub fn normalize_utf8(input: &str) -> String {
input.nfc().collect::<String>()
}

View file

@ -5,26 +5,26 @@ use std::iter::{Product, Sum};
use std::ops::{Add, Div, Index, IndexMut, Mul};
use std::sync::Arc;
use super::color::{RGB, XYZ};
use super::math::{SquareMatrix, radians, safe_acos};
use super::math::{radians, safe_acos, SquareMatrix};
use super::quaternion::Quaternion;
use crate::core::interaction::{
Interaction, InteractionData, InteractionTrait, MediumInteraction, SurfaceInteraction,
};
use crate::core::pbrt::{Float, gamma};
use crate::geometry::{
use crate::core::geometry::{
Bounds3f, Normal, Normal3f, Point, Point3f, Point3fi, Ray, Vector, Vector3f, Vector3fi,
VectorLike,
};
use crate::core::interaction::{
Interaction, InteractionData, InteractionTrait, MediumInteraction, SurfaceInteraction,
};
use crate::core::pbrt::{gamma, Float};
use crate::spectra::{RGB, XYZ};
use crate::utils::error::InversionError;
#[derive(Debug, Copy, Clone)]
pub struct Transform<T: NumFloat> {
pub struct TransformGeneric<T: NumFloat> {
m: SquareMatrix<T, 4>,
m_inv: SquareMatrix<T, 4>,
}
impl<T: NumFloat + Sum + Product> Transform<T> {
impl<T: NumFloat + Sum + Product> TransformGeneric<T> {
pub fn new(m: SquareMatrix<T, 4>, m_inv: SquareMatrix<T, 4>) -> Self {
Self { m, m_inv }
}
@ -34,6 +34,12 @@ impl<T: NumFloat + Sum + Product> Transform<T> {
Ok(Self { m, m_inv: inv })
}
pub fn from_flat(flat: &[T; 16]) -> Result<Self, InversionError> {
let m: SquareMatrix<T, 4> = SquareMatrix::from(flat);
let inv = m.inverse()?;
Ok(Self { m, m_inv: inv })
}
pub fn identity() -> Self {
let m: SquareMatrix<T, 4> = SquareMatrix::identity();
Self { m, m_inv: m }
@ -83,13 +89,15 @@ impl<T: NumFloat + Sum + Product> Transform<T> {
}
}
impl<T: NumFloat + Sum + Product> Default for Transform<T> {
impl<T: NumFloat + Sum + Product> Default for TransformGeneric<T> {
fn default() -> Self {
Self::identity()
}
}
impl Transform<Float> {
pub type Transform = TransformGeneric<Float>;
impl TransformGeneric<Float> {
pub fn apply_to_point(&self, p: Point3f) -> Point3f {
let x = p.x();
let y = p.y();
@ -407,7 +415,7 @@ impl Transform<Float> {
}
pub fn translate(delta: Vector3f) -> Self {
Transform {
TransformGeneric {
m: SquareMatrix::new([
[1.0, 0.0, 0.0, delta.x()],
[0.0, 1.0, 0.0, delta.y()],
@ -440,7 +448,11 @@ impl Transform<Float> {
Self { m, m_inv }
}
pub fn perspective(fov: Float, n: Float, f: Float) -> Result<Transform<Float>, InversionError> {
pub fn perspective(
fov: Float,
n: Float,
f: Float,
) -> Result<TransformGeneric<Float>, InversionError> {
let persp: SquareMatrix<Float, 4> = SquareMatrix::new([
[1., 0., 0., 0.],
[0., 1., 0., 0.],
@ -448,22 +460,23 @@ impl Transform<Float> {
[0., 0., 1., 0.],
]);
let inv_tan_ang = 1. / (radians(fov) / 2.).tan();
let persp_transform = Transform::from_matrix(persp)?;
Ok(Transform::scale(inv_tan_ang, inv_tan_ang, 1.) * persp_transform)
let persp_transform = TransformGeneric::from_matrix(persp)?;
Ok(TransformGeneric::scale(inv_tan_ang, inv_tan_ang, 1.) * persp_transform)
}
pub fn orthographic(z_near: Float, z_far: Float) -> Self {
Self::scale(1., 1., 1. / (z_far - z_near)) * Self::translate(Vector3f::new(0., 0., -z_near))
}
pub fn rotate_around_axis(theta: Float, axis: Vector3f) -> Self {
pub fn rotate_around_axis(theta: Float, axis: impl Into<Vector3f>) -> Self {
let sin_theta = theta.to_radians().sin();
let cos_theta = theta.to_radians().cos();
Transform::rotate(sin_theta, cos_theta, axis)
TransformGeneric::rotate(sin_theta, cos_theta, axis)
}
pub fn rotate(sin_theta: Float, cos_theta: Float, axis: Vector3f) -> Self {
let a = axis.normalize();
pub fn rotate(sin_theta: Float, cos_theta: Float, axis: impl Into<Vector3f>) -> Self {
let vec_axis: Vector3f = axis.into();
let a = vec_axis.normalize();
let mut m: SquareMatrix<Float, 4> = SquareMatrix::default();
m[0][0] = a.x() * a.x() + (1. - a.x() * a.x()) * cos_theta;
m[0][1] = a.x() * a.y() * (1. - cos_theta) - a.z() * sin_theta;
@ -479,7 +492,7 @@ impl Transform<Float> {
m[2][1] = a.y() * a.z() * (1. - cos_theta) + a.x() * sin_theta;
m[2][2] = a.z() * a.z() + (1. - a.z() * a.z()) * cos_theta;
m[2][3] = 0.;
Transform::new(m, m.transpose())
TransformGeneric::new(m, m.transpose())
}
pub fn rotate_from_to(from: Vector3f, to: Vector3f) -> Self {
@ -506,7 +519,7 @@ impl Transform<Float> {
+ 4. * uv / (uu * vv) * v[i] * u[j];
}
}
Transform::new(r, r.transpose())
TransformGeneric::new(r, r.transpose())
}
pub fn rotate_x(theta: Float) -> Self {
@ -609,47 +622,47 @@ impl Transform<Float> {
}
}
impl<T: num_traits::Float> PartialEq for Transform<T> {
impl<T: num_traits::Float> PartialEq for TransformGeneric<T> {
fn eq(&self, other: &Self) -> bool {
self.m == other.m && self.m_inv == other.m_inv
}
}
impl<T: NumFloat> Mul for Transform<T> {
type Output = Transform<T>;
impl<T: NumFloat> Mul for TransformGeneric<T> {
type Output = TransformGeneric<T>;
fn mul(self, rhs: Transform<T>) -> Self::Output {
Transform {
fn mul(self, rhs: TransformGeneric<T>) -> Self::Output {
TransformGeneric {
m: self.m * rhs.m,
m_inv: rhs.m_inv * self.m_inv,
}
}
}
impl<'b, T> Mul<&'b Transform<T>> for &Transform<T>
impl<'b, T> Mul<&'b TransformGeneric<T>> for &TransformGeneric<T>
where
T: NumFloat,
{
type Output = Transform<T>;
fn mul(self, rhs: &'b Transform<T>) -> Self::Output {
Transform {
type Output = TransformGeneric<T>;
fn mul(self, rhs: &'b TransformGeneric<T>) -> Self::Output {
TransformGeneric {
m: self.m * rhs.m,
m_inv: rhs.m_inv * self.m_inv,
}
}
}
impl Mul<Transform<Float>> for Float {
type Output = Transform<Float>;
fn mul(self, rhs: Transform<Float>) -> Self::Output {
Transform {
impl Mul<TransformGeneric<Float>> for Float {
type Output = TransformGeneric<Float>;
fn mul(self, rhs: TransformGeneric<Float>) -> Self::Output {
TransformGeneric {
m: rhs.m * self,
m_inv: rhs.m_inv * self,
}
}
}
impl<T> Mul<Point<T, 3>> for Transform<T>
impl<T> Mul<Point<T, 3>> for TransformGeneric<T>
where
T: NumFloat,
{
@ -668,7 +681,7 @@ where
}
}
impl<T> Mul<Vector<T, 3>> for Transform<T>
impl<T> Mul<Vector<T, 3>> for TransformGeneric<T>
where
T: NumFloat,
{
@ -687,7 +700,7 @@ where
}
}
impl<T> Mul<Normal<T, 3>> for Transform<T>
impl<T> Mul<Normal<T, 3>> for TransformGeneric<T>
where
T: NumFloat,
{
@ -705,7 +718,7 @@ where
}
}
}
impl From<Quaternion> for Transform<Float> {
impl From<Quaternion> for TransformGeneric<Float> {
fn from(q: Quaternion) -> Self {
let xx = q.v.x() * q.v.x();
let yy = q.v.y() * q.v.y();
@ -737,7 +750,7 @@ impl From<Quaternion> for Transform<Float> {
// For a pure rotation, the inverse is the transpose.
let m_inv_sq = m_sq.transpose();
Transform::new(m_sq, m_inv_sq)
TransformGeneric::new(m_sq, m_inv_sq)
}
}
@ -764,10 +777,10 @@ impl DerivativeTerm {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Default, Clone)]
pub struct AnimatedTransform {
pub start_transform: Transform<Float>,
pub end_transform: Transform<Float>,
pub start_transform: Transform,
pub end_transform: Transform,
pub start_time: Float,
pub end_time: Float,
actually_animated: bool,
@ -783,10 +796,14 @@ pub struct AnimatedTransform {
}
impl AnimatedTransform {
pub fn from_transform(t: &Transform) -> Self {
Self::new(t, 0., t, 1.)
}
pub fn new(
start_transform: &Transform<Float>,
start_transform: &Transform,
start_time: Float,
end_transform: &Transform<Float>,
end_transform: &Transform,
end_time: Float,
) -> Self {
let actually_animated = start_transform != end_transform;
@ -812,12 +829,14 @@ impl AnimatedTransform {
let (t0, r_temp, s0) = start_transform.decompose();
let mut rm = r_temp;
let scale_transform = Transform::from_matrix(rm).expect("Scaling matrix should invertible");
let scale_transform =
TransformGeneric::from_matrix(rm).expect("Scaling matrix should invertible");
let r0 = scale_transform.to_quaternion();
let (t1, r_temp, s1) = end_transform.decompose();
rm = r_temp;
let scale_transform = Transform::from_matrix(rm).expect("Scaling matrix should invertible");
let scale_transform =
TransformGeneric::from_matrix(rm).expect("Scaling matrix should invertible");
let mut r1 = scale_transform.to_quaternion();
if r0.dot(r1) < 0. {
@ -1993,7 +2012,7 @@ impl AnimatedTransform {
t.apply_to_interaction(si)
}
pub fn interpolate(&self, time: Float) -> Transform<Float> {
pub fn interpolate(&self, time: Float) -> TransformGeneric<Float> {
if !self.actually_animated || time <= self.start_time {
return self.start_transform;
}
@ -2008,9 +2027,9 @@ impl AnimatedTransform {
let scale = (1.0 - dt) * self.s[0] + dt * self.s[1];
let scale_transform =
Transform::from_matrix(scale).expect("Scale matrix is not inversible");
TransformGeneric::from_matrix(scale).expect("Scale matrix is not inversible");
Transform::translate(trans) * Transform::from(rotate) * scale_transform
TransformGeneric::translate(trans) * TransformGeneric::from(rotate) * scale_transform
}
pub fn apply_inverse_point(&self, p: Point3f, time: Float) -> Point3f {
@ -2042,15 +2061,22 @@ impl AnimatedTransform {
.apply_to_bounds(*b)
.union(self.end_transform.apply_to_bounds(*b))
}
pub fn is_animated(&self) -> bool {
self.actually_animated
}
}
pub fn look_at(
pos: Point3f,
look: Point3f,
up: Point3f,
) -> Result<Transform<Float>, InversionError> {
pos: impl Into<Point3f>,
look: impl Into<Point3f>,
up: impl Into<Point3f>,
) -> Result<TransformGeneric<Float>, InversionError> {
let mut world_from_camera: SquareMatrix<Float, 4> = SquareMatrix::default();
// Initialize fourth column of viewing matrix
let pos: Point3f = pos.into();
let look: Point3f = look.into();
let up: Point3f = up.into();
world_from_camera[0][3] = pos.x();
world_from_camera[1][3] = pos.y();
world_from_camera[2][3] = pos.z();
@ -2085,5 +2111,5 @@ pub fn look_at(
world_from_camera[3][2] = 0.;
let camera_from_world = world_from_camera.inverse()?;
Ok(Transform::new(camera_from_world, world_from_camera))
Ok(TransformGeneric::new(camera_from_world, world_from_camera))
}

View file

@ -1,69 +0,0 @@
use super::{CameraBase, CameraRay, CameraTrait};
use crate::core::film::FilmTrait;
use crate::core::pbrt::{Float, PI};
use crate::core::sampler::CameraSample;
use crate::geometry::{Bounds2f, Point2f, Point3f, Ray, Vector3f, spherical_direction};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square};
#[derive(Debug, PartialEq)]
pub struct EquiRectangularMapping;
#[derive(Debug, PartialEq)]
pub enum Mapping {
EquiRectangular(EquiRectangularMapping),
}
#[derive(Debug)]
pub struct SphericalCamera {
pub base: CameraBase,
pub screen: Bounds2f,
pub lens_radius: Float,
pub focal_distance: Float,
pub mapping: Mapping,
}
impl CameraTrait for SphericalCamera {
fn base(&self) -> &CameraBase {
&self.base
}
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
self.base.init_metadata(metadata)
}
fn generate_ray(
&self,
sample: CameraSample,
_lamdba: &SampledWavelengths,
) -> Option<CameraRay> {
// Compute spherical camera ray direction
let mut uv = Point2f::new(
sample.p_film.x() / self.base().film.full_resolution().x() as Float,
sample.p_film.y() / self.base().film.full_resolution().y() as Float,
);
let dir: Vector3f;
if self.mapping == Mapping::EquiRectangular(EquiRectangularMapping) {
// Compute ray direction using equirectangular mapping
let theta = PI * uv[1];
let phi = 2. * PI * uv[0];
dir = spherical_direction(theta.sin(), theta.cos(), phi);
} else {
// Compute ray direction using equal area mapping
uv = wrap_equal_area_square(&mut uv);
dir = equal_area_square_to_sphere(uv);
}
std::mem::swap(&mut dir.y(), &mut dir.z());
let ray = Ray::new(
Point3f::new(0., 0., 0.),
dir,
Some(self.sample_time(sample.time)),
self.base().medium.clone(),
);
Some(CameraRay {
ray: self.render_from_camera(&ray, &mut None),
weight: SampledSpectrum::default(),
})
}
}

View file

@ -1,116 +0,0 @@
use super::{Bounds3f, Float, PI, Point3f, Vector3f, VectorLike};
use crate::utils::math::{degrees, safe_acos, safe_asin, safe_sqrt, square};
use crate::utils::transform::Transform;
#[derive(Debug, Clone)]
pub struct DirectionCone {
pub w: Vector3f,
pub cos_theta: Float,
}
impl Default for DirectionCone {
fn default() -> Self {
Self {
w: Vector3f::zero(),
cos_theta: Float::INFINITY,
}
}
}
impl DirectionCone {
pub fn new(w: Vector3f, cos_theta: Float) -> Self {
Self {
w: w.normalize(),
cos_theta,
}
}
pub fn new_from_vector(w: Vector3f) -> Self {
Self::new(w, 1.0)
}
pub fn is_empty(&self) -> bool {
self.cos_theta == Float::INFINITY
}
pub fn entire_sphere() -> Self {
Self::new(Vector3f::new(0., 0., 1.), -1.)
}
pub fn closest_vector_income(&self, wt: Vector3f) -> Vector3f {
let wp = wt.normalize();
let w = self.w;
if wp.dot(w) > self.cos_theta {
return wp;
}
let sin_theta = -safe_sqrt(1. - self.cos_theta * self.cos_theta);
let a = wp.cross(w);
self.cos_theta * w
+ sin_theta / a.norm()
* Vector3f::new(
w.x()
* (wp.y() * w.y() + wp.z() * w.z()
- wp.x() * (square(w.y() + square(w.z())))),
w.y()
* (wp.x() * w.x() + wp.z() * w.z()
- wp.y() * (square(w.x() + square(w.z())))),
w.z()
* (wp.x() * w.x() + wp.y() * w.y()
- wp.z() * (square(w.x() + square(w.y())))),
)
}
pub fn inside(d: &DirectionCone, w: Vector3f) -> bool {
!d.is_empty() && d.w.dot(w.normalize()) > d.cos_theta
}
pub fn bound_subtended_directions(b: &Bounds3f, p: Point3f) -> DirectionCone {
let (p_center, radius) = b.bounding_sphere();
if p.distance_squared(p_center) < square(radius) {
return DirectionCone::entire_sphere();
}
let w = (p_center - p).normalize();
let sin2_theta_max = square(radius) / p_center.distance_squared(p);
let cos_theta_max = safe_sqrt(1. - sin2_theta_max);
DirectionCone::new(w, cos_theta_max)
}
pub fn union(a: &DirectionCone, b: &DirectionCone) -> DirectionCone {
if a.is_empty() {
return b.clone();
}
if b.is_empty() {
return a.clone();
}
// Handle the cases where one cone is inside the other
let theta_a = safe_acos(a.cos_theta);
let theta_b = safe_acos(b.cos_theta);
let theta_d = a.w.angle_between(b.w);
if (theta_d + theta_b).min(PI) <= theta_b {
return a.clone();
}
if (theta_d + theta_a).min(PI) <= theta_a {
return b.clone();
}
// Compute the spread angle of the merged cone, $\theta_o$
let theta_o = (theta_a + theta_d + theta_b) / 2.;
if theta_o >= PI {
return DirectionCone::entire_sphere();
}
// Find the merged cone's axis and return cone union
let theta_r = theta_o - theta_a;
let wr = a.w.cross(b.w);
if wr.norm_squared() >= 0. {
return DirectionCone::entire_sphere();
}
let w = Transform::rotate_around_axis(degrees(theta_r), wr).apply_to_vector(a.w);
DirectionCone::new(w, theta_o.cos())
}
}

View file

@ -1,122 +0,0 @@
pub mod bounds;
pub mod cone;
pub mod primitives;
pub mod ray;
pub mod traits;
pub use self::bounds::{Bounds, Bounds2f, Bounds2fi, Bounds2i, Bounds3f, Bounds3fi, Bounds3i};
pub use self::cone::DirectionCone;
pub use self::primitives::{
Frame, Normal, Normal3f, Point, Point2f, Point2fi, Point2i, Point3, Point3f, Point3fi, Point3i,
Vector, Vector2, Vector2f, Vector2fi, Vector2i, Vector3, Vector3f, Vector3fi, Vector3i,
};
pub use self::ray::{Ray, RayDifferential};
pub use self::traits::{Lerp, Sqrt, Tuple, VectorLike};
use crate::core::pbrt::{Float, PI, clamp_t};
use crate::utils::math::square;
use num_traits::Float as NumFloat;
#[inline]
pub fn min<T: PartialOrd>(a: T, b: T) -> T {
if a < b { a } else { b }
}
#[inline]
pub fn max<T: PartialOrd>(a: T, b: T) -> T {
if a > b { a } else { b }
}
#[inline]
pub fn cos_theta(w: Vector3f) -> Float {
w.z()
}
#[inline]
pub fn abs_cos_theta(w: Vector3f) -> Float {
w.z().abs()
}
#[inline]
pub fn cos2_theta(w: Vector3f) -> Float {
square(w.z())
}
#[inline]
pub fn sin2_theta(w: Vector3f) -> Float {
0_f32.max(1. - cos2_theta(w))
}
#[inline]
pub fn sin_theta(w: Vector3f) -> Float {
sin2_theta(w).sqrt()
}
#[inline]
pub fn tan_theta(w: Vector3f) -> Float {
sin_theta(w) / cos_theta(w)
}
#[inline]
pub fn tan2_theta(w: Vector3f) -> Float {
sin2_theta(w) / cos2_theta(w)
}
#[inline]
pub fn cos_phi(w: Vector3f) -> Float {
let sin_theta = sin_theta(w);
if sin_theta == 0. {
1.
} else {
clamp_t(w.x() / sin_theta, -1., 1.)
}
}
#[inline]
pub fn sin_phi(w: Vector3f) -> Float {
let sin_theta = sin_theta(w);
if sin_theta == 0. {
0.
} else {
clamp_t(w.y() / sin_theta, -1., 1.)
}
}
pub fn same_hemisphere(w: Vector3f, wp: Vector3f) -> bool {
w.z() * wp.z() > 0.
}
pub fn spherical_direction(sin_theta: Float, cos_theta: Float, phi: Float) -> Vector3f {
Vector3f::new(sin_theta * phi.cos(), sin_theta * phi.sin(), cos_theta)
}
pub fn spherical_triangle_area(a: Vector3f, b: Vector3f, c: Vector3f) -> Float {
(2.0 * (a.dot(b.cross(c))).atan2(1.0 + a.dot(b) + a.dot(c) + b.dot(c))).abs()
}
pub fn spherical_quad_area(a: Vector3f, b: Vector3f, c: Vector3f, d: Vector3f) -> Float {
let mut axb = a.cross(b);
let mut bxc = b.cross(c);
let mut cxd = c.cross(d);
let mut dxa = d.cross(a);
if axb.norm_squared() == 0.
|| bxc.norm_squared() == 0.
|| cxd.norm_squared() == 0.
|| dxa.norm_squared() == 0.
{
return 0.;
}
axb = axb.normalize();
bxc = bxc.normalize();
cxd = cxd.normalize();
dxa = dxa.normalize();
let alpha = dxa.angle_between(-axb);
let beta = axb.angle_between(-bxc);
let gamma = bxc.angle_between(-cxd);
let delta = cxd.angle_between(-dxa);
(alpha + beta + gamma + delta - 2. * PI).abs()
}
pub fn spherical_theta(v: Vector3f) -> Float {
clamp_t(v.z(), -1.0, 1.0).acos()
}
pub fn spherical_phi(v: Vector3f) -> Float {
let p = v.y().atan2(v.x());
if p < 0.0 { p + 2.0 * PI } else { p }
}

View file

@ -1,344 +0,0 @@
use super::{Float, NumFloat};
use super::{Point, Point2f, Point3, Point3f, Vector, Vector2, Vector2f, Vector3, Vector3f};
use crate::core::pbrt::lerp;
use crate::geometry::traits::{Sqrt, VectorLike};
use crate::geometry::{max, min};
use crate::utils::interval::Interval;
use num_traits::{Bounded, Num};
use std::mem;
use std::ops::{Add, Div, DivAssign, Mul, Sub};
// AABB BOUNDING BOXES
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Bounds<T, const N: usize> {
pub p_min: Point<T, N>,
pub p_max: Point<T, N>,
}
impl<'a, T, const N: usize> IntoIterator for &'a Bounds<T, N> {
type Item = &'a Point<T, N>;
type IntoIter = std::array::IntoIter<&'a Point<T, N>, 2>;
fn into_iter(self) -> Self::IntoIter {
[&self.p_min, &self.p_max].into_iter()
}
}
impl<T, const N: usize> Bounds<T, N>
where
T: Num + PartialOrd + Copy,
{
pub fn from_point(p: Point<T, N>) -> Self {
Self { p_min: p, p_max: p }
}
pub fn from_points(p1: Point<T, N>, p2: Point<T, N>) -> Self {
let mut p_min_arr = [T::zero(); N];
let mut p_max_arr = [T::zero(); N];
for i in 0..N {
if p1[i] < p2[i] {
p_min_arr[i] = p1[i];
p_max_arr[i] = p2[i];
} else {
p_min_arr[i] = p2[i];
p_max_arr[i] = p1[i];
}
}
Self {
p_min: Point(p_min_arr),
p_max: Point(p_max_arr),
}
}
pub fn union_point(self, p: Point<T, N>) -> Self {
let mut p_min = self.p_min;
let mut p_max = self.p_max;
for i in 0..N {
p_min[i] = min(p_min[i], p[i]);
p_max[i] = max(p_max[i], p[i]);
}
Self { p_min, p_max }
}
pub fn union(self, b2: Self) -> Self {
let mut p_min = self.p_min;
let mut p_max = self.p_max;
for i in 0..N {
p_min[i] = min(p_min[i], b2.p_min[i]);
p_max[i] = max(p_max[i], b2.p_max[i]);
}
Self { p_min, p_max }
}
pub fn diagonal(&self) -> Vector<T, N> {
self.p_max - self.p_min
}
pub fn centroid(&self) -> Point<T, N> {
let two = T::one() + T::one();
self.p_min + (self.diagonal() / two)
}
pub fn volume(&self) -> T {
let d = self.diagonal();
d.0.iter().fold(T::one(), |acc, &val| acc * val)
}
pub fn expand(&self, delta: T) -> Self {
let mut p_min = self.p_min;
let mut p_max = self.p_max;
p_min = p_min - Vector::fill(delta);
p_max = p_max + Vector::fill(delta);
Self { p_min, p_max }
}
pub fn lerp(&self, t: Point<T, N>) -> Point<T, N> {
let mut results_arr = [T::zero(); N];
for i in 0..N {
results_arr[i] = lerp(t[i], self.p_min[i], self.p_max[i])
}
Point(results_arr)
}
pub fn max_dimension(&self) -> usize
where
Point<T, N>: Sub<Output = Vector<T, N>>,
{
let d = self.diagonal();
let mut max_dim = 0;
let mut max_span = d[0];
for i in 1..N {
if d[i] > max_span {
max_span = d[i];
max_dim = i;
}
}
max_dim
}
pub fn offset(&self, p: &Point<T, N>) -> Vector<T, N>
where
Point<T, N>: Sub<Output = Vector<T, N>>,
Vector<T, N>: DivAssign<T>,
{
let mut o = *p - self.p_min;
let d = self.diagonal();
for i in 0..N {
if d[i] > T::zero() {
o[i] = o[i] / d[i];
}
}
o
}
pub fn corner(&self, corner_index: usize) -> Point<T, N> {
Point(std::array::from_fn(|i| {
if (corner_index >> i) & 1 == 1 {
self.p_max[i]
} else {
self.p_min[i]
}
}))
}
pub fn overlaps(&self, rhs: &Self) -> bool {
for i in 0..N {
if self.p_max[i] < rhs.p_min[i] || self.p_min[i] > rhs.p_max[i] {
return false;
}
}
true
}
pub fn contains(&self, p: Point<T, N>) -> bool {
(0..N).all(|i| p[i] >= self.p_min[i] && p[i] <= self.p_max[i])
}
pub fn contains_exclusive(&self, p: Point<T, N>) -> bool {
(0..N).all(|i| p[i] >= self.p_min[i] && p[i] < self.p_max[i])
}
pub fn is_empty(&self) -> bool {
(0..N).any(|i| self.p_min[i] >= self.p_max[i])
}
pub fn is_degenerate(&self) -> bool {
(0..N).any(|i| self.p_min[i] > self.p_max[i])
}
}
impl<T, const N: usize> Default for Bounds<T, N>
where
T: Bounded + Copy,
{
fn default() -> Self {
Self {
p_min: Point([T::max_value(); N]),
p_max: Point([T::min_value(); N]),
}
}
}
pub type Bounds2<T> = Bounds<T, 2>;
pub type Bounds2f = Bounds2<Float>;
pub type Bounds2i = Bounds2<i32>;
pub type Bounds2fi = Bounds2<Interval>;
pub type Bounds3<T> = Bounds<T, 3>;
pub type Bounds3i = Bounds3<i32>;
pub type Bounds3f = Bounds3<Float>;
pub type Bounds3fi = Bounds3<Interval>;
impl<T> Bounds3<T>
where
T: Num + PartialOrd + Copy + Default,
{
pub fn surface_area(&self) -> T {
let d = self.diagonal();
let two = T::one() + T::one();
two * (d.x() * d.y() + d.x() * d.z() + d.y() * d.z())
}
}
impl<T> Bounds3<T>
where
T: NumFloat + PartialOrd + Copy + Default + Sqrt,
{
pub fn bounding_sphere(&self) -> (Point3<T>, T) {
let two = T::one() + T::one();
let center = self.p_min + self.diagonal() / two;
let radius = if self.contains(center) {
center.distance(self.p_max)
} else {
T::zero()
};
(center, radius)
}
pub fn insersect(&self, o: Point3<T>, d: Vector3<T>, t_max: T) -> Option<(T, T)> {
let mut t0 = T::zero();
let mut t1 = t_max;
for i in 0..3 {
let inv_ray_dir = T::one() / d[i];
let mut t_near = (self.p_min[i] - o[i]) * inv_ray_dir;
let mut t_far = (self.p_max[i] - o[i]) * inv_ray_dir;
if t_near > t_far {
mem::swap(&mut t_near, &mut t_far);
}
t0 = if t_near > t0 { t_near } else { t0 };
t1 = if t_far < t1 { t_far } else { t1 };
if t0 > t1 {
return None;
}
}
Some((t0, t1))
}
}
impl<T> Bounds2<T>
where
T: Num + Copy + Default,
{
pub fn area(&self) -> T {
let d: Vector2<T> = self.p_max - self.p_min;
d.x() * d.y()
}
}
impl Bounds3f {
#[inline(always)]
pub fn intersect_p(
&self,
o: Point3f,
ray_t_max: Float,
inv_dir: Vector3f,
dir_is_neg: &[usize; 3],
) -> Option<(Float, Float)> {
let bounds = [&self.p_min, &self.p_max];
// Check X
let mut t_min = (bounds[dir_is_neg[0]].x() - o.x()) * inv_dir.x();
let mut t_max = (bounds[1 - dir_is_neg[0]].x() - o.x()) * inv_dir.x();
// Check Y
let ty_min = (bounds[dir_is_neg[1]].y() - o.y()) * inv_dir.y();
let ty_max = (bounds[1 - dir_is_neg[1]].y() - o.y()) * inv_dir.y();
if t_min > ty_max || ty_min > t_max {
return None;
}
if ty_min > t_min {
t_min = ty_min;
}
if ty_max < t_max {
t_max = ty_max;
}
// Check Z
let tz_min = (bounds[dir_is_neg[2]].z() - o.z()) * inv_dir.z();
let tz_max = (bounds[1 - dir_is_neg[2]].z() - o.z()) * inv_dir.z();
if t_min > tz_max || tz_min > t_max {
return None;
}
if tz_min > t_min {
t_min = tz_min;
}
if tz_max < t_max {
t_max = tz_max;
}
if (t_min < ray_t_max) && (t_max > 0.0) {
Some((t_min, t_max))
} else {
None
}
}
pub fn intersect_with_inverse(&self, o: Point3f, d: Vector3f, ray_t_max: Float) -> bool {
let inv_dir = Vector3::new(1.0 / d.x(), 1.0 / d.y(), 1.0 / d.z());
let dir_is_neg: [usize; 3] = [
(d.x() < 0.0) as usize,
(d.y() < 0.0) as usize,
(d.z() < 0.0) as usize,
];
let bounds = [&self.p_min, &self.p_max];
// Check for ray intersection against x and y slabs
let mut t_min = (bounds[dir_is_neg[0]].x() - o.x()) * inv_dir.x();
let mut t_max = (bounds[1 - dir_is_neg[0]].x() - o.x()) * inv_dir.x();
let ty_min = (bounds[dir_is_neg[1]].y() - o.y()) * inv_dir.y();
let ty_max = (bounds[1 - dir_is_neg[1]].y() - o.y()) * inv_dir.y();
if t_min > ty_max || ty_min > t_max {
return false;
}
if ty_min > t_min {
t_min = ty_min;
}
if ty_max < t_max {
t_max = ty_max;
}
let tz_min = (bounds[dir_is_neg[2]].z() - o.z()) * inv_dir.z();
let tz_max = (bounds[1 - dir_is_neg[2]].z() - o.z()) * inv_dir.z();
if t_min > tz_max || tz_min > t_max {
return false;
}
if tz_min > t_min {
t_min = tz_min;
}
if tz_max < t_max {
t_max = tz_max;
}
(t_min < ray_t_max) && (t_max > 0.0)
}
}

View file

@ -1,880 +0,0 @@
use super::traits::{Sqrt, Tuple, VectorLike};
use super::{Float, NumFloat, PI, clamp_t};
use crate::utils::interval::Interval;
use crate::utils::math::{difference_of_products, quadratic, safe_asin};
use num_traits::{AsPrimitive, FloatConst, Num, Signed, Zero};
use std::hash::{Hash, Hasher};
use std::iter::Sum;
use std::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
};
// N-dimensional displacement
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Vector<T, const N: usize>(pub [T; N]);
// N-dimensional location
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Point<T, const N: usize>(pub [T; N]);
// N-dimensional surface normal
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Normal<T, const N: usize>(pub [T; N]);
#[macro_export]
macro_rules! impl_tuple_core {
($Struct:ident) => {
impl<T: Copy, const N: usize> Tuple<T, N> for $Struct<T, N> {
#[inline]
fn data(&self) -> &[T; N] {
&self.0
}
#[inline]
fn data_mut(&mut self) -> &mut [T; N] {
&mut self.0
}
#[inline]
fn from_array(arr: [T; N]) -> Self {
Self(arr)
}
}
impl<T: Default + Copy, const N: usize> Default for $Struct<T, N> {
fn default() -> Self {
Self([T::default(); N])
}
}
impl<T, const N: usize> $Struct<T, N>
where
T: Zero + Copy,
{
#[inline]
pub fn zero() -> Self {
Self([T::zero(); N])
}
}
impl<const N: usize> $Struct<f32, N> {
#[inline]
pub fn floor(&self) -> $Struct<i32, N> {
$Struct(self.0.map(|v| v.floor() as i32))
}
#[inline]
pub fn average(&self) -> f32 {
let sum: f32 = self.0.iter().sum();
sum / (N as f32)
}
}
impl<T, const N: usize> $Struct<T, N>
where
T: Copy + PartialOrd,
{
#[inline]
pub fn min(&self, other: Self) -> Self {
let mut out = self.0;
for i in 0..N {
if other.0[i] < out[i] {
out[i] = other.0[i];
}
}
Self(out)
}
#[inline]
pub fn max(&self, other: Self) -> Self {
let mut out = self.0;
for i in 0..N {
if other.0[i] > out[i] {
out[i] = other.0[i]
}
}
Self(out)
}
#[inline]
pub fn max_component_value(&self) -> T {
let mut m = self.0[0];
for i in 1..N {
if self.0[i] > m {
m = self.0[i];
}
}
m
}
}
impl<T, const N: usize> $Struct<T, N>
where
T: Copy,
{
#[inline]
pub fn fill(value: T) -> Self {
Self([value; N])
}
#[inline]
pub fn cast<U>(&self) -> $Struct<U, N>
where
U: 'static + Copy,
T: 'static + Copy + AsPrimitive<U>,
{
$Struct(self.0.map(|c| c.as_()))
}
}
impl<T, const N: usize> Index<usize> for $Struct<T, N> {
type Output = T;
#[inline]
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
impl<T, const N: usize> IndexMut<usize> for $Struct<T, N> {
#[inline]
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.0[index]
}
}
impl<T, const N: usize> Neg for $Struct<T, N>
where
T: Neg<Output = T> + Copy,
{
type Output = Self;
fn neg(self) -> Self::Output {
Self(self.0.map(|c| -c))
}
}
};
}
#[macro_export]
macro_rules! impl_scalar_ops {
($Struct:ident) => {
impl<T, const N: usize> Mul<T> for $Struct<T, N>
where
T: Mul<Output = T> + Copy,
{
type Output = Self;
fn mul(self, rhs: T) -> Self::Output {
let mut result = self.0;
for i in 0..N {
result[i] = result[i] * rhs;
}
Self(result)
}
}
impl<const N: usize> Mul<$Struct<Float, N>> for Float {
type Output = $Struct<Float, N>;
fn mul(self, rhs: $Struct<Float, N>) -> Self::Output {
rhs * self
}
}
impl<T, const N: usize> MulAssign<T> for $Struct<T, N>
where
T: MulAssign + Copy,
{
fn mul_assign(&mut self, rhs: T) {
for i in 0..N {
self.0[i] *= rhs;
}
}
}
impl<T, const N: usize> Div<T> for $Struct<T, N>
where
T: Div<Output = T> + Copy,
{
type Output = Self;
fn div(self, rhs: T) -> Self::Output {
let mut result = self.0;
for i in 0..N {
result[i] = result[i] / rhs;
}
Self(result)
}
}
impl<T, const N: usize> DivAssign<T> for $Struct<T, N>
where
T: DivAssign + Copy,
{
fn div_assign(&mut self, rhs: T) {
for i in 0..N {
self.0[i] /= rhs;
}
}
}
};
}
#[macro_export]
macro_rules! impl_op {
($Op:ident, $op:ident, $Lhs:ident, $Rhs:ident, $Output:ident) => {
impl<T, const N: usize> $Op<$Rhs<T, N>> for $Lhs<T, N>
where
T: $Op<Output = T> + Copy,
{
type Output = $Output<T, N>;
fn $op(self, rhs: $Rhs<T, N>) -> Self::Output {
let mut result = self.0;
for i in 0..N {
result[i] = $Op::$op(self.0[i], rhs.0[i]);
}
$Output(result)
}
}
};
}
#[macro_export]
macro_rules! impl_op_assign {
($OpAssign:ident, $op_assign:ident, $Lhs:ident, $Rhs:ident) => {
impl<T, const N: usize> $OpAssign<$Rhs<T, N>> for $Lhs<T, N>
where
T: $OpAssign + Copy,
{
fn $op_assign(&mut self, rhs: $Rhs<T, N>) {
for i in 0..N {
$OpAssign::$op_assign(&mut self.0[i], rhs.0[i]);
}
}
}
};
}
#[macro_export]
macro_rules! impl_float_vector_ops {
($Struct:ident) => {
impl<T, const N: usize> VectorLike for $Struct<T, N>
where
T: Copy
+ Zero
+ Add<Output = T>
+ Mul<Output = T>
+ Sub<Output = T>
+ Div<Output = T>
+ Sqrt,
{
type Scalar = T;
fn dot(self, rhs: Self) -> T {
let mut sum = T::zero();
for i in 0..N {
sum = sum + self[i] * rhs[i];
}
sum
}
}
};
}
macro_rules! impl_abs {
($Struct:ident) => {
impl<T, const N: usize> $Struct<T, N>
where
T: Signed + Copy,
{
pub fn abs(self) -> Self {
let mut result = self.0;
for i in 0..N {
result[i] = result[i].abs();
}
Self(result)
}
}
};
}
macro_rules! impl_accessors {
($Struct:ident) => {
impl<T: Copy> $Struct<T, 2> {
pub fn x(&self) -> T {
self.0[0]
}
pub fn y(&self) -> T {
self.0[1]
}
}
impl<T: Copy> $Struct<T, 3> {
pub fn x(&self) -> T {
self.0[0]
}
pub fn y(&self) -> T {
self.0[1]
}
pub fn z(&self) -> T {
self.0[2]
}
}
};
}
impl_tuple_core!(Vector);
impl_tuple_core!(Point);
impl_tuple_core!(Normal);
impl_scalar_ops!(Vector);
impl_scalar_ops!(Normal);
// Addition
impl_op!(Add, add, Vector, Vector, Vector);
impl_op!(Add, add, Point, Vector, Point);
impl_op!(Add, add, Vector, Point, Point);
impl_op!(Add, add, Normal, Normal, Normal);
// Subtraction
impl_op!(Sub, sub, Vector, Vector, Vector);
impl_op!(Sub, sub, Point, Vector, Point);
impl_op!(Sub, sub, Point, Point, Vector);
impl_op!(Sub, sub, Normal, Normal, Normal);
// AddAssign
impl_op_assign!(AddAssign, add_assign, Vector, Vector);
impl_op_assign!(AddAssign, add_assign, Point, Vector);
impl_op_assign!(AddAssign, add_assign, Normal, Normal);
// SubAssign
impl_op_assign!(SubAssign, sub_assign, Vector, Vector);
impl_op_assign!(SubAssign, sub_assign, Point, Vector);
impl_op_assign!(SubAssign, sub_assign, Normal, Normal);
impl_float_vector_ops!(Vector);
impl_float_vector_ops!(Normal);
impl_abs!(Vector);
impl_abs!(Normal);
impl_abs!(Point);
impl_accessors!(Vector);
impl_accessors!(Point);
impl_accessors!(Normal);
impl<T: Copy, const N: usize> From<Vector<T, N>> for Normal<T, N> {
fn from(v: Vector<T, N>) -> Self {
Self(v.0)
}
}
impl<T: Copy, const N: usize> From<Normal<T, N>> for Vector<T, N> {
fn from(n: Normal<T, N>) -> Self {
Self(n.0)
}
}
impl<T: Copy, const N: usize> From<Vector<T, N>> for Point<T, N> {
fn from(v: Vector<T, N>) -> Self {
Self(v.0)
}
}
impl<T: Copy, const N: usize> From<Point<T, N>> for Vector<T, N> {
fn from(n: Point<T, N>) -> Self {
Self(n.0)
}
}
impl<T, const N: usize> Point<T, N>
where
T: NumFloat + Sqrt,
{
pub fn distance(self, other: Self) -> T {
(self - other).norm()
}
pub fn distance_squared(self, other: Self) -> T {
(self - other).norm_squared()
}
}
impl Point2f {
pub fn invert_bilinear(p: Point2f, vert: &[Point2f]) -> Point2f {
let a = vert[0];
let b = vert[1];
let c = vert[3];
let d = vert[2];
let e = b - a;
let f = d - a;
let g = (a - b) + (c - d);
let h = p - a;
let cross2d = |a: Vector2f, b: Vector2f| difference_of_products(a.x(), b.y(), a.y(), b.x());
let k2 = cross2d(g, f);
let k1 = cross2d(e, f) + cross2d(h, g);
let k0 = cross2d(h, e);
// if edges are parallel, this is a linear equation
if k2.abs() < 0.001 {
if (e.x() * k1 - g.x() * k0).abs() < 1e-5 {
return Point2f::new(
(h.y() * k1 + f.y() * k0) / (e.y() * k1 - g.y() * k0),
-k0 / k1,
);
} else {
return Point2f::new(
(h.x() * k1 + f.x() * k0) / (e.x() * k1 - g.x() * k0),
-k0 / k1,
);
}
}
if let Some((v0, v1)) = quadratic(k2, k1, k0) {
let u = (h.x() - f.x() * v0) / (e.x() + g.x() * v0);
if !(0.0..=1.).contains(&u) || !(0.0..=1.0).contains(&v0) {
return Point2f::new((h.x() - f.x() * v1) / (e.x() + g.x() * v1), v1);
}
Point2f::new(u, v0)
} else {
Point2f::zero()
}
}
}
// Utility aliases and functions
pub type Point2<T> = Point<T, 2>;
pub type Point2f = Point2<Float>;
pub type Point2i = Point2<i32>;
pub type Point2fi = Point2<Interval>;
pub type Point3<T> = Point<T, 3>;
pub type Point3f = Point3<Float>;
pub type Point3i = Point3<i32>;
pub type Point3fi = Point3<Interval>;
pub type Vector2<T> = Vector<T, 2>;
pub type Vector2f = Vector2<Float>;
pub type Vector2i = Vector2<i32>;
pub type Vector2fi = Vector2<Interval>;
pub type Vector3<T> = Vector<T, 3>;
pub type Vector3f = Vector3<Float>;
pub type Vector3i = Vector3<i32>;
pub type Vector3fi = Vector3<Interval>;
pub type Normal3<T> = Normal<T, 3>;
pub type Normal3f = Normal3<Float>;
pub type Normal3i = Normal3<i32>;
impl<T: Copy> Vector2<T> {
pub fn new(x: T, y: T) -> Self {
Self([x, y])
}
}
impl<T: Copy> Point2<T> {
pub fn new(x: T, y: T) -> Self {
Self([x, y])
}
}
impl<T: Copy> Vector3<T> {
pub fn new(x: T, y: T, z: T) -> Self {
Self([x, y, z])
}
}
impl<T: Copy> Point3<T> {
pub fn new(x: T, y: T, z: T) -> Self {
Self([x, y, z])
}
}
impl<T: Copy> Normal3<T> {
pub fn new(x: T, y: T, z: T) -> Self {
Self([x, y, z])
}
}
// Vector operations
impl<T> Vector3<T>
where
T: Num + Copy + Neg<Output = T>,
{
pub fn cross(self, rhs: Self) -> Self {
Self([
self[1] * rhs[2] - self[2] * rhs[1],
self[2] * rhs[0] - self[0] * rhs[2],
self[0] * rhs[1] - self[1] * rhs[0],
])
}
}
impl<T> Normal3<T>
where
T: Num + Copy + Neg<Output = T>,
{
pub fn cross(self, rhs: Self) -> Self {
Self([
self[1] * rhs[2] - self[2] * rhs[1],
self[2] * rhs[0] - self[0] * rhs[2],
self[0] * rhs[1] - self[1] * rhs[0],
])
}
}
impl<T> Vector3<T>
where
T: Num + NumFloat + Copy + Neg<Output = T>,
{
pub fn coordinate_system(&self) -> (Self, Self)
where
T: NumFloat,
{
let v2 = if self[0].abs() > self[1].abs() {
Self::new(-self[2], T::zero(), self[0]) / (self[0] * self[0] + self[2] * self[2]).sqrt()
} else {
Self::new(T::zero(), self[2], -self[1]) / (self[1] * self[1] + self[2] * self[2]).sqrt()
};
(v2, self.cross(v2))
}
pub fn coordinate_system_from_cpp(&self) -> (Self, Self) {
let sign = self.z().copysign(T::one());
let a = -T::one() / (sign + self.z());
let b = self.x() * self.y() * a;
let v2 = Self::new(
T::one() + sign * self.x().powi(2) * a,
sign * b,
-sign * self.x(),
);
let v3 = Self::new(b, sign + self.y().powi(2) * a, -self.y());
(v2, v3)
}
}
impl<T> Normal3<T>
where
T: Num + NumFloat + Copy + Neg<Output = T>,
{
pub fn coordinate_system(&self) -> (Self, Self)
where
T: NumFloat,
{
let v2 = if self[0].abs() > self[1].abs() {
Self::new(-self[2], T::zero(), self[0]) / (self[0] * self[0] + self[2] * self[2]).sqrt()
} else {
Self::new(T::zero(), self[2], -self[1]) / (self[1] * self[1] + self[2] * self[2]).sqrt()
};
(v2, self.cross(v2))
}
pub fn coordinate_system_from_cpp(&self) -> (Self, Self) {
let sign = self.z().copysign(T::one());
let a = -T::one() / (sign + self.z());
let b = self.x() * self.y() * a;
let v2 = Self::new(
T::one() + sign * self.x().powi(2) * a,
sign * b,
-sign * self.x(),
);
let v3 = Self::new(b, sign + self.y().powi(2) * a, -self.y());
(v2, v3)
}
}
impl<const N: usize> Hash for Vector<Float, N> {
fn hash<H: Hasher>(&self, state: &mut H) {
for item in self.0.iter() {
item.to_bits().hash(state);
}
}
}
impl<const N: usize> Hash for Point<Float, N> {
fn hash<H: Hasher>(&self, state: &mut H) {
for item in self.0.iter() {
item.to_bits().hash(state);
}
}
}
impl<const N: usize> Hash for Normal<Float, N> {
fn hash<H: Hasher>(&self, state: &mut H) {
for item in self.0.iter() {
item.to_bits().hash(state);
}
}
}
// INTERVAL STUFF
impl<const N: usize> Point<Interval, N> {
pub fn new_from_point(p: Point<Float, N>) -> Self {
let mut arr = [Interval::default(); N];
for i in 0..N {
arr[i] = Interval::new(p[i]);
}
Self(arr)
}
pub fn new_with_error(p: Point<Float, N>, e: Vector<Float, N>) -> Self {
let mut arr = [Interval::default(); N];
for i in 0..N {
arr[i] = Interval::new_from_value_and_error(p[i], e[i]);
}
Self(arr)
}
pub fn error(&self) -> Vector<Float, N> {
let mut arr = [0.0; N];
for i in 0..N {
arr[i] = self[i].width() / 2.0;
}
Vector(arr)
}
pub fn midpoint(&self) -> Point<Float, N> {
let mut arr = [0.0; N];
for i in 0..N {
arr[i] = self[i].midpoint();
}
Point(arr)
}
pub fn is_exact(&self) -> bool {
self.0.iter().all(|interval| interval.width() == 0.0)
}
}
impl<const N: usize> Vector<Interval, N> {
pub fn new_from_vector(v: Vector<Float, N>) -> Self {
let mut arr = [Interval::default(); N];
for i in 0..N {
arr[i] = Interval::new(v[i]);
}
Self(arr)
}
pub fn new_with_error(v: Vector<Float, N>, e: Vector<Float, N>) -> Self {
let mut arr = [Interval::default(); N];
for i in 0..N {
arr[i] = Interval::new_from_value_and_error(v[i], e[i]);
}
Self(arr)
}
pub fn error(&self) -> Vector<Float, N> {
let mut arr = [0.0; N];
for i in 0..N {
arr[i] = self[i].width() / 2.0;
}
Vector(arr)
}
pub fn midpoint(&self) -> Vector<Float, N> {
let mut arr = [0.0; N];
for i in 0..N {
arr[i] = self[i].midpoint();
}
Vector(arr)
}
pub fn is_exact(&self) -> bool {
self.0.iter().all(|interval| interval.width() == 0.0)
}
}
impl<const N: usize> From<Point<Interval, N>> for Point<Float, N> {
fn from(pi: Point<Interval, N>) -> Self {
let mut arr = [0.0; N];
for i in 0..N {
arr[i] = pi[i].midpoint();
}
Point(arr)
}
}
impl<const N: usize> From<Vector<Interval, N>> for Vector<Float, N> {
fn from(pi: Vector<Interval, N>) -> Self {
let mut arr = [0.0; N];
for i in 0..N {
arr[i] = pi[i].midpoint();
}
Vector(arr)
}
}
impl<const N: usize> From<Vector<Float, N>> for Vector<Interval, N> {
fn from(v: Vector<Float, N>) -> Self {
let mut arr = [Interval::default(); N];
for i in 0..N {
arr[i] = Interval::new(v[i]);
}
Self(arr)
}
}
impl<const N: usize> Mul<Vector<Interval, N>> for Interval {
type Output = Vector<Interval, N>;
fn mul(self, rhs: Vector<Interval, N>) -> Self::Output {
rhs * self
}
}
impl<const N: usize> Div<Vector<Interval, N>> for Interval {
type Output = Vector<Interval, N>;
fn div(self, rhs: Vector<Interval, N>) -> Self::Output {
let mut result = rhs.0;
for i in 0..N {
result[i] = self / rhs[i];
}
Vector(result)
}
}
impl<const N: usize> From<Vector<i32, N>> for Vector<f32, N> {
fn from(v: Vector<i32, N>) -> Self {
Self(v.0.map(|c| c as f32))
}
}
impl<const N: usize> From<Point<i32, N>> for Point<Float, N> {
fn from(p: Point<i32, N>) -> Self {
Point(p.0.map(|c| c as Float))
}
}
impl<T> Normal3<T>
where
T: Num + PartialOrd + Copy + Neg<Output = T> + Sqrt,
{
pub fn face_forward(self, v: Vector3<T>) -> Self {
if Vector3::<T>::from(self).dot(v) < T::zero() {
-self
} else {
self
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[repr(C)]
pub struct OctahedralVector {
x: u16,
y: u16,
}
impl OctahedralVector {
pub fn new(mut v: Vector3f) -> Self {
v /= v.x().abs() + v.y().abs() + v.z().abs();
let (x_enc, y_enc) = if v.z() >= 0.0 {
(Self::encode(v.x()), Self::encode(v.y()))
} else {
(
Self::encode((1.0 - v.y().abs()) * Self::sign(v.x())),
Self::encode((1.0 - v.x().abs()) * Self::sign(v.y())),
)
};
Self { x: x_enc, y: y_enc }
}
pub fn to_vector(self) -> Vector3f {
let mut v = Vector3f::default();
// Map [0, 65535] back to [-1, 1]
v[0] = -1.0 + 2.0 * (self.x as Float / 65535.0);
v[1] = -1.0 + 2.0 * (self.y as Float / 65535.0);
v[2] = 1.0 - (v.x().abs() + v.y().abs());
if v.z() < 0.0 {
let xo = v.x();
v[0] = (1.0 - v.y().abs()) * Self::sign(xo);
v[1] = (1.0 - xo.abs()) * Self::sign(v.y());
}
v.normalize()
}
#[inline]
pub fn sign(v: Float) -> Float {
1.0.copysign(v)
}
#[inline]
pub fn encode(f: Float) -> u16 {
(clamp_t((f + 1.0) / 2.0, 0.0, 1.0) * 65535.0).round() as u16
}
}
impl From<Vector3f> for OctahedralVector {
fn from(v: Vector3f) -> Self {
Self::new(v)
}
}
impl From<OctahedralVector> for Vector3f {
fn from(ov: OctahedralVector) -> Self {
ov.to_vector()
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct Frame {
pub x: Vector3f,
pub y: Vector3f,
pub z: Vector3f,
}
impl Frame {
pub fn new(x: Vector3f, z: Vector3f) -> Self {
Self {
x,
y: z.cross(x),
z,
}
}
pub fn from_x(x: Vector3f) -> Self {
let (y, z) = x.normalize().coordinate_system();
Self {
x: x.normalize(),
y,
z,
}
}
pub fn from_xz(x: Vector3f, z: Vector3f) -> Self {
Self {
x,
y: z.cross(x),
z,
}
}
pub fn from_xy(x: Vector3f, y: Vector3f) -> Self {
Self {
x,
y,
z: x.cross(y),
}
}
pub fn from_y(y: Vector3f) -> Self {
let (z, x) = y.normalize().coordinate_system();
Self {
x,
y: y.normalize(),
z,
}
}
pub fn from_z(z: Vector3f) -> Self {
let (x, y) = z.normalize().coordinate_system();
Self {
x,
y,
z: z.normalize(),
}
}
pub fn to_local(&self, v: Vector3f) -> Vector3f {
Vector3f::new(v.dot(self.x), v.dot(self.y), v.dot(self.z))
}
pub fn to_local_normal(&self, n: Normal3f) -> Normal3f {
let n: Vector3f = n.into();
Normal3f::new(n.dot(self.x), n.dot(self.y), n.dot(self.z))
}
pub fn from_local(&self, v: Vector3f) -> Vector3f {
self.x * v.x() + self.y * v.y() + self.z * v.z()
}
pub fn from_local_normal(&self, v: Normal3f) -> Normal3f {
Normal3f::from(self.x * v.x() + self.y * v.y() + self.z * v.z())
}
}

View file

@ -1,119 +0,0 @@
use super::{Normal3f, Point3f, Point3fi, Vector3f, VectorLike};
use crate::core::medium::Medium;
use crate::core::pbrt::Float;
use crate::utils::math::{next_float_down, next_float_up};
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct Ray {
pub o: Point3f,
pub d: Vector3f,
pub medium: Option<Arc<Medium>>,
pub time: Float,
// We do this instead of creating a trait for Rayable or some gnarly thing like that
pub differential: Option<RayDifferential>,
}
impl Default for Ray {
fn default() -> Self {
Self {
o: Point3f::new(0.0, 0.0, 0.0),
d: Vector3f::new(0.0, 0.0, 0.0),
medium: None,
time: 0.0,
differential: None,
}
}
}
impl Ray {
pub fn new(o: Point3f, d: Vector3f, time: Option<Float>, medium: Option<Arc<Medium>>) -> Self {
Self {
o,
d,
time: time.unwrap_or_else(|| Self::default().time),
medium,
..Self::default()
}
}
pub fn at(&self, t: Float) -> Point3f {
self.o + self.d * t
}
pub fn offset_origin(p: &Point3fi, n: &Normal3f, w: &Vector3f) -> Point3f {
let d: Float = Vector3f::from(n.abs()).dot(p.error());
let normal: Vector3f = Vector3f::from(*n);
let mut offset = p.midpoint();
if w.dot(normal) < 0.0 {
offset -= normal * d;
} else {
offset += normal * d;
}
for i in 0..3 {
if n[i] > 0.0 {
offset[i] = next_float_up(offset[i]);
} else if n[i] < 0.0 {
offset[i] = next_float_down(offset[i]);
}
}
offset
}
pub fn spawn(pi: &Point3fi, n: &Normal3f, time: Float, d: Vector3f) -> Ray {
let origin = Self::offset_origin(pi, n, &d);
Ray {
o: origin,
d,
time,
medium: None,
differential: None,
}
}
pub fn spawn_to_point(p_from: &Point3fi, n: &Normal3f, time: Float, p_to: Point3f) -> Ray {
let d = p_to - p_from.midpoint();
Self::spawn(p_from, n, time, d)
}
pub fn spawn_to_interaction(
p_from: &Point3fi,
n_from: &Normal3f,
time: Float,
p_to: &Point3fi,
n_to: &Normal3f,
) -> Ray {
let dir_for_offset = p_to.midpoint() - p_from.midpoint();
let pf = Self::offset_origin(p_from, n_from, &dir_for_offset);
let pt = Self::offset_origin(p_to, n_to, &(pf - p_to.midpoint()));
let d = pt - pf;
Ray {
o: pf,
d,
time,
medium: None,
differential: None,
}
}
pub fn scale_differentials(&mut self, s: Float) {
if let Some(differential) = &mut self.differential {
differential.rx_origin = self.o + (differential.rx_origin - self.o) * s;
differential.ry_origin = self.o + (differential.ry_origin - self.o) * s;
differential.rx_direction = self.d + (differential.rx_direction - self.d) * s;
differential.ry_direction = self.d + (differential.ry_direction - self.d) * s;
}
}
}
#[derive(Debug, Default, Copy, Clone)]
pub struct RayDifferential {
pub rx_origin: Point3f,
pub ry_origin: Point3f,
pub rx_direction: Vector3f,
pub ry_direction: Vector3f,
}

View file

@ -1,183 +0,0 @@
use crate::core::pbrt::Float;
use crate::utils::interval::Interval;
use crate::utils::math::{next_float_down, next_float_up};
use num_integer::Roots;
use num_traits::{Float as NumFloat, FloatConst, Num, One, Signed, Zero};
use std::ops::{Add, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub};
pub trait Tuple<T, const N: usize>:
Sized + Copy + Index<usize, Output = T> + IndexMut<usize>
{
fn data(&self) -> &[T; N];
fn data_mut(&mut self) -> &mut [T; N];
fn from_array(arr: [T; N]) -> Self;
#[inline]
fn permute(&self, p: [usize; N]) -> Self
where
T: Copy,
{
let new_data = p.map(|index| self[index]);
Self::from_array(new_data)
}
fn max_component_value(&self) -> T
where
T: PartialOrd + Copy,
{
self.data()
.iter()
.copied()
.reduce(|a, b| if a > b { a } else { b })
.expect("Cannot get max component of a zero-length tuple")
}
fn min_component_value(&self) -> T
where
T: PartialOrd + Copy,
{
self.data()
.iter()
.copied()
.reduce(|a, b| if a < b { a } else { b })
.expect("Cannot get min component of a zero-length tuple")
}
fn max_component_index(&self) -> usize
where
T: PartialOrd,
{
self.data()
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
.map(|(index, _)| index)
.unwrap_or(0)
}
fn min_component_index(&self) -> usize
where
T: PartialOrd,
{
self.data()
.iter()
.enumerate()
.min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
.map(|(index, _)| index)
.unwrap_or(0)
}
}
pub trait VectorLike:
Sized
+ Copy
+ Add<Output = Self>
+ Sub<Output = Self>
+ Div<Self::Scalar, Output = Self>
+ Mul<Self::Scalar, Output = Self>
{
type Scalar: Copy + Zero + Add<Output = Self::Scalar> + Mul<Output = Self::Scalar> + Sqrt;
fn dot(self, rhs: Self) -> Self::Scalar;
fn norm_squared(self) -> Self::Scalar {
self.dot(self)
}
fn abs_dot(self, rhs: Self) -> Self::Scalar
where
Self::Scalar: Signed,
{
self.dot(rhs).abs()
}
fn gram_schmidt(self, rhs: Self) -> Self {
self - rhs * self.dot(rhs)
}
fn norm(&self) -> Self::Scalar {
self.norm_squared().sqrt()
}
fn normalize(self) -> Self
where
Self::Scalar: NumFloat,
{
let n = self.norm();
if n.is_zero() { self } else { self / n }
}
fn angle_between(self, rhs: Self) -> Self::Scalar
where
Self::Scalar: NumFloat,
{
let dot_product = self.normalize().dot(rhs.normalize());
let clamped_dot = dot_product
.min(Self::Scalar::one())
.max(-Self::Scalar::one());
clamped_dot.acos()
}
}
pub trait Sqrt {
fn sqrt(self) -> Self;
}
impl Sqrt for Float {
fn sqrt(self) -> Self {
self.sqrt()
}
}
impl Sqrt for f64 {
fn sqrt(self) -> Self {
self.sqrt()
}
}
impl Sqrt for i32 {
fn sqrt(self) -> Self {
self.isqrt()
}
}
impl Sqrt for u32 {
fn sqrt(self) -> Self {
self.isqrt()
}
}
impl Sqrt for Interval {
fn sqrt(self) -> Self {
let low = if self.low < 0.0 {
0.0
} else {
next_float_down(self.low.sqrt())
};
let high = if self.high < 0.0 {
0.0
} else {
next_float_up(self.high.sqrt())
};
Self { low, high }
}
}
pub trait Lerp<Factor = Float>: Sized + Copy {
fn lerp(t: Factor, a: Self, b: Self) -> Self;
}
impl<T, F, Diff> Lerp<F> for T
where
T: Copy + Sub<Output = Diff> + Add<Diff, Output = T>,
Diff: Mul<F, Output = Diff>,
F: Copy,
{
#[inline(always)]
fn lerp(t: F, a: Self, b: Self) -> Self {
a + (b - a) * t
}
}

View file

View file

@ -1,13 +1,2 @@
#![allow(unused_imports, dead_code)]
#![feature(float_erf)]
#![feature(f16)]
mod camera;
mod core;
mod geometry;
mod image;
mod integrators;
mod lights;
mod shapes;
mod spectra;
mod utils;
pub mod scene;
pub mod utils;

View file

@ -1,6 +1,10 @@
use super::{
DenselySampledSpectrum, LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait,
LightType, RGBIlluminantSpectrum, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait,
LightType, RGB, RGBColorSpace, RGBIlluminantSpectrum, SampledSpectrum, SampledWavelengths,
Spectrum, SpectrumTrait,
};
use crate::core::geometry::{
Bounds3f, Normal3f, Point2f, Point2fi, Point2i, Point3f, Point3fi, Ray, Vector3f, VectorLike,
};
use crate::core::interaction::{
Interaction, InteractionTrait, SimpleInteraction, SurfaceInteraction,
@ -11,15 +15,10 @@ use crate::core::texture::{
FloatTexture, FloatTextureTrait, TextureEvalContext, TextureEvaluator,
UniversalTextureEvaluator,
};
use crate::geometry::{
Bounds3f, Normal3f, Point2f, Point2fi, Point2i, Point3f, Point3fi, Ray, Vector3f, VectorLike,
};
use crate::image::Image;
use crate::shapes::{Shape, ShapeSample, ShapeSampleContext, ShapeTrait};
use crate::utils::color::RGB;
use crate::utils::colorspace::RGBColorSpace;
use crate::utils::hash::hash_float;
use crate::utils::transform::Transform;
use crate::utils::transform::TransformGeneric;
use std::sync::Arc;
@ -39,7 +38,7 @@ pub struct DiffuseAreaLight {
impl DiffuseAreaLight {
#[allow(clippy::too_many_arguments)]
pub fn new(
render_from_light: Transform<Float>,
render_from_light: TransformGeneric<Float>,
medium_interface: MediumInterface,
le: Spectrum,
scale: Float,

861
src/scene.rs Normal file
View file

@ -0,0 +1,861 @@
use crate::Float;
use crate::camera::{Camera, CameraTransform};
use crate::core::film::{Film, FilmTrait};
use crate::core::filter::Filter;
use crate::core::geometry::{Point3f, Vector3f};
use crate::core::medium::Medium;
use crate::core::options::RenderingCoordinateSystem;
use crate::core::sampler::Sampler;
use crate::core::texture::{FloatTexture, SpectrumTexture};
use crate::image::Image;
use crate::lights::Light;
use crate::spectra::RGBColorSpace;
use crate::utils::error::FileLoc;
use crate::utils::math::SquareMatrix;
use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector};
use crate::utils::parser::ParserTarget;
use crate::utils::transform::look_at;
use crate::utils::transform::{AnimatedTransform, TransformGeneric};
use crate::utils::{normalize_utf8, resolve_filename};
use parking_lot::Mutex;
use std::char::MAX;
use std::collections::{HashMap, HashSet};
use std::ops::{Index as IndexTrait, IndexMut as IndexMutTrait};
use std::sync::Arc;
use std::sync::atomic::{AtomicI32, Ordering};
use std::thread::{self, JoinHandle};
#[derive(Clone, Debug)]
pub enum MaterialRef {
Index(usize),
Name(String),
None,
}
#[derive(Clone, Default, Debug)]
pub struct SceneEntityBase {
pub name: String,
pub loc: FileLoc,
pub parameters: ParameterDictionary,
}
#[derive(Clone, Debug)]
pub struct TransformedSceneEntity {
pub base: SceneEntityBase,
pub render_from_object: AnimatedTransform,
}
pub type MediumSceneEntity = TransformedSceneEntity;
pub type TextureSceneEntity = TransformedSceneEntity;
#[derive(Clone, Debug)]
pub struct CameraSceneEntity {
pub base: SceneEntityBase,
pub camera_transform: CameraTransform,
pub medium: String,
}
#[derive(Clone, Debug)]
pub struct ShapeSceneEntity {
pub base: SceneEntityBase,
pub render_from_object: Arc<TransformGeneric<Float>>,
pub object_from_render: Arc<TransformGeneric<Float>>,
pub reverse_orientation: bool,
pub material: MaterialRef,
pub light_index: Option<usize>,
pub inside_medium: String,
pub outside_medium: String,
}
#[derive(Clone, Debug)]
pub struct AnimatedShapeSceneEntity {
pub transformed_base: TransformedSceneEntity,
pub identity: Arc<TransformGeneric<Float>>,
pub reverse_orientation: bool,
pub material: MaterialRef,
pub light_index: Option<usize>,
pub inside_medium: String,
pub outside_medium: String,
}
#[derive(Clone, Debug)]
pub struct InstanceDefinitionSceneEntity {
pub name: String,
pub loc: FileLoc,
pub shapes: Vec<ShapeSceneEntity>,
pub animated_shapes: Vec<AnimatedShapeSceneEntity>,
}
#[derive(Clone, Debug)]
pub struct LightSceneEntity {
pub transformed_base: TransformedSceneEntity,
pub medium: String,
}
#[derive(Clone, Debug)]
pub enum InstanceTransform {
Animated(AnimatedTransform),
Static(Arc<TransformGeneric<Float>>),
}
#[derive(Clone, Debug)]
pub struct InstanceSceneEntity {
pub name: String,
pub loc: FileLoc,
pub transform: InstanceTransform,
}
pub struct TextureData {
pub serial_float_textures: Vec<(String, TextureSceneEntity)>,
pub serial_spectrum_textures: Vec<(String, TextureSceneEntity)>,
pub async_spectrum_textures: Vec<(String, TextureSceneEntity)>,
pub loading_texture_filenames: HashSet<String>,
pub float_texture_jobs: HashMap<String, JoinHandle<Arc<FloatTexture>>>,
pub spectrum_texture_jobs: HashMap<String, JoinHandle<Arc<SpectrumTexture>>>,
pub n_missing_textures: i32,
}
pub struct NamedTextures {
pub float_textures: HashMap<String, Arc<FloatTexture>>,
pub albedo_spectrum_textures: HashMap<String, Arc<SpectrumTexture>>,
pub unbounded_spectrum_textures: HashMap<String, Arc<SpectrumTexture>>,
pub illuminant_spectrum_textures: HashMap<String, Arc<SpectrumTexture>>,
}
pub struct LightData {
pub light_jobs: Vec<JoinHandle<Light>>,
pub area_lights: Vec<SceneEntityBase>,
}
pub struct MaterialData {
pub named_materials: Vec<(String, SceneEntityBase)>,
pub materials: Vec<SceneEntityBase>,
pub normal_map_jobs: HashMap<String, JoinHandle<Arc<Image>>>,
pub normal_maps: HashMap<String, Arc<Image>>,
}
pub struct BasicScene {
pub integrator: Mutex<Option<SceneEntityBase>>,
pub accelerator: Mutex<Option<SceneEntityBase>>,
pub film_colorspace: Mutex<Option<Arc<RGBColorSpace>>>,
// Collections
pub shapes: Mutex<Vec<ShapeSceneEntity>>,
pub animated_shapes: Mutex<Vec<AnimatedShapeSceneEntity>>,
pub instances: Mutex<Vec<InstanceSceneEntity>>,
pub instance_definitions: Mutex<HashMap<String, Arc<InstanceDefinitionSceneEntity>>>,
// Simple mutexes in C++
pub medium_jobs: Mutex<HashMap<String, JoinHandle<Medium>>>,
pub media_map: Mutex<HashMap<String, Medium>>,
pub material_data: Mutex<MaterialData>,
pub light_data: Mutex<LightData>,
pub texture_data: Mutex<TextureData>,
// Top level
pub camera: Mutex<Option<Arc<Camera>>>,
pub film: Mutex<Option<Arc<Film>>>,
pub sampler: Mutex<Option<Arc<Sampler>>>,
pub camera_job: Mutex<Option<JoinHandle<Camera>>>,
pub sampler_job: Mutex<Option<JoinHandle<Sampler>>>,
}
impl BasicScene {
fn set_options(
self: &Arc<Self>,
filter: SceneEntityBase,
film: SceneEntityBase,
camera: CameraSceneEntity,
sampler: SceneEntityBase,
integ: SceneEntityBase,
accel: SceneEntityBase,
) {
*self.integrator.lock() = Some(integ);
*self.accelerator.lock() = Some(accel);
if let Some(cs) = film.parameters.color_space.as_ref() {
*self.film_colorspace.lock() = Some(Arc::clone(cs));
}
let filt = Filter::create(&filter.name, filter.parameters, &filter.loc);
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;
let film_instance = Arc::new(
Film::create(
&film.name,
&film.parameters,
exposure_time,
filt.expect("Must have a filter"),
Some(camera.camera_transform.clone()),
&film.loc,
)
.expect("Must have a film"),
);
*self.film.lock() = Some(Arc::clone(&film_instance));
let sampler_film = Arc::clone(&film_instance);
let sampler_job = std::thread::spawn(move || {
let res = sampler_film.as_ref().base().full_resolution;
Sampler::create(&sampler.name, &sampler.parameters, res, &sampler.loc)
.expect("Sampler was not correctly created")
});
*self.sampler_job.lock() = Some(sampler_job);
let camera_film = Arc::clone(&film_instance);
let scene_ptr = self.clone();
let camera_job = std::thread::spawn(move || {
let medium = scene_ptr.get_medium(&camera.medium, &camera.base.loc);
Camera::create(
&camera.base.name,
&camera.base.parameters,
medium.unwrap(),
&camera.camera_transform,
camera_film,
&camera.base.loc,
)
.expect("Failed to create camera")
});
*self.camera_job.lock() = Some(camera_job);
}
fn get_medium(&self, name: &str, loc: &FileLoc) -> Option<Medium> {
if name.is_empty() || name == "none" {
return None;
}
loop {
{
let media_map = self.media_map.lock();
if let Some(m) = media_map.get(name) {
return Some(m.clone());
}
let mut jobs = self.medium_jobs.lock();
if let Some(handle) = jobs.remove(name) {
drop(jobs);
drop(media_map);
log::info!("Waiting for medium '{}' to finish loading...", name);
let m = handle.join().expect("Medium loading thread panicked");
self.media_map.lock().insert(name.to_string(), m.clone());
return Some(m);
}
if !self.medium_jobs.lock().contains_key(name)
&& !self.media_map.lock().contains_key(name)
{
log::error!("[{:?}] {}: medium is not defined.", loc, name);
return None;
}
}
thread::yield_now();
}
}
fn add_float_texture(&self, name: &str, texture: &TextureSceneEntity) {
if texture.render_from_object.is_animated() {
log::info!(
"{}: Animated world to texture not supported, using start",
texture.base.loc
);
}
let mut texture_state = self.texture_data.lock();
if name != "imagemap" && name != "ptex" {
texture_state
.serial_float_textures
.push((name.to_string(), texture.clone()));
}
let filename = resolve_filename(&texture.base.parameters.get_one_string("filename", ""));
if filename.is_empty() {
log::error!(
"[{:?}] \"string filename\" not provided for image texture.",
texture.base.loc
);
texture_state.n_missing_textures += 1;
return;
}
if !std::fs::exists(&filename).expect("Not sure what to do here, just moving on") {
log::error!("[{:?}] {}: file not found.", texture.base.loc, filename);
texture_state.n_missing_textures += 1;
return;
}
if texture_state.loading_texture_filenames.contains(&filename) {
texture_state
.serial_float_textures
.push((name.to_string(), texture.clone()));
}
}
}
const MAX_TRANSFORMS: usize = 2;
#[derive(Debug, Default, Clone, Copy)]
struct TransformSet {
t: [TransformGeneric<Float>; MAX_TRANSFORMS],
}
impl TransformSet {
pub fn is_animated(&self) -> bool {
self.t[0] != self.t[1]
}
pub fn inverse(&self) -> Self {
Self {
t: [self.t[0].inverse(), self.t[1].inverse()],
}
}
pub fn map<F>(&mut self, bits: u32, f: F)
where
F: Fn(&TransformGeneric<Float>) -> TransformGeneric<Float>,
{
if (bits & 1) != 0 {
self.t[0] = f(&self.t[0]);
}
if (bits & 2) != 0 {
self.t[1] = f(&self.t[1]);
}
}
}
impl IndexTrait<usize> for TransformSet {
type Output = TransformGeneric<Float>;
fn index(&self, index: usize) -> &Self::Output {
&self.t[index]
}
}
impl IndexMutTrait<usize> for TransformSet {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.t[index]
}
}
#[derive(Default, Debug, Clone)]
struct GraphicsState {
current_inside_medium: String,
current_outside_medium: String,
current_material_name: String,
area_light_name: String,
area_light_params: ParsedParameterVector,
area_light_loc: FileLoc,
shape_attributes: ParsedParameterVector,
light_attributes: ParsedParameterVector,
material_attributes: ParsedParameterVector,
medium_attributes: ParsedParameterVector,
texture_attributes: ParsedParameterVector,
reverse_orientation: bool,
// None implies sRGB usually
color_space: Option<Arc<RGBColorSpace>>,
ctm: TransformSet,
// 1=Start, 2=End, 3=All
active_transform_bits: u32,
transform_start_time: Float,
transform_end_time: Float,
}
#[derive(PartialEq, Eq)]
enum BlockState {
OptionsBlock,
WorldBlock,
}
pub struct BasicSceneBuilder {
scene: Arc<BasicScene>,
current_block: BlockState,
graphics_state: GraphicsState,
pushed_graphics_states: Vec<GraphicsState>,
push_stack: Vec<(char, FileLoc)>,
render_from_world: TransformGeneric<Float>,
named_coordinate_systems: HashMap<String, TransformSet>,
// Object Instancing State
// If this is Some, we are inside an "ObjectBegin" block
active_instance_definition: Option<InstanceDefinitionSceneEntity>,
shapes_buffer: Vec<ShapeSceneEntity>,
instance_uses_buffer: Vec<InstanceSceneEntity>,
named_material_names: HashSet<String>,
medium_names: HashSet<String>,
current_camera: Option<CameraSceneEntity>,
current_film: Option<SceneEntityBase>,
current_integrator: Option<SceneEntityBase>,
current_sampler: Option<SceneEntityBase>,
current_filter: Option<SceneEntityBase>,
current_accelerator: Option<SceneEntityBase>,
}
impl BasicSceneBuilder {
pub const START_TRANSFORM_BITS: u32 = 1 << 0;
pub const END_TRANSFORM_BITS: u32 = 1 << 1;
pub const ALL_TRANSFORM_BITS: u32 = (1 << MAX_TRANSFORMS) - 1;
pub fn new(scene: Arc<BasicScene>) -> Self {
Self {
scene,
current_block: BlockState::OptionsBlock,
graphics_state: GraphicsState::default(),
pushed_graphics_states: Vec::new(),
push_stack: Vec::new(),
render_from_world: TransformGeneric::identity(),
named_coordinate_systems: HashMap::new(),
active_instance_definition: None,
shapes_buffer: Vec::new(),
instance_uses_buffer: Vec::new(),
named_material_names: HashSet::new(),
medium_names: HashSet::new(),
current_camera: Some(CameraSceneEntity {
base: SceneEntityBase {
name: "perspective".into(),
..Default::default()
},
camera_transform: CameraTransform::from_world(
AnimatedTransform::default(),
RenderingCoordinateSystem::World,
),
medium: String::new(),
}),
current_sampler: Some(SceneEntityBase {
name: "zsobol".into(),
..Default::default()
}),
current_filter: Some(SceneEntityBase {
name: "gaussian".into(),
..Default::default()
}),
current_integrator: Some(SceneEntityBase {
name: "volpath".into(),
..Default::default()
}),
current_accelerator: Some(SceneEntityBase {
name: "bvh".into(),
..Default::default()
}),
current_film: Some(SceneEntityBase {
name: "rgb".into(),
..Default::default()
}),
}
}
fn for_active_transforms<F>(&mut self, f: F)
where
F: Fn(&TransformGeneric<Float>) -> TransformGeneric<Float>,
{
let bits = self.graphics_state.active_transform_bits;
if (bits & 1) != 0 {
self.graphics_state.ctm.t[0] = f(&self.graphics_state.ctm.t[0]);
}
if (bits & 2) != 0 {
self.graphics_state.ctm.t[1] = f(&self.graphics_state.ctm.t[1]);
}
}
fn verify_world(&self, name: &str, loc: &FileLoc) {
if self.current_block != BlockState::WorldBlock {
eprintln!("Error: {} not allowed outside WorldBlock at {}", name, loc);
}
}
fn verify_options(&self, name: &str, loc: &FileLoc) {
if self.current_block != BlockState::OptionsBlock {
eprintln!("Error: {} not allowed inside WorldBlock at {}", name, loc);
}
}
}
impl ParserTarget for BasicSceneBuilder {
fn reverse_orientation(&mut self, loc: FileLoc) {
self.verify_world("ReverseOrientation", &loc);
self.graphics_state.reverse_orientation = !self.graphics_state.reverse_orientation;
}
fn color_space(&mut self, name: &str, loc: FileLoc) {
let _ = match RGBColorSpace::get_named(name) {
Ok(cs) => {
self.graphics_state.color_space = Some(cs);
}
Err(_) => {
eprintln!("Error: Color space '{}' unknown at {}", name, loc);
}
};
}
fn identity(&mut self, _loc: FileLoc) {
self.for_active_transforms(|_| TransformGeneric::identity());
}
fn translate(&mut self, dx: Float, dy: Float, dz: Float, _loc: FileLoc) {
let t = TransformGeneric::translate(Vector3f::new(dx, dy, dz));
self.for_active_transforms(|cur| cur * &t);
}
fn rotate(&mut self, angle: Float, ax: Float, ay: Float, az: Float, _loc: FileLoc) {
let t = TransformGeneric::rotate_around_axis(angle, Vector3f::new(ax, ay, az));
self.for_active_transforms(|cur| cur * &t);
}
fn scale(&mut self, sx: Float, sy: Float, sz: Float, _loc: FileLoc) {
let t = TransformGeneric::scale(sx, sy, sz);
self.for_active_transforms(|cur| cur * &t);
}
fn look_at(
&mut self,
ex: Float,
ey: Float,
ez: Float,
lx: Float,
ly: Float,
lz: Float,
ux: Float,
uy: Float,
uz: Float,
loc: FileLoc,
) {
let result = look_at((ex, ey, ez), (lx, ly, lz), (ux, uy, uz));
match result {
Ok(t) => {
self.for_active_transforms(|cur| cur * &t);
}
Err(e) => {
eprintln!("Error: {} at {}", e, loc);
}
}
}
fn concat_transform(&mut self, m: &[Float; 16], loc: FileLoc) {
let result = TransformGeneric::from_flat(m);
match result {
Ok(t) => {
self.for_active_transforms(|cur| cur * &t);
}
Err(e) => {
eprintln!("Error: {} at {}", e, loc);
}
}
}
fn transform(&mut self, m: &[Float; 16], loc: FileLoc) {
let result = TransformGeneric::from_flat(m);
match result {
Ok(t) => {
self.for_active_transforms(|_| t);
}
Err(e) => {
eprintln!("Error: {} at {}", e, loc);
}
}
}
fn coordinate_system(&mut self, name: &str, _loc: FileLoc) {
self.named_coordinate_systems
.insert(name.to_string(), self.graphics_state.ctm);
}
fn coord_sys_transform(&mut self, name: &str, loc: FileLoc) {
if let Some(saved_ctm) = self.named_coordinate_systems.get(name) {
self.graphics_state.ctm = *saved_ctm;
} else {
eprintln!(
"Warning: Couldn't find named coordinate system \"{}\" at {}",
name, loc
);
}
}
fn camera(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
self.verify_options("Camera", &loc);
let camera_from_world = self.graphics_state.ctm;
let world_from_camera = camera_from_world.inverse();
self.named_coordinate_systems
.insert("camera".to_string(), world_from_camera);
let animated_world_from_cam = AnimatedTransform::new(
&world_from_camera.t[0],
self.graphics_state.transform_start_time,
&world_from_camera.t[1],
self.graphics_state.transform_end_time,
);
let rendering_space = RenderingCoordinateSystem::CameraWorld;
let camera_transform =
CameraTransform::from_world(animated_world_from_cam, rendering_space);
self.render_from_world = camera_from_world.t[0];
let parameters =
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
self.current_camera = Some(CameraSceneEntity {
base: SceneEntityBase {
name: name.to_string(),
loc,
parameters,
},
camera_transform,
medium: self.graphics_state.current_outside_medium.clone(),
});
}
fn active_transform_all(&mut self, _loc: FileLoc) {
self.graphics_state.active_transform_bits = Self::ALL_TRANSFORM_BITS;
}
fn active_transform_end_time(&mut self, _loc: FileLoc) {
self.graphics_state.active_transform_bits = Self::END_TRANSFORM_BITS;
}
fn active_transform_start_time(&mut self, _loc: FileLoc) {
self.graphics_state.active_transform_bits = Self::START_TRANSFORM_BITS;
}
fn transform_times(&mut self, start: Float, end: Float, loc: FileLoc) {
self.verify_options("TransformTimes", &loc);
self.graphics_state.transform_start_time = start;
self.graphics_state.transform_end_time = end;
}
fn option(&mut self, _name: &str, _value: &str, _loc: FileLoc) {
todo!()
}
fn pixel_filter(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
let parameters =
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
self.verify_options("PixelFilter", &loc);
self.current_filter = Some(SceneEntityBase {
name: name.to_string(),
loc,
parameters,
});
}
fn film(&mut self, type_name: &str, params: &ParsedParameterVector, loc: FileLoc) {
let parameters =
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
self.verify_options("Film", &loc);
self.current_filter = Some(SceneEntityBase {
name: type_name.to_string(),
loc,
parameters,
});
}
fn accelerator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
let parameters =
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
self.verify_options("PixelFilter", &loc);
self.current_filter = Some(SceneEntityBase {
name: name.to_string(),
loc,
parameters,
});
}
fn integrator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
let parameters =
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
self.verify_options("PixelFilter", &loc);
self.current_filter = Some(SceneEntityBase {
name: name.to_string(),
loc,
parameters,
});
}
fn make_named_medium(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
let curr_name = normalize_utf8(name);
self.verify_world("MakeNamedMaterial", &loc);
if !self.named_material_names.insert(curr_name.to_string()) {
eprintln!("Error: {}: named material '{}' redefined.", loc, name);
return;
}
let parameters =
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
let entity = SceneEntityBase {
name: name.to_string(),
loc,
parameters,
};
self.scene
.named_materials
.lock()
.push((curr_name.to_string(), entity));
}
fn medium_interface(&mut self, inside_name: &str, outside_name: &str, _loc: FileLoc) {
let inside = normalize_utf8(inside_name);
let outside = normalize_utf8(outside_name);
self.graphics_state.current_inside_medium = inside;
self.graphics_state.current_outside_medium = outside;
}
fn sampler(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
let parameters =
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
self.verify_options("Sampler", &loc);
self.current_sampler = Some(SceneEntityBase {
name: name.to_string(),
loc,
parameters,
})
}
fn world_begin(&mut self, loc: FileLoc) {
self.verify_options("WorldBegin", &loc);
self.current_block = BlockState::WorldBlock;
for i in 0..MAX_TRANSFORMS {
self.graphics_state.ctm[i] = TransformGeneric::<Float>::default();
}
self.graphics_state.active_transform_bits = Self::ALL_TRANSFORM_BITS;
self.named_coordinate_systems
.insert("world".to_string(), self.graphics_state.ctm);
let scene = Arc::clone(&self.scene);
scene.set_options(
self.current_filter
.take()
.expect("Filter not set before WorldBegin"),
self.current_film
.take()
.expect("Film not set before WorldBegin"),
self.current_camera
.take()
.expect("Camera not set before WorldBegin"),
self.current_sampler
.take()
.expect("Sampler not set before WorldBegin"),
self.current_integrator
.take()
.expect("Integrator not set before WorldBegin"),
self.current_accelerator
.take()
.expect("Accelerator not set before WorldBegin"),
);
}
fn attribute_begin(&mut self, loc: FileLoc) {
self.verify_world("AttributeBegin", &loc);
self.pushed_graphics_states
.push(self.graphics_state.clone());
self.push_stack.push(('a', loc));
}
fn attribute_end(&mut self, loc: FileLoc) {
self.verify_world("AttributeEnd", &loc);
if self.pushed_graphics_states.is_empty() {
log::error!(
"[{:?}] Unmatched AttributeEnd encountered. Ignoring it.",
loc
);
return;
}
if let Some(state) = self.pushed_graphics_states.pop() {
self.graphics_state = state;
}
if let Some((kind, start_loc)) = self.push_stack.pop() {
if kind == 'o' {
log::error!(
"[{:?}] Mismatched nesting: open ObjectBegin from {} at AttributeEnd",
loc,
start_loc
);
std::process::exit(1);
} else {
debug_assert_eq!(kind, 'a', "Expected AttributeBegin on the stack");
}
}
}
fn attribute(&mut self, target: &str, params: ParsedParameterVector, loc: FileLoc) {
let current_attributes = match target {
"shape" => &mut self.graphics_state.shape_attributes,
"light" => &mut self.graphics_state.light_attributes,
"material" => &mut self.graphics_state.material_attributes,
"medium" => &mut self.graphics_state.medium_attributes,
"texture" => &mut self.graphics_state.texture_attributes,
_ => {
log::error!(
"[{:?}] Unknown attribute target \"{}\". Must be \"shape\", \"light\", \
\"material\", \"medium\", or \"texture\".",
loc,
target
);
return;
}
};
let active_color_space = self.graphics_state.color_space.clone();
for mut p in params {
p.may_be_unused = true;
p.color_space = active_color_space.clone();
current_attributes.push(p.clone());
}
}
fn texture(
&mut self,
_name: &str,
_type_name: &str,
_tex_name: &str,
_params: &ParsedParameterVector,
loc: FileLoc,
) {
self.verify_world("Texture", &loc);
}
fn material(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) {
todo!()
}
fn make_named_material(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) {
todo!()
}
fn named_material(&mut self, _name: &str, _loc: FileLoc) {
todo!()
}
fn light_source(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) {
todo!()
}
fn area_light_source(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) {
todo!()
}
fn shape(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) {
todo!()
}
fn object_begin(&mut self, _name: &str, _loc: FileLoc) {
todo!()
}
fn object_end(&mut self, _loc: FileLoc) {
todo!()
}
fn object_instance(&mut self, _name: &str, _loc: FileLoc) {
todo!()
}
fn end_of_files(&mut self) {
todo!()
}
}

View file

@ -1,33 +0,0 @@
use super::Spectrum;
use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
use super::simple::{DenselySampledSpectrum, PiecewiseLinearSpectrum};
use crate::core::cie;
use crate::core::pbrt::Float;
use once_cell::sync::Lazy;
fn create_cie_spectrum(data: &[Float]) -> Spectrum {
let pls = PiecewiseLinearSpectrum::from_interleaved(data);
let dss = DenselySampledSpectrum::from_spectrum(
&Spectrum::PiecewiseLinear(pls),
LAMBDA_MIN,
LAMBDA_MAX,
);
Spectrum::DenselySampled(dss)
}
pub(crate) fn cie_x() -> &'static Spectrum {
static X: Lazy<Spectrum> = Lazy::new(|| create_cie_spectrum(&cie::CIE_X));
&X
}
pub(crate) fn cie_y() -> &'static Spectrum {
static Y: Lazy<Spectrum> = Lazy::new(|| create_cie_spectrum(&cie::CIE_Y));
&Y
}
pub(crate) fn cie_z() -> &'static Spectrum {
static Z: Lazy<Spectrum> = Lazy::new(|| create_cie_spectrum(&cie::CIE_Z));
&Z
}

View file

@ -1,272 +0,0 @@
use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
use crate::core::pbrt::Float;
use crate::spectra::{
N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait,
};
use std::hash::{Hash, Hasher};
#[derive(Debug, Clone, Copy)]
pub struct ConstantSpectrum {
c: Float,
}
impl ConstantSpectrum {
pub fn new(c: Float) -> Self {
Self { c }
}
}
impl SpectrumTrait for ConstantSpectrum {
fn evaluate(&self, _lambda: Float) -> Float {
self.c
}
fn max_value(&self) -> Float {
self.c
}
}
#[derive(Debug, Clone)]
pub struct DenselySampledSpectrum {
lambda_min: i32,
lambda_max: i32,
values: Vec<Float>,
}
impl DenselySampledSpectrum {
pub fn new(lambda_min: i32, lambda_max: i32) -> Self {
let n_values = (lambda_max - lambda_min + 1).max(0) as usize;
Self {
lambda_min,
lambda_max,
values: vec![0.0; n_values],
}
}
pub fn from_spectrum(spec: &Spectrum, lambda_min: i32, lambda_max: i32) -> Self {
let mut s = Self::new(lambda_min, lambda_max);
if s.values.is_empty() {
return s;
}
for lambda in lambda_min..=lambda_max {
let index = (lambda - lambda_min) as usize;
s.values[index] = spec.evaluate(lambda as Float);
}
s
}
pub fn from_function<F>(f: F, lambda_min: i32, lambda_max: i32) -> Self
where
F: Fn(Float) -> Float,
{
let mut s = Self::new(lambda_min, lambda_max);
if s.values.is_empty() {
return s;
}
for lambda in lambda_min..=lambda_max {
let index = (lambda - lambda_min) as usize;
s.values[index] = f(lambda as Float);
}
s
}
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut s = SampledSpectrum::default();
for i in 0..N_SPECTRUM_SAMPLES {
let offset = lambda[i].round() as usize - LAMBDA_MIN as usize;
if offset >= self.values.len() {
s[i] = 0.;
} else {
s[i] = self.values[offset];
}
}
s
}
pub fn min_component_value(&self) -> Float {
self.values.iter().fold(Float::INFINITY, |a, &b| a.min(b))
}
pub fn max_component_value(&self) -> Float {
self.values
.iter()
.fold(Float::NEG_INFINITY, |a, &b| a.max(b))
}
pub fn average(&self) -> Float {
self.values.iter().sum::<Float>() / (N_SPECTRUM_SAMPLES as Float)
}
pub fn safe_div(&self, rhs: SampledSpectrum) -> Self {
let mut r = Self::new(1, 1);
for i in 0..N_SPECTRUM_SAMPLES {
r.values[i] = if rhs[i] != 0.0 {
self.values[i] / rhs.values[i]
} else {
0.0
}
}
r
}
pub fn scale(&mut self, factor: Float) {
for v in &mut self.values {
*v *= factor;
}
}
}
impl PartialEq for DenselySampledSpectrum {
fn eq(&self, other: &Self) -> bool {
if self.lambda_min != other.lambda_min
|| self.lambda_max != other.lambda_max
|| self.values.len() != other.values.len()
{
return false;
}
self.values
.iter()
.zip(&other.values)
.all(|(a, b)| a.to_bits() == b.to_bits())
}
}
impl Eq for DenselySampledSpectrum {}
impl Hash for DenselySampledSpectrum {
fn hash<H: Hasher>(&self, state: &mut H) {
self.lambda_min.hash(state);
self.lambda_max.hash(state);
for v in &self.values {
v.to_bits().hash(state);
}
}
}
impl SpectrumTrait for DenselySampledSpectrum {
fn evaluate(&self, lambda: Float) -> Float {
let offset = (lambda.round() as i32) - self.lambda_min;
if offset < 0 || offset as usize >= self.values.len() {
0.0
} else {
self.values[offset as usize]
}
}
fn max_value(&self) -> Float {
self.values.iter().fold(Float::MIN, |a, b| a.max(*b))
}
}
#[derive(Debug, Clone)]
pub struct PiecewiseLinearSpectrum {
samples: Vec<(Float, Float)>,
}
impl PiecewiseLinearSpectrum {
pub fn from_interleaved(data: &[Float]) -> Self {
assert!(
data.len().is_multiple_of(2),
"Interleaved data must have an even number of elements"
);
let mut samples = Vec::new();
for pair in data.chunks(2) {
samples.push((pair[0], pair[1]));
}
samples.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
Self { samples }
}
}
impl SpectrumTrait for PiecewiseLinearSpectrum {
fn evaluate(&self, lambda: Float) -> Float {
if self.samples.is_empty() {
return 0.0;
}
// Handle boundary conditions
if lambda <= self.samples[0].0 {
return self.samples[0].1;
}
if lambda >= self.samples.last().unwrap().0 {
return self.samples.last().unwrap().1;
}
let i = self.samples.partition_point(|s| s.0 < lambda);
let s1 = self.samples[i - 1];
let s2 = self.samples[i];
let t = (lambda - s1.0) / (s2.0 - s1.0);
(1.0 - t) * s1.1 + t * s2.1
}
fn max_value(&self) -> Float {
if self.samples.is_empty() {
return 0.0;
}
self.samples
.iter()
.map(|(_, value)| *value)
.fold(0.0, |a, b| a.max(b))
}
}
#[derive(Debug, Clone, Copy)]
pub struct BlackbodySpectrum {
temperature: Float,
normalization_factor: Float,
}
// Planck's Law
impl BlackbodySpectrum {
const C: Float = 299792458.0;
const H: Float = 6.62606957e-34;
const KB: Float = 1.3806488e-23;
pub fn new(temperature: Float) -> Self {
// Physical constants
let lambda_max = 2.8977721e-3 / temperature * 1e9;
let max_val = Self::planck_law(lambda_max, temperature);
Self {
temperature,
normalization_factor: if max_val > 0.0 { 1.0 / max_val } else { 0.0 },
}
}
fn planck_law(lambda_nm: Float, temp: Float) -> Float {
if temp <= 0.0 {
return 0.0;
}
let lambda_m = lambda_nm * 1e-9;
let c1 = 2.0 * Self::H * Self::C * Self::C;
let c2 = (Self::H * Self::C) / Self::KB;
let numerator = c1 / lambda_m.powi(5);
let denominator = (c2 / (lambda_m * temp)).exp() - 1.0;
if denominator.is_infinite() {
0.0
} else {
numerator / denominator
}
}
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
SampledSpectrum::from_fn(|i| {
Self::planck_law(lambda[i], self.temperature) * self.normalization_factor
})
}
}
impl SpectrumTrait for BlackbodySpectrum {
fn evaluate(&self, lambda: Float) -> Float {
Self::planck_law(lambda, self.temperature) * self.normalization_factor
}
fn max_value(&self) -> Float {
let lambda_max = 2.8977721e-3 / self.temperature * 1e9;
Self::planck_law(lambda_max, self.temperature)
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,107 +0,0 @@
use super::color::{RGB, RGBSigmoidPolynomial, RGBToSpectrumTable, XYZ};
use super::math::SquareMatrix;
use crate::core::pbrt::Float;
use crate::geometry::Point2f;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, Spectrum};
use once_cell::sync::Lazy;
use std::cmp::{Eq, PartialEq};
use std::error::Error;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct RGBColorSpace {
pub r: Point2f,
pub g: Point2f,
pub b: Point2f,
pub w: Point2f,
pub illuminant: Spectrum,
pub rgb_to_spectrum_table: Arc<RGBToSpectrumTable>,
pub xyz_from_rgb: SquareMatrix<Float, 3>,
pub rgb_from_xyz: SquareMatrix<Float, 3>,
}
impl RGBColorSpace {
pub fn new(
r: Point2f,
g: Point2f,
b: Point2f,
illuminant: Spectrum,
rgb_to_spectrum_table: RGBToSpectrumTable,
) -> Result<Self, Box<dyn Error>> {
let w_xyz: XYZ = illuminant.to_xyz();
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()],
[r_xyz.z(), g_xyz.z(), g_xyz.z()],
];
let rgb = SquareMatrix::new(rgb_values);
let c: RGB = rgb.inverse()? * w_xyz;
let xyz_from_rgb = rgb * SquareMatrix::diag(&[c.r, c.g, c.b]);
let rgb_from_xyz = xyz_from_rgb
.inverse()
.expect("XYZ from RGB matrix is singular");
Ok(Self {
r,
g,
b,
w,
illuminant,
rgb_to_spectrum_table: Arc::new(rgb_to_spectrum_table),
xyz_from_rgb,
rgb_from_xyz,
})
}
pub fn to_xyz(&self, rgb: RGB) -> XYZ {
self.xyz_from_rgb * rgb
}
pub fn to_rgb(&self, xyz: XYZ) -> RGB {
self.rgb_from_xyz * xyz
}
pub fn to_rgb_coeffs(&self, rgb: RGB) -> RGBSigmoidPolynomial {
self.rgb_to_spectrum_table.to_polynomial(rgb)
}
pub fn convert_colorspace(&self, other: &RGBColorSpace) -> SquareMatrix<Float, 3> {
if self == other {
return SquareMatrix::default();
}
self.rgb_from_xyz * other.xyz_from_rgb
}
pub fn srgb() -> &'static Self {
static SRGB_SPACE: Lazy<RGBColorSpace> = Lazy::new(|| {
let r = Point2f::new(0.64, 0.33);
let g = Point2f::new(0.30, 0.60);
let b = Point2f::new(0.15, 0.06);
let illuminant = Spectrum::std_illuminant_d65();
let table = RGBToSpectrumTable::srgb();
RGBColorSpace::new(r, g, b, illuminant, table)
.expect("Failed to initialize standard sRGB color space")
});
&SRGB_SPACE
}
}
impl PartialEq for RGBColorSpace {
fn eq(&self, other: &Self) -> bool {
self.r == other.r
&& self.g == other.g
&& self.b == other.b
&& self.w == other.w
&& Arc::ptr_eq(&self.rgb_to_spectrum_table, &other.rgb_to_spectrum_table)
}
}

View file

@ -1,39 +1,36 @@
use image_rs::{ImageError as IError, error};
use std::fmt;
use thiserror::Error;
use crate::image::PixelFormat;
#[derive(Error, Debug)]
pub enum LlsError {
SingularMatrix,
#[derive(Clone, Debug)]
pub struct FileLoc {
pub filename: Arc<str>,
pub line: i32,
pub column: i32,
}
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum InversionError {
SingularMatrix,
EmptyMatrix,
}
impl fmt::Display for LlsError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
LlsError::SingularMatrix => write!(f, "Matrix is singular and cannot be inverted."),
impl Default for FileLoc {
fn default() -> Self {
Self {
filename: Arc::from(""),
line: 1,
column: 0,
}
}
}
impl fmt::Display for InversionError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
InversionError::SingularMatrix => {
write!(f, "Matrix is singular and cannot be inverted.")
}
InversionError::EmptyMatrix => write!(f, "Matrix is empty and cannot be inverted."),
impl FileLoc {
pub fn new(filename: &str) -> Self {
Self {
filename: Arc::from(filename),
line: 1,
column: 0,
}
}
}
impl fmt::Display for FileLoc {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}:{}", self.filename, self.line, self.column)
}
}
#[derive(Error, Debug)]
pub enum ImageError {
#[error("I/O error: {0}")]

72
src/utils/file.rs Normal file
View file

@ -0,0 +1,72 @@
use pbrt::Float;
use std::fs;
use std::io::{self, BufReader, Error, ErrorKind, Read};
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
static SEARCH_DIRECTORY: OnceLock<PathBuf> = OnceLock::new();
fn set_search_directory(filename: &str) {
let path = Path::new(filename);
let dir = if path.is_dir() {
path.to_path_buf()
} else {
path.parent().unwrap_or(Path::new("")).to_path_buf()
};
let _ = SEARCH_DIRECTORY.set(dir);
}
pub fn read_float_file(filename: &str) -> io::Result<Vec<Float>> {
let content = fs::read_to_string(filename)?;
let mut values = Vec::new();
for (line_num, line) in content.lines().enumerate() {
// Strip comments
let cleaned = line.split('#').next().unwrap_or("");
for token in cleaned.split_whitespace() {
match token.parse::<Float>() {
Ok(val) => values.push(val),
Err(e) => {
return Err(Error::new(
ErrorKind::InvalidData,
format!(
"Failed to parse float '{}' at line {}: {}",
token,
line_num + 1,
e
),
));
}
}
}
}
Ok(values)
}
pub fn resolve_filename(filename: &str) -> String {
let search_directory = SEARCH_DIRECTORY
.get()
.map(|p| p.as_path())
.unwrap_or(Path::new(""));
if search_directory.as_os_str().is_empty()
|| filename.is_empty()
|| Path::new(filename).is_absolute()
{
return filename.to_string();
}
let filepath = search_directory.join(filename);
if filepath.exists() {
filepath
.canonicalize()
.unwrap_or_else(|_| filepath.to_path_buf())
.to_string_lossy()
.to_string()
} else {
filename.to_string()
}
}

View file

@ -1,19 +1,29 @@
use crate::core::pbrt::Float;
use crate::image::{
Image, ImageAndMetadata, ImageMetadata, PixelData, PixelFormat, Point2i, WrapMode,
};
use crate::utils::color::{ColorEncoding, LINEAR, SRGB};
use crate::spectra::color::{ColorEncoding, LINEAR, SRGB};
use crate::utils::error::ImageError;
use anyhow::{Context, Result, bail};
use exr::prelude::{read_first_rgba_layer_from_file, write_rgba_file};
use image_rs::ImageReader;
use image_rs::{DynamicImage, ImageBuffer, Rgb, Rgba};
use pbrt::Float;
use pbrt::image::{
Image, ImageAndMetadata, ImageMetadata, PixelData, PixelFormat, Point2i, WrapMode,
};
use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Read, Write};
use std::path::Path;
impl Image {
pub fn read(path: &Path, encoding: Option<ColorEncoding>) -> Result<ImageAndMetadata> {
pub trait ImageIO {
fn read(path: &Path, encoding: Option<ColorEncoding>) -> Result<ImageAndMetadata>;
fn write(&self, filename: &str, metadata: &ImageMetadata) -> Result<()>;
fn write_png(&self, path: &Path) -> Result<()>;
fn write_exr(&self, path: &Path, metadata: &ImageMetadata) -> Result<()>;
fn write_qoi(&self, path: &Path) -> Result<()>;
fn write_pfm(&self, path: &Path) -> Result<()>;
fn to_u8_buffer(&self) -> Vec<u8>;
}
impl ImageIO for Image {
fn read(path: &Path, encoding: Option<ColorEncoding>) -> Result<ImageAndMetadata> {
let ext = path
.extension()
.and_then(|s| s.to_str())
@ -27,7 +37,7 @@ impl Image {
}
}
pub fn write(&self, filename: &str, metadata: &ImageMetadata) -> Result<(), ImageError> {
fn write(&self, filename: &str, metadata: &ImageMetadata) -> Result<(), ImageError> {
let path = Path::new(filename);
let ext = path.extension().and_then(|s| s.to_str()).unwrap_or("");
let res = match ext.to_lowercase().as_str() {

View file

@ -1,9 +1,8 @@
use crate::core::pbrt::{Float, lerp};
use crate::geometry::{Lerp, Point2f, Point2i, Vector2f, VectorLike};
use crate::core::geometry::{Lerp, Point2f, Point2i, Vector2f, VectorLike};
use crate::core::pbrt::Float;
use crate::image::{Image, ImageAndMetadata, PixelData, PixelFormat, WrapMode, WrapMode2D};
use crate::utils::color::{ColorEncoding, RGB};
use crate::utils::colorspace::RGBColorSpace;
use crate::utils::math::{safe_sqrt, square};
use crate::spectra::{RGB, RGBColorSpace, color::ColorEncoding};
use crate::utils::math::{lerp, safe_sqrt, square};
use std::path::Path;
use std::hash::{Hash, Hasher};

Some files were not shown because too many files have changed in this diff Show more