Compare commits

...

17 commits
main ... arena

Author SHA1 Message Date
a32cd60e9f Fixing film creation, arena allocation separation from texture creation, bad function signatures 2026-01-26 19:44:53 +00:00
640e17110a Continuing cleanup of scene creation, texture ownership, and memory management 2026-01-25 18:15:48 +00:00
64a139d108 Continuing on the quest to fix bugs. Never again 2026-01-23 20:30:55 +00:00
b6b17a8c7e Missing SpectrumTextureTrait implementations, placeholders for now. Continuing fixing errors 2026-01-23 15:23:01 +00:00
14418472d5 Some more refactoring, more error correction. When will this end? 2026-01-22 16:20:28 +00:00
380b1c9f90 Some more refactoring, more error correction. When will this end? 2026-01-22 16:18:26 +00:00
93bcd465eb Fixing errors in CPU code 2026-01-22 14:18:57 +00:00
1e21cc64f9 Unused parameters, lifetime issues in sampling 2026-01-20 01:01:19 +00:00
9a8ec13728 Finally fixed import errors due to refactoring, gotta get a better IDE 2026-01-19 23:52:12 +00:00
86c9a90f2e Fixes 2026-01-18 16:29:27 +00:00
3b3f9eb155 Working on fixing import errors, standardizing CPU/GPU container types 2026-01-18 16:25:56 +00:00
c412b6d668 Updated light creation, sampling types in shared code 2026-01-16 15:42:51 +00:00
f94c5d78c7 Implementing arena based memory allocation 2026-01-10 00:20:43 +00:00
0ef563d1a5 Implementing arena based memory allocation 2026-01-10 00:16:24 +00:00
f7c47be077 Some more refactoring 2026-01-01 09:45:00 +00:00
cc557dfa50 Forgot to actually implement FilterTrait 2025-12-30 18:19:17 +00:00
75655ed774 Continuing GPU migration, implemented new parallelization strategies, correcting some logic bugs in maths functions 2025-12-30 18:09:30 +00:00
218 changed files with 17730 additions and 11843 deletions

6
.gitignore vendored
View file

@ -1,7 +1,11 @@
/target target/
*.lock *.lock
*.log *.log
*.bak *.bak
flip.rs flip.rs
.vscode .vscode
rust-analyzer.json rust-analyzer.json
data/
src/gpu/
src/tests/
tests/

View file

@ -6,13 +6,14 @@ edition = "2024"
[features] [features]
default = [] default = []
use_f64 = [] use_f64 = []
cuda = ["cust", "cuda_builder", "shared/cuda", ] use_gpu = []
use_nvtx = []
cuda = ["dep:cudarc"]
[dependencies] [dependencies]
anyhow = "1.0.100" anyhow = "1.0.100"
exr = "1.73.0" exr = "1.73.0"
flate2 = "1.1.5" flate2 = "1.1.5"
gpu = "0.2.3"
half = "2.7.1" half = "2.7.1"
image_rs = { package = "image", version = "0.25.8" } image_rs = { package = "image", version = "0.25.8" }
indicatif = "0.18.3" indicatif = "0.18.3"
@ -30,20 +31,31 @@ unicode-normalization = "0.1.25"
wgpu = "27.0.1" wgpu = "27.0.1"
shared = { path = "shared" } shared = { path = "shared" }
kernels = { path = "kernels" } ptex-filter = { path = "crates/ptex-filter" }
# kernels = { path = "kernels" }
cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true } 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 } cust = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, features = ["glam"], optional = true }
ptex = "0.3.0" ptex = "0.3.0"
ptex-sys = "0.3.0" # ptex-sys = "0.3.0"
slice = "0.0.4" slice = "0.0.4"
crossbeam-channel = "0.5.15"
num_cpus = "1.17.0"
ply-rs = "0.1.3"
enum_dispatch = "0.3.13"
bytemuck = "1.24.0"
once_cell = "1.21.3"
smallvec = "1.15.1"
cuda-runtime-sys = "0.3.0-alpha.1"
cudarc = { version = "0.18.2", features = ["cuda-13000"], optional = true }
[build-dependencies] [build-dependencies]
spirv-builder = { git = "https://github.com/rust-gpu/rust-gpu", branch = "main", optional = true } 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 } cuda_builder = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", optional = true }
cc = "1.2.53"
[workspace] [workspace]
members = ["kernels", "shared"] members = ["shared", "crates/ptex-filter"]
[lints.clippy] [lints.clippy]
excessive_precision = "allow" excessive_precision = "allow"

43
build.rs Normal file
View file

@ -0,0 +1,43 @@
use std::process::Command;
fn main() {
println!("cargo:rerun-if-changed=kernels/");
if std::env::var("CARGO_FEATURE_CUDA").is_ok() {
compile_kernels();
}
}
fn compile_kernels() {
let out_dir = std::env::var("OUT_DIR").unwrap();
let kernels = ["test_kernels"];
for name in kernels {
let src = format!("kernels/{}.cu", name);
let dst = format!("{}/{}.ptx", out_dir, name);
println!("cargo:rerun-if-changed={}", src);
let status = Command::new("nvcc")
.args([
"-ptx",
"-o",
&dst,
&src,
"--gpu-architecture=sm_75", // Adjust for your GPU
"-O3",
"--use_fast_math",
])
.status()
.expect("Failed to run nvcc");
if !status.success() {
panic!("nvcc failed on {}", src);
}
println!("cargo:warning=Compiled {} -> {}", src, dst);
}
println!("cargo:rustc-env=KERNEL_PTX_DIR={}", out_dir);
}

View file

@ -0,0 +1,11 @@
[package]
name = "ptex-filter"
version = "0.1.0"
edition = "2021"
[build-dependencies]
cc = "1.0"
pkg-config = "0.3"
[dependencies]
ptex = "0.3.0"

View file

@ -0,0 +1,17 @@
fn main() {
println!("cargo:rerun-if-changed=cpp/ptex_filter_wrapper.cpp");
let home = std::env::var("HOME").unwrap();
let ptex_include = format!("{}/.local/include", home);
let ptex_lib = format!("{}/.local/lib", home);
cc::Build::new()
.cpp(true)
.file("cpp/ptex_filter_wrapper.cpp")
.include(&ptex_include)
.flag("-std=c++17")
.compile("ptex_filter_wrapper");
println!("cargo:rustc-link-search=native={}", ptex_lib);
println!("cargo:rustc-link-lib=Ptex");
}

View file

@ -0,0 +1,65 @@
#include "ptex_filter_wrapper.h"
#include <Ptexture.h>
extern "C" {
PtexFilterHandle ptex_filter_create(PtexTextureHandle texture, const PtexFilterOptions* opts) {
Ptex::PtexTexture* tex = static_cast<Ptex::PtexTexture*>(texture);
if (!tex || !opts) return nullptr;
Ptex::PtexFilter::Options ptex_opts;
ptex_opts.filter = static_cast<Ptex::PtexFilter::FilterType>(opts->filter);
ptex_opts.lerp = opts->lerp;
ptex_opts.sharpness = opts->sharpness;
ptex_opts.noedgeblend = opts->noedgeblend != 0;
return Ptex::PtexFilter::getFilter(tex, ptex_opts);
}
void ptex_filter_eval(
PtexFilterHandle filter,
float* result,
int32_t first_channel,
int32_t num_channels,
int32_t face_id,
float u, float v,
float dudx, float dvdx,
float dudy, float dvdy
) {
Ptex::PtexFilter* f = static_cast<Ptex::PtexFilter*>(filter);
if (f && result) {
f->eval(result, first_channel, num_channels, face_id, u, v, dudx, dvdx, dudy, dvdy);
}
}
void ptex_filter_release(PtexFilterHandle filter) {
Ptex::PtexFilter* f = static_cast<Ptex::PtexFilter*>(filter);
if (f) {
f->release();
}
}
PtexTextureHandle ptex_texture_open(const char* filename, char** error_str) {
Ptex::String error;
Ptex::PtexTexture* tex = Ptex::PtexTexture::open(filename, error);
if (!tex && error_str) {
*error_str = strdup(error.c_str());
}
return tex;
}
void ptex_texture_release(PtexTextureHandle texture) {
Ptex::PtexTexture* tex = static_cast<Ptex::PtexTexture*>(texture);
if (tex) {
tex->release();
}
}
int32_t ptex_texture_num_channels(PtexTextureHandle texture) {
Ptex::PtexTexture* tex = static_cast<Ptex::PtexTexture*>(texture);
return tex ? tex->numChannels() : 0;
}
}

View file

@ -0,0 +1,51 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
typedef void* PtexTextureHandle;
typedef void* PtexFilterHandle;
typedef enum {
PTEX_FILTER_POINT = 0,
PTEX_FILTER_BILINEAR = 1,
PTEX_FILTER_BOX = 2,
PTEX_FILTER_GAUSSIAN = 3,
PTEX_FILTER_BICUBIC = 4,
PTEX_FILTER_BSPLINE = 5,
PTEX_FILTER_CATMULLROM = 6,
PTEX_FILTER_MITCHELL = 7
} PtexFilterType;
typedef struct {
PtexFilterType filter;
int32_t lerp;
float sharpness;
int32_t noedgeblend;
} PtexFilterOptions;
PtexTextureHandle ptex_texture_open(const char* filename, char** error_str);
void ptex_filter_release(PtexFilterHandle filter);
int32_t ptex_texture_num_channels(PtexTextureHandle texture);
PtexFilterHandle ptex_filter_create(PtexTextureHandle texture, const PtexFilterOptions* opts);
void ptex_filter_eval(
PtexFilterHandle filter,
float* result,
int32_t first_channel,
int32_t num_channels,
int32_t face_id,
float u, float v,
float dudx, float dvdx,
float dudy, float dvdy
);
void ptex_filter_release(PtexFilterHandle filter);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,60 @@
use std::ffi::c_void;
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PtexFilterType {
Point = 0,
Bilinear = 1,
Box = 2,
Gaussian = 3,
Bicubic = 4,
BSpline = 5,
CatmullRom = 6,
Mitchell = 7,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct PtexFilterOptions {
pub filter: PtexFilterType,
pub lerp: i32,
pub sharpness: f32,
pub noedgeblend: i32,
}
impl Default for PtexFilterOptions {
fn default() -> Self {
Self {
filter: PtexFilterType::BSpline,
lerp: 1,
sharpness: 0.0,
noedgeblend: 0,
}
}
}
extern "C" {
pub fn ptex_filter_create(texture: *mut c_void, opts: *const PtexFilterOptions) -> *mut c_void;
pub fn ptex_filter_eval(
filter: *mut c_void,
result: *mut f32,
first_channel: i32,
num_channels: i32,
face_id: i32,
u: f32,
v: f32,
dudx: f32,
dvdx: f32,
dudy: f32,
dvdy: f32,
);
pub fn ptex_filter_release(filter: *mut c_void);
pub fn ptex_texture_open(
filename: *const std::ffi::c_char,
error_str: *mut *mut std::ffi::c_char,
) -> *mut c_void;
pub fn ptex_texture_release(texture: *mut c_void);
pub fn ptex_texture_num_channels(texture: *mut c_void) -> i32;
}

View file

@ -0,0 +1,63 @@
mod ffi;
pub use ffi::{ptex_filter_create, PtexFilterOptions, PtexFilterType};
use std::ffi::c_void;
use std::marker::PhantomData;
use std::ptr::NonNull;
pub struct PtexFilter {
handle: NonNull<c_void>,
_marker: PhantomData<*mut ()>,
}
impl PtexFilter {
/// Creates a new Ptex filter pointer
///
/// # Safety
pub unsafe fn new(texture_ptr: *mut c_void, opts: &PtexFilterOptions) -> Option<Self> {
let handle = ffi::ptex_filter_create(texture_ptr, opts);
NonNull::new(handle).map(|h| Self {
handle: h,
_marker: PhantomData,
})
}
pub fn eval(
&self,
face_id: i32,
u: f32,
v: f32,
dudx: f32,
dvdx: f32,
dudy: f32,
dvdy: f32,
num_channels: i32,
) -> [f32; 4] {
let mut result = [0.0f32; 4];
unsafe {
ffi::ptex_filter_eval(
self.handle.as_ptr(),
result.as_mut_ptr(),
0,
num_channels.min(4),
face_id,
u,
v,
dudx,
dvdx,
dudy,
dvdy,
);
}
result
}
}
impl Drop for PtexFilter {
fn drop(&mut self) {
unsafe {
ffi::ptex_filter_release(self.handle.as_ptr());
}
}
}

View file

@ -1,447 +1,30 @@
#![cfg_attr(target_arch = "nvptx64", no_std)]
#![cfg_attr(target_arch = "nvptx64", feature(abi_ptx))]
use cuda_std::prelude::*; use cuda_std::prelude::*;
pub mod wavefront; /// Scales each element: data[i] *= scale
pub mod workitem; #[kernel]
#[allow(improper_ctypes_definitions)]
use cust::context::{CacheConfig, CurrentContext, ResourceLimit}; pub unsafe fn scale_array(data: *mut f32, len: u32, scale: f32) {
use cust::device::DeviceAttribute; let idx = thread::index_1d() as u32;
use cust::memory::{DeviceCopy, DeviceMemory}; if idx >= len {
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; return;
} }
if self.stop.synchronize().is_ok() { let ptr = unsafe { data.add(idx as usize) };
// Check timing between start and stop *ptr = *ptr * scale;
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 --- /// Adds two arrays: c[i] = a[i] + b[i]
#[kernel]
struct Profiler { #[allow(improper_ctypes_definitions)]
kernel_stats: Vec<Arc<Mutex<KernelStats>>>, pub unsafe fn add_arrays(a: *const f32, b: *const f32, c: *mut f32, len: u32) {
event_pool: Vec<ProfilerEvent>, let idx = thread::index_1d() as u32;
pool_offset: usize, if idx >= len {
}
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; return;
} }
let device_id = get_options().gpu_device.unwrap_or(0); let i = idx as usize;
log::info!("Initializing GPU Device {}", device_id); *c.add(i) = *a.add(i) + *b.add(i);
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

@ -1,47 +0,0 @@
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!()
}
}

View file

@ -1,535 +0,0 @@
#![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;
}
}
}
}

27
kernels/test_kernels.cu Normal file
View file

@ -0,0 +1,27 @@
extern "C" __global__ void scale_array(float* data, unsigned int len, float scale) {
unsigned int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx >= len) return;
data[idx] *= scale;
}
extern "C" __global__ void add_arrays(
const float* __restrict__ a,
const float* __restrict__ b,
float* __restrict__ c,
unsigned int len
) {
unsigned int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx >= len) return;
c[idx] = a[idx] + b[idx];
}
extern "C" __global__ void saxpy(
float a,
const float* __restrict__ x,
float* __restrict__ y,
unsigned int len
) {
unsigned int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx >= len) return;
y[idx] = a * x[idx] + y[idx];
}

View file

@ -15,6 +15,9 @@ num-traits = "0.2.19"
once_cell = "1.21.3" once_cell = "1.21.3"
smallvec = "1.15.1" smallvec = "1.15.1"
cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true } cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true }
half = "2.7.1"
rand = "0.9.2"
anyhow = "1.0.100"
[features] [features]
use_f64 = [] use_f64 = []

5
shared/build.rs Normal file
View file

@ -0,0 +1,5 @@
fn main() {
// This allows "spirv" to be used in #[cfg(target_arch = "...")]
// without triggering a warning.
println!("cargo:rustc-check-cfg=cfg(target_arch, values(\"spirv\"))");
}

468
shared/src/bxdfs/complex.rs Normal file
View file

@ -0,0 +1,468 @@
use crate::core::bsdf::{BSDF, BSDFSample};
use crate::core::bxdf::{BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode};
use crate::core::color::RGB;
use crate::core::geometry::{
Normal3f, Point2f, Vector3f, abs_cos_theta, cos_theta, same_hemisphere,
};
use crate::core::scattering::{
TrowbridgeReitzDistribution, fr_complex_from_spectrum, fr_dielectric, fresnel_moment1, reflect,
refract,
};
use crate::spectra::{DeviceStandardColorSpaces, RGBUnboundedSpectrum, SampledSpectrum};
use crate::utils::math::{
clamp, fast_exp, i0, lerp, log_i0, radians, safe_acos, safe_asin, safe_sqrt, sample_discrete,
square, trimmed_logistic,
};
use crate::utils::sampling::{
cosine_hemisphere_pdf, sample_cosine_hemisphere, sample_trimmed_logistic,
};
use crate::{Float, INV_2_PI, INV_PI, PI};
use core::any::Any;
static P_MAX: usize = 3;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct HairBxDF {
pub h: Float,
pub eta: Float,
pub sigma_a: SampledSpectrum,
pub beta_m: Float,
pub beta_n: Float,
pub v: [Float; P_MAX + 1],
pub s: Float,
pub sin_2k_alpha: [Float; P_MAX],
pub cos_2k_alpha: [Float; P_MAX],
pub colorspaces: DeviceStandardColorSpaces,
}
impl HairBxDF {
pub fn new(
h: Float,
eta: Float,
sigma_a: SampledSpectrum,
beta_m: Float,
beta_n: Float,
alpha: Float,
colorspaces: DeviceStandardColorSpaces,
) -> Self {
let mut sin_2k_alpha = [0.; P_MAX];
let mut cos_2k_alpha = [0.; P_MAX];
sin_2k_alpha[0] = radians(alpha).sin();
cos_2k_alpha[0] = safe_sqrt(1. - square(sin_2k_alpha[0]));
for i in 0..P_MAX {
sin_2k_alpha[i] = 2. * cos_2k_alpha[i - 1] * sin_2k_alpha[i - 1];
cos_2k_alpha[i] = square(cos_2k_alpha[i - 1]) - square(sin_2k_alpha[i - 1]);
}
Self {
h,
eta,
sigma_a,
beta_m,
beta_n,
v: [0.; P_MAX + 1],
s: 0.,
sin_2k_alpha,
cos_2k_alpha,
colorspaces,
}
}
fn ap(
cos_theta_o: Float,
eta: Float,
h: Float,
t: SampledSpectrum,
) -> [SampledSpectrum; P_MAX + 1] {
let cos_gamma_o = safe_sqrt(1. - square(h));
let cos_theta = cos_theta_o * cos_gamma_o;
let f = fr_dielectric(cos_theta, eta);
let ap0 = SampledSpectrum::new(f);
let ap1 = t * (1.0 - f).powi(2);
let tf = t * f;
std::array::from_fn(|p| match p {
0 => ap0,
1 => ap1,
_ if p < P_MAX => ap1 * tf.pow_int(p - 1),
_ => ap1 * tf.pow_int(p - 1) / (SampledSpectrum::new(1.0) - tf),
})
}
fn mp(
cos_theta_i: Float,
cos_theta_o: Float,
sin_theta_i: Float,
sin_theta_o: Float,
v: Float,
) -> Float {
let a = cos_theta_i * cos_theta_o / v;
let b = sin_theta_i * sin_theta_o / v;
if v <= 0.1 {
fast_exp(log_i0(a) - b - 1. / v + 0.6931 + (1. / (2. * v).ln()))
} else {
fast_exp(-b) * i0(a) / ((1. / v).sinh() * 2. * v)
}
}
fn np(phi: Float, p: i32, s: Float, gamma_o: Float, gamma_t: Float) -> Float {
let mut dphi = phi - Self::phi(p, gamma_o, gamma_t);
while dphi > PI {
dphi -= 2. * PI;
}
while dphi < -PI {
dphi += 2. * PI;
}
trimmed_logistic(dphi, s, -PI, PI)
}
fn phi(p: i32, gamma_o: Float, gamma_t: Float) -> Float {
2. * p as Float * gamma_t - 2. * gamma_o + p as Float * PI
}
fn ap_pdf(&self, cos_theta_o: Float) -> [Float; P_MAX + 1] {
let sin_theta_o = safe_sqrt(1. - square(cos_theta_o));
let sin_theta_t = sin_theta_o / self.eta;
let cos_theta_t = safe_sqrt(1. - square(sin_theta_t));
let etap = safe_sqrt(square(self.eta) - square(sin_theta_o)) / cos_theta_o;
let sin_gamma_t = self.h / etap;
let cos_gamma_t = safe_sqrt(1. - square(sin_gamma_t));
// let gamma_t = safe_asin(sin_gamma_t);
let t_value = -self.sigma_a * (2. * cos_gamma_t / cos_theta_t);
let t = t_value.exp();
let ap = Self::ap(cos_theta_o, self.eta, self.h, t);
let sum_y: Float = ap.iter().map(|s| s.average()).sum();
std::array::from_fn(|i| ap[i].average() / sum_y)
}
pub fn sigma_a_from_concentration(
ce: Float,
cp: Float,
stdcs: DeviceStandardColorSpaces,
) -> RGBUnboundedSpectrum {
let eumelanin_sigma_a = RGB::new(0.419, 0.697, 1.37);
let pheomelanin_sigma_a = RGB::new(0.187, 0.4, 1.05);
let sigma_a = ce * eumelanin_sigma_a + cp * pheomelanin_sigma_a;
RGBUnboundedSpectrum::new(&stdcs.srgb, sigma_a)
}
}
impl BxDFTrait for HairBxDF {
fn flags(&self) -> BxDFFlags {
BxDFFlags::GLOSSY_REFLECTION
}
fn f(&self, wo: Vector3f, wi: Vector3f, _mode: TransportMode) -> SampledSpectrum {
// Compute hair coordinate system terms related to wo
let sin_theta_o = wo.x();
let cos_theta_o = safe_sqrt(1. - square(sin_theta_o));
let phi_o = wo.z().atan2(wo.y());
let gamma_o = safe_asin(self.h);
// Compute hair coordinate system terms related to wi
let sin_theta_i = wi.x();
let cos_theta_i = safe_sqrt(1. - square(sin_theta_i));
let phi_i = wi.z().atan2(wi.y());
let sin_theta_t = sin_theta_o / self.eta;
let cos_theta_t = safe_sqrt(1. - square(sin_theta_t));
let etap = safe_sqrt(square(self.eta) - square(sin_theta_o)) / cos_theta_o;
let sin_gamma_t = self.h / etap;
let cos_gamma_t = safe_sqrt(1. - square(sin_gamma_t));
let gamma_t = safe_asin(sin_gamma_t);
let sampled_value = -self.sigma_a * (2. * cos_gamma_t / cos_theta_t);
let t = sampled_value.exp();
let phi = phi_i - phi_o;
let ap_pdf = Self::ap(cos_theta_o, self.eta, self.h, t);
let mut f_sum = SampledSpectrum::new(0.);
for (p, &ap) in ap_pdf.iter().enumerate().take(P_MAX) {
let (sin_thetap_o, cos_thetap_o) = match p {
0 => (
sin_theta_o * self.cos_2k_alpha[1] - cos_theta_o * self.sin_2k_alpha[1],
cos_theta_o * self.cos_2k_alpha[1] + sin_theta_o * self.sin_2k_alpha[1],
),
1 => (
sin_theta_o * self.cos_2k_alpha[0] + cos_theta_o * self.sin_2k_alpha[0],
cos_theta_o * self.cos_2k_alpha[0] - sin_theta_o * self.sin_2k_alpha[0],
),
2 => (
sin_theta_o * self.cos_2k_alpha[2] + cos_theta_o * self.sin_2k_alpha[2],
cos_theta_o * self.cos_2k_alpha[2] - sin_theta_o * self.sin_2k_alpha[2],
),
_ => (sin_theta_o, cos_theta_o),
};
f_sum += Self::mp(
cos_theta_i,
cos_thetap_o,
sin_theta_i,
sin_thetap_o,
self.v[p],
) * ap
* Self::np(phi, p as i32, self.s, gamma_o, gamma_t);
}
if abs_cos_theta(wi) > 0. {
f_sum /= abs_cos_theta(wi);
}
f_sum
}
fn sample_f(
&self,
wo: Vector3f,
mut uc: Float,
u: Point2f,
f_args: FArgs,
) -> Option<BSDFSample> {
let sin_theta_o = wo.x();
let cos_theta_o = safe_sqrt(1. - square(sin_theta_o));
let phi_o = wo.z().atan2(wo.y());
let gamma_o = safe_asin(self.h);
// Determine which term to sample for hair scattering
let ap_pdf = self.ap_pdf(cos_theta_o);
let p = sample_discrete(&ap_pdf, uc, None, Some(&mut uc));
let (sin_thetap_o, mut cos_thetap_o) = match p {
0 => (
sin_theta_o * self.cos_2k_alpha[1] - cos_theta_o * self.sin_2k_alpha[1],
cos_theta_o * self.cos_2k_alpha[1] + sin_theta_o * self.sin_2k_alpha[1],
),
1 => (
sin_theta_o * self.cos_2k_alpha[0] + cos_theta_o * self.sin_2k_alpha[0],
cos_theta_o * self.cos_2k_alpha[0] - sin_theta_o * self.sin_2k_alpha[0],
),
2 => (
sin_theta_o * self.cos_2k_alpha[2] + cos_theta_o * self.sin_2k_alpha[2],
cos_theta_o * self.cos_2k_alpha[2] - sin_theta_o * self.sin_2k_alpha[2],
),
_ => (sin_theta_o, cos_theta_o),
};
cos_thetap_o = cos_thetap_o.abs();
let cos_theta =
1. + self.v[p] * (u[0].max(1e-5) + (1. - u[0]) * fast_exp(-2. / self.v[p])).ln();
let sin_theta = safe_sqrt(1. - square(cos_theta));
let cos_phi = (2. * PI * u[1]).cos();
let sin_theta_i = -cos_theta * sin_thetap_o + sin_theta * cos_phi * cos_thetap_o;
let cos_theta_i = safe_sqrt(1. - square(sin_theta_i));
let etap = safe_sqrt(square(self.eta) - square(sin_theta_o)) / cos_theta_o;
let sin_gamma_t = self.h / etap;
// let cos_gamma_t = safe_sqrt(1. - square(sin_gamma_t));
let gamma_t = safe_asin(sin_gamma_t);
let dphi = if p < P_MAX {
Self::phi(p as i32, gamma_o, gamma_t) + sample_trimmed_logistic(uc, self.s, -PI, PI)
} else {
2. * PI * uc
};
let phi_i = phi_o + dphi;
let wi = Vector3f::new(
sin_theta_i,
cos_theta_i * phi_i.cos(),
cos_theta_i * phi_i.sin(),
);
let mut pdf = 0.;
for (p, &ap) in ap_pdf.iter().enumerate().take(P_MAX) {
let (sin_thetap_o, cos_thetap_o_raw) = match p {
0 => (
sin_theta_o * self.cos_2k_alpha[1] - cos_theta_o * self.sin_2k_alpha[1],
cos_theta_o * self.cos_2k_alpha[1] + sin_theta_o * self.sin_2k_alpha[1],
),
1 => (
sin_theta_o * self.cos_2k_alpha[0] + cos_theta_o * self.sin_2k_alpha[0],
cos_theta_o * self.cos_2k_alpha[0] - sin_theta_o * self.sin_2k_alpha[0],
),
2 => (
sin_theta_o * self.cos_2k_alpha[2] + cos_theta_o * self.sin_2k_alpha[2],
cos_theta_o * self.cos_2k_alpha[2] - sin_theta_o * self.sin_2k_alpha[2],
),
_ => (sin_theta_o, cos_theta_o),
};
let cos_thetap_o = cos_thetap_o_raw.abs();
pdf += Self::mp(
cos_theta_i,
cos_thetap_o,
sin_theta_i,
sin_thetap_o,
self.v[p],
) * ap
* Self::np(dphi, p as i32, self.s, gamma_o, gamma_t);
}
pdf += Self::mp(
cos_theta_i,
cos_theta_o,
sin_theta_i,
sin_theta_o,
self.v[P_MAX],
) * ap_pdf[P_MAX]
* INV_2_PI;
let bsd = BSDFSample {
f: self.f(wo, wi, f_args.mode),
wi,
pdf,
flags: self.flags(),
..Default::default()
};
Some(bsd)
}
fn pdf(&self, wo: Vector3f, wi: Vector3f, _f_args: FArgs) -> Float {
let sin_theta_o = wo.x();
let cos_theta_o = safe_sqrt(1. - square(sin_theta_o));
let phi_o = wo.z().atan2(wo.y());
let gamma_o = safe_asin(self.h);
// Determine which term to sample for hair scattering
let sin_theta_i = wi.x();
let cos_theta_i = safe_sqrt(1. - square(sin_theta_i));
let phi_i = wi.z().atan2(wi.y());
// Compute $\gammat$ for refracted ray
let etap = safe_sqrt(self.eta * self.eta - square(sin_theta_o)) / cos_theta_o;
let sin_gamma_t = self.h / etap;
let gamma_t = safe_asin(sin_gamma_t);
// Compute PDF for $A_p$ terms
let ap_pdf = self.ap_pdf(cos_theta_o);
let phi = phi_i - phi_o;
let mut pdf = 0.;
for (p, &ap) in ap_pdf.iter().enumerate().take(P_MAX) {
let (sin_thetap_o, raw_cos_thetap_o) = match p {
0 => (
sin_theta_o * self.cos_2k_alpha[1] - cos_theta_o * self.sin_2k_alpha[1],
cos_theta_o * self.cos_2k_alpha[1] + sin_theta_o * self.sin_2k_alpha[1],
),
1 => (
sin_theta_o * self.cos_2k_alpha[0] + cos_theta_o * self.sin_2k_alpha[0],
cos_theta_o * self.cos_2k_alpha[0] - sin_theta_o * self.sin_2k_alpha[0],
),
2 => (
sin_theta_o * self.cos_2k_alpha[2] + cos_theta_o * self.sin_2k_alpha[2],
cos_theta_o * self.cos_2k_alpha[2] - sin_theta_o * self.sin_2k_alpha[2],
),
_ => (sin_theta_o, cos_theta_o),
};
let cos_thetap_o = raw_cos_thetap_o.abs();
pdf += Self::mp(
cos_theta_i,
cos_thetap_o,
sin_theta_i,
sin_thetap_o,
self.v[p],
) * ap
* Self::np(phi, p as i32, self.s, gamma_o, gamma_t);
}
pdf += Self::mp(
cos_theta_i,
cos_theta_o,
sin_theta_i,
sin_theta_o,
self.v[P_MAX],
) * ap_pdf[P_MAX]
* INV_2_PI;
pdf
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct NormalizedFresnelBxDF {
pub eta: Float,
}
impl BxDFTrait for NormalizedFresnelBxDF {
fn f(&self, wo: Vector3f, wi: Vector3f, mode: TransportMode) -> SampledSpectrum {
if !same_hemisphere(wo, wi) {
return SampledSpectrum::new(0.);
}
let c = 1. - 2. * fresnel_moment1(1. / self.eta);
let mut f = SampledSpectrum::new((1. - fr_dielectric(cos_theta(wi), self.eta)) / (c * PI));
if mode == TransportMode::Radiance {
f /= square(self.eta)
}
f
}
fn sample_f(&self, wo: Vector3f, _uc: Float, u: Point2f, f_args: FArgs) -> Option<BSDFSample> {
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
return None;
}
let mut wi = sample_cosine_hemisphere(u);
if wo.z() < 0. {
wi[2] *= -1.;
}
Some(BSDFSample {
f: self.f(wo, wi, f_args.mode),
wi,
pdf: self.pdf(wo, wi, f_args),
flags: BxDFFlags::DIFFUSE_REFLECTION,
..Default::default()
})
}
fn pdf(&self, wo: Vector3f, wi: Vector3f, f_args: FArgs) -> Float {
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
return 0.;
}
if !same_hemisphere(wo, wi) {
return 0.;
}
abs_cos_theta(wi) * INV_PI
}
fn flags(&self) -> BxDFFlags {
BxDFFlags::REFLECTION | BxDFFlags::DIFFUSE
}
fn regularize(&mut self) {}
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Debug)]
pub struct EmptyBxDF;
impl BxDFTrait for EmptyBxDF {
fn f(&self, _wo: Vector3f, _wi: Vector3f, _mode: TransportMode) -> SampledSpectrum {
SampledSpectrum::default()
}
fn sample_f(
&self,
_wo: Vector3f,
_u: Float,
_u2: Point2f,
_f_args: FArgs,
) -> Option<BSDFSample> {
None
}
fn pdf(&self, _wo: Vector3f, _wi: Vector3f, _f_args: FArgs) -> Float {
0.0
}
fn flags(&self) -> BxDFFlags {
BxDFFlags::UNSET
}
fn regularize(&mut self) {}
fn as_any(&self) -> &dyn Any {
self
}
}

View file

@ -0,0 +1,153 @@
use crate::core::bsdf::BSDFSample;
use crate::core::bxdf::{BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode};
use crate::core::geometry::{
Normal3f, Point2f, Vector3f, VectorLike, abs_cos_theta, same_hemisphere,
};
use crate::core::scattering::{TrowbridgeReitzDistribution, fr_complex_from_spectrum, reflect};
use crate::spectra::SampledSpectrum;
use crate::utils::sampling::{cosine_hemisphere_pdf, sample_cosine_hemisphere};
use crate::{Float, INV_PI};
use core::any::Any;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ConductorBxDF {
pub mf_distrib: TrowbridgeReitzDistribution,
pub eta: SampledSpectrum,
pub k: SampledSpectrum,
}
unsafe impl Send for ConductorBxDF {}
unsafe impl Sync for ConductorBxDF {}
impl ConductorBxDF {
pub fn new(
mf_distrib: &TrowbridgeReitzDistribution,
eta: SampledSpectrum,
k: SampledSpectrum,
) -> Self {
Self {
mf_distrib: *mf_distrib,
eta,
k,
}
}
}
impl BxDFTrait for ConductorBxDF {
fn flags(&self) -> BxDFFlags {
if self.mf_distrib.effectively_smooth() {
BxDFFlags::SPECULAR_REFLECTION
} else {
BxDFFlags::GLOSSY_REFLECTION
}
}
fn sample_f(&self, wo: Vector3f, _uc: Float, u: Point2f, f_args: FArgs) -> Option<BSDFSample> {
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
return None;
}
if self.mf_distrib.effectively_smooth() {
let wi = Vector3f::new(-wo.x(), -wo.y(), wo.z());
let f =
fr_complex_from_spectrum(abs_cos_theta(wi), self.eta, self.k) / abs_cos_theta(wi);
let bsdf = BSDFSample {
f,
wi,
pdf: 1.,
flags: BxDFFlags::SPECULAR_REFLECTION,
..Default::default()
};
return Some(bsdf);
}
if wo.z() == 0. {
return None;
}
let wm = self.mf_distrib.sample_wm(wo, u);
let wi = reflect(wo, wm.into());
if !same_hemisphere(wo, wi) {
return None;
}
let pdf = self.mf_distrib.pdf(wo, wm) / (4. * wo.dot(wm).abs());
let cos_theta_o = abs_cos_theta(wo);
let cos_theta_i = abs_cos_theta(wi);
if cos_theta_i == 0. || cos_theta_o == 0. {
return None;
}
let f_spectrum = fr_complex_from_spectrum(wo.dot(wi).abs(), self.eta, self.k);
let f = self.mf_distrib.d(wm) * f_spectrum * self.mf_distrib.g(wo, wi)
/ (4. * cos_theta_i * cos_theta_o);
let bsdf = BSDFSample {
f,
wi,
pdf,
flags: BxDFFlags::GLOSSY_REFLECTION,
..Default::default()
};
Some(bsdf)
}
fn f(&self, wo: Vector3f, wi: Vector3f, _mode: TransportMode) -> SampledSpectrum {
if !same_hemisphere(wo, wi) {
return SampledSpectrum::default();
}
if self.mf_distrib.effectively_smooth() {
return SampledSpectrum::default();
}
let cos_theta_o = abs_cos_theta(wo);
let cos_theta_i = abs_cos_theta(wi);
if cos_theta_i == 0. || cos_theta_o == 0. {
return SampledSpectrum::new(0.);
}
let wm = wi + wo;
if wm.norm_squared() == 0. {
return SampledSpectrum::new(0.);
}
let wm_norm = wm.normalize();
let f_spectrum = fr_complex_from_spectrum(wo.dot(wm).abs(), self.eta, self.k);
self.mf_distrib.d(wm_norm) * f_spectrum * self.mf_distrib.g(wo, wi)
/ (4. * cos_theta_i * cos_theta_o)
}
fn pdf(&self, wo: Vector3f, wi: Vector3f, f_args: FArgs) -> Float {
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
return 0.;
}
if !same_hemisphere(wo, wi) {
return 0.;
}
if self.mf_distrib.effectively_smooth() {
return 0.;
}
let wm = wo + wi;
if wm.norm_squared() == 0. {
return 0.;
}
let wm_corr = Normal3f::new(0., 0., 1.).face_forward(wm);
self.mf_distrib.pdf(wo, wm_corr.into()) / (4. * wo.dot(wm).abs())
}
fn regularize(&mut self) {
self.mf_distrib.regularize();
}
fn as_any(&self) -> &dyn Any {
self
}
}

View file

@ -0,0 +1,370 @@
use crate::core::bsdf::BSDFSample;
use crate::core::bxdf::{BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode};
use crate::core::geometry::{
Normal3f, Point2f, Vector3f, VectorLike, abs_cos_theta, cos_theta, same_hemisphere,
};
use crate::core::scattering::{
TrowbridgeReitzDistribution, fr_complex_from_spectrum, fr_dielectric, reflect, refract,
};
use crate::spectra::SampledSpectrum;
use crate::utils::math::square;
use crate::utils::sampling::{cosine_hemisphere_pdf, sample_cosine_hemisphere};
use crate::{Float, INV_PI};
use core::any::Any;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct DielectricBxDF {
pub eta: Float,
pub mf_distrib: TrowbridgeReitzDistribution,
}
impl DielectricBxDF {
pub fn new(eta: Float, mf_distrib: TrowbridgeReitzDistribution) -> Self {
Self { eta, mf_distrib }
}
}
impl BxDFTrait for DielectricBxDF {
fn flags(&self) -> BxDFFlags {
let flags = if self.eta == 1. {
BxDFFlags::TRANSMISSION
} else {
BxDFFlags::REFLECTION | BxDFFlags::TRANSMISSION
};
flags
| if self.mf_distrib.effectively_smooth() {
BxDFFlags::SPECULAR
} else {
BxDFFlags::GLOSSY
}
}
fn f(&self, wo: Vector3f, wi: Vector3f, mode: TransportMode) -> SampledSpectrum {
if self.eta == 1. || self.mf_distrib.effectively_smooth() {
return SampledSpectrum::new(0.);
}
// Generalized half vector wm
let cos_theta_o = cos_theta(wo);
let cos_theta_i = cos_theta(wi);
let reflect = cos_theta_i * cos_theta_o > 0.;
let mut etap = 1.;
if !reflect {
etap = if cos_theta_o > 0. {
self.eta
} else {
1. / self.eta
};
}
let wm_orig = wi * etap + wo;
if cos_theta_i == 0. || cos_theta_o == 0. || wm_orig.norm_squared() == 0. {
return SampledSpectrum::new(0.);
}
let wm = Normal3f::new(0., 0., 1.).face_forward(wm_orig.normalize());
if wi.dot(wm.into()) * cos_theta_i < 0. || wo.dot(wm.into()) * cos_theta_o < 0. {
return SampledSpectrum::new(0.);
}
let fr = fr_dielectric(wo.dot(wm.into()), self.eta);
if reflect {
SampledSpectrum::new(
self.mf_distrib.d(wm.into()) * self.mf_distrib.g(wo, wi) * fr
/ (4. * cos_theta_i * cos_theta_o).abs(),
)
} else {
let denom =
square(wi.dot(wm.into()) + wo.dot(wm.into()) / etap) * cos_theta_i * cos_theta_o;
let mut ft = self.mf_distrib.d(wm.into())
* (1. - fr)
* self.mf_distrib.g(wo, wi)
* (wi.dot(wm.into()) * wo.dot(wm.into()) / denom).abs();
if mode == TransportMode::Radiance {
ft /= square(etap)
}
SampledSpectrum::new(ft)
}
}
fn pdf(&self, wo: Vector3f, wi: Vector3f, f_args: FArgs) -> Float {
if self.eta == 1. || self.mf_distrib.effectively_smooth() {
return 0.;
}
let cos_theta_o = cos_theta(wo);
let cos_theta_i = cos_theta(wi);
let reflect = cos_theta_i * cos_theta_o > 0.;
let mut etap = 1.;
if !reflect {
etap = if cos_theta_o > 0. {
self.eta
} else {
1. / self.eta
};
}
let wm_orig = wi * etap + wo;
if cos_theta_i == 0. || cos_theta_o == 0. || wm_orig.norm_squared() == 0. {
return 0.;
}
let wm = Normal3f::new(0., 0., 1.).face_forward(wm_orig.normalize());
// Discard backfacing microfacets
if wi.dot(wm.into()) * cos_theta_i < 0. || wo.dot(wm.into()) * cos_theta_o < 0. {
return 0.;
}
let r = fr_dielectric(wo.dot(wm.into()), self.eta);
let t = 1. - r;
let mut pr = r;
let mut pt = t;
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
let transmission_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::TRANSMISSION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
pr = 0.;
}
if !f_args.sample_flags.contains(transmission_flags) {
pt = 0.;
}
if pr == 0. && pt == 0. {
return 0.;
}
if reflect {
self.mf_distrib.pdf(
wo,
Vector3f::from(wm) / (4. * wo.dot(wm.into()).abs()) * pr / (pt + pr),
)
} else {
let denom = square(wi.dot(wm.into()) + wo.dot(wm.into()) / etap);
let dwm_dwi = wi.dot(wm.into()).abs() / denom;
self.mf_distrib.pdf(wo, wm.into()) * dwm_dwi * pr / (pr + pt)
}
}
fn sample_f(&self, wo: Vector3f, uc: Float, u: Point2f, f_args: FArgs) -> Option<BSDFSample> {
if self.eta == 1. || self.mf_distrib.effectively_smooth() {
let r = fr_dielectric(cos_theta(wo), self.eta);
let t = 1. - r;
let mut pr = r;
let mut pt = t;
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
let transmission_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::TRANSMISSION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
pr = 0.;
}
if !f_args.sample_flags.contains(transmission_flags) {
pt = 0.;
}
// If probabilities are null, doesnt contribute
if pr == 0. && pt == 0. {
return None;
}
if uc < pr / (pr + pt) {
let wi = Vector3f::new(-wo.x(), -wo.y(), wo.z());
let fr = SampledSpectrum::new(r / abs_cos_theta(wi));
let bsdf = BSDFSample {
f: fr,
wi,
pdf: pr / (pr + pt),
flags: BxDFFlags::SPECULAR_REFLECTION,
..Default::default()
};
Some(bsdf)
} else {
// Compute ray direction for specular transmission
if let Some((wi, etap)) = refract(wo, Normal3f::new(0., 0., 1.), self.eta) {
let mut ft = SampledSpectrum::new(t / abs_cos_theta(wi));
if f_args.mode == TransportMode::Radiance {
ft /= square(etap);
}
let bsdf = BSDFSample {
f: ft,
wi,
pdf: pt / (pr + pt),
flags: BxDFFlags::SPECULAR_TRANSMISSION,
eta: etap,
..Default::default()
};
Some(bsdf)
} else {
None
}
}
} else {
// Sample rough dielectric BSDF
let wm = self.mf_distrib.sample_wm(wo, u);
let r = fr_dielectric(wo.dot(wm), self.eta);
let t = 1. - r;
let mut pr = r;
let mut pt = t;
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
let transmission_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::TRANSMISSION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
pr = 0.;
}
if !f_args.sample_flags.contains(transmission_flags) {
pt = 0.;
}
if pr == 0. && pt == 0. {
return None;
}
let pdf: Float;
if uc < pr / (pr + pt) {
// Sample reflection at rough dielectric interface
let wi = reflect(wo, wm.into());
if !same_hemisphere(wo, wi) {
return None;
}
pdf = self.mf_distrib.pdf(wo, wm) / (4. * wo.dot(wm).abs()) * pr / (pr + pt);
let f = SampledSpectrum::new(
self.mf_distrib.d(wm) * self.mf_distrib.g(wo, wi) * r
/ (4. * cos_theta(wi) * cos_theta(wo)),
);
let bsdf = BSDFSample {
f,
wi,
pdf,
flags: BxDFFlags::GLOSSY_REFLECTION,
..Default::default()
};
Some(bsdf)
} else {
// Sample transmission at rough dielectric interface
if let Some((wi, etap)) = refract(wo, wm.into(), self.eta) {
if same_hemisphere(wo, wi) || wi.z() == 0. {
None
} else {
let denom = square(wi.dot(wm) + wo.dot(wm) / etap);
let dwm_mi = wi.dot(wm).abs() / denom;
pdf = self.mf_distrib.pdf(wo, wm) * dwm_mi * pt / (pr + pt);
let mut ft = SampledSpectrum::new(
t * self.mf_distrib.d(wm)
* self.mf_distrib.g(wo, wi)
* (wi.dot(wm) * wo.dot(wm)).abs()
/ (cos_theta(wi) * cos_theta(wo) * denom),
);
if f_args.mode == TransportMode::Radiance {
ft /= square(etap);
}
let bsdf = BSDFSample {
f: ft,
wi,
pdf,
flags: BxDFFlags::GLOSSY_TRANSMISSION,
eta: etap,
..Default::default()
};
Some(bsdf)
}
} else {
None
}
}
}
}
fn as_any(&self) -> &dyn Any {
self
}
fn regularize(&mut self) {
self.mf_distrib.regularize();
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct ThinDielectricBxDF {
pub eta: Float,
}
impl ThinDielectricBxDF {
pub fn new(eta: Float) -> Self {
Self { eta }
}
}
impl BxDFTrait for ThinDielectricBxDF {
fn flags(&self) -> BxDFFlags {
BxDFFlags::REFLECTION | BxDFFlags::TRANSMISSION | BxDFFlags::SPECULAR
}
fn f(&self, _wo: Vector3f, _wi: Vector3f, _mode: TransportMode) -> SampledSpectrum {
SampledSpectrum::new(0.)
}
fn pdf(&self, _wo: Vector3f, _wi: Vector3f, _f_args: FArgs) -> Float {
0.
}
fn sample_f(&self, wo: Vector3f, uc: Float, _u: Point2f, f_args: FArgs) -> Option<BSDFSample> {
let mut r = fr_dielectric(abs_cos_theta(wo), self.eta);
let mut t = 1. - r;
if r < 1. {
r += square(t) * r / (1. - square(r));
t = 1. - r;
}
let mut pr = r;
let mut pt = t;
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
let transmission_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::TRANSMISSION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
pr = 0.;
}
if !f_args.sample_flags.contains(transmission_flags) {
pt = 0.;
}
if pr == 0. && pt == 0. {
return None;
}
if uc < pr / (pr + pt) {
let wi = Vector3f::new(-wo.x(), -wo.y(), wo.z());
let f = SampledSpectrum::new(r / abs_cos_theta(wi));
let bsdf = BSDFSample {
f,
wi,
pdf: pr / (pr + pt),
flags: BxDFFlags::SPECULAR_REFLECTION,
..Default::default()
};
Some(bsdf)
} else {
// Perfect specular transmission
let wi = -wo;
let f = SampledSpectrum::new(t / abs_cos_theta(wi));
let bsdf = BSDFSample {
f,
wi,
pdf: pr / (pr + pt),
flags: BxDFFlags::SPECULAR_TRANSMISSION,
..Default::default()
};
Some(bsdf)
}
}
fn as_any(&self) -> &dyn Any {
self
}
fn regularize(&mut self) {
todo!()
}
}

View file

@ -0,0 +1,78 @@
use crate::core::bsdf::BSDFSample;
use crate::core::bxdf::{BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode};
use crate::core::geometry::{Point2f, Vector3f, abs_cos_theta, same_hemisphere};
use crate::spectra::SampledSpectrum;
use crate::utils::sampling::{cosine_hemisphere_pdf, sample_cosine_hemisphere};
use crate::{Float, INV_PI};
use core::any::Any;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct DiffuseBxDF {
pub r: SampledSpectrum,
}
impl DiffuseBxDF {
pub fn new(r: SampledSpectrum) -> Self {
Self { r }
}
}
impl BxDFTrait for DiffuseBxDF {
fn flags(&self) -> BxDFFlags {
if !self.r.is_black() {
BxDFFlags::DIFFUSE_REFLECTION
} else {
BxDFFlags::UNSET
}
}
fn f(&self, wo: Vector3f, wi: Vector3f, _mode: TransportMode) -> SampledSpectrum {
if !same_hemisphere(wo, wi) {
return SampledSpectrum::new(0.);
}
self.r * INV_PI
}
fn sample_f(&self, wo: Vector3f, _uc: Float, u: Point2f, f_args: FArgs) -> Option<BSDFSample> {
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
return None;
}
let mut wi = sample_cosine_hemisphere(u);
if wo.z() == 0. {
wi[2] *= -1.;
}
let pdf = cosine_hemisphere_pdf(abs_cos_theta(wi));
let bsdf = BSDFSample {
f: self.r * INV_PI,
wi,
pdf,
flags: BxDFFlags::DIFFUSE_REFLECTION,
..Default::default()
};
Some(bsdf)
}
fn pdf(&self, wo: Vector3f, wi: Vector3f, f_args: FArgs) -> Float {
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::ALL.bits());
if !f_args.sample_flags.contains(reflection_flags) || !same_hemisphere(wo, wi) {
return 0.;
}
cosine_hemisphere_pdf(abs_cos_theta(wi))
}
fn as_any(&self) -> &dyn Any {
self
}
fn regularize(&mut self) {
return;
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct DiffuseTransmissionBxDF;

644
shared/src/bxdfs/layered.rs Normal file
View file

@ -0,0 +1,644 @@
use super::ConductorBxDF;
use super::DielectricBxDF;
use super::DiffuseBxDF;
use crate::core::bsdf::BSDFSample;
use crate::core::bxdf::{BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode};
use crate::core::color::RGB;
use crate::core::geometry::{
Frame, Normal3f, Point2f, Vector3f, VectorLike, abs_cos_theta, cos_theta, same_hemisphere,
spherical_direction, spherical_theta,
};
use crate::core::medium::{HGPhaseFunction, PhaseFunctionTrait};
use crate::core::options::get_options;
use crate::core::scattering::{
TrowbridgeReitzDistribution, fr_complex, fr_complex_from_spectrum, fr_dielectric, reflect,
refract,
};
use crate::spectra::{
DeviceStandardColorSpaces, N_SPECTRUM_SAMPLES, RGBColorSpace, RGBUnboundedSpectrum,
SampledSpectrum, SampledWavelengths,
};
use crate::utils::hash::hash_buffer;
use crate::utils::math::{
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::{Float, INV_2_PI, INV_4_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2};
use core::any::Any;
#[derive(Copy, Clone)]
pub enum TopOrBottom<'a, T, B> {
Top(&'a T),
Bottom(&'a B),
}
impl<'a, T, B> TopOrBottom<'a, T, B>
where
T: BxDFTrait,
B: BxDFTrait,
{
pub fn f(&self, wo: Vector3f, wi: Vector3f, mode: TransportMode) -> SampledSpectrum {
match self {
Self::Top(t) => t.f(wo, wi, mode),
Self::Bottom(b) => b.f(wo, wi, mode),
}
}
pub fn sample_f(
&self,
wo: Vector3f,
uc: Float,
u: Point2f,
f_args: FArgs,
) -> Option<BSDFSample> {
match self {
Self::Top(t) => t.sample_f(wo, uc, u, f_args),
Self::Bottom(b) => b.sample_f(wo, uc, u, f_args),
}
}
pub fn pdf(&self, wo: Vector3f, wi: Vector3f, f_args: FArgs) -> Float {
match self {
Self::Top(t) => t.pdf(wo, wi, f_args),
Self::Bottom(b) => b.pdf(wo, wi, f_args),
}
}
pub fn flags(&self) -> BxDFFlags {
match self {
Self::Top(t) => t.flags(),
Self::Bottom(b) => b.flags(),
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct LayeredBxDF<T, B, const TWO_SIDED: bool>
where
T: BxDFTrait,
B: BxDFTrait,
{
top: T,
bottom: B,
thickness: Float,
g: Float,
albedo: SampledSpectrum,
max_depth: u32,
n_samples: u32,
}
impl<T, B, const TWO_SIDED: bool> LayeredBxDF<T, B, TWO_SIDED>
where
T: BxDFTrait,
B: BxDFTrait,
{
pub fn new(
top: T,
bottom: B,
thickness: Float,
albedo: SampledSpectrum,
g: Float,
max_depth: u32,
n_samples: u32,
) -> Self {
Self {
top,
bottom,
thickness: thickness.max(Float::MIN),
g,
albedo,
max_depth,
n_samples,
}
}
fn tr(&self, dz: Float, w: Vector3f) -> Float {
if dz.abs() <= Float::MIN {
return 1.;
}
-(dz / w.z()).abs().exp()
}
#[allow(clippy::too_many_arguments)]
fn evaluate_sample(
&self,
wo: Vector3f,
wi: Vector3f,
mode: TransportMode,
entered_top: bool,
exit_z: Float,
interfaces: (TopOrBottom<T, B>, TopOrBottom<T, B>, TopOrBottom<T, B>),
rng: &mut Rng,
) -> SampledSpectrum {
let (enter_interface, exit_interface, non_exit_interface) = interfaces;
let trans_args = FArgs {
mode,
sample_flags: BxDFReflTransFlags::TRANSMISSION,
};
let refl_args = FArgs {
mode,
sample_flags: BxDFReflTransFlags::REFLECTION,
};
let mut r = || rng.uniform::<Float>().min(ONE_MINUS_EPSILON);
// 1. Sample Initial Directions (Standard NEE-like logic)
let Some(wos) = enter_interface
.sample_f(wo, r(), Point2f::new(r(), r()), trans_args)
.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
else {
return SampledSpectrum::new(0.0);
};
let Some(wis) = exit_interface
.sample_f(wi, r(), Point2f::new(r(), r()), trans_args)
.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
else {
return SampledSpectrum::new(0.0);
};
let mut f = SampledSpectrum::new(0.0);
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 {
// Russian Roulette
if depth > 3 {
let max_beta = beta.max_component_value();
if max_beta < 0.25 {
let q = (1.0 - max_beta).max(0.0);
if r() < q {
break;
}
beta /= 1.0 - q;
}
}
if self.albedo.is_black() {
// No medium, just move to next interface
z = if z == self.thickness {
0.0
} else {
self.thickness
};
beta *= self.tr(self.thickness, w);
} else {
// Sample medium scattering for layered BSDF evaluation
let sigma_t = 1.0;
let dz = sample_exponential(r(), sigma_t / w.z().abs());
let zp = if w.z() > 0.0 { z + dz } else { z - dz };
if zp > 0.0 && zp < self.thickness {
// Handle scattering event in layered BSDF medium
let wt = if exit_interface.flags().is_specular() {
power_heuristic(1, wis.pdf, 1, phase.pdf(-w, wis.wi))
} else {
1.0
};
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 Some(ps) = phase
.sample_p(-w, Point2f::new(r(), r()))
.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;
// Account for scattering through exit
if (z < exit_z && w.z() > 0.0) || (z > exit_z && w.z() < 0.0) {
let f_exit = exit_interface.f(-w, -wi, mode);
if !f_exit.is_black() {
let exit_pdf = exit_interface.pdf(-w, wi, trans_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(zp, 0.0, self.thickness);
}
if z == exit_z {
// Account for reflection at exitInterface
// Hitting the exit surface -> Transmission
let Some(bs) = exit_interface
.sample_f(-w, r(), Point2f::new(r(), r()), refl_args)
.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
else {
break;
};
beta *= bs.f * abs_cos_theta(bs.wi) / bs.pdf;
w = bs.wi;
} else {
// Hitting the non-exit surface -> Reflection
if !non_exit_interface.flags().is_specular() {
let wt = if exit_interface.flags().is_specular() {
power_heuristic(
1,
wis.pdf,
1,
non_exit_interface.pdf(-w, -wis.wi, refl_args),
)
} else {
1.0
};
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
let Some(bs) = non_exit_interface
.sample_f(-w, r(), Point2f::new(r(), r()), 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;
// Search reverse direction
if !exit_interface.flags().is_specular() {
let f_exit = exit_interface.f(-w, wi, mode);
if !f_exit.is_black() {
let mut wt = 1.0;
if non_exit_interface.flags().is_specular() {
wt = power_heuristic(
1,
bs.pdf,
1,
exit_interface.pdf(-w, wi, trans_args),
);
}
f += beta * self.tr(self.thickness, bs.wi) * f_exit * wt;
}
}
}
}
f
}
}
impl<T, B, const TWO_SIDED: bool> BxDFTrait for LayeredBxDF<T, B, TWO_SIDED>
where
T: BxDFTrait + Clone,
B: BxDFTrait + Clone,
{
fn flags(&self) -> BxDFFlags {
let top_flags = self.top.flags();
let bottom_flags = self.bottom.flags();
assert!(top_flags.is_transmissive() || bottom_flags.is_transmissive());
let mut flags = BxDFFlags::REFLECTION;
if top_flags.is_specular() {
flags |= BxDFFlags::SPECULAR;
}
if top_flags.is_diffuse() || bottom_flags.is_diffuse() || !self.albedo.is_black() {
flags |= BxDFFlags::DIFFUSE;
} else if top_flags.is_glossy() || bottom_flags.is_glossy() {
flags |= BxDFFlags::GLOSSY;
}
if top_flags.is_transmissive() && bottom_flags.is_transmissive() {
flags |= BxDFFlags::TRANSMISSION;
}
flags
}
fn f(&self, mut wo: Vector3f, mut wi: Vector3f, mode: TransportMode) -> SampledSpectrum {
let mut f = SampledSpectrum::new(0.);
if TWO_SIDED && wo.z() < 0. {
wo = -wo;
wi = -wi;
}
let entered_top = TWO_SIDED || wo.z() > 0.;
let enter_interface = if entered_top {
TopOrBottom::Top(&self.top)
} else {
TopOrBottom::Bottom(&self.bottom)
};
let (exit_interface, non_exit_interface) = if same_hemisphere(wo, wi) ^ entered_top {
(
TopOrBottom::Bottom(&self.bottom),
TopOrBottom::Top(&self.top),
)
} else {
(
TopOrBottom::Top(&self.top),
TopOrBottom::Bottom(&self.bottom),
)
};
let exit_z = if same_hemisphere(wo, wi) ^ entered_top {
0.
} else {
self.thickness
};
if same_hemisphere(wo, wi) {
f = self.n_samples as Float * enter_interface.f(wo, wi, mode);
}
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 inters = (enter_interface, exit_interface, non_exit_interface);
for _ in 0..self.n_samples {
f += self.evaluate_sample(wo, wi, mode, entered_top, exit_z, inters.clone(), &mut rng)
}
f / self.n_samples as Float
}
fn sample_f(
&self,
mut wo: Vector3f,
uc: Float,
u: Point2f,
f_args: FArgs,
) -> Option<BSDFSample> {
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, 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) {
self.top.regularize();
self.bottom.regularize();
}
fn as_any(&self) -> &dyn Any {
todo!()
}
}
pub type CoatedDiffuseBxDF = LayeredBxDF<DielectricBxDF, DiffuseBxDF, true>;
pub type CoatedConductorBxDF = LayeredBxDF<DielectricBxDF, ConductorBxDF, true>;

View file

@ -0,0 +1,240 @@
use crate::core::bsdf::BSDFSample;
use crate::core::bxdf::{BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode};
use crate::core::geometry::{
Point2f, Vector3f, VectorLike, abs_cos_theta, cos_theta, same_hemisphere, spherical_direction,
spherical_theta,
};
use crate::core::scattering::reflect;
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::square;
use crate::utils::ptr::Ptr;
use crate::utils::sampling::{PiecewiseLinear2D, cosine_hemisphere_pdf, sample_cosine_hemisphere};
use crate::{Float, INV_PI, PI, PI_OVER_2};
use core::any::Any;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct MeasuredBxDFData {
pub wavelengths: Ptr<Float>,
pub spectra: PiecewiseLinear2D<3>,
pub ndf: PiecewiseLinear2D<0>,
pub vndf: PiecewiseLinear2D<2>,
pub sigma: PiecewiseLinear2D<0>,
pub isotropic: bool,
pub luminance: PiecewiseLinear2D<2>,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct MeasuredBxDF {
pub brdf: Ptr<MeasuredBxDFData>,
pub lambda: SampledWavelengths,
}
unsafe impl Send for MeasuredBxDF {}
unsafe impl Sync for MeasuredBxDF {}
impl MeasuredBxDF {
pub fn new(brdf: &MeasuredBxDFData, lambda: &SampledWavelengths) -> Self {
Self {
brdf: Ptr::from(brdf),
lambda: *lambda,
}
}
pub fn theta2u(theta: Float) -> Float {
(theta * (2. / PI)).sqrt()
}
pub fn phi2u(phi: Float) -> Float {
phi * 1. / (2. * PI) + 0.5
}
pub fn u2theta(u: Float) -> Float {
square(u) * PI_OVER_2
}
pub fn u2phi(u: Float) -> Float {
(2. * u - 1.) * PI
}
}
impl BxDFTrait for MeasuredBxDF {
fn flags(&self) -> BxDFFlags {
BxDFFlags::REFLECTION | BxDFFlags::GLOSSY
}
fn f(&self, wo: Vector3f, wi: Vector3f, _mode: TransportMode) -> SampledSpectrum {
if !same_hemisphere(wo, wi) {
return SampledSpectrum::new(0.);
}
let mut wo_curr = wo;
let mut wi_curr = wi;
if wo.z() < 0. {
wo_curr = -wo_curr;
wi_curr = -wi_curr;
}
// Get half direction vector
let wm_curr = wi_curr + wo_curr;
if wm_curr.norm_squared() == 0. {
return SampledSpectrum::new(0.);
}
let wm = wm_curr.normalize();
// Map vectors to unit square
let theta_o = spherical_theta(wo_curr);
let phi_o = wo_curr.y().atan2(wo_curr.x());
let theta_m = spherical_theta(wm);
let phi_m = wm.y().atan2(wm.x());
let u_wo = Point2f::new(MeasuredBxDF::theta2u(theta_o), MeasuredBxDF::phi2u(phi_o));
let u_wm_phi = if self.brdf.isotropic {
phi_m - phi_o
} else {
phi_m
};
let mut u_wm = Point2f::new(
MeasuredBxDF::theta2u(theta_m),
MeasuredBxDF::phi2u(u_wm_phi),
);
u_wm[1] -= u_wm[1].floor();
// Inverse parametrization
let ui = self.brdf.vndf.invert(u_wm, [phi_o, theta_o]);
let fr = SampledSpectrum::from_fn(|i| {
self.brdf
.spectra
.evaluate(ui.p, [phi_o, theta_o, self.lambda[i]])
.max(0.0)
});
fr * self.brdf.ndf.evaluate(u_wm, [])
/ (4. * self.brdf.sigma.evaluate(u_wo, []) * cos_theta(wi))
}
fn pdf(&self, wo: Vector3f, wi: Vector3f, f_args: FArgs) -> Float {
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
return 0.;
}
if !same_hemisphere(wo, wi) {
return 0.;
}
let mut wo_curr = wo;
let mut wi_curr = wi;
if wo.z() < 0. {
wo_curr = -wo_curr;
wi_curr = -wi_curr;
}
let wm_curr = wi_curr + wo_curr;
if wm_curr.norm_squared() == 0. {
return 0.;
}
let wm = wm_curr.normalize();
let theta_o = spherical_theta(wo_curr);
let phi_o = wo_curr.y().atan2(wo_curr.x());
let theta_m = spherical_theta(wm);
let phi_m = wm.y().atan2(wm.x());
let u_wm_phi = if self.brdf.isotropic {
phi_m - phi_o
} else {
phi_m
};
let mut u_wm = Point2f::new(
MeasuredBxDF::theta2u(theta_m),
MeasuredBxDF::phi2u(u_wm_phi),
);
u_wm[1] = u_wm[1] - u_wm[1].floor();
let ui = self.brdf.vndf.invert(u_wm, [phi_o, theta_o]);
let sample = ui.p;
let vndf_pdf = ui.pdf;
let pdf = self.brdf.luminance.evaluate(sample, [phi_o, theta_o]);
let sin_theta_m = (square(wm.x()) + square(wm.y())).sqrt();
let jacobian = 4. * wm.dot(wo) * f32::max(2. * square(PI) * u_wm.x() * sin_theta_m, 1e-6);
vndf_pdf * pdf / jacobian
}
fn sample_f(&self, wo: Vector3f, _uc: Float, u: Point2f, f_args: FArgs) -> Option<BSDFSample> {
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
return None;
}
let mut flip_w = false;
let mut wo_curr = wo;
if wo.z() <= 0. {
wo_curr = -wo_curr;
flip_w = true;
}
let theta_o = spherical_theta(wo_curr);
let phi_o = wo_curr.y().atan2(wo_curr.x());
// Warp sample using luminance distribution
let mut s = self.brdf.luminance.sample(u, [phi_o, theta_o]);
let u = s.p;
let lum_pdf = s.pdf;
// Sample visible normal distribution of measured BRDF
s = self.brdf.vndf.sample(u, [phi_o, theta_o]);
let u_wm = s.p;
let mut pdf = s.pdf;
// Map from microfacet normal to incident direction
let mut phi_m = MeasuredBxDF::u2phi(u_wm.y());
let theta_m = MeasuredBxDF::u2theta(u_wm.x());
if self.brdf.isotropic {
phi_m += phi_o;
}
let sin_theta_m = theta_m.sin();
let cos_theta_m = theta_m.cos();
let wm = spherical_direction(sin_theta_m, cos_theta_m, phi_m);
let mut wi = reflect(wo_curr, wm.into());
if wi.z() <= 0. {
return None;
}
// Interpolate spectral BRDF
let mut f = SampledSpectrum::from_fn(|i| {
self.brdf
.spectra
.evaluate(u, [phi_o, theta_o, self.lambda[i]])
.max(0.0)
});
let u_wo = Point2f::new(MeasuredBxDF::theta2u(theta_o), MeasuredBxDF::phi2u(phi_o));
f *= self.brdf.ndf.evaluate(u_wm, [])
/ (4. * self.brdf.sigma.evaluate(u_wo, []) * abs_cos_theta(wi));
pdf /= 4. * wm.dot(wo_curr) * f32::max(2. * square(PI) * u_wm.x(), 1e-6);
if flip_w {
wi = -wi;
}
let bsdf = BSDFSample {
f,
wi,
pdf: pdf * lum_pdf,
flags: BxDFFlags::GLOSSY_REFLECTION,
..Default::default()
};
Some(bsdf)
}
fn as_any(&self) -> &dyn Any {
self
}
fn regularize(&mut self) {
return;
}
}

13
shared/src/bxdfs/mod.rs Normal file
View file

@ -0,0 +1,13 @@
pub mod complex;
pub mod conductor;
pub mod dielectric;
pub mod diffuse;
pub mod layered;
pub mod measured;
pub use complex::{EmptyBxDF, HairBxDF, NormalizedFresnelBxDF};
pub use conductor::ConductorBxDF;
pub use dielectric::{DielectricBxDF, ThinDielectricBxDF};
pub use diffuse::{DiffuseBxDF, DiffuseTransmissionBxDF};
pub use layered::{CoatedConductorBxDF, CoatedDiffuseBxDF};
pub use measured::{MeasuredBxDF, MeasuredBxDFData};

View file

@ -6,4 +6,4 @@ mod spherical;
pub use orthographic::OrthographicCamera; pub use orthographic::OrthographicCamera;
pub use perspective::PerspectiveCamera; pub use perspective::PerspectiveCamera;
pub use realistic::RealisticCamera; pub use realistic::RealisticCamera;
pub use spherical::SphericalCamera; pub use spherical::{SphericalCamera, Mapping};

View file

@ -6,7 +6,6 @@ use crate::core::geometry::{
use crate::core::medium::Medium; use crate::core::medium::Medium;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample; use crate::core::sampler::CameraSample;
use crate::images::ImageMetadata;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Transform; use crate::utils::Transform;
use crate::utils::sampling::sample_uniform_disk_concentric; use crate::utils::sampling::sample_uniform_disk_concentric;
@ -42,11 +41,12 @@ impl OrthographicCamera {
-screen_window.p_max.y(), -screen_window.p_max.y(),
0., 0.,
)); ));
let film_ptr = base.film;
if film_ptr.is_null() { let mut base_ortho = base;
let film = base.film;
if film.is_null() {
panic!("Camera must have a film"); panic!("Camera must have a film");
} }
let film = unsafe { &*film_ptr };
let raster_from_ndc = Transform::scale( let raster_from_ndc = Transform::scale(
film.full_resolution().x() as Float, film.full_resolution().x() as Float,
@ -60,7 +60,6 @@ impl OrthographicCamera {
let camera_from_raster = screen_from_camera.inverse() * screen_from_raster; 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 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.)); let dy_camera = camera_from_raster.apply_to_vector(Vector3f::new(0., 1., 0.));
let mut base_ortho = base;
base_ortho.min_dir_differential_x = Vector3f::new(0., 0., 0.); base_ortho.min_dir_differential_x = Vector3f::new(0., 0., 0.);
base_ortho.min_dir_differential_y = Vector3f::new(0., 0., 0.); base_ortho.min_dir_differential_y = Vector3f::new(0., 0., 0.);
base_ortho.min_pos_differential_x = dx_camera; base_ortho.min_pos_differential_x = dx_camera;
@ -80,11 +79,6 @@ impl OrthographicCamera {
} }
impl CameraTrait for OrthographicCamera { impl CameraTrait for OrthographicCamera {
#[cfg(not(target_os = "cuda"))]
fn init_metadata(&self, metadata: &mut ImageMetadata) {
self.base.init_metadata(metadata)
}
fn base(&self) -> &CameraBase { fn base(&self) -> &CameraBase {
&self.base &self.base
} }
@ -102,7 +96,7 @@ impl CameraTrait for OrthographicCamera {
p_camera, p_camera,
Vector3f::new(0., 0., 1.), Vector3f::new(0., 0., 1.),
Some(self.sample_time(sample.time)), Some(self.sample_time(sample.time)),
self.base().medium.clone(), &*self.base().medium,
); );
if self.lens_radius > 0. { if self.lens_radius > 0. {
let p_lens_vec = let p_lens_vec =
@ -133,11 +127,11 @@ impl CameraTrait for OrthographicCamera {
let mut rd = RayDifferential::default(); let mut rd = RayDifferential::default();
if self.lens_radius > 0.0 { if self.lens_radius > 0.0 {
let mut sample_x = sample; let mut sample_x = sample;
sample_x.p_film.x += 1.0; sample_x.p_film[0] += 1.0;
let rx = self.generate_ray(sample_x, lambda)?; let rx = self.generate_ray(sample_x, lambda)?;
let mut sample_y = sample; let mut sample_y = sample;
sample_y.p_film.y += 1.0; sample_y.p_film[1] += 1.0;
let ry = self.generate_ray(sample_y, lambda)?; let ry = self.generate_ray(sample_y, lambda)?;
rd.rx_origin = rx.ray.o; rd.rx_origin = rx.ray.o;
@ -161,7 +155,7 @@ impl CameraTrait for OrthographicCamera {
rd.rx_direction = central_cam_ray.ray.d; rd.rx_direction = central_cam_ray.ray.d;
rd.ry_direction = central_cam_ray.ray.d; rd.ry_direction = central_cam_ray.ray.d;
} }
central_cam_ray.ray.differential = Some(rd); central_cam_ray.ray.differential = rd;
Some(central_cam_ray) Some(central_cam_ray)
} }
} }

View file

@ -1,5 +1,6 @@
use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform}; use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
use crate::core::film::Film; use crate::core::film::Film;
use crate::core::filter::FilterTrait;
use crate::core::geometry::{ use crate::core::geometry::{
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike, Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
}; };
@ -10,7 +11,8 @@ use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::sampling::sample_uniform_disk_concentric; use crate::utils::sampling::sample_uniform_disk_concentric;
use crate::utils::transform::Transform; use crate::utils::transform::Transform;
#[derive(Debug)] #[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct PerspectiveCamera { pub struct PerspectiveCamera {
pub base: CameraBase, pub base: CameraBase,
pub screen_from_camera: Transform, pub screen_from_camera: Transform,
@ -77,12 +79,7 @@ impl PerspectiveCamera {
} }
} }
impl PerspectiveCamera { impl CameraTrait for PerspectiveCamera {
#[cfg(not(target_os = "cuda"))]
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
self.base.init_metadata(metadata)
}
fn base(&self) -> &CameraBase { fn base(&self) -> &CameraBase {
&self.base &self.base
} }
@ -101,7 +98,7 @@ impl PerspectiveCamera {
Point3f::new(0., 0., 0.), Point3f::new(0., 0., 0.),
p_vector.normalize(), p_vector.normalize(),
Some(self.sample_time(sample.time)), Some(self.sample_time(sample.time)),
self.base().medium.clone(), &*self.base().medium,
); );
// Modify ray for depth of field // Modify ray for depth of field
if self.lens_radius > 0. { if self.lens_radius > 0. {

View file

@ -1,16 +1,17 @@
use crate::PI; use crate::PI;
use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform}; use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
use crate::core::color::SRGB;
use crate::core::film::Film; use crate::core::film::Film;
use crate::core::geometry::{ use crate::core::geometry::{
Bounds2f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector2i, Vector3f, VectorLike, Bounds2f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector2i, Vector3f, VectorLike,
}; };
use crate::core::image::{DeviceImage, PixelFormat};
use crate::core::medium::Medium; use crate::core::medium::Medium;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample; use crate::core::sampler::CameraSample;
use crate::core::scattering::refract; use crate::core::scattering::refract;
use crate::images::{Image, PixelFormat};
use crate::spectra::color::SRGB;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::math::{lerp, quadratic, square}; use crate::utils::math::{lerp, quadratic, square};
#[repr(C)] #[repr(C)]
@ -37,8 +38,8 @@ pub struct RealisticCamera {
base: CameraBase, base: CameraBase,
focus_distance: Float, focus_distance: Float,
set_aperture_diameter: Float, set_aperture_diameter: Float,
aperture_image: *const Image, aperture_image: Ptr<DeviceImage>,
element_interfaces: *const LensElementInterface, element_interfaces: Ptr<LensElementInterface>,
n_elements: usize, n_elements: usize,
physical_extent: Bounds2f, physical_extent: Bounds2f,
exit_pupil_bounds: [Bounds2f; EXIT_PUPIL_SAMPLES], exit_pupil_bounds: [Bounds2f; EXIT_PUPIL_SAMPLES],
@ -51,13 +52,13 @@ impl RealisticCamera {
lens_params: &[Float], lens_params: &[Float],
focus_distance: Float, focus_distance: Float,
set_aperture_diameter: Float, set_aperture_diameter: Float,
aperture_image: Option<Image>, aperture_image: Ptr<DeviceImage>,
) -> Self { ) -> Self {
let film_ptr = base.film; let film_ptr = base.film;
if film_ptr.is_null() { if film_ptr.is_null() {
panic!("Camera must have a film"); panic!("Camera must have a film");
} }
let film = unsafe { &*film_ptr }; let film = &*film_ptr;
let aspect = film.full_resolution().x() as Float / film.full_resolution().y() as Float; let aspect = film.full_resolution().x() as Float / film.full_resolution().y() as Float;
let diagonal = film.diagonal(); let diagonal = film.diagonal();
@ -90,7 +91,6 @@ impl RealisticCamera {
element_interface.push(el_int); element_interface.push(el_int);
} }
let n_samples = 64;
let half_diag = film.diagonal() / 2.0; let half_diag = film.diagonal() / 2.0;
let mut exit_pupil_bounds = [Bounds2f::default(); EXIT_PUPIL_SAMPLES]; let mut exit_pupil_bounds = [Bounds2f::default(); EXIT_PUPIL_SAMPLES];
@ -107,7 +107,7 @@ impl RealisticCamera {
Self { Self {
base, base,
focus_distance, focus_distance,
element_interfaces, element_interfaces: Ptr::from(element_interfaces),
n_elements, n_elements,
physical_extent, physical_extent,
set_aperture_diameter, set_aperture_diameter,
@ -123,14 +123,16 @@ impl RealisticCamera {
} }
pub fn compute_thick_lens_approximation(&self) -> ([Float; 2], [Float; 2]) { pub fn compute_thick_lens_approximation(&self) -> ([Float; 2], [Float; 2]) {
use crate::utils::Ptr;
let x = 0.001 * self.get_film().diagonal(); let x = 0.001 * self.get_film().diagonal();
let r_scene = Ray::new( let r_scene = Ray::new(
Point3f::new(0., x, self.lens_front_z() + 1.), Point3f::new(0., x, self.lens_front_z() + 1.),
Vector3f::new(0., 0., -1.), Vector3f::new(0., 0., -1.),
None, None,
None, &Ptr::null(),
); );
let Some(r_film) = self.trace_lenses_from_film(r_scene) else { let Some((_, r_film)) = self.trace_lenses_from_film(&r_scene) else {
panic!( panic!(
"Unable to trace ray from scene to film for thick lens approx. Is aperture very small?" "Unable to trace ray from scene to film for thick lens approx. Is aperture very small?"
) )
@ -140,14 +142,14 @@ impl RealisticCamera {
Point3f::new(x, 0., self.lens_rear_z() - 1.), Point3f::new(x, 0., self.lens_rear_z() - 1.),
Vector3f::new(0., 0., 1.), Vector3f::new(0., 0., 1.),
None, None,
None, &Ptr::null(),
); );
let Some(r_scene) = self.trace_lenses_from_film(r_film) else { let Some((_, r_scene)) = self.trace_lenses_from_film(&r_film) else {
panic!( panic!(
"Unable to trace ray from scene to film for thick lens approx. Is aperture very small?" "Unable to trace ray from scene to film for thick lens approx. Is aperture very small?"
) )
}; };
let (pz1, f_1) = Self::compute_cardinal_points(r_film, r_scene); let (pz1, fz1) = Self::compute_cardinal_points(r_film, r_scene);
([pz0, pz1], [fz0, fz1]) ([pz0, pz1], [fz0, fz1])
} }
@ -155,19 +157,23 @@ impl RealisticCamera {
let (pz, fz) = self.compute_thick_lens_approximation(); let (pz, fz) = self.compute_thick_lens_approximation();
let f = fz[0] - pz[0]; let f = fz[0] - pz[0];
let z = -focus_distance; let z = -focus_distance;
let c = (pz[1] - z - pz[0]) * (pz[1] - z - 4 * f - pz[0]); let c = (pz[1] - z - pz[0]) * (pz[1] - z - 4. * f - pz[0]);
if c <= 0 { if c <= 0. {
panic!( panic!(
"Coefficient must be positive. It looks focusDistance {} is too short for a given lenses configuration", "Coefficient must be positive. It looks focusDistance {} is too short for a given lenses configuration",
focusDistance focus_distance
); );
} }
let delta = (pz[1] - z + pz[0] - c.sqrt()) / 2.; let delta = (pz[1] - z + pz[0] - c.sqrt()) / 2.;
self.element_interface.last().thickness + delta let last_interface = unsafe { self.element_interfaces.add(self.n_elements) };
last_interface.thickness + delta
} }
pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f { 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) let interface_array = unsafe {
core::slice::from_raw_parts(self.element_interfaces.as_raw(), self.n_elements as usize)
};
Self::compute_exit_pupil_bounds(interface_array, film_x_0, film_x_1)
} }
fn compute_exit_pupil_bounds( fn compute_exit_pupil_bounds(
@ -203,7 +209,10 @@ impl RealisticCamera {
// Expand pupil bounds if ray makes it through the lens system // Expand pupil bounds if ray makes it through the lens system
if !pupil_bounds.contains(Point2f::new(p_rear.x(), p_rear.y())) if !pupil_bounds.contains(Point2f::new(p_rear.x(), p_rear.y()))
&& trace_lenses_from_film(Ray::new(p_film, p_rear - p_film, None, None), None) && trace_lenses_from_film(
Ray::new(p_film, p_rear - p_film, None, &Ptr::null()),
None,
)
{ {
pupil_bounds = pupil_bounds.union_point(Point2f::new(p_rear.x(), p_rear.y())); pupil_bounds = pupil_bounds.union_point(Point2f::new(p_rear.x(), p_rear.y()));
} }
@ -223,7 +232,7 @@ impl RealisticCamera {
pub fn sample_exit_pupil(&self, p_film: Point2f, u_lens: Point2f) -> Option<ExitPupilSample> { pub fn sample_exit_pupil(&self, p_film: Point2f, u_lens: Point2f) -> Option<ExitPupilSample> {
// Find exit pupil bound for sample distance from film center // Find exit pupil bound for sample distance from film center
let film = self.film(); let film = self.get_film();
let r_film = (square(p_film.x()) + square(p_film.y())).sqrt(); let r_film = (square(p_film.x()) + square(p_film.y())).sqrt();
let mut r_index = (r_film / (film.diagonal() / 2.)) as usize * self.exit_pupil_bounds.len(); let mut r_index = (r_film / (film.diagonal() / 2.)) as usize * self.exit_pupil_bounds.len();
r_index = (self.exit_pupil_bounds.len() - 1).min(r_index); r_index = (self.exit_pupil_bounds.len() - 1).min(r_index);
@ -265,11 +274,11 @@ impl RealisticCamera {
Point3f::new(r_camera.o.x(), r_camera.o.y(), -r_camera.o.z()), Point3f::new(r_camera.o.x(), r_camera.o.y(), -r_camera.o.z()),
Vector3f::new(r_camera.d.x(), r_camera.d.y(), -r_camera.d.z()), Vector3f::new(r_camera.d.x(), r_camera.d.y(), -r_camera.d.z()),
Some(r_camera.time), Some(r_camera.time),
None, &Ptr::null(),
); );
for i in (0..self.element_interface.len() - 1).rev() { for i in (0..self.n_elements - 1).rev() {
let element: &LensElementInterface = &self.element_interface[i]; let element: &LensElementInterface = unsafe { &self.element_interfaces.add(i) };
// Update ray from film accounting for interaction with _element_ // Update ray from film accounting for interaction with _element_
element_z -= element.thickness; element_z -= element.thickness;
@ -308,8 +317,9 @@ impl RealisticCamera {
// Update ray path for element interface interaction // Update ray path for element interface interaction
if !is_stop { if !is_stop {
let eta_i = element.eta; let eta_i = element.eta;
let eta_t = if i > 0 && self.element_interface[i - 1].eta != 0. { let interface_i = unsafe { self.element_interfaces.add(i - 1) };
self.element_interface[i - 1].eta let eta_t = if i > 0 && interface_i.eta != 0. {
interface_i.eta
} else { } else {
1. 1.
}; };
@ -331,7 +341,7 @@ impl RealisticCamera {
Point3f::new(r_lens.o.x(), r_lens.o.y(), -r_lens.o.z()), Point3f::new(r_lens.o.x(), r_lens.o.y(), -r_lens.o.z()),
Vector3f::new(r_lens.d.x(), r_lens.d.y(), -r_lens.d.z()), Vector3f::new(r_lens.d.x(), r_lens.d.y(), -r_lens.d.z()),
Some(r_lens.time), Some(r_lens.time),
None, &Ptr::null(),
); );
Some((weight, r_out)) Some((weight, r_out))
@ -368,49 +378,35 @@ impl RealisticCamera {
} }
pub fn lens_rear_z(&self) -> Float { pub fn lens_rear_z(&self) -> Float {
self.element_interface.last().unwrap().thickness let last_interface = unsafe { self.element_interfaces.add(self.n_elements - 1) };
last_interface.thickness
} }
pub fn lens_front_z(&self) -> Float { pub fn lens_front_z(&self) -> Float {
let mut z_sum = 0.; let mut z_sum = 0.;
for element in &self.element_interface { for i in 0..self.n_elements {
let element = unsafe { self.element_interfaces.add(i) };
z_sum += element.thickness; z_sum += element.thickness;
} }
z_sum z_sum
} }
pub fn rear_element_radius(&self) -> Float { pub fn rear_element_radius(&self) -> Float {
self.element_interface.last().unwrap().aperture_radius let last_interface = unsafe { self.element_interfaces.add(self.n_elements - 1) };
last_interface.aperture_radius
} }
} }
impl CameraTrait for RealisticCamera { impl CameraTrait for RealisticCamera {
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
self.base.init_metadata(metadata)
}
fn base(&self) -> &CameraBase { fn base(&self) -> &CameraBase {
&self.base &self.base
} }
fn get_film(&self) -> &Film {
#[cfg(not(target_os = "cuda"))]
{
if self.base.film.is_null() {
panic!(
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
);
}
}
unsafe { &*self.base.film }
}
fn generate_ray( fn generate_ray(
&self, &self,
sample: CameraSample, sample: CameraSample,
_lambda: &SampledWavelengths, _lambda: &SampledWavelengths,
) -> Option<CameraRay> { ) -> Option<CameraRay> {
// Find point on film, _pFilm_, corresponding to _sample.pFilm_
let film = self.get_film(); let film = self.get_film();
let s = Point2f::new( let s = Point2f::new(
sample.p_film.x() / film.full_resolution().x() as Float, sample.p_film.x() / film.full_resolution().x() as Float,
@ -423,7 +419,7 @@ impl CameraTrait for RealisticCamera {
let eps = self.sample_exit_pupil(Point2f::new(p_film.x(), p_film.y()), sample.p_lens)?; let eps = self.sample_exit_pupil(Point2f::new(p_film.x(), p_film.y()), sample.p_lens)?;
let p_pupil = Point3f::new(0., 0., 0.); let p_pupil = Point3f::new(0., 0., 0.);
let r_film = Ray::new(p_film, p_pupil - p_film, None, None); let r_film = Ray::new(p_film, p_pupil - p_film, None, &Ptr::null());
let (weight, mut ray) = self.trace_lenses_from_film(&r_film)?; let (weight, mut ray) = self.trace_lenses_from_film(&r_film)?;
if weight == 0. { if weight == 0. {
return None; return None;

View file

@ -1,4 +1,4 @@
use crate::core::camera::{CameraBase, CameraRay, CameraTransform}; use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
use crate::core::film::Film; use crate::core::film::Film;
use crate::core::geometry::{Bounds2f, Point2f, Point3f, Ray, Vector3f, spherical_direction}; use crate::core::geometry::{Bounds2f, Point2f, Point3f, Ray, Vector3f, spherical_direction};
use crate::core::medium::Medium; use crate::core::medium::Medium;
@ -6,7 +6,6 @@ use crate::core::pbrt::{Float, PI};
use crate::core::sampler::CameraSample; use crate::core::sampler::CameraSample;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square}; use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square};
use std::sync::Arc;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
@ -15,36 +14,18 @@ pub enum Mapping {
EqualArea, EqualArea,
} }
#[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct SphericalCamera { pub struct SphericalCamera {
pub mapping: Mapping, pub mapping: Mapping,
pub base: CameraBase, pub base: CameraBase,
} }
#[cfg(not(target_os = "cuda"))]
impl SphericalCamera {
pub fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
self.base.init_metadata(metadata)
}
}
impl CameraTrait for SphericalCamera { impl CameraTrait for SphericalCamera {
fn base(&self) -> &CameraBase { fn base(&self) -> &CameraBase {
&self.base &self.base
} }
fn get_film(&self) -> &Film {
#[cfg(not(target_os = "cuda"))]
{
if self.base.film.is_null() {
panic!(
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
);
}
}
unsafe { &*self.base.film }
}
fn generate_ray( fn generate_ray(
&self, &self,
sample: CameraSample, sample: CameraSample,
@ -58,7 +39,6 @@ impl CameraTrait for SphericalCamera {
); );
let dir: Vector3f; let dir: Vector3f;
if self.mapping == Mapping::EquiRectangular { if self.mapping == Mapping::EquiRectangular {
// Compute ray direction using equirectangular mapping
let theta = PI * uv[1]; let theta = PI * uv[1];
let phi = 2. * PI * uv[0]; let phi = 2. * PI * uv[0];
dir = spherical_direction(theta.sin(), theta.cos(), phi); dir = spherical_direction(theta.sin(), theta.cos(), phi);
@ -67,13 +47,13 @@ impl CameraTrait for SphericalCamera {
uv = wrap_equal_area_square(&mut uv); uv = wrap_equal_area_square(&mut uv);
dir = equal_area_square_to_sphere(uv); dir = equal_area_square_to_sphere(uv);
} }
std::mem::swap(&mut dir.y(), &mut dir.z()); core::mem::swap(&mut dir.y(), &mut dir.z());
let ray = Ray::new( let ray = Ray::new(
Point3f::new(0., 0., 0.), Point3f::new(0., 0., 0.),
dir, dir,
Some(self.sample_time(sample.time)), Some(self.sample_time(sample.time)),
self.base().medium.clone(), &self.base().medium,
); );
Some(CameraRay { Some(CameraRay {
ray: self.render_from_camera(&ray, &mut None), ray: self.render_from_camera(&ray, &mut None),

190
shared/src/core/bsdf.rs Normal file
View file

@ -0,0 +1,190 @@
use crate::Float;
use crate::core::bxdf::{BxDF, BxDFFlags, BxDFTrait, FArgs, TransportMode};
use crate::core::geometry::{Frame, Normal3f, Point2f, Vector3f, VectorLike};
use crate::spectra::SampledSpectrum;
use crate::utils::Ptr;
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub struct BSDF {
bxdf: Ptr<BxDF>,
shading_frame: Frame,
}
impl BSDF {
pub fn new(ns: Normal3f, dpdus: Vector3f, bxdf: Ptr<BxDF>) -> Self {
Self {
bxdf,
shading_frame: Frame::new(dpdus.normalize(), Vector3f::from(ns)),
}
}
pub fn is_valid(&self) -> bool {
!self.bxdf.is_null()
}
pub fn flags(&self) -> BxDFFlags {
if self.bxdf.is_null() {
// Either this, or transmissive for seethrough
return BxDFFlags::empty();
}
self.bxdf.flags()
}
pub fn render_to_local(&self, v: Vector3f) -> Vector3f {
self.shading_frame.to_local(v)
}
pub fn local_to_render(&self, v: Vector3f) -> Vector3f {
self.shading_frame.from_local(v)
}
pub fn f(
&self,
wo_render: Vector3f,
wi_render: Vector3f,
mode: TransportMode,
) -> Option<SampledSpectrum> {
if self.bxdf.is_null() {
return None;
}
let wi = self.render_to_local(wi_render);
let wo = self.render_to_local(wo_render);
if wo.z() == 0.0 || wi.z() == 0.0 {
return None;
}
Some(self.bxdf.f(wo, wi, mode))
}
pub fn sample_f(
&self,
wo_render: Vector3f,
u: Float,
u2: Point2f,
f_args: FArgs,
) -> Option<BSDFSample> {
let bxdf = unsafe { self.bxdf.as_ref() };
let sampling_flags = BxDFFlags::from_bits_truncate(f_args.sample_flags.bits());
let wo = self.render_to_local(wo_render);
if wo.z() == 0.0 || !bxdf.flags().contains(sampling_flags) {
return None;
}
let mut sample = bxdf.sample_f(wo, u, u2, f_args)?;
if sample.pdf > 0.0 && sample.wi.z() != 0.0 {
sample.wi = self.local_to_render(sample.wi);
return Some(sample);
}
None
}
pub fn pdf(&self, wo_render: Vector3f, wi_render: Vector3f, f_args: FArgs) -> Float {
if self.bxdf.is_null() {
return 0.0;
}
let sample_flags = BxDFFlags::from_bits_truncate(f_args.sample_flags.bits());
let wo = self.render_to_local(wo_render);
let wi = self.render_to_local(wi_render);
if wo.z() == 0.0 || !self.bxdf.flags().contains(sample_flags) {
return 0.0;
}
self.bxdf.pdf(wo, wi, f_args)
}
pub fn rho_u(&self, u1: &[Point2f], uc: &[Float], u2: &[Point2f]) -> SampledSpectrum {
if self.bxdf.is_null() {
return SampledSpectrum::default();
}
self.bxdf.rho_u(u1, uc, u2)
}
pub fn rho_wo(&self, wo_render: Vector3f, uc: &[Float], u: &[Point2f]) -> SampledSpectrum {
if self.bxdf.is_null() {
return SampledSpectrum::default();
}
let wo = self.render_to_local(wo_render);
self.bxdf.rho_wo(wo, uc, u)
}
pub fn regularize(&mut self) {
if !self.bxdf.is_null() {
let mut bxdf = unsafe { *self.bxdf.as_raw() };
bxdf.regularize();
}
}
}
#[derive(Debug, Clone)]
pub struct BSDFSample {
pub f: SampledSpectrum,
pub wi: Vector3f,
pub pdf: Float,
pub flags: BxDFFlags,
pub eta: Float,
pub pdf_is_proportional: bool,
}
impl Default for BSDFSample {
fn default() -> Self {
Self {
f: SampledSpectrum::default(),
wi: Vector3f::default(),
pdf: 0.0,
flags: BxDFFlags::empty(),
eta: 1.0,
pdf_is_proportional: false,
}
}
}
impl BSDFSample {
pub fn new(
f: SampledSpectrum,
wi: Vector3f,
pdf: Float,
flags: BxDFFlags,
eta: Float,
pdf_is_proportional: bool,
) -> Self {
Self {
f,
wi,
pdf,
flags,
eta,
pdf_is_proportional,
}
}
#[inline]
pub fn is_reflective(&self) -> bool {
self.flags.is_reflective()
}
#[inline]
pub fn is_transmissive(&self) -> bool {
self.flags.is_transmissive()
}
#[inline]
pub fn is_diffuse(&self) -> bool {
self.flags.is_diffuse()
}
#[inline]
pub fn is_glossy(&self) -> bool {
self.flags.is_glossy()
}
#[inline]
pub fn is_specular(&self) -> bool {
self.flags.is_specular()
}
}

View file

@ -1,11 +1,13 @@
use crate::core::bxdf::BSDF; use crate::bxdfs::NormalizedFresnelBxDF;
use crate::core::bsdf::BSDF;
use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Point3fi, Vector3f}; use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Point3fi, Vector3f};
use crate::core::interaction::{InteractionData, Shadinggeom, SurfaceInteraction}; use crate::core::interaction::{InteractionBase, ShadingGeom, SurfaceInteraction};
use crate::core::pbrt::{Float, PI}; use crate::core::shape::Shape;
use crate::shapes::Shape;
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum}; use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum};
use crate::utils::Ptr;
use crate::utils::math::{catmull_rom_weights, square}; use crate::utils::math::{catmull_rom_weights, square};
use crate::utils::sampling::sample_catmull_rom_2d; use crate::utils::sampling::sample_catmull_rom_2d;
use crate::{Float, PI};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::sync::Arc; use std::sync::Arc;
@ -19,13 +21,13 @@ pub struct BSSRDFSample {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SubsurfaceInteraction { pub struct SubsurfaceInteraction {
pi: Point3fi, pub pi: Point3fi,
n: Normal3f, pub n: Normal3f,
ns: Normal3f, pub ns: Normal3f,
dpdu: Vector3f, pub dpdu: Vector3f,
dpdv: Vector3f, pub dpdv: Vector3f,
dpdus: Vector3f, pub dpdus: Vector3f,
dpdvs: Vector3f, pub dpdvs: Vector3f,
} }
impl SubsurfaceInteraction { impl SubsurfaceInteraction {
@ -63,20 +65,12 @@ impl From<SurfaceInteraction> for SubsurfaceInteraction {
impl From<&SubsurfaceInteraction> for SurfaceInteraction { impl From<&SubsurfaceInteraction> for SurfaceInteraction {
fn from(ssi: &SubsurfaceInteraction) -> SurfaceInteraction { fn from(ssi: &SubsurfaceInteraction) -> SurfaceInteraction {
SurfaceInteraction { SurfaceInteraction {
common: InteractionData { common: InteractionBase::new_minimal(ssi.pi, ssi.n),
pi: ssi.pi,
n: ssi.n,
wo: Vector3f::zero(),
time: 0.,
medium_interface: None,
medium: None,
},
uv: Point2f::zero(),
dpdu: ssi.dpdu, dpdu: ssi.dpdu,
dpdv: ssi.dpdv, dpdv: ssi.dpdv,
dndu: Normal3f::zero(), dndu: Normal3f::zero(),
dndv: Normal3f::zero(), dndv: Normal3f::zero(),
shading: Shadinggeom { shading: ShadingGeom {
n: ssi.ns, n: ssi.ns,
dpdu: ssi.dpdus, dpdu: ssi.dpdus,
dpdv: ssi.dpdvs, dpdv: ssi.dpdvs,
@ -84,70 +78,84 @@ impl From<&SubsurfaceInteraction> for SurfaceInteraction {
dndv: Normal3f::zero(), dndv: Normal3f::zero(),
}, },
face_index: 0, face_index: 0,
area_light: None, area_light: Ptr::null(),
material: None, material: Ptr::null(),
dpdx: Vector3f::zero(), dpdx: Vector3f::zero(),
dpdy: Vector3f::zero(), dpdy: Vector3f::zero(),
dudx: 0., dudx: 0.,
dvdx: 0., dvdx: 0.,
dudy: 0., dudy: 0.,
dvdy: 0., dvdy: 0.,
shape: Shape::default().into(), shape: Ptr::from(&Shape::default()),
} }
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct BSSRDFTable { pub struct BSSRDFTable {
rho_samples: Vec<Float>, pub n_rho: u32,
radius_samples: Vec<Float>, pub n_radius: u32,
profile: Vec<Float>, pub rho_samples: Ptr<Float>,
rho_eff: Vec<Float>, pub radius_samples: Ptr<Float>,
profile_cdf: Vec<Float>, pub profile: Ptr<Float>,
pub rho_eff: Ptr<Float>,
pub profile_cdf: Ptr<Float>,
} }
impl BSSRDFTable { impl BSSRDFTable {
pub fn new(n_rho_samples: usize, n_radius_samples: usize) -> Self { pub fn get_rho(&self) -> &[Float] {
let rho_samples: Vec<Float> = Vec::with_capacity(n_rho_samples); unsafe { core::slice::from_raw_parts(self.rho_samples.as_ref(), self.n_rho as usize) }
let radius_samples: Vec<Float> = Vec::with_capacity(n_radius_samples); }
let profile: Vec<Float> = Vec::with_capacity(n_radius_samples * n_rho_samples);
let rho_eff: Vec<Float> = Vec::with_capacity(n_rho_samples); pub fn get_radius(&self) -> &[Float] {
let profile_cdf: Vec<Float> = Vec::with_capacity(n_radius_samples * n_rho_samples); unsafe { core::slice::from_raw_parts(self.radius_samples.as_ref(), self.n_radius as usize) }
Self { }
rho_samples,
radius_samples, pub fn get_profile(&self) -> &[Float] {
profile, let n_profile = (self.n_rho * self.n_radius) as usize;
rho_eff, unsafe { core::slice::from_raw_parts(self.profile.as_ref(), n_profile) }
profile_cdf, }
pub fn get_cdf(&self) -> &[Float] {
let n_profile = (self.n_rho * self.n_radius) as usize;
unsafe { core::slice::from_raw_parts(self.profile_cdf.as_ref(), n_profile) }
}
pub fn eval_profile(&self, rho_index: u32, radius_index: u32) -> Float {
debug_assert!(rho_index < self.n_rho);
debug_assert!(radius_index < self.n_radius);
let idx = (rho_index * self.n_radius + radius_index) as usize;
unsafe { *self.profile.add(idx) }
} }
} }
pub fn eval_profile(&self, rho_index: usize, radius_index: usize) -> Float { #[repr(C)]
assert!(rho_index < self.rho_samples.len()); #[derive(Copy, Clone, Default, Debug)]
assert!(radius_index < self.radius_samples.len());
self.profile[rho_index * self.radius_samples.len() + radius_index]
}
}
#[derive(Clone, Default, Debug)]
pub struct BSSRDFProbeSegment { pub struct BSSRDFProbeSegment {
pub p0: Point3f, pub p0: Point3f,
pub p1: Point3f, pub p1: Point3f,
} }
#[enum_dispatch] #[enum_dispatch]
pub trait BSSRDFTrait: Send + Sync + std::fmt::Debug { pub trait BSSRDFTrait {
fn sample_sp(&self, u1: Float, u2: Point2f) -> Option<BSSRDFProbeSegment>; 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,
bxdf: NormalizedFresnelBxDF,
) -> BSSRDFSample;
} }
#[repr(C)]
#[enum_dispatch(BSSRDFTrait)] #[enum_dispatch(BSSRDFTrait)]
#[derive(Debug, Clone)] #[derive(Debug, Copy, Clone)]
pub enum BSSRDF { pub enum BSSRDF {
Tabulated(TabulatedBSSRDF), Tabulated(TabulatedBSSRDF),
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct TabulatedBSSRDF { pub struct TabulatedBSSRDF {
po: Point3f, po: Point3f,
wo: Vector3f, wo: Vector3f,
@ -155,7 +163,7 @@ pub struct TabulatedBSSRDF {
eta: Float, eta: Float,
sigma_t: SampledSpectrum, sigma_t: SampledSpectrum,
rho: SampledSpectrum, rho: SampledSpectrum,
table: Arc<BSSRDFTable>, table: Ptr<BSSRDFTable>,
} }
impl TabulatedBSSRDF { impl TabulatedBSSRDF {
@ -166,7 +174,7 @@ impl TabulatedBSSRDF {
eta: Float, eta: Float,
sigma_a: &SampledSpectrum, sigma_a: &SampledSpectrum,
sigma_s: &SampledSpectrum, sigma_s: &SampledSpectrum,
table: Arc<BSSRDFTable>, table: &BSSRDFTable,
) -> Self { ) -> Self {
let sigma_t = *sigma_a + *sigma_s; let sigma_t = *sigma_a + *sigma_s;
let rho = SampledSpectrum::safe_div(sigma_s, &sigma_t); let rho = SampledSpectrum::safe_div(sigma_s, &sigma_t);
@ -175,9 +183,9 @@ impl TabulatedBSSRDF {
wo, wo,
ns, ns,
eta, eta,
table,
sigma_t, sigma_t,
rho, rho,
table: Ptr::from(table),
} }
} }
@ -187,16 +195,17 @@ impl TabulatedBSSRDF {
pub fn sr(&self, r: Float) -> SampledSpectrum { pub fn sr(&self, r: Float) -> SampledSpectrum {
let mut sr_spectrum = SampledSpectrum::new(0.); let mut sr_spectrum = SampledSpectrum::new(0.);
let rho_samples = self.table.get_rho();
let radius_samples = self.table.get_radius();
for i in 0..N_SPECTRUM_SAMPLES { for i in 0..N_SPECTRUM_SAMPLES {
let r_optical = r * self.sigma_t[i]; let r_optical = r * self.sigma_t[i];
let (rho_offset, rho_weights) = let (rho_offset, rho_weights) = match catmull_rom_weights(rho_samples, self.rho[i]) {
match catmull_rom_weights(&self.table.rho_samples, self.rho[i]) {
Some(res) => res, Some(res) => res,
None => continue, None => continue,
}; };
let (radius_offset, radius_weights) = let (radius_offset, radius_weights) =
match catmull_rom_weights(&self.table.radius_samples, r_optical) { match catmull_rom_weights(radius_samples, r_optical) {
Some(res) => res, Some(res) => res,
None => continue, None => continue,
}; };
@ -206,7 +215,10 @@ impl TabulatedBSSRDF {
for (k, radius_weight) in radius_weights.iter().enumerate() { for (k, radius_weight) in radius_weights.iter().enumerate() {
let weight = rho_weight * radius_weight; let weight = rho_weight * radius_weight;
if weight != 0. { if weight != 0. {
sr += weight * self.table.eval_profile(rho_offset + j, radius_offset + k); sr += weight
* self
.table
.eval_profile(rho_offset + j as u32, radius_offset + k as u32);
} }
} }
} }
@ -216,7 +228,7 @@ impl TabulatedBSSRDF {
sr_spectrum[i] = sr; sr_spectrum[i] = sr;
} }
sr_spectrum *= self.sigma_t * self.sigma_t; sr_spectrum *= square(self.sigma_t);
SampledSpectrum::clamp_zero(&sr_spectrum) SampledSpectrum::clamp_zero(&sr_spectrum)
} }
@ -224,29 +236,30 @@ impl TabulatedBSSRDF {
if self.sigma_t[0] == 0. { if self.sigma_t[0] == 0. {
return None; return None;
} }
let (ret, _, _) = sample_catmull_rom_2d(
&self.table.rho_samples, let rho_samples = self.table.get_rho();
&self.table.radius_samples, let radius_samples = self.table.get_radius();
&self.table.profile, let profile = self.table.get_profile();
&self.table.profile_cdf, let cdf = self.table.get_cdf();
self.rho[0],
u, let (ret, _, _) =
); sample_catmull_rom_2d(rho_samples, radius_samples, profile, cdf, self.rho[0], u);
Some(ret / self.sigma_t[0]) Some(ret / self.sigma_t[0])
} }
pub fn pdf_sr(&self, r: Float) -> SampledSpectrum { pub fn pdf_sr(&self, r: Float) -> SampledSpectrum {
let mut pdf = SampledSpectrum::new(0.); let mut pdf = SampledSpectrum::new(0.);
let rhoeff_samples = self.table.get_rho();
let radius_samples = self.table.get_radius();
for i in 0..N_SPECTRUM_SAMPLES { for i in 0..N_SPECTRUM_SAMPLES {
let r_optical = r * self.sigma_t[i]; let r_optical = r * self.sigma_t[i];
let (rho_offset, rho_weights) = let (rho_offset, rho_weights) = match catmull_rom_weights(rhoeff_samples, self.rho[i]) {
match catmull_rom_weights(&self.table.rho_samples, self.rho[i]) {
Some(res) => res, Some(res) => res,
None => continue, None => continue,
}; };
let (radius_offset, radius_weights) = let (radius_offset, radius_weights) =
match catmull_rom_weights(&self.table.radius_samples, r_optical) { match catmull_rom_weights(radius_samples, r_optical) {
Some(res) => res, Some(res) => res,
None => continue, None => continue,
}; };
@ -256,12 +269,14 @@ impl TabulatedBSSRDF {
for (j, rho_weight) in rho_weights.iter().enumerate() { for (j, rho_weight) in rho_weights.iter().enumerate() {
if *rho_weight != 0. { if *rho_weight != 0. {
// Update _rhoEff_ and _sr_ for wavelength // Update _rhoEff_ and _sr_ for wavelength
rho_eff += self.table.rho_eff[rho_offset + j] * rho_weight; rho_eff += rhoeff_samples[rho_offset as usize + j] * rho_weight;
// Fix: Use .iter().enumerate() for 'k' // Fix: Use .iter().enumerate() for 'k'
for (k, radius_weight) in radius_weights.iter().enumerate() { for (k, radius_weight) in radius_weights.iter().enumerate() {
if *radius_weight != 0. { if *radius_weight != 0. {
sr += self.table.eval_profile(rho_offset + j, radius_offset + k) sr += self
.table
.eval_profile(rho_offset + j as u32, radius_offset + k as u32)
* rho_weight * rho_weight
* radius_weight; * radius_weight;
} }
@ -321,7 +336,11 @@ impl BSSRDFTrait for TabulatedBSSRDF {
}) })
} }
fn probe_intersection_to_sample(&self, _si: &SubsurfaceInteraction) -> BSSRDFSample { fn probe_intersection_to_sample(
&self,
_si: &SubsurfaceInteraction,
_bxdf: NormalizedFresnelBxDF,
) -> BSSRDFSample {
todo!() todo!()
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -8,9 +8,9 @@ use crate::core::medium::Medium;
use crate::core::options::RenderingCoordinateSystem; use crate::core::options::RenderingCoordinateSystem;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample; use crate::core::sampler::CameraSample;
use crate::images::ImageMetadata;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::lerp; use crate::utils::math::lerp;
use crate::utils::ptr::Ptr;
use crate::utils::transform::{AnimatedTransform, Transform}; use crate::utils::transform::{AnimatedTransform, Transform};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
@ -109,27 +109,17 @@ pub struct CameraBase {
pub camera_transform: CameraTransform, pub camera_transform: CameraTransform,
pub shutter_open: Float, pub shutter_open: Float,
pub shutter_close: Float, pub shutter_close: Float,
pub film: *const Film,
pub medium: *const Medium,
pub min_pos_differential_x: Vector3f, pub min_pos_differential_x: Vector3f,
pub min_pos_differential_y: Vector3f, pub min_pos_differential_y: Vector3f,
pub min_dir_differential_x: Vector3f, pub min_dir_differential_x: Vector3f,
pub min_dir_differential_y: Vector3f, pub min_dir_differential_y: Vector3f,
pub film: Ptr<Film>,
pub medium: Ptr<Medium>,
} }
#[cfg(not(target_os = "cuda"))]
impl CameraBase {
pub fn init_metadata(&self, metadata: &mut ImageMetadata) {
let camera_from_world: Transform =
self.camera_transform.camera_from_world(self.shutter_open);
metadata.camera_from_world = Some(camera_from_world.get_matrix());
}
}
#[enum_dispatch(CameraTrait)]
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
#[enum_dispatch(CameraTrait)]
pub enum Camera { pub enum Camera {
Perspective(PerspectiveCamera), Perspective(PerspectiveCamera),
Orthographic(OrthographicCamera), Orthographic(OrthographicCamera),
@ -139,19 +129,19 @@ pub enum Camera {
#[enum_dispatch] #[enum_dispatch]
pub trait CameraTrait { pub trait CameraTrait {
#[cfg(not(target_os = "cuda"))]
fn init_metadata(&self, metadata: &mut ImageMetadata);
fn base(&self) -> &CameraBase; fn base(&self) -> &CameraBase;
fn generate_ray(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option<CameraRay>; fn generate_ray(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option<CameraRay>;
fn get_film(&self) -> &Film {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
fn get_film(&self) -> Result<&Film, String> { {
if self.film.is_null() { if self.base().film.is_null() {
return Err("Camera error: Film pointer is null.".to_string()); panic!(
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
);
} }
Ok(unsafe { &*self.film }) }
&self.base().film
} }
fn sample_time(&self, u: Float) -> Float { fn sample_time(&self, u: Float) -> Float {
@ -173,9 +163,6 @@ pub trait CameraTrait {
sample: CameraSample, sample: CameraSample,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> Option<CameraRay> { ) -> Option<CameraRay> {
match self {
Camera::Orthographic(c) => c.generate_ray_differential(sample, lambda),
_ => {
let mut central_cam_ray = self.generate_ray(sample, lambda)?; let mut central_cam_ray = self.generate_ray(sample, lambda)?;
let mut rd = RayDifferential::default(); let mut rd = RayDifferential::default();
let mut rx_found = false; let mut rx_found = false;
@ -186,10 +173,10 @@ pub trait CameraTrait {
s_shift.p_film[0] += eps; s_shift.p_film[0] += eps;
if let Some(rx_cam_ray) = self.generate_ray(s_shift, lambda) { if let Some(rx_cam_ray) = self.generate_ray(s_shift, lambda) {
rd.rx_origin = central_cam_ray.ray.o rd.rx_origin =
+ (rx_cam_ray.ray.o - central_cam_ray.ray.o) / eps; central_cam_ray.ray.o + (rx_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
rd.rx_direction = central_cam_ray.ray.d rd.rx_direction =
+ (rx_cam_ray.ray.d - central_cam_ray.ray.d) / eps; central_cam_ray.ray.d + (rx_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
rx_found = true; rx_found = true;
break; break;
} }
@ -200,23 +187,21 @@ pub trait CameraTrait {
s_shift.p_film[1] += eps; s_shift.p_film[1] += eps;
if let Some(ry_cam_ray) = self.generate_ray(s_shift, lambda) { if let Some(ry_cam_ray) = self.generate_ray(s_shift, lambda) {
rd.ry_origin = central_cam_ray.ray.o rd.ry_origin =
+ (ry_cam_ray.ray.o - central_cam_ray.ray.o) / eps; central_cam_ray.ray.o + (ry_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
rd.ry_direction = central_cam_ray.ray.d rd.ry_direction =
+ (ry_cam_ray.ray.d - central_cam_ray.ray.d) / eps; central_cam_ray.ray.d + (ry_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
ry_found = true; ry_found = true;
break; break;
} }
} }
if rx_found && ry_found { if rx_found && ry_found {
central_cam_ray.ray.differential = Some(rd); central_cam_ray.ray.differential = rd;
} }
Some(central_cam_ray) Some(central_cam_ray)
} }
}
}
fn approximate_dp_dxy( fn approximate_dp_dxy(
&self, &self,
@ -242,13 +227,13 @@ pub trait CameraTrait {
Point3f::new(0., 0., 0.) + self.base().min_pos_differential_x, Point3f::new(0., 0., 0.) + self.base().min_pos_differential_x,
Vector3f::new(0., 0., 1.) + self.base().min_dir_differential_x, Vector3f::new(0., 0., 1.) + self.base().min_dir_differential_x,
None, None,
None, &Ptr::default(),
); );
let y_ray = Ray::new( let y_ray = Ray::new(
Point3f::new(0., 0., 0.) + self.base().min_pos_differential_y, Point3f::new(0., 0., 0.) + self.base().min_pos_differential_y,
Vector3f::new(0., 0., 1.) + self.base().min_dir_differential_y, Vector3f::new(0., 0., 1.) + self.base().min_dir_differential_y,
None, None,
None, &Ptr::default(),
); );
let n_down = Vector3f::from(n_down_z); let n_down = Vector3f::from(n_down_z);
let tx = -(n_down.dot(y_ray.o.into())) / n_down.dot(x_ray.d); let tx = -(n_down.dot(y_ray.o.into())) / n_down.dot(x_ray.d);

View file

@ -4,14 +4,17 @@ use std::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
}; };
use crate::Float;
use crate::core::geometry::Point2f; use crate::core::geometry::Point2f;
use crate::core::pbrt::{Float, find_interval};
use crate::core::spectrum::Spectrum; use crate::core::spectrum::Spectrum;
use crate::utils::Ptr;
use crate::utils::find_interval;
use crate::utils::math::{SquareMatrix, SquareMatrix3f, clamp, evaluate_polynomial, lerp}; use crate::utils::math::{SquareMatrix, SquareMatrix3f, clamp, evaluate_polynomial, lerp};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy)]
pub struct XYZ { pub struct XYZ {
pub x: Float, pub x: Float,
pub y: Float, pub y: Float,
@ -24,6 +27,12 @@ impl From<(Float, Float, Float)> for XYZ {
} }
} }
impl From<[Float; 3]> for XYZ {
fn from(triplet: [Float; 3]) -> Self {
XYZ::new(triplet[0], triplet[1], triplet[2])
}
}
impl<'a> IntoIterator for &'a XYZ { impl<'a> IntoIterator for &'a XYZ {
type Item = &'a Float; type Item = &'a Float;
type IntoIter = std::array::IntoIter<&'a Float, 3>; type IntoIter = std::array::IntoIter<&'a Float, 3>;
@ -81,9 +90,9 @@ impl XYZ {
} }
} }
impl Index<usize> for XYZ { impl Index<u32> for XYZ {
type Output = Float; type Output = Float;
fn index(&self, index: usize) -> &Self::Output { fn index(&self, index: u32) -> &Self::Output {
debug_assert!(index < 3); debug_assert!(index < 3);
match index { match index {
0 => &self.x, 0 => &self.x,
@ -93,8 +102,8 @@ impl Index<usize> for XYZ {
} }
} }
impl IndexMut<usize> for XYZ { impl IndexMut<u32> for XYZ {
fn index_mut(&mut self, index: usize) -> &mut Self::Output { fn index_mut(&mut self, index: u32) -> &mut Self::Output {
debug_assert!(index < 3); debug_assert!(index < 3);
match index { match index {
0 => &mut self.x, 0 => &mut self.x,
@ -247,7 +256,8 @@ impl fmt::Display for XYZ {
} }
} }
#[derive(Debug, Default, Copy, Clone)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy)]
pub struct RGB { pub struct RGB {
pub r: Float, pub r: Float,
pub g: Float, pub g: Float,
@ -282,7 +292,19 @@ impl RGB {
self.r.max(self.g).max(self.b) self.r.max(self.g).max(self.b)
} }
pub fn max_component_index(&self) -> usize { pub fn min_component_value(&self) -> Float {
self.r.min(self.g).min(self.b)
}
pub fn min_component_index(&self) -> u32 {
if self.r < self.g {
if self.r < self.b { 0 } else { 2 }
} else {
if self.g < self.b { 1 } else { 2 }
}
}
pub fn max_component_index(&self) -> u32 {
if self.r > self.g { if self.r > self.g {
if self.r > self.b { 0 } else { 2 } if self.r > self.b { 0 } else { 2 }
} else { } else {
@ -290,8 +312,40 @@ impl RGB {
} }
} }
pub fn clamp_zero(rgb: Self) -> Self { pub fn clamp(&self, min: Float, max: Float) -> Self {
RGB::new(rgb.r.max(0.), rgb.b.max(0.), rgb.g.max(0.)) RGB::new(
clamp(self.r, min, max),
clamp(self.g, min, max),
clamp(self.b, min, max),
)
}
pub fn clamp_zero(&self) -> Self {
RGB::new(self.r.max(0.), self.b.max(0.), self.g.max(0.))
}
}
impl Index<u32> for RGB {
type Output = Float;
fn index(&self, index: u32) -> &Self::Output {
debug_assert!(index < 3);
match index {
0 => &self.r,
1 => &self.g,
_ => &self.b,
}
}
}
impl Index<i32> for RGB {
type Output = Float;
fn index(&self, index: i32) -> &Self::Output {
debug_assert!(index < 3);
match index {
0 => &self.r,
1 => &self.g,
_ => &self.b,
}
} }
} }
@ -307,6 +361,28 @@ impl Index<usize> for RGB {
} }
} }
impl IndexMut<u32> for RGB {
fn index_mut(&mut self, index: u32) -> &mut Self::Output {
debug_assert!(index < 3);
match index {
0 => &mut self.r,
1 => &mut self.g,
_ => &mut self.b,
}
}
}
impl IndexMut<i32> for RGB {
fn index_mut(&mut self, index: i32) -> &mut Self::Output {
debug_assert!(index < 3);
match index {
0 => &mut self.r,
1 => &mut self.g,
_ => &mut self.b,
}
}
}
impl IndexMut<usize> for RGB { impl IndexMut<usize> for RGB {
fn index_mut(&mut self, index: usize) -> &mut Self::Output { fn index_mut(&mut self, index: usize) -> &mut Self::Output {
debug_assert!(index < 3); debug_assert!(index < 3);
@ -549,10 +625,23 @@ impl RGBSigmoidPolynomial {
} }
#[enum_dispatch] #[enum_dispatch]
pub trait ColorEncodingTrait: 'static + Send + Sync + fmt::Debug + fmt::Display { pub trait ColorEncodingTrait: 'static + Send + Sync {
fn from_linear_slice(&self, vin: &[Float], vout: &mut [u8]); fn from_linear(&self, vin: &[Float], vout: &mut [u8]);
fn to_linear_slice(&self, vin: &[u8], vout: &mut [Float]); fn to_linear(&self, vin: &[u8], vout: &mut [Float]);
fn to_float_linear(&self, v: Float) -> Float; fn to_float_linear(&self, v: Float) -> Float;
fn from_linear_scalar(&self, v: Float) -> u8 {
let mut out = [0u8; 1];
self.from_linear(&[v], &mut out);
out[0]
}
fn to_linear_scalar(&self, v: u8) -> Float {
let mut out = [0.0; 1];
self.to_linear(&[v], &mut out);
out[0]
}
fn type_id(&self) -> TypeId { fn type_id(&self) -> TypeId {
TypeId::of::<Self>() TypeId::of::<Self>()
} }
@ -576,19 +665,29 @@ impl fmt::Display for ColorEncoding {
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct LinearEncoding; pub struct LinearEncoding;
impl ColorEncodingTrait for LinearEncoding { impl ColorEncodingTrait for LinearEncoding {
fn from_linear_slice(&self, vin: &[Float], vout: &mut [u8]) { fn from_linear(&self, vin: &[Float], vout: &mut [u8]) {
for (i, &v) in vin.iter().enumerate() { for (i, &v) in vin.iter().enumerate() {
vout[i] = (v.clamp(0.0, 1.0) * 255.0 + 0.5) as u8; vout[i] = (v.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
} }
} }
fn to_linear_slice(&self, vin: &[u8], vout: &mut [Float]) {
fn to_linear(&self, vin: &[u8], vout: &mut [Float]) {
for (i, &v) in vin.iter().enumerate() { for (i, &v) in vin.iter().enumerate() {
vout[i] = v as Float / 255.0; vout[i] = v as Float / 255.0;
} }
} }
fn to_float_linear(&self, v: Float) -> Float { fn to_float_linear(&self, v: Float) -> Float {
v v
} }
fn from_linear_scalar(&self, v: Float) -> u8 {
(v.clamp(0.0, 1.0) * 255.0 + 0.5) as u8
}
fn to_linear_scalar(&self, v: u8) -> Float {
v as Float / 255.0
}
} }
impl fmt::Display for LinearEncoding { impl fmt::Display for LinearEncoding {
@ -601,7 +700,7 @@ impl fmt::Display for LinearEncoding {
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct SRGBEncoding; pub struct SRGBEncoding;
impl ColorEncodingTrait for SRGBEncoding { impl ColorEncodingTrait for SRGBEncoding {
fn from_linear_slice(&self, vin: &[Float], vout: &mut [u8]) { fn from_linear(&self, vin: &[Float], vout: &mut [u8]) {
for (i, &v_linear) in vin.iter().enumerate() { for (i, &v_linear) in vin.iter().enumerate() {
let v = v_linear.clamp(0.0, 1.0); let v = v_linear.clamp(0.0, 1.0);
let v_encoded = if v <= 0.0031308 { let v_encoded = if v <= 0.0031308 {
@ -613,12 +712,33 @@ impl ColorEncodingTrait for SRGBEncoding {
} }
} }
fn to_linear_slice(&self, vin: &[u8], vout: &mut [Float]) { fn to_linear(&self, vin: &[u8], vout: &mut [Float]) {
for (i, &v) in vin.iter().enumerate() { for (i, &v) in vin.iter().enumerate() {
vout[i] = SRGB_TO_LINEAR_LUT[v as usize]; vout[i] = SRGB_TO_LINEAR_LUT[v as usize];
} }
} }
fn from_linear_scalar(&self, v: Float) -> u8 {
let v_clamped = v.clamp(0.0, 1.0);
let v_encoded = if v_clamped <= 0.0031308 {
v_clamped * 12.92
} else {
1.055 * v_clamped.powf(1.0 / 2.4) - 0.055
};
(v_encoded * 255.0 + 0.5) as u8
}
fn to_linear_scalar(&self, v: u8) -> Float {
// Normalize 0-255 to 0.0-1.0 first
let v_float = v as Float / 255.0;
// Apply sRGB -> Linear math
if v_float <= 0.04045 {
v_float / 12.92
} else {
((v_float + 0.055) / 1.055).powf(2.4)
}
}
fn to_float_linear(&self, v: Float) -> Float { fn to_float_linear(&self, v: Float) -> Float {
let v = v.clamp(0.0, 1.0); let v = v.clamp(0.0, 1.0);
if v <= 0.04045 { if v <= 0.04045 {
@ -897,7 +1017,7 @@ const SRGB_TO_LINEAR_LUT: [Float; 256] = [
1.0000000000, 1.0000000000,
]; ];
pub const RES: usize = 64; pub const RES: u32 = 64;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default)] #[derive(Clone, Copy, Debug, Default)]
@ -919,6 +1039,18 @@ impl Add for Coeffs {
} }
} }
impl Sub for Coeffs {
type Output = Self;
#[inline(always)]
fn sub(self, rhs: Self) -> Self {
Self {
c0: self.c0 - rhs.c0,
c1: self.c1 - rhs.c1,
c2: self.c2 - rhs.c2,
}
}
}
impl Mul<Float> for Coeffs { impl Mul<Float> for Coeffs {
type Output = Self; type Output = Self;
#[inline(always)] #[inline(always)]
@ -934,8 +1066,9 @@ impl Mul<Float> for Coeffs {
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct RGBToSpectrumTable { pub struct RGBToSpectrumTable {
pub z_nodes: *const Float, pub z_nodes: Ptr<Float>,
pub coeffs: *const Coeffs, pub coeffs: Ptr<Coeffs>,
pub n_nodes: u32,
} }
unsafe impl Send for RGBToSpectrumTable {} unsafe impl Send for RGBToSpectrumTable {}
@ -943,16 +1076,16 @@ unsafe impl Sync for RGBToSpectrumTable {}
impl RGBToSpectrumTable { impl RGBToSpectrumTable {
#[inline(always)] #[inline(always)]
fn get_coeffs(&self, bucket: usize, z: usize, y: usize, x: usize) -> Coeffs { fn get_coeffs(&self, bucket: u32, z: u32, y: u32, x: u32) -> Coeffs {
let offset = bucket * (RES * RES * RES) + z * (RES * RES) + y * (RES) + x; let offset = bucket * (RES * RES * RES) + z * (RES * RES) + y * (RES) + x;
unsafe { *self.coeffs.add(offset) } unsafe { *self.coeffs.add(offset as usize) }
} }
pub fn evaluate(&self, rgb: RGB) -> RGBSigmoidPolynomial { pub fn evaluate(&self, rgb: RGB) -> RGBSigmoidPolynomial {
let m = rgb.max_component_value(); let m = rgb.max_component_value();
let min_val = rgb.min_component_value(); let min_val = rgb.min_component_value();
if m - min_val < 1e-4 { if m - min_val < 1e-4 {
let x = clamp(rgb[0], 1e-4, 0.9999); let x: Float = clamp(rgb[0], 1e-4, 0.9999);
let c2 = (0.5 - x) / (x * (1.0 - x)).sqrt(); let c2 = (0.5 - x) / (x * (1.0 - x)).sqrt();
return RGBSigmoidPolynomial::new(0.0, 0.0, c2); return RGBSigmoidPolynomial::new(0.0, 0.0, c2);
} }
@ -981,25 +1114,26 @@ impl RGBToSpectrumTable {
let x = coord_a / z; let x = coord_a / z;
let y = coord_b / z; let y = coord_b / z;
let z_nodes_slice = unsafe { core::slice::from_raw_parts(self.z_nodes, RES) }; let z_nodes_slice =
let zi = find_interval(RES, |i| z_nodes_slice[i] < z); unsafe { core::slice::from_raw_parts(self.z_nodes.as_raw(), RES as usize) };
let zi = find_interval(RES, |i| z_nodes_slice[i as usize] < z) as usize;
let dz = (z - z_nodes_slice[zi]) / (z_nodes_slice[zi + 1] - z_nodes_slice[zi]); let dz = (z - z_nodes_slice[zi]) / (z_nodes_slice[zi + 1] - z_nodes_slice[zi]);
let x_float = x * (RES - 1) as Float; let x_float = x * (RES - 1) as Float;
let xi = (x_float as usize).min(RES - 2); let xi = (x_float as u32).min(RES - 2);
let dx = x_float - xi as Float; let dx = x_float - xi as Float;
let y_float = y * (RES - 1) as Float; let y_float = y * (RES - 1) as Float;
let yi = (y_float as usize).min(RES - 2); let yi = (y_float as u32).min(RES - 2);
let dy = y_float - yi as Float; let dy = y_float - yi as Float;
let c000 = self.get_coeffs(c_idx, zi, yi, xi); let c000 = self.get_coeffs(c_idx, zi as u32, yi, xi);
let c001 = self.get_coeffs(c_idx, zi, yi, xi + 1); let c001 = self.get_coeffs(c_idx, zi as u32, yi, xi + 1);
let c010 = self.get_coeffs(c_idx, zi, yi + 1, xi); let c010 = self.get_coeffs(c_idx, zi as u32, yi + 1, xi);
let c011 = self.get_coeffs(c_idx, zi, yi + 1, xi + 1); let c011 = self.get_coeffs(c_idx, zi as u32, yi + 1, xi + 1);
let c100 = self.get_coeffs(c_idx, zi + 1, yi, xi); let c100 = self.get_coeffs(c_idx, zi as u32 + 1, yi, xi);
let c101 = self.get_coeffs(c_idx, zi + 1, yi, xi + 1); let c101 = self.get_coeffs(c_idx, zi as u32 + 1, yi, xi + 1);
let c110 = self.get_coeffs(c_idx, zi + 1, yi + 1, xi); let c110 = self.get_coeffs(c_idx, zi as u32 + 1, yi + 1, xi);
let c111 = self.get_coeffs(c_idx, zi + 1, yi + 1, xi + 1); let c111 = self.get_coeffs(c_idx, zi as u32 + 1, yi + 1, xi + 1);
let c00 = lerp(dx, c000, c001); let c00 = lerp(dx, c000, c001);
let c01 = lerp(dx, c010, c011); let c01 = lerp(dx, c010, c011);
let c10 = lerp(dx, c100, c101); let c10 = lerp(dx, c100, c101);
@ -1015,3 +1149,28 @@ impl RGBToSpectrumTable {
} }
} }
} }
const LMS_FROM_XYZ: SquareMatrix3f = SquareMatrix::new([
[0.8951, 0.2664, -0.1614],
[-0.7502, 1.7135, 0.0367],
[0.0389, -0.0685, 1.0296],
]);
const XYZ_FROM_LMS: SquareMatrix3f = SquareMatrix::new([
[0.986993, -0.147054, 0.159963],
[0.432305, 0.51836, 0.0492912],
[-0.00852866, 0.0400428, 0.968487],
]);
pub fn white_balance(src_white: Point2f, target_white: Point2f) -> SquareMatrix3f {
let src_xyz = XYZ::from_xyy(src_white, None);
let dst_xyz = XYZ::from_xyy(target_white, None);
let src_lms = LMS_FROM_XYZ * src_xyz;
let dst_lms = LMS_FROM_XYZ * dst_xyz;
let lms_correct = SquareMatrix3f::diag(&[
dst_lms[0] / src_lms[0],
dst_lms[1] / src_lms[1],
dst_lms[2] / src_lms[2],
]);
XYZ_FROM_LMS * lms_correct * LMS_FROM_XYZ
}

View file

@ -1,84 +1,44 @@
use crate::core::camera::CameraTransform; use crate::core::camera::CameraTransform;
use crate::core::color::{MatrixMulColor, RGB, SRGB, XYZ, white_balance}; use crate::core::color::{MatrixMulColor, RGB, SRGB, XYZ, white_balance};
use crate::core::filter::Filter; use crate::core::filter::{Filter, FilterTrait};
use crate::core::geometry::{ use crate::core::geometry::{
Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Tuple, Vector2f, Vector2fi, Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Tuple, Vector2f, Vector2fi,
Vector2i, Vector3f, Vector2i, Vector3f,
}; };
use crate::core::image::{DeviceImage, PixelFormat};
use crate::core::interaction::SurfaceInteraction; use crate::core::interaction::SurfaceInteraction;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::spectrum::{Spectrum, SpectrumTrait, StandardSpectra}; use crate::core::spectrum::{Spectrum, SpectrumTrait, StandardSpectra};
use crate::images::{Image, ImageChannelDesc, ImageChannelValues, ImageMetadata, PixelFormat};
use crate::spectra::{ use crate::spectra::{
ConstantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, ConstantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES,
PiecewiseLinearSpectrum, RGBColorSpace, SampledSpectrum, SampledWavelengths, colorspace, PiecewiseLinearSpectrum, RGBColorSpace, SampledSpectrum, SampledWavelengths, colorspace,
get_named_spectrum,
}; };
use crate::utils::AtomicFloat; use crate::utils::containers::DeviceArray2D;
use crate::utils::containers::Array2D;
use crate::utils::math::linear_least_squares; use crate::utils::math::linear_least_squares;
use crate::utils::math::{SquareMatrix, wrap_equal_area_square}; use crate::utils::math::{SquareMatrix, wrap_equal_area_square};
use crate::utils::sampling::VarianceEstimator; use crate::utils::sampling::VarianceEstimator;
use crate::utils::transform::AnimatedTransform; use crate::utils::transform::AnimatedTransform;
use crate::utils::{AtomicFloat, Ptr};
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Debug)]
pub struct RGBFilm { pub struct RGBFilm {
pub base: FilmBase, pub base: FilmBase,
pub max_component_value: Float, pub max_component_value: Float,
pub write_fp16: bool, pub write_fp16: bool,
pub filter_integral: Float, pub filter_integral: Float,
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>, pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
pub pixels: Array2D<RGBPixel>, pub pixels: DeviceArray2D<RGBPixel>,
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Debug)]
pub struct RGBPixel { pub struct RGBPixel {
rgb_sum: [AtomicFloat; 3], rgb_sum: [AtomicFloat; 3],
weight_sum: AtomicFloat, weight_sum: AtomicFloat,
rgb_splat: [AtomicFloat; 3], rgb_splat: [AtomicFloat; 3],
} }
#[cfg(not(target_os = "cuda"))]
impl RGBFilm {
pub fn new(
base: FilmBase,
colorspace: &RGBColorSpace,
max_component_value: Float,
write_fp16: bool,
) -> Self {
let sensor_ptr = base.sensor;
if sensor_ptr.is_null() {
panic!("Film must have a sensor");
}
let sensor = unsafe { &*sensor_ptr };
let filter_integral = base.filter.integral();
let sensor_matrix = sensor.xyz_from_sensor_rgb;
let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor_matrix;
let width = base.pixel_bounds.p_max.x() - base.pixel_bounds.p_min.x();
let height = base.pixel_bounds.p_max.y() - base.pixel_bounds.p_min.y();
let count = (width * height) as usize;
let mut pixel_vec = Vec::with_capacity(count);
for _ in 0..count {
pixel_vec.push(RGBPixel::default());
}
let pixels_array = Array2D::new(base.pixel_bounds);
Self {
base,
max_component_value,
write_fp16,
filter_integral,
output_rgbf_from_sensor_rgb,
pixels: std::sync::Arc::new(pixels_array),
}
}
}
impl RGBFilm { impl RGBFilm {
pub fn base(&self) -> &FilmBase { pub fn base(&self) -> &FilmBase {
&self.base &self.base
@ -88,16 +48,16 @@ impl RGBFilm {
&mut self.base &mut self.base
} }
pub fn get_sensor(&self) -> &PixelSensor { pub fn get_sensor(&self) -> &DevicePixelSensor {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
{ {
if self.sensor.is_null() { if self.base.sensor.is_null() {
panic!( panic!(
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction." "FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
); );
} }
} }
unsafe { &*self.sensor } &self.base.sensor
} }
pub fn add_sample( pub fn add_sample(
@ -108,7 +68,7 @@ impl RGBFilm {
_vi: Option<&VisibleSurface>, _vi: Option<&VisibleSurface>,
weight: Float, weight: Float,
) { ) {
let sensor = unsafe { self.get_sensor() }; let sensor = self.get_sensor();
let mut rgb = sensor.to_sensor_rgb(l, lambda); let mut rgb = sensor.to_sensor_rgb(l, lambda);
let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max); let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max);
if m > self.max_component_value { if m > self.max_component_value {
@ -117,13 +77,13 @@ impl RGBFilm {
let pixel = &self.pixels[p_film]; let pixel = &self.pixels[p_film];
for c in 0..3 { for c in 0..3 {
pixel.rgb_sum[c].add((weight * rgb[c]) as f64); pixel.rgb_sum[c].add(weight * rgb[c as u32]);
} }
pixel.weight_sum.add(weight as f64); pixel.weight_sum.add(weight);
} }
pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) { pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) {
let sensor = unsafe { self.get_sensor() }; let sensor = self.get_sensor();
let mut rgb = sensor.to_sensor_rgb(l, lambda); let mut rgb = sensor.to_sensor_rgb(l, lambda);
let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max); let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max);
if m > self.max_component_value { if m > self.max_component_value {
@ -131,48 +91,49 @@ impl RGBFilm {
} }
let p_discrete = p + Vector2f::new(0.5, 0.5); let p_discrete = p + Vector2f::new(0.5, 0.5);
let radius = self.get_filter().radius(); let radius = self.base.filter.radius();
let splat_bounds = Bounds2i::from_points( let splat_bounds = Bounds2i::from_points(
(p_discrete - radius).floor(), (p_discrete - radius).floor(),
(p_discrete + radius).floor() + Vector2i::new(1, 1), (p_discrete + radius).floor() + Vector2i::new(1, 1),
); );
let splat_intersect = splat_bounds.union(self.pixel_bounds()); let splat_intersect = splat_bounds.union(self.base().pixel_bounds);
for pi in &splat_intersect { for pi in &splat_intersect {
let pi_f: Point2f = (*pi).into(); let pi_f: Point2f = (*pi).into();
let wt = self let wt = self
.get_filter() .base()
.filter
.evaluate((p - pi_f - Vector2f::new(0.5, 0.5)).into()); .evaluate((p - pi_f - Vector2f::new(0.5, 0.5)).into());
if wt != 0. { if wt != 0. {
let pixel = &self.pixels[*pi]; let pixel = &self.pixels[*pi];
for i in 0..3 { for i in 0..3 {
pixel.rgb_splat[i].add((wt * rgb[i]) as f64); pixel.rgb_splat[i].add((wt * rgb[i as u32]) as f32);
} }
} }
} }
} }
pub fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option<Float>) -> RGB { pub fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option<Float>) -> RGB {
let pixel = unsafe { &self.pixels.get(p.x(), p.y())[p] }; let pixel = &self.pixels.get(p);
let mut rgb = RGB::new( let mut rgb = RGB::new(
pixel.rgb_sum[0].load() as Float, pixel.rgb_sum[0].get() as Float,
pixel.rgb_sum[1].load() as Float, pixel.rgb_sum[1].get() as Float,
pixel.rgb_sum[2].load() as Float, pixel.rgb_sum[2].get() as Float,
); );
let weight_sum = pixel.weight_sum.load(); let weight_sum = pixel.weight_sum.get();
if weight_sum != 0. { if weight_sum != 0. {
rgb /= weight_sum as Float rgb /= weight_sum as Float
} }
if let Some(splat) = splat_scale { if let Some(splat) = splat_scale {
for c in 0..3 { for c in 0..3 {
let splat_val = pixel.rgb_splat[c].load(); let splat_val = pixel.rgb_splat[c].get();
rgb[c] += splat * splat_val as Float / self.filter_integral; rgb[c] += splat * splat_val as Float / self.filter_integral;
} }
} else { } else {
for c in 0..3 { for c in 0..3 {
let splat_val = pixel.rgb_splat[c].load(); let splat_val = pixel.rgb_splat[c].get();
rgb[c] += splat_val as Float / self.filter_integral; rgb[c] += splat_val as Float / self.filter_integral;
} }
} }
@ -180,8 +141,8 @@ impl RGBFilm {
} }
pub fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB { pub fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB {
let sensor = unsafe { self.get_sensor() }; let sensor = self.get_sensor();
let mut sensor_rgb = sensor.to_sensor_rgb(l, lambda); let sensor_rgb = sensor.to_sensor_rgb(l, lambda);
self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb) self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb)
} }
@ -190,8 +151,10 @@ impl RGBFilm {
} }
} }
#[derive(Debug, Clone, Copy, Default)] #[repr(C)]
struct GBufferPixel { #[derive(Debug, Default)]
#[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
pub struct GBufferPixel {
pub rgb_sum: [AtomicFloat; 3], pub rgb_sum: [AtomicFloat; 3],
pub weight_sum: AtomicFloat, pub weight_sum: AtomicFloat,
pub g_bugger_weight_sum: AtomicFloat, pub g_bugger_weight_sum: AtomicFloat,
@ -206,51 +169,19 @@ struct GBufferPixel {
pub rgb_variance: VarianceEstimator, pub rgb_variance: VarianceEstimator,
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Debug)]
#[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
pub struct GBufferFilm { pub struct GBufferFilm {
pub base: FilmBase, pub base: FilmBase,
output_from_render: AnimatedTransform, pub output_from_render: AnimatedTransform,
apply_inverse: bool, pub apply_inverse: bool,
pixels: Array2D<GBufferPixel>, pub pixels: DeviceArray2D<GBufferPixel>,
colorspace: RGBColorSpace, pub colorspace: RGBColorSpace,
max_component_value: Float, pub max_component_value: Float,
write_fp16: bool, pub write_fp16: bool,
filter_integral: Float, pub filter_integral: Float,
output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>, pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
}
#[cfg(not(target_os = "cuda"))]
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 sensor_ptr = base.sensor;
if sensor_ptr.is_null() {
panic!("Film must have a sensor");
}
let sensor = unsafe { &*sensor_ptr };
let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor.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,
colorspace: colorspace.clone(),
max_component_value,
write_fp16,
filter_integral,
output_rgbf_from_sensor_rgb,
}
}
} }
impl GBufferFilm { impl GBufferFilm {
@ -262,20 +193,31 @@ impl GBufferFilm {
&mut self.base &mut self.base
} }
pub fn get_sensor(&self) -> &PixelSensor { pub fn get_sensor(&self) -> &DevicePixelSensor {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
{ {
if self.sensor.is_null() { if self.base.sensor.is_null() {
panic!( panic!(
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction." "FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
); );
} }
} }
unsafe { &*self.sensor } &self.base.sensor
}
pub fn add_sample(
&mut self,
_p_film: Point2i,
_l: SampledSpectrum,
_lambda: &SampledWavelengths,
_visible_surface: Option<&VisibleSurface>,
_weight: Float,
) {
todo!()
} }
pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) { pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) {
let sensor = unsafe { self.get_sensor() }; let sensor = self.get_sensor();
let mut rgb = sensor.to_sensor_rgb(l, lambda); let mut rgb = sensor.to_sensor_rgb(l, lambda);
let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max); let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max);
if m > self.max_component_value { if m > self.max_component_value {
@ -283,54 +225,55 @@ impl GBufferFilm {
} }
let p_discrete = p + Vector2f::new(0.5, 0.5); let p_discrete = p + Vector2f::new(0.5, 0.5);
let radius = self.get_filter().radius(); let radius = self.base().filter.radius();
let splat_bounds = Bounds2i::from_points( let splat_bounds = Bounds2i::from_points(
(p_discrete - radius).floor(), (p_discrete - radius).floor(),
(p_discrete + radius).floor() + Vector2i::new(1, 1), (p_discrete + radius).floor() + Vector2i::new(1, 1),
); );
let splat_intersect = splat_bounds.union(self.pixel_bounds()); let splat_intersect = splat_bounds.union(self.base.pixel_bounds);
for pi in &splat_intersect { for pi in &splat_intersect {
let pi_f: Point2f = (*pi).into(); let pi_f: Point2f = (*pi).into();
let wt = self let wt = self
.get_filter() .base
.filter
.evaluate((p - pi_f - Vector2f::new(0.5, 0.5)).into()); .evaluate((p - pi_f - Vector2f::new(0.5, 0.5)).into());
if wt != 0. { if wt != 0. {
let pixel = &self.pixels[*pi]; let pixel = &self.pixels[*pi];
for i in 0..3 { for i in 0..3 {
pixel.rgb_splat[i].add((wt * rgb[i]) as f64); pixel.rgb_splat[i].add((wt * rgb[i]) as f32);
} }
} }
} }
} }
pub fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB { pub fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB {
let sensor = unsafe { self.get_sensor() }; let sensor = self.get_sensor();
let sensor_rgb = sensor.to_sensor_rgb(l, lambda); let sensor_rgb = sensor.to_sensor_rgb(l, lambda);
self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb) self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb)
} }
pub fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option<Float>) -> RGB { pub fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option<Float>) -> RGB {
let pixel = unsafe { &self.pixels.get(p.x(), p.y()) }; let pixel = &self.pixels.get(p);
let mut rgb = RGB::new( let mut rgb = RGB::new(
pixel.rgb_sum[0].load() as Float, pixel.rgb_sum[0].get() as Float,
pixel.rgb_sum[1].load() as Float, pixel.rgb_sum[1].get() as Float,
pixel.rgb_sum[2].load() as Float, pixel.rgb_sum[2].get() as Float,
); );
let weight_sum = pixel.weight_sum.load(); let weight_sum = pixel.weight_sum.get();
if weight_sum != 0. { if weight_sum != 0. {
rgb /= weight_sum as Float rgb /= weight_sum as Float
} }
if let Some(splat) = splat_scale { if let Some(splat) = splat_scale {
for c in 0..3 { for c in 0..3 {
let splat_val = pixel.rgb_splat[c].load(); let splat_val = pixel.rgb_splat[c].get();
rgb[c] += splat * splat_val as Float / self.filter_integral; rgb[c] += splat * splat_val as Float / self.filter_integral;
} }
} else { } else {
for c in 0..3 { for c in 0..3 {
let splat_val = pixel.rgb_splat[c].load(); let splat_val = pixel.rgb_splat[c].get();
rgb[c] += splat_val as Float / self.filter_integral; rgb[c] += splat_val as Float / self.filter_integral;
} }
} }
@ -343,7 +286,8 @@ impl GBufferFilm {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Default, Copy, Clone)] #[derive(Debug, Default)]
#[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
pub struct SpectralPixel { pub struct SpectralPixel {
pub rgb_sum: [AtomicFloat; 3], pub rgb_sum: [AtomicFloat; 3],
pub rgb_weigh_sum: AtomicFloat, pub rgb_weigh_sum: AtomicFloat,
@ -351,15 +295,9 @@ pub struct SpectralPixel {
pub bucket_offset: usize, 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],
}
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug)]
#[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
pub struct SpectralFilm { pub struct SpectralFilm {
pub base: FilmBase, pub base: FilmBase,
pub colorspace: RGBColorSpace, pub colorspace: RGBColorSpace,
@ -369,62 +307,15 @@ pub struct SpectralFilm {
pub max_component_value: Float, pub max_component_value: Float,
pub write_fp16: bool, pub write_fp16: bool,
pub filter_integral: Float, pub filter_integral: Float,
pub pixels: Array2D<SpectralPixel>, pub pixels: DeviceArray2D<SpectralPixel>,
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>, pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
pub bucket_sums: Vec<f64>, pub bucket_sums: *mut f64,
pub weight_sums: Vec<f64>, pub weight_sums: *mut f64,
pub bucket_splats: Vec<AtomicFloat>, pub bucket_splats: *mut AtomicFloat,
} }
#[cfg(not(target_os = "cuda"))] unsafe impl Send for SpectralFilm {}
impl SpectralFilm { unsafe impl Sync for 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_ptr = base.sensor;
if sensor_ptr.is_null() {
panic!("Film must have a sensor");
}
let sensor = unsafe { &*sensor_ptr };
let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor.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,
}
}
}
impl SpectralFilm { impl SpectralFilm {
pub fn base(&self) -> &FilmBase { pub fn base(&self) -> &FilmBase {
@ -439,23 +330,25 @@ impl SpectralFilm {
true true
} }
pub fn get_pixel_view(&self, p: Point2i) -> SpectralPixelView { pub fn add_sample(
let metadata = unsafe { &self.pixels.get(p.x(), p.y()) }; &mut self,
let start = metadata.bucket_offset; _p_film: Point2i,
let end = start + self.n_buckets; _l: SampledSpectrum,
_lambda: &SampledWavelengths,
SpectralPixelView { _visible_surface: Option<&VisibleSurface>,
metadata, _weight: Float,
bucket_sums: &self.bucket_sums[start..end], ) {
weight_sums: &self.weight_sums[start..end], todo!()
bucket_splats: &self.bucket_splats[start..end],
} }
pub fn add_splat(&mut self, _p: Point2f, _v: SampledSpectrum, _lambda: &SampledWavelengths) {
todo!()
} }
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct PixelSensor { pub struct DevicePixelSensor {
pub xyz_from_sensor_rgb: SquareMatrix<Float, 3>, pub xyz_from_sensor_rgb: SquareMatrix<Float, 3>,
pub r_bar: DenselySampledSpectrum, pub r_bar: DenselySampledSpectrum,
pub g_bar: DenselySampledSpectrum, pub g_bar: DenselySampledSpectrum,
@ -463,101 +356,7 @@ pub struct PixelSensor {
pub imaging_ratio: Float, pub imaging_ratio: Float,
} }
impl PixelSensor { impl DevicePixelSensor {
const N_SWATCH_REFLECTANCES: usize = 24;
#[cfg(not(target_os = "cuda"))]
pub fn new(
r: Spectrum,
g: Spectrum,
b: Spectrum,
output_colorspace: RGBColorSpace,
sensor_illum: Option<std::sync::Arc<Spectrum>>,
imaging_ratio: Float,
swatches: &[Spectrum; 24],
) -> Result<Self, Box<dyn Error>> {
// 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]; Self::N_SWATCH_REFLECTANCES];
let swatches = Self::get_swatches();
for i in 0..Self::N_SWATCH_REFLECTANCES {
let rgb = Self::project_reflectance::<RGB>(
&swatches[i],
illum,
&Spectrum::DenselySampled(r_bar.clone()),
&Spectrum::DenselySampled(g_bar.clone()),
&Spectrum::DenselySampled(b_bar.clone()),
);
for c in 0..3 {
rgb_camera[i][c] = rgb[c];
}
}
let mut xyz_output = [[0.; 3]; Self::N_SWATCH_REFLECTANCES];
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..Self::N_SWATCH_REFLECTANCES {
let s = swatches[i].clone();
let xyz = Self::project_reflectance::<XYZ>(
&s,
&output_colorspace.illuminant,
cie_x(),
cie_y(),
cie_z(),
) * (sensor_white_y / sensor_white_g);
for c in 0..3 {
xyz_output[i][c] = xyz[c];
}
}
let xyz_from_sensor_rgb = linear_least_squares(rgb_camera, xyz_output)?;
Ok(Self {
xyz_from_sensor_rgb,
r_bar,
g_bar,
b_bar,
imaging_ratio,
})
}
pub fn new_with_white_balance(
output_colorspace: &RGBColorSpace,
sensor_illum: Option<std::sync::Arc<Spectrum>>,
imaging_ratio: Float,
) -> Self {
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 {
let source_white = illum.to_xyz().xy();
let target_white = output_colorspace.w;
xyz_from_sensor_rgb = white_balance(source_white, target_white);
} else {
xyz_from_sensor_rgb = SquareMatrix::<Float, 3>::default();
}
Self {
xyz_from_sensor_rgb,
r_bar,
g_bar,
b_bar,
imaging_ratio,
}
}
pub fn project_reflectance<T>( pub fn project_reflectance<T>(
refl: &Spectrum, refl: &Spectrum,
illum: &Spectrum, illum: &Spectrum,
@ -589,11 +388,9 @@ impl PixelSensor {
result[2] *= inv_g; result[2] *= inv_g;
} }
T::from((result[0], result[1], result[2])) T::from([result[0], result[1], result[2]])
}
} }
impl PixelSensor {
pub fn to_sensor_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB { pub fn to_sensor_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB {
let l_norm = SampledSpectrum::safe_div(&l, &lambda.pdf()); let l_norm = SampledSpectrum::safe_div(&l, &lambda.pdf());
self.imaging_ratio self.imaging_ratio
@ -633,23 +430,27 @@ impl VisibleSurface {
} }
#[repr(C)] #[repr(C)]
#[derive(Default, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct FilmBase { pub struct FilmBase {
pub full_resolution: Point2i, pub full_resolution: Point2i,
pub pixel_bounds: Bounds2i, pub pixel_bounds: Bounds2i,
pub filter: Filter, pub filter: Filter,
pub diagonal: Float, pub diagonal: Float,
pub sensor: *const PixelSensor, pub sensor: Ptr<DevicePixelSensor>,
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug)]
#[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
pub enum Film { pub enum Film {
RGB(RGBFilm), RGB(RGBFilm),
GBuffer(GBufferFilm), GBuffer(GBufferFilm),
Spectral(SpectralFilm), Spectral(SpectralFilm),
} }
unsafe impl Send for Film {}
unsafe impl Sync for Film {}
impl Film { impl Film {
pub fn base(&self) -> &FilmBase { pub fn base(&self) -> &FilmBase {
match self { match self {
@ -668,7 +469,7 @@ impl Film {
} }
pub fn add_sample( pub fn add_sample(
&self, &mut self,
p_film: Point2i, p_film: Point2i,
l: SampledSpectrum, l: SampledSpectrum,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,

View file

@ -1,9 +1,9 @@
use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f}; use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f};
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::filters::*; use crate::filters::*;
use crate::utils::containers::Array2D; use crate::utils::containers::DeviceArray2D;
use crate::utils::math::{gaussian, gaussian_integral, lerp, sample_tent, windowed_sinc}; use crate::utils::math::{gaussian, gaussian_integral, lerp, sample_tent, windowed_sinc};
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::DevicePiecewiseConstant2D;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
pub struct FilterSample { pub struct FilterSample {
@ -11,41 +11,15 @@ pub struct FilterSample {
pub weight: Float, pub weight: Float,
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct FilterSampler { pub struct FilterSampler {
pub domain: Bounds2f, pub domain: Bounds2f,
pub distrib: PiecewiseConstant2D, pub distrib: DevicePiecewiseConstant2D,
pub f: Array2D<Float>, pub f: DeviceArray2D<Float>,
} }
impl FilterSampler { impl FilterSampler {
#[cfg(not(target_os = "cuda"))]
pub fn new<F>(radius: Vector2f, func: F) -> Self
where
F: Fn(Point2f) -> Float,
{
let domain = Bounds2f::from_points(
Point2f::new(-radius.x(), -radius.y()),
Point2f::new(radius.x(), radius.y()),
);
let nx = (32.0 * radius.x()) as usize;
let ny = (32.0 * radius.y()) as usize;
let mut f = Array2D::new_with_dims(nx, ny);
for y in 0..f.y_size() {
for x in 0..f.x_size() {
let p = domain.lerp(Point2f::new(
(x as Float + 0.5) / f.x_size() as Float,
(y as Float + 0.5) / f.y_size() as Float,
));
f[(x as i32, y as i32)] = func(p);
}
}
let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain);
Self { domain, f, distrib }
}
pub fn sample(&self, u: Point2f) -> FilterSample { pub fn sample(&self, u: Point2f) -> FilterSample {
let (p, pdf, pi) = self.distrib.sample(u); let (p, pdf, pi) = self.distrib.sample(u);
@ -53,11 +27,13 @@ impl FilterSampler {
return FilterSample { p, weight: 0.0 }; return FilterSample { p, weight: 0.0 };
} }
let weight = *self.f.get_linear(pi.x() as usize + self.f.x_size()) / pdf; let idx = pi.x() as u32 + self.f.x_size();
let weight = *self.f.get_linear(idx as usize) / pdf;
FilterSample { p, weight } FilterSample { p, weight }
} }
} }
#[enum_dispatch]
pub trait FilterTrait { pub trait FilterTrait {
fn radius(&self) -> Vector2f; fn radius(&self) -> Vector2f;
fn evaluate(&self, p: Point2f) -> Float; fn evaluate(&self, p: Point2f) -> Float;
@ -67,7 +43,7 @@ pub trait FilterTrait {
#[repr(C)] #[repr(C)]
#[enum_dispatch(FilterTrait)] #[enum_dispatch(FilterTrait)]
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Filter { pub enum Filter {
Box(BoxFilter), Box(BoxFilter),
Gaussian(GaussianFilter), Gaussian(GaussianFilter),

View file

@ -250,6 +250,12 @@ where
} }
} }
impl Bounds2f {
pub fn unit() -> Self {
Self::from_points(Point2f::new(0.0, 0.0), Point2f::new(1.0, 1.0))
}
}
impl Bounds3f { impl Bounds3f {
#[inline(always)] #[inline(always)]
pub fn intersect_p( pub fn intersect_p(

View file

@ -7,8 +7,9 @@ pub mod traits;
pub use self::bounds::{Bounds, Bounds2f, Bounds2fi, Bounds2i, Bounds3f, Bounds3fi, Bounds3i}; pub use self::bounds::{Bounds, Bounds2f, Bounds2fi, Bounds2i, Bounds3f, Bounds3fi, Bounds3i};
pub use self::cone::DirectionCone; pub use self::cone::DirectionCone;
pub use self::primitives::{ pub use self::primitives::{
Frame, Normal, Normal3f, Point, Point2f, Point2fi, Point2i, Point3, Point3f, Point3fi, Point3i, Frame, MulAdd, Normal, Normal3f, Point, Point2f, Point2fi, Point2i, Point3, Point3f, Point3fi,
Vector, Vector2, Vector2f, Vector2fi, Vector2i, Vector3, Vector3f, Vector3fi, Vector3i, Point3i, Vector, Vector2, Vector2f, Vector2fi, Vector2i, Vector3, Vector3f, Vector3fi,
Vector3i,
}; };
pub use self::ray::{Ray, RayDifferential}; pub use self::ray::{Ray, RayDifferential};
pub use self::traits::{Lerp, Sqrt, Tuple, VectorLike}; pub use self::traits::{Lerp, Sqrt, Tuple, VectorLike};

View file

@ -9,6 +9,19 @@ use std::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
}; };
pub trait MulAdd<M = Self, A = Self> {
type Output;
fn mul_add(self, multiplier: M, addend: A) -> Self::Output;
}
impl MulAdd<Float, Float> for Float {
type Output = Float;
#[inline(always)]
fn mul_add(self, multiplier: Float, addend: Float) -> Self::Output {
self.mul_add(multiplier, addend)
}
}
// N-dimensional displacement // N-dimensional displacement
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -260,6 +273,27 @@ macro_rules! impl_op_assign {
}; };
} }
#[macro_export]
macro_rules! impl_mul_add {
($Struct:ident) => {
impl<T, const N: usize> MulAdd<T, $Struct<T, N>> for $Struct<T, N>
where
T: MulAdd<T, T, Output = T> + Copy,
{
type Output = $Struct<T, N>;
#[inline(always)]
fn mul_add(self, multiplier: T, addend: $Struct<T, N>) -> Self::Output {
let mut result = self.0;
for i in 0..N {
result[i] = self.0[i].mul_add(multiplier, addend.0[i]);
}
Self(result)
}
}
};
}
#[macro_export] #[macro_export]
macro_rules! impl_float_vector_ops { macro_rules! impl_float_vector_ops {
($Struct:ident) => { ($Struct:ident) => {
@ -381,6 +415,10 @@ impl_accessors!(Vector);
impl_accessors!(Point); impl_accessors!(Point);
impl_accessors!(Normal); impl_accessors!(Normal);
impl_mul_add!(Vector);
impl_mul_add!(Point);
impl_mul_add!(Normal);
// Convert from tuple of Floats, for parsing issues // Convert from tuple of Floats, for parsing issues
impl_tuple_conversions!(Vector); impl_tuple_conversions!(Vector);
impl_tuple_conversions!(Point); impl_tuple_conversions!(Point);
@ -778,7 +816,8 @@ impl<T> Normal3<T>
where where
T: Num + PartialOrd + Copy + Neg<Output = T> + Sqrt, T: Num + PartialOrd + Copy + Neg<Output = T> + Sqrt,
{ {
pub fn face_forward(self, v: Vector3<T>) -> Self { pub fn face_forward(self, v: impl Into<Vector3<T>>) -> Self {
let v: Vector3<T> = v.into();
if Vector3::<T>::from(self).dot(v) < T::zero() { if Vector3::<T>::from(self).dot(v) < T::zero() {
-self -self
} else { } else {
@ -787,8 +826,8 @@ where
} }
} }
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct OctahedralVector { pub struct OctahedralVector {
x: u16, x: u16,
y: u16, y: u16,
@ -850,6 +889,7 @@ impl From<OctahedralVector> for Vector3f {
} }
} }
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, PartialEq)] #[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct Frame { pub struct Frame {
pub x: Vector3f, pub x: Vector3f,

View file

@ -2,16 +2,18 @@ use super::{Normal3f, Point3f, Point3fi, Vector3f, VectorLike};
use crate::core::medium::Medium; use crate::core::medium::Medium;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::utils::math::{next_float_down, next_float_up}; use crate::utils::math::{next_float_down, next_float_up};
use crate::utils::ptr::Ptr;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct Ray { pub struct Ray {
pub o: Point3f, pub o: Point3f,
pub d: Vector3f, pub d: Vector3f,
pub medium: *const Medium,
pub time: Float, pub time: Float,
pub medium: Ptr<Medium>,
// We do this instead of creating a trait for Rayable or some gnarly thing like that // We do this instead of creating a trait for Rayable or some gnarly thing like that
pub differential: *const RayDifferential, pub has_differentials: bool,
pub differential: RayDifferential,
} }
impl Default for Ray { impl Default for Ray {
@ -19,20 +21,21 @@ impl Default for Ray {
Self { Self {
o: Point3f::new(0.0, 0.0, 0.0), o: Point3f::new(0.0, 0.0, 0.0),
d: Vector3f::new(0.0, 0.0, 0.0), d: Vector3f::new(0.0, 0.0, 0.0),
medium: None, medium: Ptr::null(),
time: 0.0, time: 0.0,
differential: None, has_differentials: false,
differential: RayDifferential::default(),
} }
} }
} }
impl Ray { impl Ray {
pub fn new(o: Point3f, d: Vector3f, time: Option<Float>, medium: *const Medium) -> Self { pub fn new(o: Point3f, d: Vector3f, time: Option<Float>, medium: &Medium) -> Self {
Self { Self {
o, o,
d, d,
time: time.unwrap_or_else(|| Self::default().time), time: time.unwrap_or_else(|| Self::default().time),
medium, medium: Ptr::from(medium),
..Self::default() ..Self::default()
} }
} }
@ -68,8 +71,9 @@ impl Ray {
o: origin, o: origin,
d, d,
time, time,
medium: None, medium: Ptr::null(),
differential: None, has_differentials: false,
differential: RayDifferential::default(),
} }
} }
@ -95,13 +99,15 @@ impl Ray {
o: pf, o: pf,
d, d,
time, time,
medium: None, medium: Ptr::null(),
differential: None, has_differentials: false,
differential: RayDifferential::default(),
} }
} }
pub fn scale_differentials(&mut self, s: Float) { pub fn scale_differentials(&mut self, s: Float) {
if let Some(differential) = &mut self.differential { if self.has_differentials {
let differential = &mut self.differential;
differential.rx_origin = self.o + (differential.rx_origin - self.o) * s; differential.rx_origin = self.o + (differential.rx_origin - self.o) * s;
differential.ry_origin = self.o + (differential.ry_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.rx_direction = self.d + (differential.rx_direction - self.d) * s;

206
shared/src/core/image.rs Normal file
View file

@ -0,0 +1,206 @@
use crate::Float;
use crate::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR};
use crate::core::geometry::{Bounds2f, Point2f, Point2fi, Point2i};
use crate::utils::Ptr;
use crate::utils::containers::DeviceArray2D;
use crate::utils::math::{f16_to_f32, lerp, square};
use core::hash;
use half::f16;
use smallvec::{SmallVec, smallvec};
use std::ops::{Deref, DerefMut};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WrapMode {
Black,
Clamp,
Repeat,
OctahedralSphere,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct WrapMode2D {
pub uv: [WrapMode; 2],
}
impl From<WrapMode> for WrapMode2D {
fn from(w: WrapMode) -> Self {
Self { uv: [w, w] }
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PixelFormat {
U8,
F16,
F32,
}
impl std::fmt::Display for PixelFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PixelFormat::U8 => write!(f, "U256"),
PixelFormat::F16 => write!(f, "Half"),
PixelFormat::F32 => write!(f, "Float"),
}
}
}
impl PixelFormat {
pub fn is_8bit(&self) -> bool {
matches!(self, PixelFormat::U8)
}
pub fn is_16bit(&self) -> bool {
matches!(self, PixelFormat::F16)
}
pub fn is_32bit(&self) -> bool {
matches!(self, PixelFormat::F32)
}
pub fn texel_bytes(&self) -> usize {
match self {
PixelFormat::U8 => 1,
PixelFormat::F16 => 2,
PixelFormat::F32 => 4,
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub enum Pixels {
U8(Ptr<u8>),
F16(Ptr<f16>),
F32(Ptr<f32>),
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct ImageBase {
pub format: PixelFormat,
pub encoding: ColorEncoding,
pub resolution: Point2i,
pub n_channels: i32,
}
impl ImageBase {
pub fn remap_pixel_coords(&self, p: &mut Point2i, wrap_mode: WrapMode2D) -> bool {
let resolution = self.resolution;
for i in 0..2 {
if p[i] >= 0 && p[i] < resolution[i] {
continue;
}
match wrap_mode.uv[i] {
WrapMode::Black => return false,
WrapMode::Clamp => p[i] = p[i].clamp(0, resolution[i] - 1),
WrapMode::Repeat => p[i] = p[i].rem_euclid(resolution[i]),
WrapMode::OctahedralSphere => {
p[i] = p[i].clamp(0, resolution[i] - 1);
}
}
}
true
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct DeviceImage {
pub base: ImageBase,
pub pixels: Pixels,
}
impl DeviceImage {
pub fn base(&self) -> ImageBase {
self.base
}
pub fn resolution(&self) -> Point2i {
self.base.resolution
}
pub fn is_valid(&self) -> bool {
self.resolution().x() > 0 && self.resolution().y() > 0
}
pub fn format(&self) -> PixelFormat {
self.base().format
}
pub fn n_channels(&self) -> i32 {
self.base().n_channels
}
pub fn pixel_offset(&self, p: Point2i) -> u32 {
let width = self.resolution().x() as u32;
let idx = p.y() as u32 * width + p.x() as u32;
idx * (self.n_channels() as u32)
}
pub fn bilerp_channel(&self, p: Point2f, c: i32) -> Float {
self.bilerp_channel_with_wrap(p, c, WrapMode::Clamp.into())
}
pub fn bilerp_channel_with_wrap(&self, p: Point2f, c: i32, wrap_mode: WrapMode2D) -> Float {
let x = p.x() * self.resolution().x() as Float - 0.5;
let y = p.y() * self.resolution().y() as Float - 0.5;
let xi = x.floor() as i32;
let yi = y.floor() as i32;
let dx = x - xi as Float;
let dy = y - yi as Float;
let v00 = self.get_channel_with_wrap(Point2i::new(xi, yi), c, wrap_mode);
let v10 = self.get_channel_with_wrap(Point2i::new(xi + 1, yi), c, wrap_mode);
let v01 = self.get_channel_with_wrap(Point2i::new(xi, yi + 1), c, wrap_mode);
let v11 = self.get_channel_with_wrap(Point2i::new(xi + 1, yi + 1), c, wrap_mode);
lerp(dy, lerp(dx, v00, v10), lerp(dx, v01, v11))
}
}
pub trait ImageAccess {
fn get_channel_with_wrap(&self, p: Point2i, c: i32, wrap_mode: WrapMode2D) -> Float;
fn get_channel(&self, p: Point2i, c: i32) -> Float;
fn lookup_nearest_channel_with_wrap(&self, p: Point2f, c: i32, wrap_mode: WrapMode2D) -> Float;
fn lookup_nearest_channel(&self, p: Point2f, c: i32) -> Float;
}
impl ImageAccess for DeviceImage {
fn get_channel_with_wrap(&self, mut p: Point2i, c: i32, wrap_mode: WrapMode2D) -> Float {
if !self.base.remap_pixel_coords(&mut p, wrap_mode) {
return 0.;
}
let offset = self.pixel_offset(p) + c as u32;
unsafe {
match self.pixels {
Pixels::U8(ptr) => {
let raw_val = *ptr.add(offset as usize);
self.base().encoding.to_linear_scalar(raw_val)
}
Pixels::F16(ptr) => {
let raw_val = *ptr.add(offset as usize);
raw_val.to_f32()
}
Pixels::F32(ptr) => *ptr.add(offset as usize),
}
}
}
fn get_channel(&self, p: Point2i, c: i32) -> Float {
self.get_channel_with_wrap(p, c, WrapMode::Clamp.into())
}
fn lookup_nearest_channel_with_wrap(&self, p: Point2f, c: i32, wrap_mode: WrapMode2D) -> Float {
let pi = Point2i::new(
p.x() as i32 * self.resolution().x(),
p.y() as i32 * self.resolution().y(),
);
self.get_channel_with_wrap(pi, c, wrap_mode)
}
fn lookup_nearest_channel(&self, p: Point2f, c: i32) -> Float {
self.lookup_nearest_channel_with_wrap(p, c, WrapMode::Clamp.into())
}
}

View file

@ -1,41 +1,94 @@
use crate::Float;
use crate::bxdfs::DiffuseBxDF;
use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF; use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::{BSDF, BxDF, BxDFFlags, DiffuseBxDF}; use crate::core::bxdf::{BxDF, BxDFFlags};
use crate::core::camera::Camera; use crate::core::camera::{Camera, CameraTrait};
use crate::core::geometry::{ use crate::core::geometry::{
Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike, Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike,
}; };
use crate::core::light::Light; use crate::core::image::DeviceImage;
use crate::core::light::{Light, LightTrait};
use crate::core::material::{ use crate::core::material::{
Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map, Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map,
}; };
use crate::core::medium::{Medium, MediumInterface, PhaseFunction}; use crate::core::medium::{Medium, MediumInterface, PhaseFunction};
use crate::core::options::get_options; use crate::core::options::get_options;
use crate::core::pbrt::Float;
use crate::core::sampler::{Sampler, SamplerTrait}; use crate::core::sampler::{Sampler, SamplerTrait};
use crate::core::shape::Shape;
use crate::core::texture::{GPUFloatTexture, UniversalTextureEvaluator}; use crate::core::texture::{GPUFloatTexture, UniversalTextureEvaluator};
use crate::images::Image;
use crate::shapes::Shape;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::math::{clamp, difference_of_products, square}; use crate::utils::math::{clamp, difference_of_products, square};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::any::Any; use std::any::Any;
use std::default;
#[repr(C)] #[repr(C)]
#[derive(Default, Copy, Clone, Debug)] #[derive(Default, Copy, Clone, Debug)]
pub struct InteractionData { pub struct InteractionBase {
pub pi: Point3fi, pub pi: Point3fi,
pub n: Normal3f, pub n: Normal3f,
pub time: Float, pub time: Float,
pub wo: Vector3f, pub wo: Vector3f,
pub uv: Point2f,
pub medium_interface: MediumInterface, pub medium_interface: MediumInterface,
pub medium: *const Medium, pub medium: Ptr<Medium>,
}
impl InteractionBase {
pub fn new_surface_geom(
pi: Point3fi,
n: Normal3f,
uv: Point2f,
wo: Vector3f,
time: Float,
) -> Self {
Self {
pi,
n,
uv,
wo: wo.normalize(),
time,
medium_interface: MediumInterface::default(),
medium: Ptr::null(),
}
}
pub fn new_medium(p: Point3f, wo: Vector3f, time: Float, medium: Ptr<Medium>) -> Self {
Self {
pi: Point3fi::new_from_point(p),
n: Normal3f::zero(),
uv: Point2f::default(),
wo: wo.normalize(),
time,
medium_interface: MediumInterface::default(),
medium,
}
}
pub fn new_minimal(pi: Point3fi, n: Normal3f) -> Self {
Self {
pi,
n,
..Default::default()
}
}
pub fn new_boundary(p: Point3f, time: Float, medium_interface: MediumInterface) -> Self {
Self {
pi: Point3fi::new_from_point(p),
time,
medium_interface,
..Default::default()
}
}
} }
#[enum_dispatch] #[enum_dispatch]
pub trait InteractionTrait: Send + Sync + std::fmt::Debug { pub trait InteractionTrait {
fn get_common(&self) -> &InteractionData; fn get_common(&self) -> &InteractionBase;
fn get_common_mut(&mut self) -> &mut InteractionData; fn get_common_mut(&mut self) -> &mut InteractionBase;
fn p(&self) -> Point3f { fn p(&self) -> Point3f {
self.get_common().pi.into() self.get_common().pi.into()
@ -43,6 +96,7 @@ pub trait InteractionTrait: Send + Sync + std::fmt::Debug {
fn pi(&self) -> Point3fi { fn pi(&self) -> Point3fi {
self.get_common().pi self.get_common().pi
} }
fn time(&self) -> Float { fn time(&self) -> Float {
self.get_common().time self.get_common().time
} }
@ -62,13 +116,13 @@ pub trait InteractionTrait: Send + Sync + std::fmt::Debug {
false false
} }
fn get_medium(&self, w: Vector3f) -> *const Medium { fn get_medium(&self, w: Vector3f) -> Ptr<Medium> {
let data = self.get_common(); let data = self.get_common();
if let Some(mi) = &data.medium_interface { if !data.medium_interface.inside.is_null() || !data.medium_interface.outside.is_null() {
if w.dot(data.n.into()) > 0.0 { if w.dot(data.n.into()) > 0.0 {
mi.outside data.medium_interface.outside
} else { } else {
mi.inside data.medium_interface.inside
} }
} else { } else {
data.medium data.medium
@ -90,11 +144,10 @@ pub trait InteractionTrait: Send + Sync + std::fmt::Debug {
ray ray
} }
fn spawn_ray_to_interaction(&self, other: InteractionData) -> Ray { fn spawn_ray_to_interaction(&self, other: InteractionBase) -> Ray {
let data = self.get_common(); let data = self.get_common();
let mut ray = let mut ray = Ray::spawn_to_interaction(&data.pi, &data.n, data.time, &other.pi, &other.n);
Ray::spawn_to_interaction(&data.pi, &data.n, data.time, &other_data.pi, &other_data.n);
ray.medium = self.get_medium(ray.d); ray.medium = self.get_medium(ray.d);
ray ray
} }
@ -118,57 +171,40 @@ pub enum Interaction {
} }
impl Interaction { impl Interaction {
pub fn set_medium_interface(&mut self, mi: Option<MediumInterface>) { pub fn set_medium_interface(&mut self, mi: MediumInterface) {
match self { match self {
Interaction::Surface(si) => si.common.medium_interface = mi, Interaction::Surface(si) => si.common.medium_interface = mi,
Interaction::Simple(si) => si.common.medium_interface = mi,
Interaction::Medium(_) => {} // Medium interactions don't usually sit on boundaries Interaction::Medium(_) => {} // Medium interactions don't usually sit on boundaries
Interaction::Simple(si) => si.common.medium_interface = mi,
} }
} }
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Default, Clone, Copy)]
pub struct SimpleInteraction { pub struct SimpleInteraction {
pub common: InteractionData, pub common: InteractionBase,
} }
impl SimpleInteraction { impl SimpleInteraction {
pub fn new(pi: Point3fi, time: Float, medium_interface: Option<MediumInterface>) -> Self { pub fn new(common: InteractionBase) -> Self {
Self { Self { common }
common: InteractionData {
pi,
time,
medium_interface,
n: Normal3f::default(),
wo: Vector3f::default(),
medium: None,
},
}
}
pub fn new_interface(p: Point3f, medium_interface: Option<MediumInterface>) -> Self {
Self {
common: InteractionData {
pi: Point3fi::new_from_point(p),
n: Normal3f::zero(),
wo: Vector3f::zero(),
time: 0.0,
medium: None,
medium_interface,
},
}
} }
} }
impl InteractionTrait for SimpleInteraction { impl InteractionTrait for SimpleInteraction {
fn get_common(&self) -> &InteractionData { fn get_common(&self) -> &InteractionBase {
&self.common &self.common
} }
fn get_common_mut(&mut self) -> &mut InteractionBase {
fn get_common_mut(&mut self) -> &mut InteractionData {
&mut self.common &mut self.common
} }
fn is_surface_interaction(&self) -> bool {
false
}
fn is_medium_interaction(&self) -> bool {
false
}
} }
#[repr(C)] #[repr(C)]
@ -184,19 +220,18 @@ pub struct ShadingGeom {
#[repr(C)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
pub struct SurfaceInteraction { pub struct SurfaceInteraction {
pub common: InteractionData, pub area_light: Ptr<Light>,
pub uv: Point2f, pub material: Ptr<Material>,
pub shape: Ptr<Shape>,
pub common: InteractionBase,
pub shading: ShadingGeom,
pub dpdu: Vector3f, pub dpdu: Vector3f,
pub dpdv: Vector3f, pub dpdv: Vector3f,
pub dndu: Normal3f, pub dndu: Normal3f,
pub dndv: Normal3f, pub dndv: Normal3f,
pub shading: ShadingGeom,
pub face_index: u32,
pub area_light: *const Light,
pub material: *const Material,
pub shape: *const Shape,
pub dpdx: Vector3f, pub dpdx: Vector3f,
pub dpdy: Vector3f, pub dpdy: Vector3f,
pub face_index: i32,
pub dudx: Float, pub dudx: Float,
pub dvdx: Float, pub dvdx: Float,
pub dudy: Float, pub dudy: Float,
@ -208,15 +243,17 @@ unsafe impl Sync for SurfaceInteraction {}
impl SurfaceInteraction { impl SurfaceInteraction {
pub fn le(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { pub fn le(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
if let Some(area_light) = &self.area_light { if !self.area_light.is_null() {
area_light.l(self.p(), self.n(), self.uv, w, lambda) self.area_light
.l(self.p(), self.n(), self.common.uv, w, lambda)
} else { } else {
SampledSpectrum::new(0.) SampledSpectrum::new(0.)
} }
} }
pub fn compute_differentials(&mut self, r: &Ray, camera: &Camera, samples_per_pixel: i32) { pub fn compute_differentials(&mut self, r: &Ray, camera: &Camera, samples_per_pixel: i32) {
let computed = if let Some(diff) = &r.differential { let computed = if !r.has_differentials {
let diff = r.differential;
let dot_rx = self.common.n.dot(diff.rx_direction.into()); let dot_rx = self.common.n.dot(diff.rx_direction.into());
let dot_ry = self.common.n.dot(diff.ry_direction.into()); let dot_ry = self.common.n.dot(diff.ry_direction.into());
@ -303,7 +340,8 @@ impl SurfaceInteraction {
let new_ray = Ray::spawn(&self.pi(), &self.n(), ray.time, ray.d); let new_ray = Ray::spawn(&self.pi(), &self.n(), ray.time, ray.d);
ray.o = new_ray.o; ray.o = new_ray.o;
// Skipping other variables, since they should not change when passing through surface // Skipping other variables, since they should not change when passing through surface
if let Some(diff) = &mut ray.differential { if !ray.has_differentials {
let mut diff = ray.differential;
diff.rx_origin += diff.rx_direction * t; diff.rx_origin += diff.rx_direction * t;
diff.ry_origin += diff.ry_direction * t; diff.ry_origin += diff.ry_direction * t;
} }
@ -320,13 +358,13 @@ impl SurfaceInteraction {
self.compute_differentials(r, camera, sampler.samples_per_pixel() as i32); self.compute_differentials(r, camera, sampler.samples_per_pixel() as i32);
let material = { let material = {
let root_mat = self.material.as_deref()?; let root_mat = self.material;
let mut active_mat: &Material = root_mat; let mut active_mat: &Material = &*root_mat;
let tex_eval = UniversalTextureEvaluator; let tex_eval = UniversalTextureEvaluator;
while let Material::Mix(mix) = active_mat { while let Material::Mix(mix) = active_mat {
// We need a context to evaluate the 'amount' texture // We need a context to evaluate the 'amount' texture
let ctx = MaterialEvalContext::from(&*self); let ctx = MaterialEvalContext::from(&*self);
active_mat = mix.choose_material(&tex_eval, &ctx); active_mat = mix.choose_material(&tex_eval, &ctx)?;
} }
active_mat.clone() active_mat.clone()
}; };
@ -334,8 +372,8 @@ impl SurfaceInteraction {
let ctx = MaterialEvalContext::from(&*self); let ctx = MaterialEvalContext::from(&*self);
let tex_eval = UniversalTextureEvaluator; let tex_eval = UniversalTextureEvaluator;
let displacement = material.get_displacement(); let displacement = material.get_displacement();
let normal_map = material.get_normal_map(); let normal_map = Ptr::from(material.get_normal_map().unwrap());
if displacement.is_some() || normal_map.is_some() { if !displacement.is_null() || !normal_map.is_null() {
// This calls the function defined above // This calls the function defined above
self.compute_bump_geom(&tex_eval, displacement, normal_map); self.compute_bump_geom(&tex_eval, displacement, normal_map);
} }
@ -344,7 +382,7 @@ impl SurfaceInteraction {
if get_options().force_diffuse { if get_options().force_diffuse {
let r = bsdf.rho_wo(self.common.wo, &[sampler.get1d()], &[sampler.get2d()]); let r = bsdf.rho_wo(self.common.wo, &[sampler.get1d()], &[sampler.get2d()]);
let diff_bxdf = BxDF::Diffuse(DiffuseBxDF::new(r)); let diff_bxdf = BxDF::Diffuse(DiffuseBxDF::new(r));
bsdf = BSDF::new(self.shading.n, self.shading.dpdu, Some(diff_bxdf)); bsdf = BSDF::new(self.shading.n, self.shading.dpdu, Ptr::from(&diff_bxdf));
} }
Some(bsdf) Some(bsdf)
} }
@ -357,13 +395,12 @@ impl SurfaceInteraction {
_camera: &Camera, _camera: &Camera,
) -> Option<BSSRDF> { ) -> Option<BSSRDF> {
let material = { let material = {
let root_mat = self.material.as_deref()?; let mut active_mat = unsafe { self.material.as_ref() };
let mut active_mat: &Material = root_mat;
let tex_eval = UniversalTextureEvaluator; let tex_eval = UniversalTextureEvaluator;
while let Material::Mix(mix) = active_mat { while let Material::Mix(mix) = active_mat {
// We need a context to evaluate the 'amount' texture // We need a context to evaluate the 'amount' texture
let ctx = MaterialEvalContext::from(self); let ctx = MaterialEvalContext::from(self);
active_mat = mix.choose_material(&tex_eval, &ctx); active_mat = mix.choose_material(&tex_eval, &ctx)?;
} }
active_mat.clone() active_mat.clone()
}; };
@ -376,14 +413,15 @@ impl SurfaceInteraction {
fn compute_bump_geom( fn compute_bump_geom(
&mut self, &mut self,
tex_eval: &UniversalTextureEvaluator, tex_eval: &UniversalTextureEvaluator,
displacement: *const GPUFloatTexture, displacement: Ptr<GPUFloatTexture>,
normal_image: *const Image, normal_image: Ptr<DeviceImage>,
) { ) {
let ctx = NormalBumpEvalContext::from(&*self); let ctx = NormalBumpEvalContext::from(&*self);
let (dpdu, dpdv) = if let Some(disp) = displacement { let (dpdu, dpdv) = if !displacement.is_null() {
bump_map(tex_eval, &disp, &ctx) bump_map(tex_eval, &displacement, &ctx)
} else if let Some(map) = normal_image { } else if !normal_image.is_null() {
normal_map(map.as_ref(), &ctx) let map = unsafe { normal_image.as_ref() };
normal_map(map, &ctx)
} else { } else {
(self.shading.dpdu, self.shading.dpdv) (self.shading.dpdu, self.shading.dpdv)
}; };
@ -405,7 +443,8 @@ impl SurfaceInteraction {
) -> Ray { ) -> Ray {
let mut rd = self.spawn_ray(wi); let mut rd = self.spawn_ray(wi);
if let Some(diff_i) = &ray_i.differential { if ray_i.has_differentials {
let diff_i = ray_i.differential;
let mut n = self.shading.n; let mut n = self.shading.n;
let mut dndx = self.shading.dndu * self.dudx + self.shading.dndv * self.dvdx; let mut dndx = self.shading.dndu * self.dudx + self.shading.dndv * self.dvdx;
@ -475,14 +514,14 @@ impl SurfaceInteraction {
|| Vector3f::from(new_diff_rx_origin).norm_squared() > threshold || Vector3f::from(new_diff_rx_origin).norm_squared() > threshold
|| Vector3f::from(new_diff_ry_origin).norm_squared() > threshold || Vector3f::from(new_diff_ry_origin).norm_squared() > threshold
{ {
rd.differential = None; rd.differential = RayDifferential::default();
} else { } else {
rd.differential = Some(RayDifferential { rd.differential = RayDifferential {
rx_origin: new_diff_rx_origin, rx_origin: new_diff_rx_origin,
ry_origin: new_diff_ry_origin, ry_origin: new_diff_ry_origin,
rx_direction: new_diff_rx_dir, rx_direction: new_diff_rx_dir,
ry_direction: new_diff_ry_dir, ry_direction: new_diff_ry_dir,
}); };
} }
} }
} }
@ -492,22 +531,21 @@ impl SurfaceInteraction {
} }
impl InteractionTrait for SurfaceInteraction { impl InteractionTrait for SurfaceInteraction {
fn get_common(&self) -> &InteractionData { fn get_common(&self) -> &InteractionBase {
&self.common &self.common
} }
fn get_common_mut(&mut self) -> &mut InteractionData { fn get_common_mut(&mut self) -> &mut InteractionBase {
&mut self.common &mut self.common
} }
fn get_medium(&self, w: Vector3f) -> *const Medium { fn get_medium(&self, w: Vector3f) -> Ptr<Medium> {
self.common.medium_interface.as_ref().and_then(|interface| { let interface = self.common.medium_interface;
if self.n().dot(w.into()) > 0.0 { if self.n().dot(w.into()) > 0.0 {
interface.outside interface.outside
} else { } else {
interface.inside interface.inside
} }
})
} }
fn is_surface_interaction(&self) -> bool { fn is_surface_interaction(&self) -> bool {
@ -536,15 +574,7 @@ impl SurfaceInteraction {
} }
Self { Self {
common: InteractionData { common: InteractionBase::new_surface_geom(pi, n, uv, wo, time),
pi,
n,
time,
wo,
medium_interface: None,
medium: None,
},
uv,
dpdu, dpdu,
dpdv, dpdv,
dndu, dndu,
@ -556,16 +586,16 @@ impl SurfaceInteraction {
dndu, dndu,
dndv, dndv,
}, },
material: None, material: Ptr::null(),
face_index: 0, face_index: 0,
area_light: None, area_light: Ptr::null(),
dpdx: Vector3f::zero(), dpdx: Vector3f::zero(),
dpdy: Vector3f::zero(), dpdy: Vector3f::zero(),
dudx: 0.0, dudx: 0.0,
dudy: 0.0, dudy: 0.0,
dvdx: 0.0, dvdx: 0.0,
dvdy: 0.0, dvdy: 0.0,
shape: core::ptr::null(), shape: Ptr::null(),
} }
} }
@ -579,7 +609,7 @@ impl SurfaceInteraction {
dndv: Normal3f, dndv: Normal3f,
time: Float, time: Float,
flip: bool, flip: bool,
face_index: usize, face_index: i32,
) -> Self { ) -> Self {
let mut si = Self::new(pi, uv, wo, dpdu, dpdv, dndu, dndv, time, flip); let mut si = Self::new(pi, uv, wo, dpdu, dpdv, dndu, dndv, time, flip);
si.face_index = face_index; si.face_index = face_index;
@ -597,7 +627,7 @@ impl SurfaceInteraction {
) { ) {
self.shading.n = ns; self.shading.n = ns;
if orientation { if orientation {
self.common.n = self.n().face_forward(self.shading.n.into()); self.common.n = self.n().face_forward(self.shading.n);
} }
self.shading.dpdu = dpdus; self.shading.dpdu = dpdus;
self.shading.dpdv = dpdvs; self.shading.dpdv = dpdvs;
@ -607,26 +637,18 @@ impl SurfaceInteraction {
pub fn new_simple(pi: Point3fi, n: Normal3f, uv: Point2f) -> Self { pub fn new_simple(pi: Point3fi, n: Normal3f, uv: Point2f) -> Self {
Self { Self {
common: InteractionData { common: InteractionBase::new_surface_geom(pi, n, uv, Vector3f::zero(), 0.),
pi,
n,
time: 0.,
wo: Vector3f::zero(),
medium_interface: None,
medium: None,
},
uv,
..Default::default() ..Default::default()
} }
} }
pub fn new_minimal(pi: Point3fi, uv: Point2f) -> Self { pub fn new_minimal(pi: Point3fi, uv: Point2f) -> Self {
Self { Self {
common: InteractionData { common: InteractionBase {
pi, pi,
uv,
..Default::default() ..Default::default()
}, },
uv,
..Default::default() ..Default::default()
} }
} }
@ -634,18 +656,18 @@ impl SurfaceInteraction {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
pub fn set_intersection_properties( pub fn set_intersection_properties(
&mut self, &mut self,
mtl: *const Material, mtl: &Material,
area: *const Light, area: &Light,
ray_medium: &Medium,
prim_medium_interface: MediumInterface, prim_medium_interface: MediumInterface,
ray_medium: *const Medium,
) { ) {
self.material = mtl; self.material = Ptr::from(mtl);
self.area_light = area; self.area_light = Ptr::from(area);
if prim_medium_interface.is_medium_transition() { if prim_medium_interface.is_medium_transition() {
self.common.medium_interface = *prim_medium_interface; self.common.medium_interface = prim_medium_interface;
} else { } else {
self.common.medium = ray_medium; self.common.medium = Ptr::from(ray_medium);
} }
} }
} }
@ -653,10 +675,8 @@ impl SurfaceInteraction {
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct MediumInteraction { pub struct MediumInteraction {
pub common: InteractionData, pub common: InteractionBase,
pub medium: *const Medium,
pub phase: PhaseFunction, pub phase: PhaseFunction,
pub medium_interface: MediumInterface,
} }
impl MediumInteraction { impl MediumInteraction {
@ -664,21 +684,12 @@ impl MediumInteraction {
p: Point3f, p: Point3f,
wo: Vector3f, wo: Vector3f,
time: Float, time: Float,
medium: *const Medium, medium: Ptr<Medium>,
phase: PhaseFunction, phase: PhaseFunction,
) -> Self { ) -> Self {
Self { Self {
common: InteractionData { common: InteractionBase::new_medium(p, wo, time, medium),
pi: Point3fi::new_from_point(p),
n: Normal3f::default(),
time,
wo: wo.normalize(),
medium_interface: None,
medium,
},
medium,
phase, phase,
medium_interface: MediumInterface::empty(),
} }
} }
} }
@ -688,11 +699,11 @@ impl InteractionTrait for MediumInteraction {
true true
} }
fn get_common(&self) -> &InteractionData { fn get_common(&self) -> &InteractionBase {
&self.common &self.common
} }
fn get_common_mut(&mut self) -> &mut InteractionData { fn get_common_mut(&mut self) -> &mut InteractionBase {
&mut self.common &mut self.common
} }
} }

View file

@ -3,13 +3,13 @@ use crate::core::geometry::{
Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray, Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray,
Vector3f, VectorLike, cos_theta, Vector3f, VectorLike, cos_theta,
}; };
use crate::core::image::DeviceImage;
use crate::core::interaction::{ use crate::core::interaction::{
Interaction, InteractionData, InteractionTrait, MediumInteraction, SimpleInteraction, Interaction, InteractionBase, InteractionTrait, MediumInteraction, SimpleInteraction,
SurfaceInteraction, SurfaceInteraction,
}; };
use crate::core::medium::MediumInterface; use crate::core::medium::MediumInterface;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::images::Image;
use crate::lights::*; use crate::lights::*;
use crate::spectra::{ use crate::spectra::{
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBColorSpace, RGBIlluminantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBColorSpace, RGBIlluminantSpectrum,
@ -17,7 +17,7 @@ use crate::spectra::{
}; };
use crate::utils::Transform; use crate::utils::Transform;
use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square}; use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square};
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::DevicePiecewiseConstant2D;
use crate::{Float, PI}; use crate::{Float, PI};
use bitflags::bitflags; use bitflags::bitflags;
@ -49,9 +49,9 @@ impl LightType {
pub struct LightLeSample { pub struct LightLeSample {
pub l: SampledSpectrum, pub l: SampledSpectrum,
pub ray: Ray, pub ray: Ray,
pub intr: *const InteractionData,
pub pdf_pos: Float, pub pdf_pos: Float,
pub pdf_dir: Float, pub pdf_dir: Float,
pub intr: Interaction,
} }
#[repr(C)] #[repr(C)]
@ -60,12 +60,12 @@ pub struct LightLiSample {
pub l: SampledSpectrum, pub l: SampledSpectrum,
pub wi: Vector3f, pub wi: Vector3f,
pub pdf: Float, pub pdf: Float,
pub p_light: InteractionData, pub p_light: Interaction,
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
impl LightLiSample { impl LightLiSample {
pub fn new(l: SampledSpectrum, wi: Vector3f, pdf: Float, p_light: InteractionData) -> Self { pub fn new(l: SampledSpectrum, wi: Vector3f, pdf: Float, p_light: Interaction) -> Self {
Self { Self {
l, l,
wi, wi,
@ -140,32 +140,23 @@ impl From<&Interaction> for LightSampleContext {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct LightBase { pub struct LightBase {
pub render_from_light: Transform, pub render_from_light: Transform,
pub light_type: u32, pub light_type: LightType,
pub medium_interface: MediumInterface, pub medium_interface: MediumInterface,
} }
#[cfg(not(target_os = "cuda"))]
impl LightBase { impl LightBase {
pub fn new( pub fn new(
light_type: LightType, light_type: LightType,
render_from_light: &Transform, render_from_light: Transform,
medium_interface: &MediumInterface, medium_interface: MediumInterface,
) -> Self { ) -> Self {
Self { Self {
light_type, light_type,
render_from_light: *render_from_light, render_from_light,
medium_interface: medium_interface.clone(), medium_interface,
} }
} }
pub fn lookup_spectrum(s: &Spectrum) -> DenselySampledSpectrum {
let cache = SPECTRUM_CACHE.get_or_init(InternCache::new);
let dense_spectrum = DenselySampledSpectrum::from_spectrum(s);
cache.lookup(dense_spectrum).as_ref()
}
}
impl LightBase {
fn l( fn l(
&self, &self,
_p: Point3f, _p: Point3f,
@ -348,9 +339,9 @@ pub enum Light {
DiffuseArea(DiffuseAreaLight), DiffuseArea(DiffuseAreaLight),
Distant(DistantLight), Distant(DistantLight),
Goniometric(GoniometricLight), Goniometric(GoniometricLight),
InfiniteUniform(InfiniteUniformLight), InfiniteUniform(UniformInfiniteLight),
InfiniteImage(InfiniteImageLight), InfiniteImage(ImageInfiniteLight),
InfinitePortal(InfinitePortalLight), InfinitePortal(PortalInfiniteLight),
Point(PointLight), Point(PointLight),
Projection(ProjectionLight), Projection(ProjectionLight),
Spot(SpotLight), Spot(SpotLight),

View file

@ -1,21 +1,23 @@
use crate::materials::*;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc;
use crate::core::bssrdf::BSSRDF; use crate::Float;
use crate::core::bxdf::{ use crate::bxdfs::{
BSDF, BxDF, CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF, CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF,
}; };
use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::BxDF;
use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike}; use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike};
use crate::core::interaction::InteractionTrait; use crate::core::image::{DeviceImage, WrapMode, WrapMode2D};
use crate::core::interaction::{Interaction, ShadingGeom, SurfaceInteraction}; use crate::core::interaction::{Interaction, InteractionTrait, ShadingGeom, SurfaceInteraction};
use crate::core::pbrt::Float;
use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{ use crate::core::texture::{
GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext, TextureEvaluator, GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext, TextureEvaluator,
}; };
use crate::images::{Image, WrapMode, WrapMode2D}; use crate::materials::*;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::utils::hash::hash_float; use crate::utils::hash::hash_float;
@ -63,14 +65,14 @@ pub struct NormalBumpEvalContext {
pub dudy: Float, pub dudy: Float,
pub dvdx: Float, pub dvdx: Float,
pub dvdy: Float, pub dvdy: Float,
pub face_index: usize, pub face_index: i32,
} }
impl From<&SurfaceInteraction> for NormalBumpEvalContext { impl From<&SurfaceInteraction> for NormalBumpEvalContext {
fn from(si: &SurfaceInteraction) -> Self { fn from(si: &SurfaceInteraction) -> Self {
Self { Self {
p: si.p(), p: si.p(),
uv: si.uv, uv: si.common.uv,
n: si.n(), n: si.n(),
shading: si.shading.clone(), shading: si.shading.clone(),
dudx: si.dudx, dudx: si.dudx,
@ -101,7 +103,7 @@ impl From<&NormalBumpEvalContext> for TextureEvalContext {
} }
} }
pub fn normal_map(normal_map: &Image, ctx: &NormalBumpEvalContext) -> (Vector3f, Vector3f) { pub fn normal_map(normal_map: &DeviceImage, ctx: &NormalBumpEvalContext) -> (Vector3f, Vector3f) {
let wrap = WrapMode2D::from(WrapMode::Repeat); let wrap = WrapMode2D::from(WrapMode::Repeat);
let uv = Point2f::new(ctx.uv[0], 1. - ctx.uv[1]); let uv = Point2f::new(ctx.uv[0], 1. - ctx.uv[1]);
let r = normal_map.bilerp_channel_with_wrap(uv, 0, wrap); let r = normal_map.bilerp_channel_with_wrap(uv, 0, wrap);
@ -123,7 +125,7 @@ pub fn bump_map<T: TextureEvaluator>(
displacement: &GPUFloatTexture, displacement: &GPUFloatTexture,
ctx: &NormalBumpEvalContext, ctx: &NormalBumpEvalContext,
) -> (Vector3f, Vector3f) { ) -> (Vector3f, Vector3f) {
debug_assert!(tex_eval.can_evaluate(&[displacement], &[])); debug_assert!(tex_eval.can_evaluate(&[Ptr::from(displacement)], &[]));
let mut du = 0.5 * (ctx.dudx.abs() + ctx.dudy.abs()); let mut du = 0.5 * (ctx.dudx.abs() + ctx.dudy.abs());
if du == 0.0 { if du == 0.0 {
du = 0.0005; du = 0.0005;
@ -155,7 +157,7 @@ pub fn bump_map<T: TextureEvaluator>(
} }
#[enum_dispatch] #[enum_dispatch]
pub trait MaterialTrait: Send + Sync + std::fmt::Debug { pub trait MaterialTrait {
fn get_bsdf<T: TextureEvaluator>( fn get_bsdf<T: TextureEvaluator>(
&self, &self,
tex_eval: &T, tex_eval: &T,
@ -171,9 +173,9 @@ pub trait MaterialTrait: Send + Sync + std::fmt::Debug {
) -> Option<BSSRDF>; ) -> Option<BSSRDF>;
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool; fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool;
fn get_normal_map(&self) -> *const Image; fn get_normal_map(&self) -> Option<&DeviceImage>;
fn get_displacement(&self) -> Option<GPUFloatTexture>; fn get_displacement(&self) -> Ptr<GPUFloatTexture>;
fn has_surface_scattering(&self) -> bool; fn has_subsurface_scattering(&self) -> bool;
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -191,729 +193,3 @@ pub enum Material {
ThinDielectric(ThinDielectricMaterial), ThinDielectric(ThinDielectricMaterial),
Mix(MixMaterial), Mix(MixMaterial),
} }
#[derive(Clone, Debug)]
pub struct CoatedDiffuseMaterial {
pub displacement: GPUFloatTexture,
pub normal_map: *const Image,
pub reflectance: GPUSpectrumTexture,
pub albedo: GPUSpectrumTexture,
pub u_roughness: GPUFloatTexture,
pub v_roughness: GPUFloatTexture,
pub thickness: GPUFloatTexture,
pub g: GPUFloatTexture,
pub eta: Spectrum,
pub remap_roughness: bool,
pub max_depth: usize,
pub n_samples: usize,
}
impl CoatedDiffuseMaterial {
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "cuda"))]
pub fn new(
reflectance: GPUSpectrumTexture,
u_roughness: GPUFloatTexture,
v_roughness: GPUFloatTexture,
thickness: GPUFloatTexture,
albedo: GPUSpectrumTexture,
g: GPUFloatTexture,
eta: Spectrum,
displacement: GPUFloatTexture,
normal_map: *const 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_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,
) -> Option<BSSRDF> {
None
}
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) -> *const Image {
self.normal_map
}
fn get_displacement(&self) -> Option<FloatTexture> {
Some(self.displacement.clone())
}
fn has_surface_scattering(&self) -> bool {
false
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct CoatedConductorMaterial {
displacement: FloatTexture,
normal_map: *const 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)]
#[cfg(not(target_os = "cuda"))]
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_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,
) -> Option<BSSRDF> {
None
}
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 get_normal_map(&self) -> *const Image {
self.normal_map
}
fn get_displacement(&self) -> Option<FloatTexture> {
Some(self.displacement.clone())
}
fn has_surface_scattering(&self) -> bool {
false
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct ConductorMaterial;
impl MaterialTrait for ConductorMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> *const Image {
todo!()
}
fn get_displacement(&self) -> Option<FloatTexture> {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct DielectricMaterial {
normal_map: *const Image,
displacement: FloatTexture,
u_roughness: FloatTexture,
v_roughness: FloatTexture,
remap_roughness: bool,
eta: Spectrum,
}
impl MaterialTrait for DielectricMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSDF {
let mut sampled_eta = self.eta.evaluate(lambda[0]);
if !self.eta.is_constant() {
lambda.terminate_secondary();
}
if sampled_eta == 0.0 {
sampled_eta = 1.0;
}
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 bxdf = BxDF::Dielectric(DielectricBxDF::new(sampled_eta, distrib));
BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf))
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
None
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(&[&self.u_roughness, &self.v_roughness], &[])
}
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
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct DiffuseMaterial {
normal_map: *const Image,
displacement: FloatTexture,
reflectance: SpectrumTexture,
}
impl MaterialTrait for DiffuseMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSDF {
let r = tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda);
let bxdf = BxDF::Diffuse(DiffuseBxDF::new(r));
BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf))
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(&[], &[&self.reflectance])
}
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
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct DiffuseTransmissionMaterial;
impl MaterialTrait for DiffuseTransmissionMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<Arc<Image>> {
todo!()
}
fn get_displacement(&self) -> Option<FloatTexture> {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct HairMaterial;
impl MaterialTrait for HairMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<Arc<Image>> {
todo!()
}
fn get_displacement(&self) -> Option<FloatTexture> {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct MeasuredMaterial;
impl MaterialTrait for MeasuredMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<Arc<Image>> {
todo!()
}
fn get_displacement(&self) -> Option<FloatTexture> {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct SubsurfaceMaterial;
impl MaterialTrait for SubsurfaceMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<Arc<Image>> {
todo!()
}
fn get_displacement(&self) -> Option<FloatTexture> {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct ThinDielectricMaterial;
impl MaterialTrait for ThinDielectricMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<Arc<Image>> {
todo!()
}
fn get_displacement(&self) -> Option<FloatTexture> {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct MixMaterial {
pub amount: FloatTexture,
pub materials: [Ptr<Material>; 2],
}
impl MixMaterial {
pub fn choose_material<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
) -> Option<&Material> {
let amt = tex_eval.evaluate_float(&self.amount, ctx);
let index = if amt <= 0.0 {
0
} else if amt >= 1.0 {
1
} else {
let u = hash_float(&(ctx.p, ctx.wo));
if amt < u { 0 } else { 1 }
};
self.materials[index].get()
}
}
impl MaterialTrait for MixMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSDF {
if let Some(mat) = self.choose_material(tex_eval, ctx) {
mat.get_bsdf(tex_eval, ctx, lambda)
} else {
BSDF::empty()
}
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
None
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(&[&self.amount], &[])
}
fn get_normal_map(&self) -> Option<Arc<Image>> {
None
}
fn get_displacement(&self) -> Option<FloatTexture> {
panic!(
"MixMaterial::get_displacement() shouldn't be called. \
Displacement is not supported on Mix materials directly."
);
}
fn has_surface_scattering(&self) -> bool {
false
}
}

View file

@ -5,14 +5,16 @@ use crate::core::geometry::{
Bounds3f, Frame, Point2f, Point3f, Point3i, Ray, Vector3f, VectorLike, spherical_direction, Bounds3f, Frame, Point2f, Point3f, Point3i, Ray, Vector3f, VectorLike, spherical_direction,
}; };
use crate::core::pbrt::{Float, INV_4_PI, PI}; use crate::core::pbrt::{Float, INV_4_PI, PI};
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::spectra::{ use crate::spectra::{
BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum, BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum,
RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths,
}; };
use crate::utils::containers::SampledGrid; use crate::utils::containers::SampledGrid;
use crate::utils::math::{clamp, square}; use crate::utils::math::{clamp, square};
use crate::utils::ptr::Ptr;
use crate::utils::rng::Rng; use crate::utils::rng::Rng;
use crate::utils::transform::TransformGeneric; use crate::utils::transform::Transform;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -37,7 +39,7 @@ pub trait PhaseFunctionTrait {
#[repr(C)] #[repr(C)]
#[enum_dispatch(PhaseFunctionTrait)] #[enum_dispatch(PhaseFunctionTrait)]
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub enum PhaseFunction { pub enum PhaseFunction {
HenyeyGreenstein(HGPhaseFunction), HenyeyGreenstein(HGPhaseFunction),
} }
@ -88,26 +90,30 @@ impl PhaseFunctionTrait for HGPhaseFunction {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub struct MajorantGrid { pub struct MajorantGrid {
pub bounds: Bounds3f, pub bounds: Bounds3f,
pub res: Point3i, pub res: Point3i,
pub voxels: *const Float, pub voxels: *mut Float,
pub n_voxels: u32,
} }
unsafe impl Send for MajorantGrid {} unsafe impl Send for MajorantGrid {}
unsafe impl Sync for MajorantGrid {} unsafe impl Sync for MajorantGrid {}
impl MajorantGrid { impl MajorantGrid {
#[cfg(not(target_os = "cuda"))] // #[cfg(not(target_os = "cuda"))]
pub fn new(bounds: Bounds3f, res: Point3i) -> Self { // pub fn new(bounds: Bounds3f, res: Point3i) -> Self {
Self { // let n_voxels = (res.x() * res.y() * res.z()) as usize;
bounds, // let voxels = Vec::with_capacity(n_voxels);
res, // Self {
voxels: Vec::with_capacity((res.x() * res.y() * res.z()) as usize), // bounds,
} // res,
} // voxels: voxels.as_ptr(),
// n_voxels: n_voxels as u32,
// }
// }
//
#[inline(always)] #[inline(always)]
fn is_valid(&self) -> bool { fn is_valid(&self) -> bool {
!self.voxels.is_null() !self.voxels.is_null()
@ -121,7 +127,7 @@ impl MajorantGrid {
let idx = z * self.res.x() * self.res.y() + y * self.res.x() + x; let idx = z * self.res.x() * self.res.y() + y * self.res.x() + x;
if idx >= 0 && (idx as usize) < self.voxels.len() { if idx >= 0 && (idx as u32) < self.n_voxels {
unsafe { *self.voxels.add(idx as usize) } unsafe { *self.voxels.add(idx as usize) }
} else { } else {
0.0 0.0
@ -129,7 +135,7 @@ impl MajorantGrid {
} }
#[inline(always)] #[inline(always)]
pub fn set(&self, x: i32, y: i32, z: i32, v: Float) { pub fn set(&mut self, x: i32, y: i32, z: i32, v: Float) {
if !self.is_valid() { if !self.is_valid() {
return; return;
} }
@ -262,7 +268,7 @@ impl DDAMajorantIterator {
let p_grid_start = grid.bounds.offset(&ray.at(t_min)); let p_grid_start = grid.bounds.offset(&ray.at(t_min));
let grid_intersect = Vector3f::from(p_grid_start); let grid_intersect = Vector3f::from(p_grid_start);
let res = [grid.res.x, grid.res.y, grid.res.z]; let res = [grid.res.x(), grid.res.y(), grid.res.z()];
for axis in 0..3 { for axis in 0..3 {
iter.voxel[axis] = clamp( iter.voxel[axis] = clamp(
@ -433,7 +439,8 @@ pub trait MediumTrait: Send + Sync + std::fmt::Debug {
} }
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Clone, Copy)]
#[enum_dispatch(MediumTrait)] #[enum_dispatch(MediumTrait)]
pub enum Medium { pub enum Medium {
Homogeneous(HomogeneousMedium), Homogeneous(HomogeneousMedium),
@ -443,38 +450,13 @@ pub enum Medium {
NanoVDB(NanoVDBMedium), NanoVDB(NanoVDBMedium),
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct HomogeneousMedium { pub struct HomogeneousMedium {
sigma_a_spec: DenselySampledSpectrum, pub sigma_a_spec: DenselySampledSpectrum,
sigma_s_spec: DenselySampledSpectrum, pub sigma_s_spec: DenselySampledSpectrum,
le_spec: DenselySampledSpectrum, pub le_spec: DenselySampledSpectrum,
phase: HGPhaseFunction, pub phase: HGPhaseFunction,
}
impl HomogeneousMedium {
pub fn new(
sigma_a: Spectrum,
sigma_s: Spectrum,
sigma_scale: Float,
le: Spectrum,
le_scale: Float,
g: Float,
) -> Self {
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);
le_spec.scale(le_scale);
Self {
sigma_a_spec,
sigma_s_spec,
le_spec,
phase: HGPhaseFunction::new(g),
}
}
} }
impl MediumTrait for HomogeneousMedium { impl MediumTrait for HomogeneousMedium {
@ -513,70 +495,17 @@ impl MediumTrait for HomogeneousMedium {
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct GridMedium { pub struct GridMedium {
bounds: Bounds3f, pub bounds: Bounds3f,
render_from_medium: TransformGeneric<Float>, pub render_from_medium: Transform,
sigma_a_spec: DenselySampledSpectrum, pub sigma_a_spec: DenselySampledSpectrum,
sigma_s_spec: DenselySampledSpectrum, pub sigma_s_spec: DenselySampledSpectrum,
density_grid: SampledGrid<Float>, pub density_grid: SampledGrid<Float>,
phase: HGPhaseFunction, pub phase: HGPhaseFunction,
temperature_grid: SampledGrid<Float>, pub temperature_grid: Option<SampledGrid<Float>>,
le_spec: DenselySampledSpectrum, pub le_spec: DenselySampledSpectrum,
le_scale: SampledGrid<Float>, pub le_scale: SampledGrid<Float>,
is_emissive: bool, pub is_emissive: bool,
majorant_grid: MajorantGrid, pub majorant_grid: MajorantGrid,
}
impl GridMedium {
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "cuda"))]
pub fn new(
bounds: &Bounds3f,
render_from_medium: &Transform,
sigma_a: &Spectrum,
sigma_s: &Spectrum,
sigma_scale: Float,
g: Float,
density_grid: SampledGrid<Float>,
temperature_grid: SampledGrid<Float>,
le: &Spectrum,
le_scale: SampledGrid<Float>,
) -> Self {
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);
let mut majorant_grid = MajorantGrid::new(*bounds, Point3i::new(16, 16, 16));
let is_emissive = if temperature_grid.is_some() {
true
} else {
le_spec.max_value() > 0.
};
for z in 0..majorant_grid.res.z() {
for y in 0..majorant_grid.res.y() {
for x in 0..majorant_grid.res.x() {
let bounds = majorant_grid.voxel_bounds(x, y, z);
majorant_grid.set(x, y, z, density_grid.max_value(bounds));
}
}
}
Self {
bounds: *bounds,
render_from_medium: *render_from_medium,
sigma_a_spec,
sigma_s_spec,
density_grid,
phase: HGPhaseFunction::new(g),
temperature_grid,
le_spec,
le_scale,
is_emissive,
majorant_grid,
}
}
} }
impl MediumTrait for GridMedium { impl MediumTrait for GridMedium {
@ -599,12 +528,11 @@ impl MediumTrait for GridMedium {
}; };
let le = if scale > 0.0 { let le = if scale > 0.0 {
let raw_emission = match &self.temperature_grid { let raw_emission = if let Some(temp_grid) = &self.temperature_grid {
Some(grid) => { let temp = temp_grid.lookup(p);
let temp = grid.lookup(p);
BlackbodySpectrum::new(temp).sample(lambda) BlackbodySpectrum::new(temp).sample(lambda)
} } else {
None => self.le_spec.sample(lambda), self.le_spec.sample(lambda)
}; };
raw_emission * scale raw_emission * scale
@ -660,59 +588,15 @@ impl MediumTrait for GridMedium {
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct RGBGridMedium { pub struct RGBGridMedium {
bounds: Bounds3f, pub bounds: Bounds3f,
render_from_medium: Transform, pub render_from_medium: Transform,
phase: HGPhaseFunction, pub phase: HGPhaseFunction,
le_scale: Float, pub le_scale: Float,
sigma_scale: Float, pub sigma_scale: Float,
sigma_a_grid: SampledGrid<RGBUnboundedSpectrum>, pub sigma_a_grid: SampledGrid<RGBUnboundedSpectrum>,
sigma_s_grid: SampledGrid<RGBUnboundedSpectrum>, pub sigma_s_grid: SampledGrid<RGBUnboundedSpectrum>,
le_grid: SampledGrid<RGBIlluminantSpectrum>, pub le_grid: SampledGrid<RGBIlluminantSpectrum>,
majorant_grid: MajorantGrid, pub majorant_grid: MajorantGrid,
}
impl RGBGridMedium {
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "cuda"))]
pub fn new(
bounds: &Bounds3f,
render_from_medium: &TransformGeneric<Float>,
g: Float,
sigma_a_grid: SampledGrid<RGBUnboundedSpectrum>,
sigma_s_grid: SampledGrid<RGBUnboundedSpectrum>,
sigma_scale: Float,
le_grid: SampledGrid<RGBIlluminantSpectrum>,
le_scale: Float,
) -> Self {
let mut majorant_grid = MajorantGrid::new(*bounds, Point3i::new(16, 16, 16));
for z in 0..majorant_grid.res.x() {
for y in 0..majorant_grid.res.y() {
for x in 0..majorant_grid.res.x() {
let bounds = majorant_grid.voxel_bounds(x, y, z);
let convert = |s: &RGBUnboundedSpectrum| s.max_value();
let max_sigma_t = sigma_a_grid
.as_ref()
.map_or(1.0, |g| g.max_value_convert(bounds, convert))
+ sigma_s_grid
.as_ref()
.map_or(1.0, |g| g.max_value_convert(bounds, convert));
majorant_grid.set(x, y, z, sigma_scale * max_sigma_t);
}
}
}
Self {
bounds: *bounds,
render_from_medium: *render_from_medium,
le_grid,
le_scale,
phase: HGPhaseFunction::new(g),
sigma_a_grid,
sigma_s_grid,
sigma_scale,
majorant_grid,
}
}
} }
impl MediumTrait for RGBGridMedium { impl MediumTrait for RGBGridMedium {
@ -785,7 +669,8 @@ impl MediumTrait for RGBGridMedium {
} }
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct CloudMedium; pub struct CloudMedium;
impl MediumTrait for CloudMedium { impl MediumTrait for CloudMedium {
fn is_emissive(&self) -> bool { fn is_emissive(&self) -> bool {
@ -803,7 +688,9 @@ impl MediumTrait for CloudMedium {
todo!() todo!()
} }
} }
#[derive(Debug, Clone)]
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct NanoVDBMedium; pub struct NanoVDBMedium;
impl MediumTrait for NanoVDBMedium { impl MediumTrait for NanoVDBMedium {
fn is_emissive(&self) -> bool { fn is_emissive(&self) -> bool {
@ -825,8 +712,8 @@ impl MediumTrait for NanoVDBMedium {
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct MediumInterface { pub struct MediumInterface {
pub inside: *const Medium, pub inside: Ptr<Medium>,
pub outside: *const Medium, pub outside: Ptr<Medium>,
} }
unsafe impl Send for MediumInterface {} unsafe impl Send for MediumInterface {}
@ -835,22 +722,37 @@ unsafe impl Sync for MediumInterface {}
impl Default for MediumInterface { impl Default for MediumInterface {
fn default() -> Self { fn default() -> Self {
Self { Self {
inside: core::ptr::null(), inside: Ptr::null(),
outside: core::ptr::null(), outside: Ptr::null(),
} }
} }
} }
impl From<Medium> for MediumInterface {
fn from(medium: Medium) -> Self {
Self {
inside: Ptr::from(&medium),
outside: Ptr::from(&medium),
}
}
}
impl From<&Medium> for MediumInterface {
fn from(medium: &Medium) -> Self {
Self::from(medium.clone())
}
}
impl MediumInterface { impl MediumInterface {
pub fn new(inside: *const Medium, outside: *const Medium) -> Self { pub fn new(inside: &Medium, outside: &Medium) -> Self {
Self { inside, outside } Self {
inside: Ptr::from(inside),
outside: Ptr::from(outside),
}
} }
pub fn empty() -> Self { pub fn empty() -> Self {
Self { Self::default()
inside: core::ptr::null(),
outside: core::ptr::null(),
}
} }
pub fn is_medium_transition(&self) -> bool { pub fn is_medium_transition(&self) -> bool {

View file

@ -1,4 +1,4 @@
pub mod aggregates; pub mod bsdf;
pub mod bssrdf; pub mod bssrdf;
pub mod bxdf; pub mod bxdf;
pub mod camera; pub mod camera;
@ -6,6 +6,7 @@ pub mod color;
pub mod film; pub mod film;
pub mod filter; pub mod filter;
pub mod geometry; pub mod geometry;
pub mod image;
pub mod interaction; pub mod interaction;
pub mod light; pub mod light;
pub mod material; pub mod material;
@ -15,5 +16,6 @@ pub mod pbrt;
pub mod primitive; pub mod primitive;
pub mod sampler; pub mod sampler;
pub mod scattering; pub mod scattering;
pub mod shape;
pub mod spectrum; pub mod spectrum;
pub mod texture; pub mod texture;

View file

@ -1,12 +1,54 @@
use crate::core::geometry::Lerp; use crate::core::geometry::Lerp;
use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
use num_traits::{Num, PrimInt}; use num_traits::{Num, PrimInt};
use std::hash::Hash; use std::hash::Hash;
use std::ops::{Add, Mul}; use std::ops::{Add, Mul};
use std::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use crate::core::image::DeviceImage;
use crate::core::light::LightTrait;
use crate::core::shape::Shape;
use crate::core::texture::GPUFloatTexture;
use crate::lights::*;
use crate::spectra::{DenselySampledSpectrum, RGBColorSpace};
use crate::utils::Ptr;
pub type Float = f32; pub type Float = f32;
// #[derive(Copy, Clone, Debug)]
// pub struct Host;
//
// #[derive(Copy, Clone, Debug)]
// pub struct Device;
//
// pub trait Backend: Copy + Clone + 'static {
// type ShapeRef: Copy + Clone;
// type TextureRef: Copy + Clone;
// type ImageRef: Copy + Clone;
// type DenseSpectrumRef = Ptr<DenselySampledSpectrum>;
// type ColorSpaceRef = Ptr<RGBColorSpace>;
// type DiffuseLight: LightTrait;
// type PointLight: LightTrait;
// type UniformInfiniteLight: LightTrait;
// type PortalInfiniteLight: LightTrait;
// type ImageInfiniteLight: LightTrait;
// type SpotLight: LightTrait;
// }
//
// impl Backend for Device {
// type ShapeRef = Ptr<Shape>;
// type TextureRef = Ptr<GPUFloatTexture>;
// type ColorSpaceRef = Ptr<RGBColorSpace>;
// type DenseSpectrumRef = Ptr<DenselySampledSpectrum>;
// type ImageRef = Ptr<DeviceImage>;
// type DiffuseLight = Ptr<DiffuseAreaLight<Device>>;
// type PointLight = Ptr<PointLight<Device>>;
// type UniformInfiniteLight = Ptr<UniformInfiniteLight<Device>>;
// type PortalInfiniteLight = Ptr<PortalInfiniteLight<Device>>;
// type ImageInfiniteLight = Ptr<ImageInfiniteLight<Device>>;
// type SpotLight = Ptr<SpotLight<Device>>;
// }
//
#[cfg(not(feature = "use_f64"))] #[cfg(not(feature = "use_f64"))]
pub type FloatBits = u32; pub type FloatBits = u32;
@ -100,50 +142,18 @@ 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 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; pub const SQRT_2: Float = 1.414_213_562_373_095_048_80;
#[inline]
pub fn find_interval<T, P>(sz: T, pred: P) -> T
where
T: PrimInt,
P: Fn(T) -> bool,
{
let zero = T::zero();
let one = T::one();
let two = one + one;
if sz <= two {
return zero;
}
let mut low = one;
let mut high = sz - one;
while low < high {
// mid = low + (high - low) / 2
let mid = low + (high - low) / two;
if pred(mid) {
low = mid + one;
} else {
high = mid;
}
}
let result = low - one;
num_traits::clamp(result, zero, sz - two)
}
#[inline] #[inline]
pub fn gamma(n: i32) -> Float { pub fn gamma(n: i32) -> Float {
n as Float * MACHINE_EPSILON / (1. - n as Float * MACHINE_EPSILON) n as Float * MACHINE_EPSILON / (1. - n as Float * MACHINE_EPSILON)
} }
// Define the static counters. These are thread-safe.
pub static RARE_EVENT_TOTAL_CALLS: AtomicU64 = AtomicU64::new(0); pub static RARE_EVENT_TOTAL_CALLS: AtomicU64 = AtomicU64::new(0);
pub static RARE_EVENT_CONDITION_MET: AtomicU64 = AtomicU64::new(0); pub static RARE_EVENT_CONDITION_MET: AtomicU64 = AtomicU64::new(0);
#[macro_export] #[macro_export]
macro_rules! check_rare { macro_rules! check_rare {
($frequency_threshold:expr, $condition:expr) => { ($frequency_threshold:expr, $condition:expr) => {
use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
const CHECK_INTERVAL: u64 = 4096; const CHECK_INTERVAL: u64 = 4096;
let total_calls = RARE_EVENT_TOTAL_CALLS.fetch_add(1, SyncOrdering::Relaxed); let total_calls = RARE_EVENT_TOTAL_CALLS.fetch_add(1, SyncOrdering::Relaxed);

View file

@ -1,14 +1,14 @@
use crate::core::aggregates::LinearBVHNode;
use crate::core::geometry::{Bounds3f, Ray}; use crate::core::geometry::{Bounds3f, Ray};
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::light::Light; use crate::core::light::Light;
use crate::core::material::Material; use crate::core::material::Material;
use crate::core::medium::{Medium, MediumInterface}; use crate::core::medium::{Medium, MediumInterface};
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::shape::{Shape, ShapeIntersection, ShapeTrait};
use crate::core::texture::{GPUFloatTexture, TextureEvalContext}; use crate::core::texture::{GPUFloatTexture, TextureEvalContext};
use crate::shapes::{Shape, ShapeIntersection, ShapeTrait}; use crate::utils::Ptr;
use crate::utils::hash::hash_float; use crate::utils::hash::hash_float;
use crate::utils::transform::{AnimatedTransform, TransformGeneric}; use crate::utils::transform::{AnimatedTransform, Transform};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::sync::Arc; use std::sync::Arc;
@ -23,11 +23,11 @@ pub trait PrimitiveTrait {
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct GeometricPrimitive { pub struct GeometricPrimitive {
shape: *const Shape, pub shape: Ptr<Shape>,
material: *const Material, pub material: Ptr<Material>,
area_light: *const Light, pub area_light: Ptr<Light>,
medium_interface: MediumInterface, pub medium_interface: MediumInterface,
alpha: *const GPUFloatTexture, pub alpha: Ptr<GPUFloatTexture>,
} }
unsafe impl Send for GeometricPrimitive {} unsafe impl Send for GeometricPrimitive {}
@ -40,7 +40,8 @@ impl PrimitiveTrait for GeometricPrimitive {
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> { fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
let mut si = self.shape.intersect(r, t_max)?; let mut si = self.shape.intersect(r, t_max)?;
if let Some(ref alpha) = self.alpha { if !self.alpha.is_null() {
let alpha = unsafe { &self.alpha.as_ref() };
let ctx = TextureEvalContext::from(&si.intr); let ctx = TextureEvalContext::from(&si.intr);
let a = alpha.evaluate(&ctx); let a = alpha.evaluate(&ctx);
if a < 1.0 { if a < 1.0 {
@ -65,18 +66,21 @@ impl PrimitiveTrait for GeometricPrimitive {
} }
} }
if r.medium.is_null() {
return None;
}
si.set_intersection_properties( si.set_intersection_properties(
self.material.clone(), self.material,
self.area_light.clone(), self.area_light,
Some(self.medium_interface.clone()), self.medium_interface.clone(),
Some(r.medium.clone().expect("Medium not set")), r.medium,
); );
Some(si) Some(si)
} }
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool { fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool {
if self.alpha.is_some() { if !self.alpha.is_null() {
self.intersect(r, t_max).is_some() self.intersect(r, t_max).is_some()
} else { } else {
self.shape.intersect_p(r, t_max) self.shape.intersect_p(r, t_max)
@ -87,14 +91,28 @@ impl PrimitiveTrait for GeometricPrimitive {
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct SimplePrimitive { pub struct SimplePrimitive {
shape: Arc<Shape>, pub shape: Ptr<Shape>,
material: Arc<Material>, pub material: Ptr<Material>,
}
impl PrimitiveTrait for SimplePrimitive {
fn bounds(&self) -> Bounds3f {
todo!()
}
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
todo!()
}
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool {
todo!()
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TransformedPrimitive { pub struct TransformedPrimitive {
primitive: Arc<dyn PrimitiveTrait>, pub primitive: Ptr<Primitive>,
render_from_primitive: TransformGeneric<Float>, pub render_from_primitive: Transform,
} }
impl PrimitiveTrait for TransformedPrimitive { impl PrimitiveTrait for TransformedPrimitive {
@ -125,9 +143,10 @@ impl PrimitiveTrait for TransformedPrimitive {
} }
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct AnimatedPrimitive { pub struct AnimatedPrimitive {
primitive: Arc<dyn PrimitiveTrait>, primitive: Ptr<Primitive>,
render_from_primitive: AnimatedTransform, render_from_primitive: AnimatedTransform,
} }
@ -158,31 +177,38 @@ impl PrimitiveTrait for AnimatedPrimitive {
} }
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct LinearBVHNode {
bounds: Bounds3f,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct BVHAggregatePrimitive { pub struct BVHAggregatePrimitive {
max_prims_in_node: usize, max_prims_in_node: u32,
primitives: Vec<Arc<dyn PrimitiveTrait>>, primitives: *const Ptr<Primitive>,
nodes: Vec<LinearBVHNode>, nodes: Ptr<LinearBVHNode>,
} }
impl PrimitiveTrait for BVHAggregatePrimitive { impl PrimitiveTrait for BVHAggregatePrimitive {
fn bounds(&self) -> Bounds3f { fn bounds(&self) -> Bounds3f {
if !self.nodes.is_empty() { if !self.nodes.is_null() {
self.nodes[0].bounds self.nodes.bounds
} else { } else {
Bounds3f::default() Bounds3f::default()
} }
} }
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> { fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
if self.nodes.is_empty() { if !self.nodes.is_null() {
return None; return None;
} }
self.intersect(r, t_max) self.intersect(r, t_max)
} }
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool { fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool {
if self.nodes.is_empty() { if !self.nodes.is_null() {
return false; return false;
} }
self.intersect_p(r, t_max) self.intersect_p(r, t_max)
@ -209,6 +235,7 @@ impl PrimitiveTrait for KdTreeAggregate {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[enum_dispatch(PrimitiveTrait)] #[enum_dispatch(PrimitiveTrait)]
pub enum Primitive { pub enum Primitive {
Simple(SimplePrimitive),
Geometric(GeometricPrimitive), Geometric(GeometricPrimitive),
Transformed(TransformedPrimitive), Transformed(TransformedPrimitive),
Animated(AnimatedPrimitive), Animated(AnimatedPrimitive),

View file

@ -1,27 +1,23 @@
use std::ops::RangeFull;
use enum_dispatch::enum_dispatch;
use rand::seq::index::sample;
use crate::core::filter::FilterTrait; use crate::core::filter::FilterTrait;
use crate::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f}; use crate::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f};
use crate::core::options::{PBRTOptions, get_options}; 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::core::pbrt::{Float, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4};
use crate::utils::containers::Array2D; use crate::utils::Ptr;
use crate::utils::error::FileLoc; use crate::utils::containers::DeviceArray2D;
use crate::utils::math::{ use crate::utils::math::{
BinaryPermuteScrambler, DigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler, BinaryPermuteScrambler, DeviceDigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler,
PRIME_TABLE_SIZE, Scrambler, clamp, compute_radical_inverse_permutations, encode_morton_2, PRIME_TABLE_SIZE, Scrambler, clamp, encode_morton_2, inverse_radical_inverse, lerp, log2_int,
inverse_radical_inverse, lerp, log2_int, owen_scrambled_radical_inverse, permutation_element, owen_scrambled_radical_inverse, permutation_element, radical_inverse, round_up_pow2,
radical_inverse, round_up_pow2, scrambled_radical_inverse, sobol_interval_to_index, scrambled_radical_inverse, sobol_interval_to_index, sobol_sample,
sobol_sample,
}; };
use crate::utils::parameters::ParameterDictionary;
use crate::utils::rng::Rng; use crate::utils::rng::Rng;
use crate::utils::sobol::N_SOBOL_DIMENSIONS; use crate::utils::sobol::N_SOBOL_DIMENSIONS;
use crate::utils::{hash::*, sobol}; use crate::utils::{hash::*, sobol};
use enum_dispatch::enum_dispatch;
use rand::seq::index::sample;
#[derive(Debug, Clone, Copy)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy)]
pub struct CameraSample { pub struct CameraSample {
pub p_film: Point2f, pub p_film: Point2f,
pub p_lens: Point2f, pub p_lens: Point2f,
@ -29,17 +25,6 @@ pub struct CameraSample {
pub filter_weight: Float, pub filter_weight: Float,
} }
impl Default for CameraSample {
fn default() -> Self {
Self {
p_film: Point2f::default(),
p_lens: Point2f::default(),
time: 0.0,
filter_weight: 1.0,
}
}
}
pub fn get_camera_sample<S, F>(sampler: &mut S, p_pixel: Point2i, filter: &F) -> CameraSample pub fn get_camera_sample<S, F>(sampler: &mut S, p_pixel: Point2i, filter: &F) -> CameraSample
where where
S: SamplerTrait, S: SamplerTrait,
@ -54,43 +39,29 @@ where
} }
} }
#[derive(Default, Debug, Clone)] #[repr(C)]
#[derive(Default, Debug, Clone, Copy)]
pub struct IndependentSampler { pub struct IndependentSampler {
samples_per_pixel: usize, pub samples_per_pixel: i32,
seed: u64, pub seed: u64,
rng: Rng, pub rng: Rng,
} }
impl IndependentSampler { impl IndependentSampler {
pub fn new(samples_per_pixel: usize, seed: u64) -> Self { pub fn new(samples_per_pixel: i32, seed: u64) -> Self {
Self { Self {
samples_per_pixel, samples_per_pixel,
seed, seed,
rng: Rng::default(), 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 { impl SamplerTrait for IndependentSampler {
fn samples_per_pixel(&self) -> usize { fn samples_per_pixel(&self) -> i32 {
self.samples_per_pixel self.samples_per_pixel
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>) { fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
let hash_input = [p.x() as u64, p.y() as u64, self.seed]; let hash_input = [p.x() as u64, p.y() as u64, self.seed];
let sequence_index = hash_buffer(&hash_input, 0); let sequence_index = hash_buffer(&hash_input, 0);
self.rng.set_sequence(sequence_index); self.rng.set_sequence(sequence_index);
@ -109,9 +80,10 @@ impl SamplerTrait for IndependentSampler {
} }
} }
const MAX_HALTON_RESOLUTION: i32 = 128; pub const MAX_HALTON_RESOLUTION: i32 = 128;
#[derive(Debug, Default, Clone, PartialEq, Eq)] #[repr(C)]
#[derive(Debug, Default, Clone, PartialEq, Eq, Copy)]
pub enum RandomizeStrategy { pub enum RandomizeStrategy {
#[default] #[default]
None, None,
@ -120,75 +92,26 @@ pub enum RandomizeStrategy {
Owen, Owen,
} }
#[derive(Default, Debug, Clone)] #[repr(C)]
#[derive(Default, Debug, Clone, Copy)]
pub struct HaltonSampler { pub struct HaltonSampler {
samples_per_pixel: usize, pub samples_per_pixel: i32,
randomize: RandomizeStrategy, pub randomize: RandomizeStrategy,
digit_permutations: Vec<DigitPermutation>, pub base_scales: [u64; 2],
base_scales: [u64; 2], pub base_exponents: [u64; 2],
base_exponents: [u64; 2], pub mult_inverse: [u64; 2],
mult_inverse: [u64; 2], pub halton_index: u64,
halton_index: u64, pub dim: u32,
dim: usize, pub digit_permutations: Ptr<DeviceDigitPermutation>,
} }
impl HaltonSampler { impl HaltonSampler {
pub fn new( pub fn sample_dimension(&self, dimension: u32) -> Float {
samples_per_pixel: usize,
full_res: Point2i,
randomize: RandomizeStrategy,
seed: u64,
) -> Self {
let digit_permutations = compute_radical_inverse_permutations(seed);
let mut base_scales = [0u64; 2];
let mut base_exponents = [0u64; 2];
let bases = [2, 3];
let res_coords = [full_res.x(), full_res.y()];
for i in 0..2 {
let base = bases[i] as u64;
let mut scale = 1u64;
let mut exp = 0u64;
let limit = std::cmp::min(res_coords[i], MAX_HALTON_RESOLUTION) as u64;
while scale < limit {
scale *= base;
exp += 1;
}
base_scales[i] = scale;
base_exponents[i] = exp;
}
let mut mult_inverse = [0u64; 2];
mult_inverse[0] =
Self::multiplicative_inverse(base_scales[0] as i64, base_scales[0] as i64);
mult_inverse[1] =
Self::multiplicative_inverse(base_scales[1] as i64, base_scales[1] as i64);
Self {
samples_per_pixel,
randomize,
digit_permutations,
base_scales,
base_exponents,
mult_inverse,
halton_index: 0,
dim: 0,
}
}
fn sample_dimension(&self, dimension: usize) -> Float {
if self.randomize == RandomizeStrategy::None { if self.randomize == RandomizeStrategy::None {
radical_inverse(dimension, self.halton_index) radical_inverse(dimension, self.halton_index)
} else if self.randomize == RandomizeStrategy::PermuteDigits { } else if self.randomize == RandomizeStrategy::PermuteDigits {
scrambled_radical_inverse( let digit_perm = unsafe { &*self.digit_permutations.add(dimension as usize) };
dimension, scrambled_radical_inverse(dimension, self.halton_index, digit_perm)
self.halton_index,
&self.digit_permutations[dimension],
)
} else { } else {
owen_scrambled_radical_inverse( owen_scrambled_radical_inverse(
dimension, dimension,
@ -198,12 +121,12 @@ impl HaltonSampler {
} }
} }
fn multiplicative_inverse(a: i64, n: i64) -> u64 { pub fn multiplicative_inverse(a: i64, n: i64) -> u64 {
let (x, _) = Self::extended_gcd(a as u64, n as u64); let (x, _) = Self::extended_gcd(a as u64, n as u64);
x.rem_euclid(n) as u64 x.rem_euclid(n) as u64
} }
fn extended_gcd(a: u64, b: u64) -> (i64, i64) { pub fn extended_gcd(a: u64, b: u64) -> (i64, i64) {
if b == 0 { if b == 0 {
return (1, 0); return (1, 0);
} }
@ -214,45 +137,14 @@ impl HaltonSampler {
(yp, xp - d * yp) (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 { impl SamplerTrait for HaltonSampler {
fn samples_per_pixel(&self) -> usize { fn samples_per_pixel(&self) -> i32 {
self.samples_per_pixel self.samples_per_pixel
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>) { fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
self.halton_index = 0; self.halton_index = 0;
let sample_stride = self.base_scales[0] * self.base_scales[1]; let sample_stride = self.base_scales[0] * self.base_scales[1];
@ -287,14 +179,14 @@ impl SamplerTrait for HaltonSampler {
} }
fn get1d(&mut self) -> Float { fn get1d(&mut self) -> Float {
if self.dim > PRIME_TABLE_SIZE { if self.dim > PRIME_TABLE_SIZE as u32 {
self.dim = 2; self.dim = 2;
} }
self.sample_dimension(self.dim) self.sample_dimension(self.dim)
} }
fn get2d(&mut self) -> Point2f { fn get2d(&mut self) -> Point2f {
if self.dim > PRIME_TABLE_SIZE { if self.dim > PRIME_TABLE_SIZE as u32 {
self.dim = 2; self.dim = 2;
} }
let dim = self.dim; let dim = self.dim;
@ -310,22 +202,23 @@ impl SamplerTrait for HaltonSampler {
} }
} }
#[derive(Default, Debug, Clone)] #[repr(C)]
#[derive(Default, Debug, Clone, Copy)]
pub struct StratifiedSampler { pub struct StratifiedSampler {
x_pixel_samples: usize, x_pixel_samples: i32,
y_pixel_samples: usize, y_pixel_samples: i32,
jitter: bool, jitter: bool,
seed: u64, seed: u64,
rng: Rng, rng: Rng,
pixel: Point2i, pixel: Point2i,
sample_index: usize, sample_index: i32,
dim: usize, dim: u32,
} }
impl StratifiedSampler { impl StratifiedSampler {
pub fn new( pub fn new(
x_pixel_samples: usize, x_pixel_samples: i32,
y_pixel_samples: usize, y_pixel_samples: i32,
seed: Option<u64>, seed: Option<u64>,
jitter: bool, jitter: bool,
) -> Self { ) -> Self {
@ -340,43 +233,14 @@ impl StratifiedSampler {
dim: 0, 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 { impl SamplerTrait for StratifiedSampler {
fn samples_per_pixel(&self) -> usize { fn samples_per_pixel(&self) -> i32 {
self.x_pixel_samples * self.y_pixel_samples self.x_pixel_samples * self.y_pixel_samples
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>) { fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
self.pixel = p; self.pixel = p;
self.sample_index = sample_index; self.sample_index = sample_index;
let hash_input = [p.x() as u64, p.y() as u64, self.seed]; let hash_input = [p.x() as u64, p.y() as u64, self.seed];
@ -446,18 +310,19 @@ impl SamplerTrait for StratifiedSampler {
} }
} }
#[derive(Default, Debug, Clone)] #[repr(C)]
#[derive(Default, Debug, Clone, Copy)]
pub struct PaddedSobolSampler { pub struct PaddedSobolSampler {
samples_per_pixel: usize, samples_per_pixel: i32,
seed: u64, seed: u64,
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
pixel: Point2i, pixel: Point2i,
sample_index: usize, sample_index: i32,
dim: usize, dim: u32,
} }
impl PaddedSobolSampler { impl PaddedSobolSampler {
pub fn new(samples_per_pixel: usize, randomize: RandomizeStrategy, seed: Option<u64>) -> Self { pub fn new(samples_per_pixel: i32, randomize: RandomizeStrategy, seed: Option<u64>) -> Self {
Self { Self {
samples_per_pixel, samples_per_pixel,
seed: seed.unwrap_or(0), seed: seed.unwrap_or(0),
@ -468,7 +333,7 @@ impl PaddedSobolSampler {
} }
} }
fn sample_dimension(&self, dimension: usize, a: u32, hash: u32) -> Float { fn sample_dimension(&self, dimension: u32, a: u32, hash: u32) -> Float {
if self.randomize == RandomizeStrategy::None { if self.randomize == RandomizeStrategy::None {
return sobol_sample(a as u64, dimension, NoRandomizer); return sobol_sample(a as u64, dimension, NoRandomizer);
} }
@ -483,41 +348,13 @@ impl PaddedSobolSampler {
RandomizeStrategy::None => unreachable!(), 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 { impl SamplerTrait for PaddedSobolSampler {
fn samples_per_pixel(&self) -> usize { fn samples_per_pixel(&self) -> i32 {
self.samples_per_pixel self.samples_per_pixel
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>) { fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
self.pixel = p; self.pixel = p;
self.sample_index = sample_index; self.sample_index = sample_index;
self.dim = dim.unwrap_or(0); self.dim = dim.unwrap_or(0);
@ -530,13 +367,13 @@ impl SamplerTrait for PaddedSobolSampler {
self.dim as u64, self.dim as u64,
self.seed, self.seed,
]; ];
let hash = hash_buffer(&hash_input, 0) as u32; let hash = hash_buffer(&hash_input, 0);
let index = permutation_element( let index = permutation_element(
self.sample_index as u32, self.sample_index as u32,
self.samples_per_pixel as u32, self.samples_per_pixel as u32,
hash, hash as u32,
); );
self.sample_dimension(0, index, hash >> 32) self.sample_dimension(0, index, (hash >> 32) as u32)
} }
fn get2d(&mut self) -> Point2f { fn get2d(&mut self) -> Point2f {
let hash_input = [ let hash_input = [
@ -545,16 +382,16 @@ impl SamplerTrait for PaddedSobolSampler {
self.dim as u64, self.dim as u64,
self.seed, self.seed,
]; ];
let hash = hash_buffer(&hash_input, 0) as u32; let hash = hash_buffer(&hash_input, 0);
let index = permutation_element( let index = permutation_element(
self.sample_index as u32, self.sample_index as u32,
self.samples_per_pixel as u32, self.samples_per_pixel as u32,
hash, hash as u32,
); );
self.dim += 2; self.dim += 2;
Point2f::new( Point2f::new(
self.sample_dimension(0, index, hash), self.sample_dimension(0, index, hash as u32),
self.sample_dimension(1, index, hash >> 32), self.sample_dimension(1, index, (hash >> 32) as u32),
) )
} }
@ -565,18 +402,18 @@ impl SamplerTrait for PaddedSobolSampler {
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct SobolSampler { pub struct SobolSampler {
samples_per_pixel: usize, samples_per_pixel: i32,
scale: i32, scale: i32,
seed: u64, seed: u64,
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
pixel: Point2i, pixel: Point2i,
dim: usize, dim: u32,
sobol_index: u64, sobol_index: u64,
} }
impl SobolSampler { impl SobolSampler {
pub fn new( pub fn new(
samples_per_pixel: usize, samples_per_pixel: i32,
full_resolution: Point2i, full_resolution: Point2i,
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
seed: Option<u64>, seed: Option<u64>,
@ -593,7 +430,7 @@ impl SobolSampler {
} }
} }
fn sample_dimension(&self, dimension: usize) -> Float { fn sample_dimension(&self, dimension: u32) -> Float {
if self.randomize == RandomizeStrategy::None { if self.randomize == RandomizeStrategy::None {
return sobol_sample(self.sobol_index, dimension, NoRandomizer); return sobol_sample(self.sobol_index, dimension, NoRandomizer);
} }
@ -614,41 +451,13 @@ impl SobolSampler {
RandomizeStrategy::None => unreachable!(), 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 { impl SamplerTrait for SobolSampler {
fn samples_per_pixel(&self) -> usize { fn samples_per_pixel(&self) -> i32 {
self.samples_per_pixel self.samples_per_pixel
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>) { fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
self.pixel = p; self.pixel = p;
self.dim = 2.max(dim.unwrap_or(0)); self.dim = 2.max(dim.unwrap_or(0));
self.sobol_index = self.sobol_index =
@ -656,7 +465,7 @@ impl SamplerTrait for SobolSampler {
} }
fn get1d(&mut self) -> Float { fn get1d(&mut self) -> Float {
if self.dim >= N_SOBOL_DIMENSIONS { if self.dim >= N_SOBOL_DIMENSIONS as u32 {
self.dim = 2; self.dim = 2;
} }
@ -666,7 +475,7 @@ impl SamplerTrait for SobolSampler {
} }
fn get2d(&mut self) -> Point2f { fn get2d(&mut self) -> Point2f {
if self.dim >= N_SOBOL_DIMENSIONS { if self.dim >= N_SOBOL_DIMENSIONS as u32 {
self.dim = 2; self.dim = 2;
} }
let u = Point2f::new( let u = Point2f::new(
@ -696,19 +505,20 @@ impl SamplerTrait for SobolSampler {
} }
} }
#[derive(Default, Debug, Clone)] #[repr(C)]
#[derive(Default, Copy, Debug, Clone)]
pub struct ZSobolSampler { pub struct ZSobolSampler {
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
seed: u64, seed: u64,
log2_samples_per_pixel: u32, log2_samples_per_pixel: i32,
n_base4_digits: u32, n_base4_digits: u32,
morton_index: u64, morton_index: u64,
dim: usize, dim: u32,
} }
impl ZSobolSampler { impl ZSobolSampler {
pub fn new( pub fn new(
samples_per_pixel: u32, samples_per_pixel: i32,
full_resolution: Point2i, full_resolution: Point2i,
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
seed: Option<u64>, seed: Option<u64>,
@ -716,11 +526,11 @@ impl ZSobolSampler {
let log2_samples_per_pixel = log2_int(samples_per_pixel as Float) as u32; let log2_samples_per_pixel = log2_int(samples_per_pixel as Float) as u32;
let res = round_up_pow2(full_resolution.x().max(full_resolution.y())); let res = round_up_pow2(full_resolution.x().max(full_resolution.y()));
let log4_samples_per_pixel = log2_samples_per_pixel.div_ceil(2); let log4_samples_per_pixel = log2_samples_per_pixel.div_ceil(2);
let n_base4_digits = log2_int(res as Float) as u32 + log4_samples_per_pixel; let n_base4_digits = log2_int(res as Float) as u32 + log4_samples_per_pixel as u32;
Self { Self {
randomize, randomize,
seed: seed.unwrap_or(0), seed: seed.unwrap_or(0),
log2_samples_per_pixel, log2_samples_per_pixel: log2_samples_per_pixel as i32,
n_base4_digits, n_base4_digits,
morton_index: 0, morton_index: 0,
dim: 0, dim: 0,
@ -781,46 +591,13 @@ impl ZSobolSampler {
sample_index 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 { impl SamplerTrait for ZSobolSampler {
fn samples_per_pixel(&self) -> usize { fn samples_per_pixel(&self) -> i32 {
todo!() todo!()
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>) { fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
self.dim = dim.unwrap_or(0); self.dim = dim.unwrap_or(0);
self.morton_index = (encode_morton_2(p.x() as u32, p.y() as u32) self.morton_index = (encode_morton_2(p.x() as u32, p.y() as u32)
<< self.log2_samples_per_pixel) << self.log2_samples_per_pixel)
@ -886,10 +663,10 @@ impl SamplerTrait for ZSobolSampler {
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct MLTSampler; pub struct MLTSampler;
impl SamplerTrait for MLTSampler { impl SamplerTrait for MLTSampler {
fn samples_per_pixel(&self) -> usize { fn samples_per_pixel(&self) -> i32 {
todo!() todo!()
} }
fn start_pixel_sample(&mut self, _p: Point2i, _sample_index: usize, _dim: Option<usize>) { fn start_pixel_sample(&mut self, _p: Point2i, _sample_index: i32, _dim: Option<u32>) {
todo!() todo!()
} }
fn get1d(&mut self) -> Float { fn get1d(&mut self) -> Float {
@ -905,8 +682,8 @@ impl SamplerTrait for MLTSampler {
#[enum_dispatch] #[enum_dispatch]
pub trait SamplerTrait { pub trait SamplerTrait {
fn samples_per_pixel(&self) -> usize; fn samples_per_pixel(&self) -> i32;
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>); fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>);
fn get1d(&mut self) -> Float; fn get1d(&mut self) -> Float;
fn get2d(&mut self) -> Point2f; fn get2d(&mut self) -> Point2f;
fn get_pixel2d(&mut self) -> Point2f; fn get_pixel2d(&mut self) -> Point2f;
@ -923,40 +700,3 @@ pub enum Sampler {
ZSobol(ZSobolSampler), ZSobol(ZSobolSampler),
MLT(MLTSampler), 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

@ -9,6 +9,7 @@ use crate::utils::sampling::sample_uniform_disk_polar;
use num::complex::Complex; use num::complex::Complex;
#[repr(C)]
#[derive(Debug, Default, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
pub struct TrowbridgeReitzDistribution { pub struct TrowbridgeReitzDistribution {
alpha_x: Float, alpha_x: Float,
@ -170,3 +171,40 @@ pub fn fr_complex_from_spectrum(
} }
result result
} }
pub fn fresnel_moment1(eta: Float) -> Float {
let eta2 = eta * eta;
let eta3 = eta2 * eta;
let eta4 = eta3 * eta;
let eta5 = eta4 * eta;
if eta < 1. {
return 0.45966 - 1.73965 * eta + 3.37668 * eta2 - 3.904945 * eta3 + 2.49277 * eta4
- 0.68441 * eta5;
} else {
return -4.61686 + 11.1136 * eta - 10.4646 * eta2 + 5.11455 * eta3 - 1.27198 * eta4
+ 0.12746 * eta5;
}
}
pub fn fresnel_moment2(eta: Float) -> Float {
let eta2 = eta * eta;
let eta3 = eta2 * eta;
let eta4 = eta3 * eta;
let eta5 = eta4 * eta;
if eta < 1. {
return 0.27614 - 0.87350 * eta + 1.12077 * eta2 - 0.65095 * eta3
+ 0.07883 * eta4
+ 0.04860 * eta5;
} else {
let r_eta = 1. / eta;
let r_eta2 = r_eta * r_eta;
let r_eta3 = r_eta2 * r_eta;
return -547.033 + 45.3087 * r_eta3 - 218.725 * r_eta2 + 458.843 * r_eta + 404.557 * eta
- 189.519 * eta2
+ 54.9327 * eta3
- 9.00603 * eta4
+ 0.63942 * eta5;
}
}

154
shared/src/core/shape.rs Normal file
View file

@ -0,0 +1,154 @@
use crate::core::geometry::{
Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
Vector3fi, VectorLike, ray,
};
use crate::core::interaction::{
Interaction, InteractionTrait, MediumInteraction, SurfaceInteraction,
};
use crate::core::light::Light;
use crate::core::material::Material;
use crate::core::medium::{Medium, MediumInterface};
use crate::shapes::*;
use crate::utils::math::{next_float_down, next_float_up};
use crate::utils::{Ptr, Transform};
use crate::{Float, PI};
use enum_dispatch::enum_dispatch;
// Define Intersection objects. This only varies for
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ShapeIntersection {
pub intr: SurfaceInteraction,
pub t_hit: Float,
}
impl ShapeIntersection {
pub fn new(intr: SurfaceInteraction, t_hit: Float) -> Self {
Self { intr, t_hit }
}
pub fn t_hit(&self) -> Float {
self.t_hit
}
pub fn set_t_hit(&mut self, new_t: Float) {
self.t_hit = new_t;
}
pub fn set_intersection_properties(
&mut self,
mtl: Ptr<Material>,
area: Ptr<Light>,
prim_medium_interface: MediumInterface,
ray_medium: Ptr<Medium>,
) {
self.intr
.set_intersection_properties(&mtl, &area, &ray_medium, prim_medium_interface);
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct QuadricIntersection {
pub t_hit: Float,
pub p_obj: Point3f,
pub phi: Float,
}
impl QuadricIntersection {
pub fn new(t_hit: Float, p_obj: Point3f, phi: Float) -> Self {
Self { t_hit, p_obj, phi }
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ShapeSample {
pub intr: Interaction,
pub pdf: Float,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ShapeSampleContext {
pub pi: Point3fi,
pub n: Normal3f,
pub ns: Normal3f,
pub time: Float,
}
impl ShapeSampleContext {
pub fn new(pi: Point3fi, n: Normal3f, ns: Normal3f, time: Float) -> Self {
Self { pi, n, ns, time }
}
pub fn new_from_interaction(si: &SurfaceInteraction) -> Self {
Self {
pi: si.pi(),
n: si.n(),
ns: si.shading.n,
time: si.time(),
}
}
pub fn p(&self) -> Point3f {
Point3f::from(self.pi)
}
pub fn offset_ray_origin(&self, w: Vector3f) -> Point3f {
let d = self.n.abs().dot(self.pi.error().into());
let mut offset = d * Vector3f::from(self.n);
if w.dot(self.n.into()) < 0.0 {
offset = -offset;
}
let mut po = Point3f::from(self.pi) + offset;
for i in 0..3 {
if offset[i] > 0.0 {
po[i] = next_float_up(po[i]);
} else {
po[i] = next_float_down(po[i]);
}
}
po
}
pub fn offset_ray_origin_from_point(&self, pt: Point3f) -> Point3f {
self.offset_ray_origin(pt - self.p())
}
pub fn spawn_ray(&self, w: Vector3f) -> Ray {
Ray::new(self.offset_ray_origin(w), w, Some(self.time), &Ptr::null())
}
}
#[enum_dispatch]
pub trait ShapeTrait {
fn bounds(&self) -> Bounds3f;
fn normal_bounds(&self) -> DirectionCone;
fn area(&self) -> Float;
fn sample(&self, u: Point2f) -> Option<ShapeSample>;
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample>;
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection>;
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool;
fn pdf(&self, interaction: &Interaction) -> Float;
fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float;
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
#[enum_dispatch(ShapeTrait)]
pub enum Shape {
Sphere(SphereShape),
Cylinder(CylinderShape),
Disk(DiskShape),
Triangle(TriangleShape),
BilinearPatch(BilinearPatchShape),
Curve(CurveShape),
}
impl Default for Shape {
fn default() -> Self {
Shape::Sphere(SphereShape::default())
}
}

View file

@ -1,8 +1,9 @@
use crate::Float; use crate::Float;
use crate::core::color::{RGB, XYZ}; use crate::core::color::{RGB, XYZ};
use crate::spectra::*;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
pub use crate::spectra::*;
#[enum_dispatch] #[enum_dispatch]
pub trait SpectrumTrait: Copy { pub trait SpectrumTrait: Copy {
fn evaluate(&self, lambda: Float) -> Float; fn evaluate(&self, lambda: Float) -> Float;
@ -43,14 +44,14 @@ impl Spectrum {
} }
pub fn to_xyz(&self, std: &StandardSpectra) -> XYZ { pub fn to_xyz(&self, std: &StandardSpectra) -> XYZ {
let x = self.inner_product(&std.x()); let x = self.inner_product(&Spectrum::Dense(std.x));
let y = self.inner_product(&std.y()); let y = self.inner_product(&Spectrum::Dense(std.y));
let z = self.inner_product(&std.z()); let z = self.inner_product(&Spectrum::Dense(std.z));
XYZ::new(x, y, z) / CIE_Y_INTEGRAL XYZ::new(x, y, z) / CIE_Y_INTEGRAL
} }
fn to_rgb(&self, cs: &RGBColorSpace, std: &StandardSpectra) -> RGB { pub fn to_rgb(&self, cs: &RGBColorSpace, std: &StandardSpectra) -> RGB {
let xyz = self.to_xyz(std); let xyz = self.to_xyz(std);
cs.to_rgb(xyz) cs.to_rgb(xyz)
} }

View file

@ -2,18 +2,21 @@ use crate::core::color::ColorEncoding;
use crate::core::geometry::{ use crate::core::geometry::{
Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike, spherical_phi, spherical_theta, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike, spherical_phi, spherical_theta,
}; };
use crate::core::image::WrapMode;
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::images::WrapMode;
use crate::spectra::{ use crate::spectra::{
RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
SampledWavelengths, SampledWavelengths,
}; };
use crate::textures::*;
use crate::utils::Ptr;
use crate::utils::Transform; use crate::utils::Transform;
use crate::utils::math::square; use crate::utils::math::square;
use crate::{Float, INV_2_PI, INV_PI, PI}; use crate::{Float, INV_2_PI, INV_PI, PI};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
pub use crate::textures::*;
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, Copy)] #[derive(Clone, Debug, Copy)]
pub struct TexCoord2D { pub struct TexCoord2D {
@ -258,7 +261,7 @@ impl PointTransformMapping {
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Default, Debug)]
pub struct TextureEvalContext { pub struct TextureEvalContext {
pub p: Point3f, pub p: Point3f,
pub dpdx: Vector3f, pub dpdx: Vector3f,
@ -269,7 +272,7 @@ pub struct TextureEvalContext {
pub dudy: Float, pub dudy: Float,
pub dvdx: Float, pub dvdx: Float,
pub dvdy: Float, pub dvdy: Float,
pub face_index: usize, pub face_index: i32,
} }
impl TextureEvalContext { impl TextureEvalContext {
@ -284,7 +287,7 @@ impl TextureEvalContext {
dudy: Float, dudy: Float,
dvdx: Float, dvdx: Float,
dvdy: Float, dvdy: Float,
face_index: usize, face_index: i32,
) -> Self { ) -> Self {
Self { Self {
p, p,
@ -308,7 +311,7 @@ impl From<&SurfaceInteraction> for TextureEvalContext {
dpdx: si.dpdx, dpdx: si.dpdx,
dpdy: si.dpdy, dpdy: si.dpdy,
n: si.common.n, n: si.common.n,
uv: si.uv, uv: si.common.uv,
dudx: si.dudx, dudx: si.dudx,
dudy: si.dudy, dudy: si.dudy,
dvdx: si.dvdx, dvdx: si.dvdx,
@ -347,7 +350,6 @@ pub enum GPUFloatTexture {
FBm(FBmTexture), FBm(FBmTexture),
Windy(WindyTexture), Windy(WindyTexture),
Wrinkled(WrinkledTexture), Wrinkled(WrinkledTexture),
Ptex(GPUFloatPtexTexture),
Image(GPUFloatImageTexture), Image(GPUFloatImageTexture),
Mix(GPUFloatMixTexture), Mix(GPUFloatMixTexture),
} }
@ -363,8 +365,7 @@ impl GPUFloatTexture {
GPUFloatTexture::Dots(t) => t.evaluate(ctx), GPUFloatTexture::Dots(t) => t.evaluate(ctx),
GPUFloatTexture::FBm(t) => t.evaluate(ctx), GPUFloatTexture::FBm(t) => t.evaluate(ctx),
GPUFloatTexture::Windy(t) => t.evaluate(ctx), GPUFloatTexture::Windy(t) => t.evaluate(ctx),
GPUFloatTexture::Wrinkle(t) => t.evaluate(ctx), GPUFloatTexture::Wrinkled(t) => t.evaluate(ctx),
GPUFloatTexture::Ptex(t) => t.evaluate(ctx),
GPUFloatTexture::Image(t) => t.evaluate(ctx), GPUFloatTexture::Image(t) => t.evaluate(ctx),
GPUFloatTexture::Mix(t) => t.evaluate(ctx), GPUFloatTexture::Mix(t) => t.evaluate(ctx),
} }
@ -396,7 +397,11 @@ pub enum GPUSpectrumTexture {
} }
impl GPUSpectrumTexture { impl GPUSpectrumTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> Float { pub fn evaluate(
&self,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum {
match self { match self {
GPUSpectrumTexture::Constant(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Constant(t) => t.evaluate(ctx, lambda),
GPUSpectrumTexture::Bilerp(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Bilerp(t) => t.evaluate(ctx, lambda),
@ -421,7 +426,11 @@ pub trait TextureEvaluator: Send + Sync {
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum; ) -> SampledSpectrum;
fn can_evaluate(&self, _ftex: &[&GPUFloatTexture], _stex: &[&GPUSpectrumTexture]) -> bool; fn can_evaluate(
&self,
_ftex: &[Ptr<GPUFloatTexture>],
_stex: &[Ptr<GPUSpectrumTexture>],
) -> bool;
} }
#[repr(C)] #[repr(C)]
@ -444,8 +453,8 @@ impl TextureEvaluator for UniversalTextureEvaluator {
fn can_evaluate( fn can_evaluate(
&self, &self,
_float_textures: &[&GPUFloatTexture], _float_textures: &[Ptr<GPUFloatTexture>],
_spectrum_textures: &[&GPUSpectrumTexture], _spectrum_textures: &[Ptr<GPUSpectrumTexture>],
) -> bool { ) -> bool {
true true
} }

View file

@ -2,55 +2,39 @@ use crate::Float;
use bytemuck::cast_slice; use bytemuck::cast_slice;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
static SRGB_SCALE_BYTES: &[u8] = include_bytes!("../../data/srgb_scale.dat"); #[repr(C, align(16))]
static SRGB_COEFFS_BYTES: &[u8] = include_bytes!("../../data/srgb_coeffs.dat"); struct AlignedData<const N: usize>(pub [u8; N]);
pub static SRGB_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(SRGB_SCALE_BYTES)); macro_rules! load_static_table {
($name:ident, $path:literal) => {
pub static $name: &[Float] = {
static RAW_DATA: AlignedData<{ include_bytes!($path).len() }> =
AlignedData(*include_bytes!($path));
pub static SRGB_COEFFS: Lazy<&[Float]> = unsafe {
Lazy::new(|| match bytemuck::try_cast_slice(SRGB_COEFFS_BYTES) { let bytes = &RAW_DATA.0;
Ok(s) => s,
Err(_) => { let stride = core::mem::size_of::<Float>();
let v: Vec<Float> = bytemuck::pod_collect_to_vec(SRGB_COEFFS_BYTES); let len = bytes.len() / stride;
Box::leak(v.into_boxed_slice()) debug_assert!(
bytes.len() % stride == 0,
"Data file size is not a multiple of Float size"
);
core::slice::from_raw_parts(bytes.as_ptr() as *const Float, len)
} }
}); };
};
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"); load_static_table!(SRGB_SCALE, "../../data/srgb_scale.dat");
static ACES_COEFFS_BYTES: &[u8] = include_bytes!("../../data/aces_coeffs.dat"); load_static_table!(SRGB_COEFFS, "../../data/srgb_coeffs.dat");
pub static ACES_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(ACES_SCALE_BYTES)); load_static_table!(DCI_P3_SCALE, "../../data/dcip3_scale.dat");
load_static_table!(DCI_P3_COEFFS, "../../data/dcip3_coeffs.dat");
pub static ACES_COEFFS: Lazy<&[Float]> = load_static_table!(ACES_SCALE, "../../data/aces_scale.dat");
Lazy::new(|| match bytemuck::try_cast_slice(ACES_COEFFS_BYTES) { load_static_table!(ACES_COEFFS, "../../data/aces_coeffs.dat");
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"); load_static_table!(REC2020_SCALE, "../../data/rec2020_scale.dat");
static REC2020_COEFFS_BYTES: &[u8] = include_bytes!("../../data/rec2020_coeffs.dat"); load_static_table!(REC2020_COEFFS, "../../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,8 +1,10 @@
use crate::Float; use crate::Float;
use crate::core::filter::FilterSample; use crate::core::filter::{FilterSample, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f}; use crate::core::geometry::{Point2f, Vector2f};
use crate::utils::math::lerp;
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct BoxFilter { pub struct BoxFilter {
pub radius: Vector2f, pub radius: Vector2f,
} }
@ -11,12 +13,13 @@ impl BoxFilter {
pub fn new(radius: Vector2f) -> Self { pub fn new(radius: Vector2f) -> Self {
Self { radius } Self { radius }
} }
}
pub fn radius(&self) -> Vector2f { impl FilterTrait for BoxFilter {
fn radius(&self) -> Vector2f {
self.radius self.radius
} }
pub fn evaluate(&self, p: Point2f) -> Float { fn evaluate(&self, p: Point2f) -> Float {
if p.x().abs() <= self.radius.x() && p.y().abs() <= self.radius.y() { if p.x().abs() <= self.radius.x() && p.y().abs() <= self.radius.y() {
1. 1.
} else { } else {
@ -24,11 +27,11 @@ impl BoxFilter {
} }
} }
pub fn integral(&self) -> Float { fn integral(&self) -> Float {
(2.0 * self.radius.x()) * (2.0 * self.radius.y()) (2.0 * self.radius.x()) * (2.0 * self.radius.y())
} }
pub fn sample(&self, u: Point2f) -> FilterSample { fn sample(&self, u: Point2f) -> FilterSample {
let p = Point2f::new( let p = Point2f::new(
lerp(u[0], -self.radius.x(), self.radius.x()), lerp(u[0], -self.radius.x(), self.radius.x()),
lerp(u[1], -self.radius.y(), self.radius.y()), lerp(u[1], -self.radius.y(), self.radius.y()),

View file

@ -1,4 +1,10 @@
#[derive(Clone, Debug)] use crate::Float;
use crate::core::filter::{FilterSample, FilterSampler, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f};
use crate::utils::math::{gaussian, gaussian_integral};
#[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct GaussianFilter { pub struct GaussianFilter {
pub radius: Vector2f, pub radius: Vector2f,
pub sigma: Float, pub sigma: Float,
@ -7,43 +13,24 @@ pub struct GaussianFilter {
pub sampler: FilterSampler, pub sampler: FilterSampler,
} }
impl GaussianFilter { impl FilterTrait for GaussianFilter {
pub fn new(radius: Vector2f, sigma: Float) -> Self { fn radius(&self) -> Vector2f {
let exp_x = gaussian(radius.x(), 0., sigma);
let exp_y = gaussian(radius.y(), 0., sigma);
let sampler = FilterSampler::new(radius, move |p: Point2f| {
let gx = (gaussian(p.x(), 0., sigma) - exp_x).max(0.0);
let gy = (gaussian(p.y(), 0., sigma) - exp_y).max(0.0);
gx * gy
});
Self {
radius,
sigma,
exp_x: gaussian(radius.x(), 0., sigma),
exp_y: gaussian(radius.y(), 0., sigma),
sampler,
}
}
pub fn radius(&self) -> Vector2f {
self.radius self.radius
} }
pub fn evaluate(&self, p: Point2f) -> Float { fn evaluate(&self, p: Point2f) -> Float {
(gaussian(p.x(), 0.0, self.sigma) - self.exp_x).max(0.0) (gaussian(p.x(), 0.0, self.sigma) - self.exp_x).max(0.0)
* (gaussian(p.y(), 0.0, self.sigma) - self.exp_y).max(0.0) * (gaussian(p.y(), 0.0, self.sigma) - self.exp_y).max(0.0)
} }
pub fn integral(&self) -> Float { fn integral(&self) -> Float {
(gaussian_integral(-self.radius.x(), self.radius.x(), 0.0, self.sigma) (gaussian_integral(-self.radius.x(), self.radius.x(), 0.0, self.sigma)
- 2.0 * self.radius.x() * self.exp_x) - 2.0 * self.radius.x() * self.exp_x)
* (gaussian_integral(-self.radius.y(), self.radius.y(), 0.0, self.sigma) * (gaussian_integral(-self.radius.y(), self.radius.y(), 0.0, self.sigma)
- 2.0 * self.radius.y() * self.exp_y) - 2.0 * self.radius.y() * self.exp_y)
} }
pub fn sample(&self, u: Point2f) -> FilterSample { fn sample(&self, u: Point2f) -> FilterSample {
self.sampler.sample(u) self.sampler.sample(u)
} }
} }

View file

@ -1,33 +1,28 @@
#[derive(Clone, Debug)] use crate::Float;
use crate::core::filter::{FilterSample, FilterSampler, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f};
use crate::utils::math::{lerp, windowed_sinc};
use rand::Rng;
#[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct LanczosSincFilter { pub struct LanczosSincFilter {
pub radius: Vector2f, pub radius: Vector2f,
pub tau: Float, pub tau: Float,
pub sampler: FilterSampler, pub sampler: FilterSampler,
} }
impl LanczosSincFilter { impl FilterTrait for LanczosSincFilter {
pub fn new(radius: Vector2f, tau: Float) -> Self { fn radius(&self) -> Vector2f {
let sampler = FilterSampler::new(radius, move |p: Point2f| {
windowed_sinc(p.x(), radius.x(), tau) * windowed_sinc(p.y(), radius.y(), tau)
});
Self {
radius,
tau,
sampler,
}
}
pub fn radius(&self) -> Vector2f {
self.radius self.radius
} }
pub fn evaluate(&self, p: Point2f) -> Float { fn evaluate(&self, p: Point2f) -> Float {
windowed_sinc(p.x(), self.radius.x(), self.tau) windowed_sinc(p.x(), self.radius.x(), self.tau)
* windowed_sinc(p.y(), self.radius.y(), self.tau) * windowed_sinc(p.y(), self.radius.y(), self.tau)
} }
pub fn integral(&self) -> Float { fn integral(&self) -> Float {
let sqrt_samples = 64; let sqrt_samples = 64;
let n_samples = sqrt_samples * sqrt_samples; let n_samples = sqrt_samples * sqrt_samples;
let area = (2.0 * self.radius.x()) * (2.0 * self.radius.y()); let area = (2.0 * self.radius.x()) * (2.0 * self.radius.y());
@ -50,7 +45,7 @@ impl LanczosSincFilter {
sum / n_samples as Float * area sum / n_samples as Float * area
} }
pub fn sample(&self, u: Point2f) -> FilterSample { fn sample(&self, u: Point2f) -> FilterSample {
self.sampler.sample(u) self.sampler.sample(u)
} }
} }

View file

@ -1,8 +1,9 @@
use crate::Float; use crate::Float;
use crate::core::filter::FilterSampler; use crate::core::filter::{FilterSample, FilterSampler, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f}; use crate::core::geometry::{Point2f, Vector2f};
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct MitchellFilter { pub struct MitchellFilter {
pub radius: Vector2f, pub radius: Vector2f,
pub b: Float, pub b: Float,
@ -11,22 +12,7 @@ pub struct MitchellFilter {
} }
impl MitchellFilter { impl MitchellFilter {
pub fn new(radius: Vector2f, b: Float, c: Float) -> Self { pub fn mitchell_1d_eval(b: Float, c: Float, x: Float) -> Float {
let sampler = FilterSampler::new(radius, move |p: Point2f| {
let nx = 2.0 * p.x() / radius.x();
let ny = 2.0 * p.y() / radius.y();
Self::mitchell_1d_eval(b, c, nx) * Self::mitchell_1d_eval(b, c, ny)
});
Self {
radius,
b,
c,
sampler,
}
}
fn mitchell_1d_eval(b: Float, c: Float, x: Float) -> Float {
let x = x.abs(); let x = x.abs();
if x <= 1.0 { if x <= 1.0 {
((12.0 - 9.0 * b - 6.0 * c) * x.powi(3) ((12.0 - 9.0 * b - 6.0 * c) * x.powi(3)
@ -47,21 +33,23 @@ impl MitchellFilter {
fn mitchell_1d(&self, x: Float) -> Float { fn mitchell_1d(&self, x: Float) -> Float {
Self::mitchell_1d_eval(self.b, self.c, x) Self::mitchell_1d_eval(self.b, self.c, x)
} }
}
pub fn radius(&self) -> Vector2f { impl FilterTrait for MitchellFilter {
fn radius(&self) -> Vector2f {
self.radius self.radius
} }
pub fn evaluate(&self, p: Point2f) -> Float { fn evaluate(&self, p: Point2f) -> Float {
self.mitchell_1d(2.0 * p.x() / self.radius.x()) self.mitchell_1d(2.0 * p.x() / self.radius.x())
* self.mitchell_1d(2.0 * p.y() / self.radius.y()) * self.mitchell_1d(2.0 * p.y() / self.radius.y())
} }
pub fn integral(&self) -> Float { fn integral(&self) -> Float {
self.radius.x() * self.radius.y() / 4.0 self.radius.x() * self.radius.y() / 4.0
} }
pub fn sample(&self, u: Point2f) -> FilterSample { fn sample(&self, u: Point2f) -> FilterSample {
self.sampler.sample(u) self.sampler.sample(u)
} }
} }

View file

@ -1,4 +1,10 @@
#[derive(Clone, Debug)] use crate::Float;
use crate::core::filter::{FilterSample, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f};
use crate::utils::math::sample_tent;
#[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct TriangleFilter { pub struct TriangleFilter {
pub radius: Vector2f, pub radius: Vector2f,
} }
@ -7,20 +13,22 @@ impl TriangleFilter {
pub fn new(radius: Vector2f) -> Self { pub fn new(radius: Vector2f) -> Self {
Self { radius } Self { radius }
} }
}
pub fn radius(&self) -> Vector2f { impl FilterTrait for TriangleFilter {
fn radius(&self) -> Vector2f {
self.radius self.radius
} }
pub fn evaluate(&self, p: Point2f) -> Float { fn evaluate(&self, p: Point2f) -> Float {
(self.radius.x() - p.x().abs()).max(0.0) * (self.radius.y() - p.y().abs()).max(0.0) (self.radius.x() - p.x().abs()).max(0.0) * (self.radius.y() - p.y().abs()).max(0.0)
} }
pub fn integral(&self) -> Float { fn integral(&self) -> Float {
self.radius.x().powi(2) * self.radius.y().powi(2) self.radius.x().powi(2) * self.radius.y().powi(2)
} }
pub fn sample(&self, u: Point2f) -> FilterSample { fn sample(&self, u: Point2f) -> FilterSample {
let p = Point2f::new( let p = Point2f::new(
sample_tent(u[0], self.radius.x()), sample_tent(u[0], self.radius.x()),
sample_tent(u[1], self.radius.y()), sample_tent(u[1], self.radius.y()),

View file

@ -1,118 +0,0 @@
use crate::core::geometry::{Bounds2i, Point2i};
use crate::core::pbrt::Float;
use crate::spectra::colorspace::RGBColorSpace;
use crate::utils::math::SquareMatrix;
use smallvec::SmallVec;
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
#[derive(Clone, Debug, Default)]
pub struct ImageChannelValues(pub SmallVec<[Float; 4]>);
impl ImageChannelValues {
pub fn average(&self) -> Float {
if self.0.is_empty() {
return 0.0;
}
let sum: Float = self.0.iter().sum();
sum / (self.0.len() as Float)
}
pub fn max_value(&self) -> Float {
self.0.iter().fold(Float::MIN, |a, &b| a.max(b))
}
}
impl From<&[Float]> for ImageChannelValues {
fn from(slice: &[Float]) -> Self {
Self(SmallVec::from_slice(slice))
}
}
impl From<Vec<Float>> for ImageChannelValues {
fn from(vec: Vec<Float>) -> Self {
Self(SmallVec::from_vec(vec))
}
}
impl<const N: usize> From<[Float; N]> for ImageChannelValues {
fn from(arr: [Float; N]) -> Self {
Self(SmallVec::from_slice(&arr))
}
}
impl Deref for ImageChannelValues {
type Target = SmallVec<[Float; 4]>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for ImageChannelValues {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WrapMode {
Black,
Clamp,
Repeat,
OctahedralSphere,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct WrapMode2D {
pub uv: [WrapMode; 2],
}
impl From<WrapMode> for WrapMode2D {
fn from(w: WrapMode) -> Self {
Self { uv: [w, w] }
}
}
#[derive(Debug, Clone, Default)]
pub struct ImageChannelDesc {
pub offset: Vec<usize>,
}
impl ImageChannelDesc {
pub fn new(offset: &[usize]) -> Self {
Self {
offset: offset.into(),
}
}
pub fn size(&self) -> usize {
self.offset.len()
}
pub fn is_empty(&self) -> bool {
self.offset.is_empty()
}
pub fn is_identity(&self) -> bool {
for i in 0..self.size() {
if self.offset[i] != i {
return false;
}
}
true
}
}
#[derive(Debug, Default)]
pub struct ImageMetadata {
pub render_time_seconds: Option<Float>,
pub camera_from_world: Option<SquareMatrix<Float, 4>>,
pub ndc_from_world: Option<SquareMatrix<Float, 4>>,
pub pixel_bounds: Option<Bounds2i>,
pub full_resolution: Option<Point2i>,
pub samples_per_pixel: Option<i32>,
pub mse: Option<Float>,
pub colorspace: Option<RGBColorSpace>,
pub strings: HashMap<String, String>,
pub string_vectors: HashMap<String, Vec<String>>,
}

View file

@ -1,443 +0,0 @@
pub mod metadata;
pub mod ops;
pub mod pixel;
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::{lerp, square};
use core::hash;
use half::f16;
use pixel::PixelStorage;
use rayon::prelude::*;
use smallvec::{SmallVec, smallvec};
use std::ops::{Deref, DerefMut};
pub use metadata::{ImageChannelDesc, ImageChannelValues, ImageMetadata, WrapMode, WrapMode2D};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PixelFormat {
U8,
F16,
F32,
}
impl PixelFormat {
pub fn is_8bit(&self) -> bool {
matches!(self, PixelFormat::U8)
}
pub fn is_16bit(&self) -> bool {
matches!(self, PixelFormat::F16)
}
pub fn is_32bit(&self) -> bool {
matches!(self, PixelFormat::F32)
}
pub fn texel_bytes(&self) -> usize {
match self {
PixelFormat::U8 => 1,
PixelFormat::F16 => 2,
PixelFormat::F32 => 4,
}
}
}
impl std::fmt::Display for PixelFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PixelFormat::U8 => write!(f, "U256"),
PixelFormat::F16 => write!(f, "Half"),
PixelFormat::F32 => write!(f, "Float"),
}
}
}
#[derive(Debug, Clone)]
pub enum PixelData {
U8(Vec<u8>),
F16(Vec<f16>),
F32(Vec<f32>),
}
#[derive(Debug, Clone)]
pub struct Image {
pub format: PixelFormat,
pub resolution: Point2i,
pub channel_names: Vec<String>,
pub encoding: ColorEncoding,
pub pixels: PixelData,
}
#[derive(Debug)]
pub struct ImageAndMetadata {
pub image: Image,
pub metadata: ImageMetadata,
}
impl Image {
fn from_vector(
format: PixelFormat,
resolution: Point2i,
channel_names: Vec<String>,
encoding: ColorEncoding,
) -> Self {
let size = (resolution.x() * resolution.y()) as usize * channel_names.len();
let pixels = match format {
PixelFormat::U8 => PixelData::U8(vec![0; size]),
PixelFormat::F16 => PixelData::F16(vec![f16::ZERO; size]),
PixelFormat::F32 => PixelData::F32(vec![0.0; size]),
};
Self {
format,
resolution,
channel_names,
encoding,
pixels,
}
}
pub fn new(
format: PixelFormat,
resolution: Point2i,
channel_names: &[&str],
encoding: ColorEncoding,
) -> Self {
let owned_names = channel_names.iter().map(|s| s.to_string()).collect();
Self::from_vector(format, resolution, owned_names, encoding)
}
pub fn format(&self) -> PixelFormat {
self.format
}
pub fn resolution(&self) -> Point2i {
self.resolution
}
pub fn n_channels(&self) -> usize {
self.channel_names.len()
}
pub fn channel_names(&self) -> Vec<&str> {
self.channel_names.iter().map(|s| s.as_str()).collect()
}
pub fn channel_names_from_desc(&self, desc: &ImageChannelDesc) -> Vec<&str> {
desc.offset
.iter()
.map(|&i| self.channel_names[i].as_str())
.collect()
}
pub fn encoding(&self) -> ColorEncoding {
self.encoding
}
pub fn pixel_offset(&self, p: Point2i) -> usize {
(p.y() as usize * self.resolution.x() as usize + p.x() as usize) * self.n_channels()
}
pub fn get_channel(&self, p: Point2i, c: usize) -> Float {
self.get_channel_with_wrap(p, c, WrapMode::Clamp.into())
}
pub fn get_channel_with_wrap(&self, p: Point2i, c: usize, wrap: WrapMode2D) -> Float {
let mut pp = p;
if !self.remap_pixel_coords(&mut pp, wrap) {
return 0.0;
}
let idx = self.pixel_offset(pp) + c;
match &self.pixels {
PixelData::U8(d) => u8::to_linear(d[idx], self.encoding),
PixelData::F16(d) => f16::to_linear(d[idx], self.encoding),
PixelData::F32(d) => f32::to_linear(d[idx], self.encoding),
}
}
pub fn get_channels(&self, p: Point2i, wrap: WrapMode2D) -> ImageChannelValues {
let mut pp = p;
if !self.remap_pixel_coords(&mut pp, wrap) {
return ImageChannelValues(smallvec![0.0; self.n_channels()]);
}
let start_idx = self.pixel_offset(pp);
let n_channels = self.n_channels();
let mut values: SmallVec<[Float; 4]> = SmallVec::with_capacity(n_channels);
match &self.pixels {
PixelData::U8(data) => {
let slice = &data[start_idx..start_idx + n_channels];
for &v in slice {
values.push(u8::to_linear(v, self.encoding));
}
}
PixelData::F16(data) => {
let slice = &data[start_idx..start_idx + n_channels];
for &v in slice {
values.push(f16::to_linear(v, self.encoding));
}
}
PixelData::F32(data) => {
let slice = &data[start_idx..start_idx + n_channels];
for &v in slice {
values.push(f32::to_linear(v, self.encoding));
}
}
}
ImageChannelValues(values)
}
pub fn get_channels_desc(
&self,
p: Point2i,
desc: &ImageChannelDesc,
wrap: WrapMode2D,
) -> ImageChannelValues {
let mut pp = p;
if !self.remap_pixel_coords(&mut pp, wrap) {
return ImageChannelValues(smallvec![0.0; desc.offset.len()]);
}
let pixel_offset = self.pixel_offset(pp);
let mut values: SmallVec<[Float; 4]> = SmallVec::with_capacity(desc.offset.len());
match &self.pixels {
PixelData::U8(data) => {
for &channel_idx in &desc.offset {
let val = data[pixel_offset + channel_idx];
values.push(u8::to_linear(val, self.encoding));
}
}
PixelData::F16(data) => {
for &channel_idx in &desc.offset {
let val = data[pixel_offset + channel_idx];
values.push(f16::to_linear(val, self.encoding));
}
}
PixelData::F32(data) => {
for &channel_idx in &desc.offset {
let val = data[pixel_offset + channel_idx];
values.push(f32::to_linear(val, self.encoding));
}
}
}
ImageChannelValues(values)
}
pub fn get_channels_default(&self, p: Point2i) -> ImageChannelValues {
self.get_channels(p, WrapMode::Clamp.into())
}
pub fn all_channels_desc(&self) -> ImageChannelDesc {
ImageChannelDesc {
offset: (0..self.n_channels()).collect(),
}
}
pub fn get_channel_desc(
&self,
requested_channels: &[&str],
) -> Result<ImageChannelDesc, String> {
let mut offset = Vec::with_capacity(requested_channels.len());
for &req in requested_channels.iter() {
match self.channel_names.iter().position(|n| n == req) {
Some(idx) => {
offset.push(idx);
}
None => {
return Err(format!(
"Image is missing requested channel '{}'. Available channels: {:?}",
req, self.channel_names
));
}
}
}
Ok(ImageChannelDesc { offset })
}
pub fn set_channel(&mut self, p: Point2i, c: usize, value: Float) {
let val_no_nan = if value.is_nan() { 0.0 } else { value };
let offset = self.pixel_offset(p) + c;
match &mut self.pixels {
PixelData::U8(data) => {
let linear = [val_no_nan];
self.encoding
.from_linear_slice(&linear, &mut data[offset..offset + 1]);
}
PixelData::F16(data) => data[offset] = f16::from_f32(val_no_nan),
PixelData::F32(data) => data[offset] = val_no_nan,
}
}
pub fn set_channels(
&mut self,
p: Point2i,
desc: &ImageChannelDesc,
values: &ImageChannelValues,
) {
assert_eq!(desc.size(), values.len());
for i in 0..desc.size() {
self.set_channel(p, desc.offset[i], values[i]);
}
}
pub fn set_channels_all(&mut self, p: Point2i, values: &ImageChannelValues) {
self.set_channels(p, &self.all_channels_desc(), values)
}
fn remap_pixel_coords(&self, p: &mut Point2i, wrap_mode: WrapMode2D) -> bool {
for i in 0..2 {
if p[i] >= 0 && p[i] < self.resolution[i] {
continue;
}
match wrap_mode.uv[i] {
WrapMode::Black => return false,
WrapMode::Clamp => p[i] = p[i].clamp(0, self.resolution[i] - 1),
WrapMode::Repeat => p[i] = p[i].rem_euclid(self.resolution[i]),
WrapMode::OctahedralSphere => {
p[i] = p[i].clamp(0, self.resolution[i] - 1);
}
}
}
true
}
pub fn bilerp_channel(&self, p: Point2f, c: usize) -> Float {
self.bilerp_channel_with_wrap(p, c, WrapMode::Clamp.into())
}
pub fn bilerp_channel_with_wrap(&self, p: Point2f, c: usize, wrap_mode: WrapMode2D) -> Float {
let x = p.x() * self.resolution.x() as Float - 0.5;
let y = p.y() * self.resolution.y() as Float - 0.5;
let xi = x.floor() as i32;
let yi = y.floor() as i32;
let dx = x - xi as Float;
let dy = y - yi as Float;
let v00 = self.get_channel_with_wrap(Point2i::new(xi, yi), c, wrap_mode);
let v10 = self.get_channel_with_wrap(Point2i::new(xi + 1, yi), c, wrap_mode);
let v01 = self.get_channel_with_wrap(Point2i::new(xi, yi + 1), c, wrap_mode);
let v11 = self.get_channel_with_wrap(Point2i::new(xi + 1, yi + 1), c, wrap_mode);
lerp(dy, lerp(dx, v00, v10), lerp(dx, v01, v11))
}
pub fn lookup_nearest_channel_with_wrap(
&self,
p: Point2f,
c: usize,
wrap_mode: WrapMode2D,
) -> Float {
let pi = Point2i::new(
p.x() as i32 * self.resolution.x(),
p.y() as i32 * self.resolution.y(),
);
self.get_channel_with_wrap(pi, c, wrap_mode)
}
pub fn lookup_nearest_channel(&self, p: Point2f, c: usize) -> Float {
self.lookup_nearest_channel_with_wrap(p, c, WrapMode::Clamp.into())
}
pub fn get_sampling_distribution<F>(&self, dxd_a: F, domain: Bounds2f) -> Array2D<Float>
where
F: Fn(Point2f) -> Float + Sync + Send,
{
let width = self.resolution.x();
let height = self.resolution.y();
let mut dist = Array2D::new_with_dims(width as usize, height as usize);
dist.values
.par_chunks_mut(width as usize)
.enumerate()
.for_each(|(y, row)| {
let y = y as i32;
for (x, out_val) in row.iter_mut().enumerate() {
let x = x as i32;
let value = self.get_channels_default(Point2i::new(x, y)).average();
let u = (x as Float + 0.5) / width as Float;
let v = (y as Float + 0.5) / height as Float;
let p = domain.lerp(Point2f::new(u, v));
*out_val = value * dxd_a(p);
}
});
dist
}
pub fn get_sampling_distribution_uniform(&self) -> Array2D<Float> {
let default_domain = Bounds2f::from_points(Point2f::new(0.0, 0.0), Point2f::new(1.0, 1.0));
self.get_sampling_distribution(|_| 1.0, default_domain)
}
pub fn mse(
&self,
desc: ImageChannelDesc,
ref_img: &Image,
generate_mse_image: bool,
) -> (ImageChannelValues, Option<Image>) {
let mut sum_se: Vec<f64> = vec![0.; desc.size()];
let names_ref = self.channel_names_from_desc(&desc);
let ref_desc = ref_img
.get_channel_desc(&self.channel_names_from_desc(&desc))
.expect("Channels not found in image");
assert_eq!(self.resolution(), ref_img.resolution());
let width = self.resolution.x() as usize;
let height = self.resolution.y() as usize;
let n_channels = desc.offset.len();
let mut mse_pixels = if generate_mse_image {
vec![0.0f32; width * height * n_channels]
} else {
Vec::new()
};
for y in 0..self.resolution().y() {
for x in 0..self.resolution().x() {
let v = self.get_channels_desc(Point2i::new(x, y), &desc, WrapMode::Clamp.into());
let v_ref =
self.get_channels_desc(Point2i::new(x, y), &ref_desc, WrapMode::Clamp.into());
for c in 0..desc.size() {
let se = square(v[c] as f64 - v_ref[c] as f64);
if se.is_infinite() {
continue;
}
sum_se[c] += se;
if generate_mse_image {
let idx = (y as usize * width + x as usize) * n_channels + c;
mse_pixels[idx] = se as f32;
}
}
}
}
let pixel_count = (self.resolution.x() * self.resolution.y()) as f64;
let mse_values: SmallVec<[Float; 4]> =
sum_se.iter().map(|&s| (s / pixel_count) as Float).collect();
let mse_image = if generate_mse_image {
Some(Image::new(
PixelFormat::F32,
self.resolution,
&names_ref,
LINEAR,
))
} else {
None
};
(ImageChannelValues(mse_values), mse_image)
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,17 +1,18 @@
#![allow(unused_imports, dead_code)] #![allow(unused_imports, dead_code)]
#![feature(float_erf)] #![feature(float_erf)]
#![feature(f16)] #![feature(f16)]
#![feature(associated_type_defaults)]
mod cameras; pub mod bxdfs;
mod core; pub mod cameras;
mod data; pub mod core;
mod filters; pub mod data;
mod images; pub mod filters;
mod integrators; pub mod lights;
mod lights; pub mod materials;
mod shapes; pub mod shapes;
mod spectra; pub mod spectra;
mod textures; pub mod textures;
mod utils; pub mod utils;
pub use core::pbrt::*; pub use core::pbrt::*;

View file

@ -1,114 +1,54 @@
use crate::PI;
use crate::core::color::{RGB, XYZ}; use crate::core::color::{RGB, XYZ};
use crate::core::geometry::*; use crate::core::geometry::*;
use crate::core::interaction::{Interaction, MediumInteraction, SurfaceInteraction}; use crate::core::image::{DeviceImage, ImageAccess};
use crate::core::interaction::{
Interaction, InteractionTrait, MediumInteraction, SurfaceInteraction,
};
use crate::core::light::{ use crate::core::light::{
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
}; };
use crate::core::medium::MediumInterface; use crate::core::medium::MediumInterface;
use crate::core::pbrt::Float; use crate::core::shape::{Shape, ShapeSampleContext, ShapeTrait};
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{GPUFloatTexture, TextureEvalContext, UniversalTextureEvaluator}; use crate::core::texture::{
use crate::images::Image; GPUFloatTexture, TextureEvalContext, TextureEvaluator, UniversalTextureEvaluator,
use crate::shapes::{Shape, ShapeSampleContext}; };
use crate::spectra::*; use crate::spectra::*;
use crate::utils::Transform;
use crate::utils::hash::hash_float; use crate::utils::hash::hash_float;
use crate::utils::{Ptr, Transform};
use crate::{Float, PI};
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct DiffuseAreaLight { pub struct DiffuseAreaLight {
pub base: LightBase, pub base: LightBase,
pub shape: *const Shape, pub shape: Ptr<Shape>,
pub alpha: *const GPUFloatTexture, pub alpha: Ptr<GPUFloatTexture>,
pub colorspace: Ptr<RGBColorSpace>,
pub lemit: Ptr<DenselySampledSpectrum>,
pub image: Ptr<DeviceImage>,
pub area: Float, pub area: Float,
pub two_sided: bool, pub two_sided: bool,
pub lemit: DenselySampledSpectrum,
pub scale: Float, pub scale: Float,
pub image: *const Image,
pub image_color_space: RGBColorSpace,
} }
#[cfg(not(target_os = "cuda"))] unsafe impl Send for DiffuseAreaLight {}
unsafe impl Sync for DiffuseAreaLight {}
impl DiffuseAreaLight { impl DiffuseAreaLight {
#[allow(clippy::too_many_arguments)]
pub fn new(
render_from_light: Transform,
medium_interface: MediumInterface,
le: Spectrum,
scale: Float,
shape: Shape,
alpha: *const GPUFloatTexture,
image: *const Image,
image_color_space: *const RGBColorSpace,
two_sided: bool,
) -> Self {
let is_constant_zero = match &alpha {
GPUFloatTexture::Constant(tex) => tex.evaluate(&TextureEvalContext::default()) == 0.0,
_ => false,
};
let (light_type, stored_alpha) = if is_constant_zero {
(LightType::DeltaPosition, None)
} else {
(LightType::Area, Some(alpha))
};
let base = LightBase::new(light_type, &render_from_light, &medium_interface);
let lemit = LightBase::lookup_spectrum(&le);
if let Some(im) = &image {
let desc = im
.get_channel_desc(&["R", "G", "B"])
.expect("Image used for DiffuseAreaLight doesn't have R, G, B channels");
assert_eq!(3, desc.size(), "Image channel description size mismatch");
assert!(
desc.is_identity(),
"Image channel description is not identity"
);
assert!(
image_color_space.is_some(),
"Image provided but ColorSpace is missing"
);
}
let is_triangle_or_bilinear = matches!(shape, Shape::Triangle(_) | Shape::BilinearPatch(_));
if render_from_light.has_scale(None) && !is_triangle_or_bilinear {
println!(
"Scaling detected in rendering to light space transformation! \
The system has numerous assumptions, implicit and explicit, \
that this transform will have no scale factors in it. \
Proceed at your own risk; your image may have errors."
);
}
Self {
base,
area: shape.area(),
shape,
alpha: stored_alpha,
two_sided,
lemit,
scale,
image,
image_color_space,
}
}
fn l_base(&self, n: Normal3f, wo: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { fn l_base(&self, n: Normal3f, wo: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
if !self.two_sided && n.dot(wo) <= 0.0 { if !self.two_sided && n.dot(wo.into()) <= 0.0 {
return SampledSpectrum::new(0.0); return SampledSpectrum::new(0.0);
} }
let spec = DenselySampledSpectrum::from_array(&self.lemit_coeffs); self.lemit.sample(lambda) * self.scale
spec.sample(lambda) * self.scale
} }
fn alpha_masked(&self, intr: &Interaction) -> bool { fn alpha_masked(&self, intr: &Interaction) -> bool {
let Some(alpha_tex) = &self.alpha else { if self.alpha.is_null() {
return false; return false;
}; };
let ctx = TextureEvalContext::from(intr); let ctx = TextureEvalContext::from(intr);
let a = UniversalTextureEvaluator.evaluate_float(alpha_tex, &ctx); let a = UniversalTextureEvaluator.evaluate_float(&self.alpha, &ctx);
if a >= 1.0 { if a >= 1.0 {
return false; return false;
} }
@ -133,15 +73,14 @@ impl LightTrait for DiffuseAreaLight {
) -> Option<LightLiSample> { ) -> Option<LightLiSample> {
let shape_ctx = ShapeSampleContext::new(ctx.pi, ctx.n, ctx.ns, 0.0); let shape_ctx = ShapeSampleContext::new(ctx.pi, ctx.n, ctx.ns, 0.0);
let ss = self.shape.sample_from_context(&shape_ctx, u)?; let ss = self.shape.sample_from_context(&shape_ctx, u)?;
let mut intr: SurfaceInteraction = ss.intr.as_ref().clone(); let mut intr = ss.intr;
intr.set_medium_interface(self.base.medium_interface);
intr.common.medium_interface = Some(self.base.medium_interface.clone());
let p = intr.p(); let p = intr.p();
let n = intr.n(); let n = intr.n();
let uv = intr.uv; let uv = intr.get_common().uv;
let generic_intr = Interaction::Surface(intr); // let generic_intr = Interaction::Surface(intr);
if self.alpha_masked(&generic_intr) { if self.alpha_masked(&intr) {
return None; return None;
} }
@ -152,7 +91,7 @@ impl LightTrait for DiffuseAreaLight {
return None; return None;
} }
Some(LightLiSample::new(le, wi, ss.pdf, generic_intr)) Some(LightLiSample::new(le, wi, ss.pdf, intr))
} }
fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, _allow_incomplete_pdf: bool) -> Float { fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, _allow_incomplete_pdf: bool) -> Float {
@ -179,17 +118,15 @@ impl LightTrait for DiffuseAreaLight {
if self.alpha_masked(&intr) { if self.alpha_masked(&intr) {
return SampledSpectrum::new(0.); return SampledSpectrum::new(0.);
} }
if let Some(image) = &self.image { if !self.image.is_null() {
let mut rgb = RGB::default(); let mut rgb = RGB::default();
uv[1] = 1. - uv[1]; uv[1] = 1. - uv[1];
for c in 0..3 { for c in 0..3 {
rgb[c] = image.bilerp_channel(uv, c); rgb[c] = self.image.bilerp_channel(uv, c as i32);
} }
let spec = RGBIlluminantSpectrum::new( let cs_ref = unsafe { self.colorspace.as_ref() };
self.image_color_space.as_ref().unwrap(), let spec = RGBIlluminantSpectrum::new(cs_ref, rgb.clamp_zero());
RGB::clamp_zero(rgb),
);
self.scale * spec.sample(lambda) self.scale * spec.sample(lambda)
} else { } else {
@ -204,21 +141,19 @@ impl LightTrait for DiffuseAreaLight {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
let mut l = SampledSpectrum::new(0.); let mut l = SampledSpectrum::new(0.);
if let Some(image) = &self.image { if !self.image.is_null() {
for y in 0..image.resolution().y() { for y in 0..self.image.resolution().y() {
for x in 0..image.resolution().x() { for x in 0..self.image.resolution().x() {
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {
rgb[c] = image.get_channel(Point2i::new(x, y), c); rgb[c] = self.image.get_channel(Point2i::new(x, y), c as i32);
} }
l += RGBIlluminantSpectrum::new(
self.image_color_space.as_ref().unwrap(), let cs_ref = unsafe { self.colorspace.as_ref() };
RGB::clamp_zero(rgb), l += RGBIlluminantSpectrum::new(cs_ref, rgb.clamp_zero()).sample(&lambda);
)
.sample(&lambda);
} }
} }
l *= self.scale / (image.resolution().x() * image.resolution().y()) as Float; l *= self.scale / (self.image.resolution().x() * self.image.resolution().y()) as Float;
} else { } else {
l = self.lemit.sample(&lambda) * self.scale; l = self.lemit.sample(&lambda) * self.scale;
} }
@ -234,11 +169,11 @@ impl LightTrait for DiffuseAreaLight {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
fn bounds(&self) -> Option<LightBounds> { fn bounds(&self) -> Option<LightBounds> {
let mut phi = 0.; let mut phi = 0.;
if let Some(image) = &self.image { if !self.image.is_null() {
for y in 0..image.resolution.y() { for y in 0..self.image.base.resolution.y() {
for x in 0..image.resolution.x() { for x in 0..self.image.base.resolution.x() {
for c in 0..3 { for c in 0..3 {
phi += image.get_channel(Point2i::new(x, y), c); phi += self.image.get_channel(Point2i::new(x, y), c);
} }
} }
} }

View file

@ -1,14 +1,18 @@
use crate::core::geometry::{Bounds3f, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector3f}; use crate::core::geometry::{
use crate::core::interaction::{Interaction, InteractionData}; Bounds3f, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector3f, VectorLike,
};
use crate::core::interaction::{Interaction, InteractionBase, SimpleInteraction};
use crate::core::light::{LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait}; use crate::core::light::{LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait};
use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths}; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::{Float, PI}; use crate::{Float, PI};
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct DistantLight { pub struct DistantLight {
pub base: LightBase, pub base: LightBase,
pub lemit_coeffs: [Float; 32], pub lemit: Ptr<DenselySampledSpectrum>,
pub scale: Float, pub scale: Float,
pub scene_center: Point3f, pub scene_center: Point3f,
pub scene_radius: Float, pub scene_radius: Float,
@ -26,8 +30,7 @@ impl DistantLight {
.apply_to_vector(Vector3f::new(0., 0., 1.)) .apply_to_vector(Vector3f::new(0., 0., 1.))
.normalize(); .normalize();
let p_outside = ctx_p + wi * 2. * self.scene_radius; let p_outside = ctx_p + wi * 2. * self.scene_radius;
let spectrum = DenselySampledSpectrum::from_array(&self.lemit_coeffs); let li = self.scale * self.lemit.sample(lambda);
let li = self.scale * spectrum.sample(lambda);
(li, wi, 1.0, p_outside) (li, wi, 1.0, p_outside)
} }
} }
@ -56,11 +59,8 @@ impl LightTrait for DistantLight {
let p_outside = ctx.p() + wi * 2. * self.scene_radius; let p_outside = ctx.p() + wi * 2. * self.scene_radius;
let li = self.scale * self.lemit.sample(lambda); let li = self.scale * self.lemit.sample(lambda);
let intr = SimpleInteraction::new( let base = InteractionBase::new_boundary(p_outside, 0.0, self.base.medium_interface);
Point3fi::new_from_point(p_outside), let intr = SimpleInteraction::new(base);
0.0,
Some(self.base.medium_interface.clone()),
);
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr))) Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
} }

View file

@ -1,46 +1,26 @@
use crate::Float; use crate::core::geometry::{Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f};
use crate::core::geometry::{Bounds3f, Normal3f, Point2f, Point3f, Vector3f}; use crate::core::image::{DeviceImage, ImageAccess};
use crate::core::light::{LightBase, LightBounds, LightLiSample, LightTrait}; use crate::core::light::{
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
};
use crate::core::medium::MediumInterface; use crate::core::medium::MediumInterface;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::utils::Transform; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::math::equal_area_sphere_to_square;
use crate::utils::sampling::DevicePiecewiseConstant2D;
use crate::utils::{Ptr, Transform};
use crate::{Float, PI};
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub struct GoniometricLight { pub struct GoniometricLight {
pub base: LightBase, pub base: LightBase,
iemit: DenselySampledSpectrum, pub iemit: Ptr<DenselySampledSpectrum>,
scale: Float, pub scale: Float,
image: Image, pub image: Ptr<DeviceImage>,
distrib: PiecewiseConstant2D, pub distrib: Ptr<DevicePiecewiseConstant2D>,
} }
impl GoniometricLight { impl GoniometricLight {
pub fn new(
render_from_light: &Transform,
medium_interface: &MediumInterface,
iemit: Spectrum,
scale: Float,
image: Image,
) -> Self {
let base = LightBase::new(
LightType::DeltaPosition,
render_from_light,
medium_interface,
);
let i_interned = LightBase::lookup_spectrum(&iemit);
let d = image.get_sampling_distribution_uniform();
let distrib = PiecewiseConstant2D::new_with_data(&d);
Self {
base,
iemit: i_interned,
scale,
image,
distrib,
}
}
pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
let uv = equal_area_sphere_to_square(w); let uv = equal_area_sphere_to_square(w);
self.scale * self.iemit.sample(lambda) * self.image.lookup_nearest_channel(uv, 0) self.scale * self.iemit.sample(lambda) * self.image.lookup_nearest_channel(uv, 0)
@ -97,13 +77,14 @@ impl LightTrait for GoniometricLight {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
let resolution = self.image.resolution();
let mut sum_y = 0.; let mut sum_y = 0.;
for y in 0..self.image.resolution.y() { for y in 0..resolution.y() {
for x in 0..self.image.resolution.x() { for x in 0..resolution.x() {
sum_y += self.image.get_channel(Point2i::new(x, y), 0); sum_y += self.image.get_channel(Point2i::new(x, y), 0);
} }
} }
self.scale * self.iemit.sample(&lambda) * 4. * PI * sum_y self.scale * self.iemit.sample(&lambda) * 4. * PI * sum_y
/ (self.image.resolution.x() * self.image.resolution.y()) as Float / (resolution.x() * resolution.y()) as Float
} }
} }

View file

@ -1,48 +1,41 @@
use crate::{ use crate::core::color::RGB;
core::geometry::Frame, use crate::core::geometry::{
core::medium::Medium, Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector3f,
spectra::{RGB, RGBColorSpace, RGBIlluminantSpectrum},
utils::{
math::{clamp, equal_area_square_to_sphere},
sampling::{
AliasTable, PiecewiseConstant2D, WindowedPiecewiseConstant2D, sample_uniform_sphere,
uniform_sphere_pdf,
},
},
}; };
use crate::core::geometry::{Frame, VectorLike};
use crate::images::{PixelFormat, WrapMode}; use crate::core::image::{DeviceImage, ImageAccess, PixelFormat, WrapMode};
use crate::core::interaction::InteractionBase;
use crate::core::interaction::{Interaction, SimpleInteraction};
use crate::core::light::{
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
};
use crate::core::medium::{Medium, MediumInterface};
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::spectra::{RGBColorSpace, RGBIlluminantSpectrum};
use crate::utils::math::{clamp, equal_area_sphere_to_square, equal_area_square_to_sphere, square};
use crate::utils::sampling::{
AliasTable, DevicePiecewiseConstant2D, DeviceWindowedPiecewiseConstant2D,
sample_uniform_sphere, uniform_sphere_pdf,
};
use crate::utils::{Ptr, Transform};
use crate::{Float, PI};
use std::sync::Arc;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct InfiniteUniformLight { pub struct UniformInfiniteLight {
pub base: LightBase, pub base: LightBase,
pub lemit: u32, pub lemit: Ptr<DenselySampledSpectrum>,
pub scale: Float, pub scale: Float,
pub scene_center: Point3f, pub scene_center: Point3f,
pub scene_radius: Float, pub scene_radius: Float,
} }
#[cfg(not(target_os = "cuda"))] unsafe impl Send for UniformInfiniteLight {}
impl InfiniteUniformLight { unsafe impl Sync for UniformInfiniteLight {}
pub fn new(render_from_light: TransformGeneric<Float>, le: Spectrum, scale: Float) -> Self {
let base = LightBase::new(
LightType::Infinite,
&render_from_light,
&MediumInterface::default(),
);
let lemit = LightBase::lookup_spectrum(&le);
Self {
base,
lemit,
scale,
scene_center: Point3f::default(),
scene_radius: 0.,
}
}
}
impl LightTrait for InfiniteUniformLight { impl LightTrait for UniformInfiniteLight {
fn base(&self) -> &LightBase { fn base(&self) -> &LightBase {
&self.base &self.base
} }
@ -58,10 +51,12 @@ impl LightTrait for InfiniteUniformLight {
} }
let wi = sample_uniform_sphere(u); let wi = sample_uniform_sphere(u);
let pdf = uniform_sphere_pdf(); let pdf = uniform_sphere_pdf();
let intr_simple = SimpleInteraction::new_interface( let base = InteractionBase::new_boundary(
ctx.p() + wi * (2. * self.scene_radius), ctx.p() + wi * (2. * self.scene_radius),
Some(MediumInterface::default()), 0.,
MediumInterface::default(),
); );
let intr_simple = SimpleInteraction::new(base);
let intr = Interaction::Simple(intr_simple); let intr = Interaction::Simple(intr_simple);
Some(LightLiSample::new( Some(LightLiSample::new(
@ -98,91 +93,40 @@ impl LightTrait for InfiniteUniformLight {
fn le(&self, _ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum { fn le(&self, _ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum {
self.scale * self.lemit.sample(lambda) self.scale * self.lemit.sample(lambda)
} }
#[cfg(not(target_os = "cuda"))]
fn preprocess(&mut self, _scene_bounds: &Bounds3f) { fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
todo!() todo!()
} }
#[cfg(not(target_os = "cuda"))]
fn bounds(&self) -> Option<LightBounds> { fn bounds(&self) -> Option<LightBounds> {
todo!() todo!()
} }
#[cfg(not(target_os = "cuda"))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
4. * PI * PI * square(self.scene_radius) * self.scale * self.lemit.sample(&lambda) 4. * PI * PI * square(self.scene_radius) * self.scale * self.lemit.sample(&lambda)
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
pub struct InfiniteImageLight { #[derive(Clone, Copy, Debug)]
base: LightBase, pub struct ImageInfiniteLight {
image: Image, pub base: LightBase,
image_color_space: u32, pub image: Ptr<DeviceImage>,
scale: Float, pub image_color_space: Ptr<RGBColorSpace>,
scene_radius: Float, pub distrib: Ptr<DevicePiecewiseConstant2D>,
scene_center: Point3f, pub compensated_distrib: Ptr<DevicePiecewiseConstant2D>,
distrib: PiecewiseConstant2D, pub scale: Float,
compensated_distrib: PiecewiseConstant2D, pub scene_radius: Float,
pub scene_center: Point3f,
} }
#[cfg(not(target_os = "cuda"))] unsafe impl Send for ImageInfiniteLight {}
impl InfiniteImageLight { unsafe impl Sync for ImageInfiniteLight {}
pub fn new(
render_from_light: TransformGeneric<Float>,
image: Image,
image_color_space: Arc<RGBColorSpace>,
scale: Float,
filename: String,
) -> Self {
let base = LightBase::new(
LightType::Infinite,
&render_from_light,
&MediumInterface::default(),
);
let desc = image
.get_channel_desc(&["R", "G", "B"])
.expect("Image used for DiffuseAreaLight doesn't have R, G, B channels");
assert_eq!(3, desc.size());
assert!(desc.is_identity());
if image.resolution().x() != image.resolution().y() {
panic!(
"{}: image resolution ({}, {}) is non-square. It's unlikely this is an equal area environment map.",
filename,
image.resolution.x(),
image.resolution.y()
);
}
let mut d = image.get_sampling_distribution_uniform();
let domain = Bounds2f::from_points(Point2f::new(0., 0.), Point2f::new(1., 1.));
let distrib = PiecewiseConstant2D::new_with_bounds(&d, domain);
let slice = &mut d.values; // or d.as_slice_mut()
let count = slice.len() as Float;
let sum: Float = slice.iter().sum();
let average = sum / count;
for v in slice.iter_mut() {
*v = (*v - average).max(0.0);
}
let all_zero = slice.iter().all(|&v| v == 0.0);
if all_zero {
for v in slice.iter_mut() {
*v = 1.0;
}
}
let compensated_distrib = PiecewiseConstant2D::new_with_bounds(&d, domain);
Self {
base,
image,
image_color_space,
scene_center: Point3f::default(),
scene_radius: 0.,
scale,
distrib,
compensated_distrib,
}
}
impl ImageInfiniteLight {
fn image_le(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum { fn image_le(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {
@ -192,13 +136,12 @@ impl InfiniteImageLight {
WrapMode::OctahedralSphere.into(), WrapMode::OctahedralSphere.into(),
); );
} }
let spec = let spec = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero());
RGBIlluminantSpectrum::new(self.image_color_space.as_ref(), RGB::clamp_zero(rgb));
self.scale * spec.sample(lambda) self.scale * spec.sample(lambda)
} }
} }
impl LightTrait for InfiniteImageLight { impl LightTrait for ImageInfiniteLight {
fn base(&self) -> &LightBase { fn base(&self) -> &LightBase {
&self.base &self.base
} }
@ -225,13 +168,14 @@ impl LightTrait for InfiniteImageLight {
let pdf = map_pdf / (4. * PI); let pdf = map_pdf / (4. * PI);
// Return radiance value for infinite light direction // Return radiance value for infinite light direction
let mut simple_intr = SimpleInteraction::new_interface( let base = InteractionBase::new_boundary(
ctx.p() + wi * (2. * self.scene_radius), ctx.p() + wi * (2. * self.scene_radius),
Some(MediumInterface::default()), 0.,
self.base.medium_interface,
); );
let simple_intr = SimpleInteraction::new(base);
simple_intr.common.medium_interface = Some(self.base.medium_interface.clone());
let intr = Interaction::Simple(simple_intr); let intr = Interaction::Simple(simple_intr);
Some(LightLiSample::new(self.image_le(uv, lambda), wi, pdf, intr)) Some(LightLiSample::new(self.image_le(uv, lambda), wi, pdf, intr))
} }
@ -270,8 +214,8 @@ impl LightTrait for InfiniteImageLight {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
let mut sum_l = SampledSpectrum::new(0.); let mut sum_l = SampledSpectrum::new(0.);
let width = self.image.resolution.x(); let width = self.image.resolution().x();
let height = self.image.resolution.y(); let height = self.image.resolution().y();
for v in 0..height { for v in 0..height {
for u in 0..width { for u in 0..width {
let mut rgb = RGB::default(); let mut rgb = RGB::default();
@ -282,10 +226,7 @@ impl LightTrait for InfiniteImageLight {
WrapMode::OctahedralSphere.into(), WrapMode::OctahedralSphere.into(),
); );
} }
sum_l += RGBIlluminantSpectrum::new( sum_l += RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero())
self.image_color_space.as_ref(),
RGB::clamp_zero(rgb),
)
.sample(&lambda); .sample(&lambda);
} }
} }
@ -307,157 +248,25 @@ impl LightTrait for InfiniteImageLight {
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct InfinitePortalLight { pub struct PortalInfiniteLight {
pub base: LightBase, pub base: LightBase,
pub image: Image, pub image: Ptr<DeviceImage>,
pub image_color_space: RGBColorSpace, pub image_color_space: Ptr<RGBColorSpace>,
pub scale: Float, pub scale: Float,
pub filename: String,
pub portal: [Point3f; 4], pub portal: [Point3f; 4],
pub portal_frame: Frame, pub portal_frame: Frame,
pub distribution: WindowedPiecewiseConstant2D, pub distribution: DeviceWindowedPiecewiseConstant2D,
pub scene_center: Point3f, pub scene_center: Point3f,
pub scene_radius: Float, pub scene_radius: Float,
} }
#[cfg(not(target_os = "cuda"))] impl PortalInfiniteLight {
impl InfinitePortalLight {
pub fn new(
render_from_light: TransformGeneric<Float>,
equal_area_image: &Image,
image_color_space: Arc<RGBColorSpace>,
scale: Float,
filename: String,
points: Vec<Point3f>,
) -> Self {
let base = LightBase::new(
LightType::Infinite,
&render_from_light,
&MediumInterface::default(),
);
let desc = equal_area_image
.get_channel_desc(&["R", "G", "B"])
.unwrap_or_else(|_| {
panic!(
"{}: image used for PortalImageInfiniteLight doesn't have R, G, B channels.",
filename
)
});
assert_eq!(3, desc.offset.len());
let src_res = equal_area_image.resolution;
if src_res.x() != src_res.y() {
panic!(
"{}: image resolution ({}, {}) is non-square. It's unlikely this is an equal area environment map.",
filename,
src_res.x(),
src_res.y()
);
}
if points.len() != 4 {
panic!(
"Expected 4 vertices for infinite light portal but given {}",
points.len()
);
}
let portal: [Point3f; 4] = [points[0], points[1], points[2], points[3]];
let p01 = (portal[1] - portal[0]).normalize();
let p12 = (portal[2] - portal[1]).normalize();
let p32 = (portal[2] - portal[3]).normalize();
let p03 = (portal[3] - portal[0]).normalize();
if (p01.dot(p32) - 1.0).abs() > 0.001 || (p12.dot(p03) - 1.0).abs() > 0.001 {
panic!("Infinite light portal isn't a planar quadrilateral (opposite edges)");
}
if p01.dot(p12).abs() > 0.001
|| p12.dot(p32).abs() > 0.001
|| p32.dot(p03).abs() > 0.001
|| p03.dot(p01).abs() > 0.001
{
panic!("Infinite light portal isn't a planar quadrilateral (perpendicular edges)");
}
let portal_frame = Frame::from_xy(p03, p01);
let width = src_res.x();
let height = src_res.y();
let mut new_pixels = vec![0.0 as Float; (width * height * 3) as usize];
new_pixels
.par_chunks_mut((width * 3) as usize)
.enumerate()
.for_each(|(y, row_pixels)| {
let y = y as i32;
for x in 0..width {
let uv = Point2f::new(
(x as Float + 0.5) / width as Float,
(y as Float + 0.5) / height as Float,
);
let (w_world, _) = Self::render_from_image(portal_frame, uv);
let w_local = render_from_light.apply_inverse_vector(w_world).normalize();
let uv_equi = equal_area_sphere_to_square(w_local);
let pixel_idx = (x * 3) as usize;
for c in 0..3 {
let val = equal_area_image.bilerp_channel_with_wrap(
uv_equi,
c,
WrapMode::OctahedralSphere.into(),
);
row_pixels[pixel_idx + c] = val;
}
}
});
let image = Image::new(
PixelFormat::F32,
src_res,
&["R", "G", "B"],
equal_area_image.encoding,
);
let duv_dw_closure = |p: Point2f| -> Float {
let (_, jacobian) = Self::render_from_image(portal_frame, p);
jacobian
};
let d = image.get_sampling_distribution(
duv_dw_closure,
Bounds2f::from_points(Point2f::new(0., 0.), Point2f::new(1., 1.)),
);
let distribution = WindowedPiecewiseConstant2D::new(d);
Self {
base,
image,
image_color_space,
scale,
scene_center: Point3f::default(),
scene_radius: 0.,
filename,
portal,
portal_frame,
distribution,
}
}
pub fn image_lookup(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum { pub fn image_lookup(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {
rgb[c] = self.image.lookup_nearest_channel(uv, c) rgb[c] = self.image.lookup_nearest_channel(uv, c)
} }
let spec = let spec = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero());
RGBIlluminantSpectrum::new(self.image_color_space.as_ref(), RGB::clamp_zero(rgb));
self.scale * spec.sample(lambda) self.scale * spec.sample(lambda)
} }
@ -508,7 +317,7 @@ impl InfinitePortalLight {
} }
} }
impl LightTrait for InfinitePortalLight { impl LightTrait for PortalInfiniteLight {
fn base(&self) -> &LightBase { fn base(&self) -> &LightBase {
&self.base &self.base
} }
@ -529,7 +338,8 @@ impl LightTrait for InfinitePortalLight {
let pdf = map_pdf / duv_dw; let pdf = map_pdf / duv_dw;
let l = self.image_lookup(uv, lambda); let l = self.image_lookup(uv, lambda);
let pl = ctx.p() + 2. * self.scene_radius * wi; let pl = ctx.p() + 2. * self.scene_radius * wi;
let sintr = SimpleInteraction::new_interface(pl, Some(self.base.medium_interface.clone())); let base = InteractionBase::new_boundary(pl, 0., self.base.medium_interface);
let sintr = SimpleInteraction::new(base);
let intr = Interaction::Simple(sintr); let intr = Interaction::Simple(sintr);
Some(LightLiSample::new(l, wi, pdf, intr)) Some(LightLiSample::new(l, wi, pdf, intr))
} }
@ -571,8 +381,8 @@ impl LightTrait for InfinitePortalLight {
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
fn preprocess(&mut self, _scene_bounds: &Bounds3f) { fn preprocess(&mut self, scene_bounds: &Bounds3f) {
todo!() (self.scene_center, self.scene_radius) = scene_bounds.bounding_sphere();
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]

View file

@ -10,6 +10,7 @@ pub mod spot;
pub use diffuse::DiffuseAreaLight; pub use diffuse::DiffuseAreaLight;
pub use distant::DistantLight; pub use distant::DistantLight;
pub use goniometric::GoniometricLight; pub use goniometric::GoniometricLight;
pub use infinite::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight}; pub use infinite::{ImageInfiniteLight, PortalInfiniteLight, UniformInfiniteLight};
pub use point::PointLight; pub use point::PointLight;
pub use projection::ProjectionLight; pub use projection::ProjectionLight;
pub use spot::SpotLight;

View file

@ -1,33 +1,24 @@
use crate::Float; use crate::core::geometry::{
use crate::core::light::LightBaseData; Bounds3f, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector3f, VectorLike,
use crate::spectra::SampledSpectrum; };
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::core::interaction::{Interaction, InteractionBase, SimpleInteraction};
use crate::core::light::{
Light, LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
};
use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::{Float, PI};
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct PointLight { pub struct PointLight {
pub base: LightBasea, pub base: LightBase,
pub i:
pub scale: Float, pub scale: Float,
pub i: Ptr<DenselySampledSpectrum>,
} }
impl LightTrait for PointLight { impl LightTrait for PointLight {
fn sample_li_base(
&self,
ctx_p: Point3f,
lambda: &SampledWavelengths,
) -> (SampledSpectrum, Vector3f, Float, Point3fi) {
let pi = self
.base
.render_from_light
.apply_to_interval(&Point3fi::default());
let p: Point3f = pi.into();
let wi = (p - ctx_p).normalize();
let spectrum = DenselySampledSpectrum::from_array(&self.i_coeffs);
let li = self.scale * spectrum.sample(lambda) / p.distance_squared(ctx_p);
(li, wi, 1.0, pi)
}
fn base(&self) -> &LightBase { fn base(&self) -> &LightBase {
&self.base &self.base
} }
@ -46,7 +37,8 @@ impl LightTrait for PointLight {
let p: Point3f = pi.into(); let p: Point3f = pi.into();
let wi = (p - ctx.p()).normalize(); let wi = (p - ctx.p()).normalize();
let li = self.scale * self.i.sample(lambda) / p.distance_squared(ctx.p()); let li = self.scale * self.i.sample(lambda) / p.distance_squared(ctx.p());
let intr = SimpleInteraction::new(pi, 0.0, Some(self.base.medium_interface.clone())); let base = InteractionBase::new_boundary(p, 0., self.base.medium_interface);
let intr = SimpleInteraction::new(base);
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr))) Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
} }

View file

@ -1,6 +1,19 @@
use crate::Float;
use crate::core::color::RGB;
use crate::core::geometry::{
Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f, VectorLike, cos_theta,
};
use crate::core::image::{DeviceImage, ImageAccess};
use crate::core::light::{
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
};
use crate::core::medium::MediumInterface;
use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{radians, square};
use crate::{ use crate::{
spectra::{RGB, RGBColorSpace}, spectra::{RGBColorSpace, RGBIlluminantSpectrum},
utils::{Transform, sampling::PiecewiseConstant2D}, utils::{Ptr, Transform, sampling::DevicePiecewiseConstant2D},
}; };
#[repr(C)] #[repr(C)]
@ -12,66 +25,13 @@ pub struct ProjectionLight {
pub screen_bounds: Bounds2f, pub screen_bounds: Bounds2f,
pub screen_from_light: Transform, pub screen_from_light: Transform,
pub light_from_screen: Transform, pub light_from_screen: Transform,
pub image_id: u32,
pub a: Float, pub a: Float,
pub distrib: PiecewiseConstant2D, pub image: Ptr<DeviceImage>,
pub image_color_space: *const RGBColorSpace, pub distrib: Ptr<DevicePiecewiseConstant2D>,
pub image_color_space: Ptr<RGBColorSpace>,
} }
impl ProjectionLight { impl ProjectionLight {
pub fn new(
render_from_light: Transform,
medium_interface: MediumInterface,
image_id: u32,
image_color_space: RGBColorSpace,
scale: Float,
fov: Float,
) -> Self {
let base = LightBase::new(
LightType::DeltaPosition,
&render_from_light,
&medium_interface,
);
let image = Image::new();
let aspect = image.resolution().x() as Float / image.resolution().y() as Float;
let screen_bounds = if aspect > 1. {
Bounds2f::from_points(Point2f::new(-aspect, -1.), Point2f::new(aspect, 1.))
} else {
Bounds2f::from_points(
Point2f::new(-1., 1. / aspect),
Point2f::new(1., 1. / aspect),
)
};
let hither = 1e-3;
let screen_from_light = 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 };
let a = 4. * square(opposite) * aspect_ratio;
let dwda = |p: Point2f| {
let w =
Vector3f::from(light_from_screen.apply_to_point(Point3f::new(p.x(), p.y(), 0.)));
cos_theta(w.normalize()).powi(3)
};
let d = image.get_sampling_distribution(dwda, screen_bounds);
let distrib = PiecewiseConstant2D::new_with_bounds(&d, screen_bounds);
Self {
base,
image_id,
image_color_space,
screen_bounds,
screen_from_light,
light_from_screen,
scale,
hither,
a,
distrib,
}
}
pub fn i(&self, w: Vector3f, lambda: SampledWavelengths) -> SampledSpectrum { pub fn i(&self, w: Vector3f, lambda: SampledWavelengths) -> SampledSpectrum {
if w.z() < self.hither { if w.z() < self.hither {
return SampledSpectrum::new(0.); return SampledSpectrum::new(0.);
@ -83,9 +43,9 @@ impl ProjectionLight {
let uv = Point2f::from(self.screen_bounds.offset(&Point2f::new(ps.x(), ps.y()))); let uv = Point2f::from(self.screen_bounds.offset(&Point2f::new(ps.x(), ps.y())));
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {
rgb[c] = self.image.lookup_nearest_channel(uv, c); rgb[c] = self.image.lookup_nearest_channel(uv, c as i32);
} }
let s = RGBIlluminantSpectrum::new(self.image_color_space.as_ref(), RGB::clamp_zero(rgb)); let s = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero());
self.scale * s.sample(&lambda) self.scale * s.sample(&lambda)
} }
} }
@ -131,11 +91,12 @@ impl LightTrait for ProjectionLight {
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
let mut sum = SampledSpectrum::new(0.); let mut sum = SampledSpectrum::new(0.);
for y in 0..self.image.resolution.y() { let res = self.image.resolution();
for x in 0..self.image.resolution.x() { for y in 0..res.y() {
for x in 0..res.x() {
let ps = self.screen_bounds.lerp(Point2f::new( let ps = self.screen_bounds.lerp(Point2f::new(
(x as Float + 0.5) / self.image.resolution.x() as Float, (x as Float + 0.5) / res.x() as Float,
(y as Float + 0.5) / self.image.resolution.y() as Float, (y as Float + 0.5) / res.y() as Float,
)); ));
let w_raw = Vector3f::from(self.light_from_screen.apply_to_point(Point3f::new( let w_raw = Vector3f::from(self.light_from_screen.apply_to_point(Point3f::new(
ps.x(), ps.x(),
@ -146,17 +107,14 @@ impl LightTrait for ProjectionLight {
let dwda = cos_theta(w).powi(3); let dwda = cos_theta(w).powi(3);
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {
rgb[c] = self.image.get_channel(Point2i::new(x, y), c); rgb[c] = self.image.get_channel(Point2i::new(x, y), c as i32);
} }
let s = RGBIlluminantSpectrum::new( let s = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero());
self.image_color_space.as_ref(),
RGB::clamp_zero(rgb),
);
sum += s.sample(&lambda) * dwda; sum += s.sample(&lambda) * dwda;
} }
} }
self.scale * self.a * sum / (self.image.resolution.x() * self.image.resolution.y()) as Float self.scale * self.a * sum / (res.x() * res.y()) as Float
} }
fn preprocess(&mut self, _scene_bounds: &Bounds3f) { fn preprocess(&mut self, _scene_bounds: &Bounds3f) {

View file

@ -1,13 +1,12 @@
use crate::core::geometry::primitives::OctahedralVector; use crate::core::geometry::primitives::OctahedralVector;
use crate::core::geometry::{Bounds3f, Normal3f, Point3f, Vector3f, VectorLike}; use crate::core::geometry::{Bounds3f, Normal3f, Point3f, Vector3f, VectorLike};
use crate::core::geometry::{DirectionCone, Normal}; use crate::core::geometry::{DirectionCone, Normal};
use crate::utils::math::{clamp, lerp, sample_discrete}; use crate::core::light::Light;
use std::collections::HashMap;
use std::sync::Arc;
use crate::core::light::{LightBounds, LightSampleContext}; use crate::core::light::{LightBounds, LightSampleContext};
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{clamp, lerp, sample_discrete};
use crate::utils::math::{safe_sqrt, square}; use crate::utils::math::{safe_sqrt, square};
use crate::utils::ptr::Ptr;
use crate::utils::sampling::AliasTable; use crate::utils::sampling::AliasTable;
use crate::{Float, ONE_MINUS_EPSILON, PI}; use crate::{Float, ONE_MINUS_EPSILON, PI};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
@ -155,22 +154,25 @@ impl CompactLightBounds {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SampledLight { pub struct SampledLight {
pub light: Arc<Light>, pub light: Ptr<Light>,
pub p: Float, pub p: Float,
} }
impl SampledLight { impl SampledLight {
pub fn new(light: Arc<Light>, p: Float) -> Self { pub fn new(light: Light, p: Float) -> Self {
Self { light, p } Self {
light: Ptr::from(&light),
p,
}
} }
} }
#[enum_dispatch] #[enum_dispatch]
pub trait LightSamplerTrait: Send + Sync + std::fmt::Debug { pub trait LightSamplerTrait {
fn sample_with_context(&self, ctx: &LightSampleContext, u: Float) -> Option<SampledLight>; fn sample_with_context(&self, ctx: &LightSampleContext, u: Float) -> Option<SampledLight>;
fn pmf_with_context(&self, ctx: &LightSampleContext, light: &Arc<Light>) -> Float; fn pmf_with_context(&self, ctx: &LightSampleContext, light: &Light) -> Float;
fn sample(&self, u: Float) -> Option<SampledLight>; fn sample(&self, u: Float) -> Option<SampledLight>;
fn pmf(&self, light: &Arc<Light>) -> Float; fn pmf(&self, light: &Light) -> Float;
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -183,14 +185,18 @@ pub enum LightSampler {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct UniformLightSampler { pub struct UniformLightSampler {
lights: Vec<Arc<Light>>, lights: *const Light,
lights_len: u32,
} }
impl UniformLightSampler { impl UniformLightSampler {
pub fn new(lights: &[Arc<Light>]) -> Self { pub fn new(lights: *const Light, lights_len: u32) -> Self {
Self { Self { lights, lights_len }
lights: lights.to_vec(),
} }
#[inline(always)]
fn light(&self, idx: usize) -> Light {
unsafe { *self.lights.add(idx) }
} }
} }
@ -198,77 +204,52 @@ impl LightSamplerTrait for UniformLightSampler {
fn sample_with_context(&self, _ctx: &LightSampleContext, u: Float) -> Option<SampledLight> { fn sample_with_context(&self, _ctx: &LightSampleContext, u: Float) -> Option<SampledLight> {
self.sample(u) self.sample(u)
} }
fn pmf_with_context(&self, _ctx: &LightSampleContext, light: &Arc<Light>) -> Float { fn pmf_with_context(&self, _ctx: &LightSampleContext, light: &Light) -> Float {
self.pmf(light) self.pmf(light)
} }
fn sample(&self, u: Float) -> Option<SampledLight> { fn sample(&self, u: Float) -> Option<SampledLight> {
if self.lights.is_empty() { if self.lights_len == 0 {
return None; return None;
} }
let light_index = (u as usize * self.lights.len()).min(self.lights.len() - 1); let light_index = (u as u32 * self.lights_len).min(self.lights_len - 1) as usize;
Some(SampledLight { Some(SampledLight {
light: self.lights[light_index].clone(), light: Ptr::from(&self.light(light_index)),
p: 1. / self.lights.len() as Float, p: 1. / self.lights_len as Float,
}) })
} }
fn pmf(&self, _light: &Arc<Light>) -> Float { fn pmf(&self, _light: &Light) -> Float {
if self.lights.is_empty() { if self.lights_len == 0 {
return 0.; return 0.;
} }
1. / self.lights.len() as Float 1. / self.lights_len as Float
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct Alias {
pub q: Float,
pub alias: u32,
}
#[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct PowerLightSampler { pub struct PowerLightSampler {
lights: Vec<Arc<Light>>, pub lights: Ptr<Light>,
light_to_index: HashMap<usize, usize>, pub lights_len: u32,
alias_table: AliasTable, pub alias_table: AliasTable,
} }
impl PowerLightSampler { unsafe impl Send for PowerLightSampler {}
pub fn new(lights: &[Arc<Light>]) -> Self { unsafe impl Sync for PowerLightSampler {}
if lights.is_empty() {
return Self {
lights: Vec::new(),
light_to_index: HashMap::new(),
alias_table: AliasTable::new(&[]),
};
}
let mut lights_vec = Vec::with_capacity(lights.len());
let mut light_to_index = HashMap::with_capacity(lights.len());
let mut light_power = Vec::with_capacity(lights.len());
let lambda = SampledWavelengths::sample_visible(0.5);
for (i, light) in lights.iter().enumerate() {
lights_vec.push(light.clone());
let ptr = Arc::as_ptr(light) as usize;
light_to_index.insert(ptr, i);
let phi = SampledSpectrum::safe_div(&light.phi(lambda), &lambda.pdf());
light_power.push(phi.average());
}
let alias_table = AliasTable::new(&light_power);
Self {
lights: lights_vec,
light_to_index,
alias_table,
}
}
}
impl LightSamplerTrait for PowerLightSampler { impl LightSamplerTrait for PowerLightSampler {
fn sample_with_context(&self, _ctx: &LightSampleContext, u: Float) -> Option<SampledLight> { fn sample_with_context(&self, _ctx: &LightSampleContext, u: Float) -> Option<SampledLight> {
self.sample(u) self.sample(u)
} }
fn pmf_with_context(&self, _ctx: &LightSampleContext, light: &Arc<Light>) -> Float { fn pmf_with_context(&self, _ctx: &LightSampleContext, light: &Light) -> Float {
self.pmf(light) self.pmf(light)
} }
@ -279,32 +260,32 @@ impl LightSamplerTrait for PowerLightSampler {
let (light_index, pmf, _) = self.alias_table.sample(u); let (light_index, pmf, _) = self.alias_table.sample(u);
let light_ref = unsafe { self.lights.add(light_index as usize) };
Some(SampledLight { Some(SampledLight {
light: self.lights[light_index].clone(), light: light_ref,
p: pmf, p: pmf,
}) })
} }
fn pmf(&self, light: &Arc<Light>) -> Float { fn pmf(&self, light: &Light) -> Float {
if self.alias_table.size() == 0 { let array_start = self.lights.as_raw();
return 0.; let target = light as *const Light as *mut Light;
}
let ptr = Arc::as_ptr(light) as usize; unsafe {
let index = target.offset_from(array_start);
if let Some(&index) = self.light_to_index.get(&ptr) { if index >= 0 && index < self.lights_len as isize {
self.alias_table.pmf(index) return self.alias_table.pmf(index as u32);
} else {
0.0
} }
} }
0.
}
} }
#[derive(Clone, Copy, Debug, Default)] #[derive(Clone, Copy, Debug, Default)]
#[repr(C, align(32))] #[repr(C, align(32))]
pub struct LightBVHNode { pub struct LightBVHNode {
pub light_bounds: CompactLightBounds, pub light_bounds: CompactLightBounds,
// Bit 31 (MSB) : isLeaf (1 bit) // Bit 31 (MSB) : isLeaf (1 bit)
// Bits 0..31 : childOrLightIndex (31 bits) // Bits 0..31 : childOrLightIndex (31 bits)
packed_data: u32, packed_data: u32,
@ -373,14 +354,40 @@ impl LightBVHNode {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct BVHLightSampler { pub struct BVHLightSampler {
lights: Vec<Arc<Light>>, pub nodes: *const LightBVHNode,
infinite_lights: Vec<Arc<Light>>, pub lights: *const Light,
all_light_bounds: Bounds3f, pub infinite_lights: *const Light,
nodes: Vec<LightBVHNode>, pub bit_trails: *const u64,
light_to_bit_trail: HashMap<usize, usize>, pub nodes_len: u32,
pub lights_len: u32,
pub infinite_lights_len: u32,
pub all_light_bounds: Bounds3f,
} }
unsafe impl Send for BVHLightSampler {}
unsafe impl Sync for BVHLightSampler {}
impl BVHLightSampler { impl BVHLightSampler {
#[inline(always)]
fn node(&self, idx: usize) -> &LightBVHNode {
unsafe { &*self.nodes.add(idx) }
}
#[inline(always)]
fn light(&self, idx: usize) -> Light {
unsafe { *self.lights.add(idx) }
}
#[inline(always)]
fn infinite_light(&self, idx: usize) -> Light {
unsafe { *self.infinite_lights.add(idx) }
}
#[inline(always)]
fn bit_trail(&self, idx: usize) -> u64 {
unsafe { *self.bit_trails.add(idx) }
}
fn evaluate_cost(&self, b: &LightBounds, bounds: &Bounds3f, dim: usize) -> Float { fn evaluate_cost(&self, b: &LightBounds, bounds: &Bounds3f, dim: usize) -> Float {
let theta_o = b.cos_theta_o.acos(); let theta_o = b.cos_theta_o.acos();
let theta_e = b.cos_theta_e.acos(); let theta_e = b.cos_theta_e.acos();
@ -397,9 +404,9 @@ impl BVHLightSampler {
impl LightSamplerTrait for BVHLightSampler { impl LightSamplerTrait for BVHLightSampler {
fn sample_with_context(&self, ctx: &LightSampleContext, mut u: Float) -> Option<SampledLight> { fn sample_with_context(&self, ctx: &LightSampleContext, mut u: Float) -> Option<SampledLight> {
let empty_nodes = if self.nodes.is_empty() { 0. } else { 1. }; let empty_nodes = if self.nodes_len == 0 { 0. } else { 1. };
let inf_size = self.infinite_lights.len() as Float; let inf_size = self.infinite_lights_len as Float;
let light_size = self.lights.len() as Float; let light_size = self.lights_len as Float;
let p_inf = inf_size / (inf_size + empty_nodes); let p_inf = inf_size / (inf_size + empty_nodes);
@ -407,9 +414,10 @@ impl LightSamplerTrait for BVHLightSampler {
u /= p_inf; u /= p_inf;
let ind = (u * light_size).min(light_size - 1.) as usize; let ind = (u * light_size).min(light_size - 1.) as usize;
let pmf = p_inf / inf_size; let pmf = p_inf / inf_size;
Some(SampledLight::new(self.infinite_lights[ind].clone(), pmf)) return Some(SampledLight::new(self.infinite_light(ind), pmf));
} else { }
if self.nodes.is_empty() {
if self.nodes_len == 0 {
return None; return None;
} }
let p = ctx.p(); let p = ctx.p();
@ -419,19 +427,16 @@ impl LightSamplerTrait for BVHLightSampler {
let mut pmf = 1. - p_inf; let mut pmf = 1. - p_inf;
loop { loop {
let node = self.nodes[node_ind]; let node = self.node(node_ind);
if !node.is_leaf() { if !node.is_leaf() {
let children: [LightBVHNode; 2] = [ let child0_idx = node_ind + 1;
self.nodes[node_ind + 1], let child1_idx = node.child_or_light_index() as usize;
self.nodes[node.child_or_light_index() as usize], let child0 = self.node(child0_idx);
]; let child1 = self.node(child1_idx);
let ci: [Float; 2] = [ let ci: [Float; 2] = [
children[0] child0.light_bounds.importance(p, n, &self.all_light_bounds),
.light_bounds child1.light_bounds.importance(p, n, &self.all_light_bounds),
.importance(p, n, &self.all_light_bounds),
children[1]
.light_bounds
.importance(p, n, &self.all_light_bounds),
]; ];
if ci[0] == 0. && ci[1] == 0. { if ci[0] == 0. && ci[1] == 0. {
@ -441,81 +446,96 @@ impl LightSamplerTrait for BVHLightSampler {
let mut node_pmf: Float = 0.; let mut node_pmf: Float = 0.;
let child = sample_discrete(&ci, u, Some(&mut node_pmf), Some(&mut u)); let child = sample_discrete(&ci, u, Some(&mut node_pmf), Some(&mut u));
pmf *= node_pmf; pmf *= node_pmf;
node_ind = if child == 0 { node_ind = if child == 0 { child0_idx } else { child1_idx };
node_ind + 1
} else { } else {
node.child_or_light_index() as usize if node_ind > 0 || node.light_bounds.importance(p, n, &self.all_light_bounds) > 0. {
}; let light_idx = node.child_or_light_index() as usize;
} else { return Some(SampledLight::new(self.light(light_idx), pmf));
if node_ind > 0
|| node.light_bounds.importance(p, n, &self.all_light_bounds) > 0.
{
return Some(SampledLight::new(
self.lights[node.child_or_light_index() as usize].clone(),
pmf,
));
} }
return None; return None;
} }
} }
} }
fn pmf_with_context(&self, ctx: &LightSampleContext, light: &Light) -> Float {
let light_ptr = light as *const Light;
let empty_nodes = if self.nodes_len == 0 { 0. } else { 1. };
let n_infinite = self.infinite_lights_len as Float;
let inf_start = self.infinite_lights;
let inf_end = unsafe { self.infinite_lights.add(self.infinite_lights_len as usize) };
if light_ptr >= inf_start && light_ptr < inf_end {
return 1.0 / (n_infinite + empty_nodes);
} }
fn pmf_with_context(&self, ctx: &LightSampleContext, light: &Arc<Light>) -> Float { let finite_start = self.lights;
let ptr = Arc::as_ptr(light) as usize; let finite_end = unsafe { self.lights.add(self.lights_len as usize) };
let empty_nodes = if self.nodes.is_empty() { 0. } else { 1. };
if self.light_to_bit_trail.contains_key(&ptr) { if light_ptr < finite_start || light_ptr >= finite_end {
return 1. / (self.infinite_lights.len() as Float + empty_nodes); return 0.0;
} }
let mut bit_trail = self.light_to_bit_trail[&ptr]; let light_index = unsafe { light_ptr.offset_from(finite_start) as usize };
let mut bit_trail = self.bit_trail(light_index);
let p_inf = n_infinite / (n_infinite + empty_nodes);
let mut pmf = 1.0 - p_inf;
let mut node_ind = 0;
let p = ctx.p(); let p = ctx.p();
let n = ctx.ns; let n = ctx.ns;
let p_inf = self.infinite_lights.len() as Float
/ (self.infinite_lights.len() as Float + empty_nodes);
let mut pmf = 1. - p_inf;
let mut node_ind = 0;
loop { loop {
let node = self.nodes[node_ind]; let node = self.node(node_ind);
if node.is_leaf() { if node.is_leaf() {
return pmf; return pmf;
} }
let child0 = self.nodes[node_ind + 1]; let child0 = self.node(node_ind + 1);
let child1 = self.nodes[node.child_or_light_index() as usize]; let child1 = self.node(node.child_or_light_index() as usize);
let ci = [ let ci = [
child0.light_bounds.importance(p, n, &self.all_light_bounds), child0.light_bounds.importance(p, n, &self.all_light_bounds),
child1.light_bounds.importance(p, n, &self.all_light_bounds), child1.light_bounds.importance(p, n, &self.all_light_bounds),
]; ];
pmf *= ci[bit_trail & 1] / (ci[0] + ci[1]);
node_ind = if (bit_trail & 1) != 0 { let sum_importance = ci[0] + ci[1];
if sum_importance == 0.0 {
return 0.0;
}
let which_child = (bit_trail & 1) as usize;
// Update probability: prob of picking the correct child
pmf *= ci[which_child] / sum_importance;
// Advance
node_ind = if which_child == 1 {
node.child_or_light_index() as usize node.child_or_light_index() as usize
} else { } else {
node_ind + 1 node_ind + 1
}; };
bit_trail >>= 1; bit_trail >>= 1;
} }
} }
fn sample(&self, u: Float) -> Option<SampledLight> { fn sample(&self, u: Float) -> Option<SampledLight> {
if self.lights.is_empty() { if self.lights_len == 0 {
return None; return None;
} }
let light_ind = let light_ind = (u * self.lights_len as Float).min(self.lights_len as Float - 1.) as usize;
(u * self.lights.len() as Float).min(self.lights.len() as Float - 1.) as usize;
Some(SampledLight::new( Some(SampledLight::new(
self.lights[light_ind].clone(), self.light(light_ind),
1. / self.lights.len() as Float, 1. / self.lights_len as Float,
)) ))
} }
fn pmf(&self, _light: &Arc<Light>) -> Float { fn pmf(&self, _light: &Light) -> Float {
if self.lights.is_empty() { if self.lights_len == 0 {
return 0.; return 0.;
} }
1. / self.lights.len() as Float 1. / self.lights_len as Float
} }
} }

View file

@ -1,16 +1,24 @@
use crate::core::light::{LightBase, LightLiSample, LightSampleContext, LightTrait}; use crate::core::geometry::{
Bounds3f, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector3f, VectorLike,
};
use crate::core::interaction::{Interaction, InteractionBase, InteractionTrait, SimpleInteraction};
use crate::core::light::{LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait};
use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::{Float, PI};
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct SpotLight { pub struct SpotLight {
pub base: LightBase, pub base: LightBase,
pub iemit_coeffs: [Float; 32], pub iemit: Ptr<DenselySampledSpectrum>,
pub scale: Float, pub scale: Float,
pub cos_falloff_start: Float, pub cos_falloff_start: Float,
pub cos_falloff_end: Float, pub cos_falloff_end: Float,
} }
impl SpotLightData { impl SpotLight {
pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
let cos_theta = w.z(); // assuming normalized in light space let cos_theta = w.z(); // assuming normalized in light space
let falloff = crate::utils::math::smooth_step( let falloff = crate::utils::math::smooth_step(
@ -18,8 +26,7 @@ impl SpotLightData {
self.cos_falloff_end, self.cos_falloff_end,
self.cos_falloff_start, self.cos_falloff_start,
); );
let spectrum = DenselySampledSpectrum::from_array(&self.iemit_coeffs); falloff * self.scale * self.iemit.sample(lambda)
falloff * self.scale * spectrum.sample(lambda)
} }
} }
@ -42,9 +49,10 @@ impl LightTrait for SpotLight {
let p: Point3f = pi.into(); let p: Point3f = pi.into();
let wi = (p - ctx.p()).normalize(); let wi = (p - ctx.p()).normalize();
let w_light = self.base.render_from_light.apply_inverse_vector(-wi); let w_light = self.base.render_from_light.apply_inverse_vector(-wi);
let li = self.i(w_light, *lambda) / p.distance_squared(ctx.p()); let li = self.i(w_light, lambda) / p.distance_squared(ctx.p());
let intr = SimpleInteraction::new(pi, 0.0, Some(self.base.medium_interface.clone())); let base = InteractionBase::new_boundary(p, 0., self.base.medium_interface);
let intr = SimpleInteraction::new(base);
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr))) Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
} }
@ -78,7 +86,7 @@ impl LightTrait for SpotLight {
* self.iemit.sample(&lambda) * self.iemit.sample(&lambda)
* 2. * 2.
* PI * PI
* ((1. - self.cos_fallof_start) + (self.cos_fallof_start - self.cos_fallof_end) / 2.) * ((1. - self.cos_falloff_start) + (self.cos_falloff_start - self.cos_falloff_end) / 2.)
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
@ -98,12 +106,12 @@ impl LightTrait for SpotLight {
.apply_to_vector(Vector3f::new(0., 0., 1.)) .apply_to_vector(Vector3f::new(0., 0., 1.))
.normalize(); .normalize();
let phi = self.scale * self.iemit.max_value() * 4. * PI; let phi = self.scale * self.iemit.max_value() * 4. * PI;
let cos_theta_e = (self.cos_fallof_end.acos() - self.cos_fallof_start.acos()).cos(); let cos_theta_e = (self.cos_falloff_end.acos() - self.cos_falloff_start.acos()).cos();
Some(LightBounds::new( Some(LightBounds::new(
&Bounds3f::from_points(p, p), &Bounds3f::from_points(p, p),
w, w,
phi, phi,
self.cos_fallof_start, self.cos_falloff_start,
cos_theta_e, cos_theta_e,
false, false,
)) ))

View file

@ -0,0 +1,335 @@
use crate::bxdfs::{
CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF,
};
use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::BxDF;
use crate::core::image::DeviceImage;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::math::clamp;
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct CoatedDiffuseMaterial {
pub normal_map: Ptr<DeviceImage>,
pub displacement: Ptr<GPUFloatTexture>,
pub reflectance: Ptr<GPUSpectrumTexture>,
pub albedo: Ptr<GPUSpectrumTexture>,
pub u_roughness: Ptr<GPUFloatTexture>,
pub v_roughness: Ptr<GPUFloatTexture>,
pub thickness: Ptr<GPUFloatTexture>,
pub g: Ptr<GPUFloatTexture>,
pub eta: Ptr<Spectrum>,
pub max_depth: u32,
pub n_samples: u32,
pub remap_roughness: bool,
}
impl CoatedDiffuseMaterial {
#[allow(clippy::too_many_arguments)]
pub fn new(
reflectance: Ptr<GPUSpectrumTexture>,
u_roughness: Ptr<GPUFloatTexture>,
v_roughness: Ptr<GPUFloatTexture>,
thickness: Ptr<GPUFloatTexture>,
albedo: Ptr<GPUSpectrumTexture>,
g: Ptr<GPUFloatTexture>,
eta: Ptr<Spectrum>,
displacement: Ptr<GPUFloatTexture>,
normal_map: Ptr<DeviceImage>,
remap_roughness: bool,
max_depth: u32,
n_samples: u32,
) -> 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_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, Ptr::from(&bxdf))
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
None
}
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<&DeviceImage> {
Some(&*self.normal_map)
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement
}
fn has_subsurface_scattering(&self) -> bool {
false
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct CoatedConductorMaterial {
normal_map: Ptr<DeviceImage>,
displacement: Ptr<GPUFloatTexture>,
interface_uroughness: Ptr<GPUFloatTexture>,
interface_vroughness: Ptr<GPUFloatTexture>,
thickness: Ptr<GPUFloatTexture>,
interface_eta: Ptr<Spectrum>,
g: Ptr<GPUFloatTexture>,
albedo: Ptr<GPUSpectrumTexture>,
conductor_uroughness: Ptr<GPUFloatTexture>,
conductor_vroughness: Ptr<GPUFloatTexture>,
conductor_eta: Ptr<GPUSpectrumTexture>,
k: Ptr<GPUSpectrumTexture>,
reflectance: Ptr<GPUSpectrumTexture>,
max_depth: u32,
n_samples: u32,
remap_roughness: bool,
}
impl CoatedConductorMaterial {
#[allow(clippy::too_many_arguments)]
pub fn new(
normal_map: Ptr<DeviceImage>,
displacement: Ptr<GPUFloatTexture>,
interface_uroughness: Ptr<GPUFloatTexture>,
interface_vroughness: Ptr<GPUFloatTexture>,
thickness: Ptr<GPUFloatTexture>,
interface_eta: Ptr<Spectrum>,
g: Ptr<GPUFloatTexture>,
albedo: Ptr<GPUSpectrumTexture>,
conductor_uroughness: Ptr<GPUFloatTexture>,
conductor_vroughness: Ptr<GPUFloatTexture>,
conductor_eta: Ptr<GPUSpectrumTexture>,
k: Ptr<GPUSpectrumTexture>,
reflectance: Ptr<GPUSpectrumTexture>,
max_depth: u32,
n_samples: u32,
remap_roughness: bool,
) -> 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_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 !self.conductor_eta.is_null() {
let k_tex = self.k;
let ce = tex_eval.evaluate_spectrum(&self.conductor_eta, ctx, lambda);
let ck = tex_eval.evaluate_spectrum(unsafe { k_tex.as_ref() }, 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, Ptr::from(&bxdf))
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
None
}
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 !self.conductor_eta.is_null() {
spectrum_textures.push(self.conductor_eta);
}
if !self.k.is_null() {
spectrum_textures.push(self.k);
}
if !self.conductor_eta.is_null() {
spectrum_textures.push(self.reflectance);
}
tex_eval.can_evaluate(&float_textures, &spectrum_textures)
}
fn get_normal_map(&self) -> Option<&DeviceImage> {
Some(&*self.normal_map)
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement
}
fn has_subsurface_scattering(&self) -> bool {
false
}
}

View file

@ -0,0 +1,188 @@
use crate::Float;
use crate::bxdfs::{
CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF, HairBxDF,
MeasuredBxDF, MeasuredBxDFData,
};
use crate::core::bsdf::BSDF;
use crate::core::bssrdf::{BSSRDF, BSSRDFTable};
use crate::core::bxdf::BxDF;
use crate::core::image::DeviceImage;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::textures::GPUSpectrumMixTexture;
use crate::utils::Ptr;
use crate::utils::math::clamp;
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct HairMaterial {
pub sigma_a: Ptr<GPUSpectrumTexture>,
pub color: Ptr<GPUSpectrumTexture>,
pub eumelanin: Ptr<GPUFloatTexture>,
pub pheomelanin: Ptr<GPUFloatTexture>,
pub eta: Ptr<GPUFloatTexture>,
pub beta_m: Ptr<GPUFloatTexture>,
pub beta_n: Ptr<GPUFloatTexture>,
pub alpha: Ptr<GPUFloatTexture>,
}
impl HairMaterial {
#[cfg(not(target_os = "cuda"))]
#[allow(clippy::too_many_arguments)]
pub fn new(
sigma_a: Ptr<GPUSpectrumTexture>,
color: Ptr<GPUSpectrumTexture>,
eumelanin: Ptr<GPUFloatTexture>,
pheomelanin: Ptr<GPUFloatTexture>,
eta: Ptr<GPUFloatTexture>,
beta_m: Ptr<GPUFloatTexture>,
beta_n: Ptr<GPUFloatTexture>,
alpha: Ptr<GPUFloatTexture>,
) -> Self {
Self {
sigma_a,
color,
eumelanin,
pheomelanin,
eta,
beta_m,
beta_n,
alpha,
}
}
}
impl MaterialTrait for HairMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<&DeviceImage> {
todo!()
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
Ptr::null()
}
fn has_subsurface_scattering(&self) -> bool {
false
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct MeasuredMaterial {
pub displacement: Ptr<GPUFloatTexture>,
pub normal_map: Ptr<DeviceImage>,
pub brdf: Ptr<MeasuredBxDFData>,
}
impl MaterialTrait for MeasuredMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
// MeasuredBxDF::new(&self.brdf, lambda)
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
None
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
true
}
fn get_normal_map(&self) -> Option<&DeviceImage> {
Some(&*self.normal_map)
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement
}
fn has_subsurface_scattering(&self) -> bool {
false
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct SubsurfaceMaterial {
pub normal_map: Ptr<DeviceImage>,
pub displacement: Ptr<GPUFloatTexture>,
pub sigma_a: Ptr<GPUSpectrumTexture>,
pub sigma_s: Ptr<GPUSpectrumMixTexture>,
pub reflectance: Ptr<GPUSpectrumMixTexture>,
pub mfp: Ptr<GPUSpectrumMixTexture>,
pub eta: Float,
pub scale: Float,
pub u_roughness: Ptr<GPUFloatTexture>,
pub v_roughness: Ptr<GPUFloatTexture>,
pub remap_roughness: bool,
pub table: BSSRDFTable,
}
impl MaterialTrait for SubsurfaceMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<&DeviceImage> {
todo!()
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
todo!()
}
fn has_subsurface_scattering(&self) -> bool {
true
}
}

View file

@ -0,0 +1,64 @@
use crate::bxdfs::{
CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF, HairBxDF,
};
use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::BxDF;
use crate::core::image::DeviceImage;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::math::clamp;
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct ConductorMaterial {
pub displacement: Ptr<GPUFloatTexture>,
pub eta: Ptr<GPUSpectrumTexture>,
pub k: Ptr<GPUSpectrumTexture>,
pub reflectance: Ptr<GPUSpectrumTexture>,
pub u_roughness: Ptr<GPUFloatTexture>,
pub v_roughness: Ptr<GPUFloatTexture>,
pub remap_roughness: bool,
pub normal_map: Ptr<DeviceImage>,
}
impl MaterialTrait for ConductorMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(
&[self.u_roughness, self.v_roughness],
&[self.eta, self.k, self.reflectance],
)
}
fn get_normal_map(&self) -> Option<&DeviceImage> {
todo!()
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
todo!()
}
fn has_subsurface_scattering(&self) -> bool {
todo!()
}
}

View file

@ -0,0 +1,124 @@
use crate::bxdfs::{
CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF, HairBxDF,
};
use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::BxDF;
use crate::core::image::DeviceImage;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::math::clamp;
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct DielectricMaterial {
pub normal_map: Ptr<DeviceImage>,
pub displacement: Ptr<GPUFloatTexture>,
pub u_roughness: Ptr<GPUFloatTexture>,
pub v_roughness: Ptr<GPUFloatTexture>,
pub eta: Ptr<Spectrum>,
pub remap_roughness: bool,
}
impl MaterialTrait for DielectricMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSDF {
let mut sampled_eta = self.eta.evaluate(lambda[0]);
if !self.eta.is_constant() {
lambda.terminate_secondary();
}
if sampled_eta == 0.0 {
sampled_eta = 1.0;
}
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 bxdf = BxDF::Dielectric(DielectricBxDF::new(sampled_eta, distrib));
BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf))
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
None
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(&[self.u_roughness, self.v_roughness], &[])
}
fn get_normal_map(&self) -> Option<&DeviceImage> {
Some(&*self.normal_map)
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement
}
fn has_subsurface_scattering(&self) -> bool {
false
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct ThinDielectricMaterial {
pub displacement: Ptr<GPUFloatTexture>,
pub normal_map: Ptr<DeviceImage>,
pub eta: Ptr<Spectrum>,
}
impl MaterialTrait for ThinDielectricMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
true
}
fn get_normal_map(&self) -> Option<&DeviceImage> {
Some(&*self.normal_map)
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement
}
fn has_subsurface_scattering(&self) -> bool {
false
}
}

View file

@ -0,0 +1,106 @@
use crate::Float;
use crate::bxdfs::{
CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF, HairBxDF,
};
use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::BxDF;
use crate::core::image::DeviceImage;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::math::clamp;
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct DiffuseMaterial {
pub normal_map: Ptr<DeviceImage>,
pub displacement: Ptr<GPUFloatTexture>,
pub reflectance: Ptr<GPUSpectrumTexture>,
}
impl MaterialTrait for DiffuseMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSDF {
let r = tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda);
let bxdf = BxDF::Diffuse(DiffuseBxDF::new(r));
BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf))
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(&[], &[self.reflectance])
}
fn get_normal_map(&self) -> Option<&DeviceImage> {
Some(&*self.normal_map)
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement
}
fn has_subsurface_scattering(&self) -> bool {
false
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct DiffuseTransmissionMaterial {
pub image: Ptr<DeviceImage>,
pub displacement: Ptr<GPUFloatTexture>,
pub reflectance: Ptr<GPUFloatTexture>,
pub transmittance: Ptr<GPUFloatTexture>,
pub scale: Float,
}
impl MaterialTrait for DiffuseTransmissionMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(&[self.reflectance, self.transmittance], &[])
}
fn get_normal_map(&self) -> Option<&DeviceImage> {
Some(&*self.image)
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement
}
fn has_subsurface_scattering(&self) -> bool {
false
}
}

View file

@ -0,0 +1,86 @@
use crate::bxdfs::{
CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF, HairBxDF,
};
use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::BxDF;
use crate::core::image::DeviceImage;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::hash::hash_float;
use crate::utils::math::clamp;
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct MixMaterial {
pub amount: Ptr<GPUFloatTexture>,
pub materials: [Ptr<Material>; 2],
}
impl MixMaterial {
pub fn choose_material<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
) -> Option<&Material> {
let amt = tex_eval.evaluate_float(&self.amount, ctx);
let index = if amt <= 0.0 {
0
} else if amt >= 1.0 {
1
} else {
let u = hash_float(&(ctx.p, ctx.wo));
if amt < u { 0 } else { 1 }
};
self.materials[index].get()
}
}
impl MaterialTrait for MixMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSDF {
if let Some(mat) = self.choose_material(tex_eval, ctx) {
mat.get_bsdf(tex_eval, ctx, lambda)
} else {
BSDF::default()
}
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
None
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(&[self.amount], &[])
}
fn get_normal_map(&self) -> Option<&DeviceImage> {
None
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
panic!(
"MixMaterial::get_displacement() shouldn't be called. \
Displacement is not supported on Mix materials directly."
);
}
fn has_subsurface_scattering(&self) -> bool {
false
}
}

View file

@ -0,0 +1,13 @@
pub mod coated;
pub mod complex;
pub mod conductor;
pub mod dielectric;
pub mod diffuse;
pub mod mix;
pub use coated::*;
pub use complex::*;
pub use conductor::*;
pub use dielectric::*;
pub use diffuse::*;
pub use mix::*;

View file

@ -1,58 +1,132 @@
use super::{ use crate::core::geometry::{
BilinearIntersection, BilinearPatchShape, Bounds3f, DirectionCone, Interaction, Normal3f, Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Tuple, Vector3f,
Point2f, Point3f, Point3fi, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext, VectorLike, spherical_quad_area,
ShapeTrait, SurfaceInteraction, Transform, Vector3f,
}; };
use crate::core::geometry::{Tuple, VectorLike, spherical_quad_area}; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::interaction::InteractionTrait;
use crate::core::pbrt::{Float, gamma}; use crate::core::pbrt::{Float, gamma};
use crate::core::shape::{Shape, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait};
use crate::utils::Ptr;
use crate::utils::Transform;
use crate::utils::math::{SquareMatrix, clamp, difference_of_products, lerp, quadratic}; use crate::utils::math::{SquareMatrix, clamp, difference_of_products, lerp, quadratic};
use crate::utils::mesh::BilinearPatchMesh; use crate::utils::mesh::DeviceBilinearPatchMesh;
use crate::utils::sampling::{ use crate::utils::sampling::{
bilinear_pdf, invert_spherical_rectangle_sample, sample_bilinear, sample_spherical_rectangle, bilinear_pdf, invert_spherical_rectangle_sample, sample_bilinear, sample_spherical_rectangle,
}; };
use std::sync::Arc; use core::ops::Add;
use std::sync::OnceLock;
struct PatchData<'a> {
mesh: &'a BilinearPatchMesh,
p00: Point3f,
p10: Point3f,
p01: Point3f,
p11: Point3f,
n: Option<[Normal3f; 4]>,
uv: Option<[Point2f; 4]>,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
struct IntersectionData { struct IntersectionData {
t: Float, pub t: Float,
u: Float, pub u: Float,
v: Float, pub v: Float,
} }
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
struct TextureDerivative { struct TextureDerivative {
duds: Float, pub duds: Float,
dvds: Float, pub dvds: Float,
dudt: Float, pub dudt: Float,
dvdt: Float, pub dvdt: Float,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct BilinearIntersection {
pub uv: Point2f,
pub t: Float,
}
impl BilinearIntersection {
pub fn new(uv: Point2f, t: Float) -> Self {
Self { uv, t }
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct BilinearPatchShape {
pub mesh: Ptr<DeviceBilinearPatchMesh>,
pub blp_index: i32,
pub area: Float,
pub rectangle: bool,
} }
static BILINEAR_MESHES: OnceLock<Vec<Arc<BilinearPatchMesh>>> = OnceLock::new();
impl BilinearPatchShape { impl BilinearPatchShape {
pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 1e-4; pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 1e-4;
fn mesh(&self) -> Ptr<DeviceBilinearPatchMesh> {
self.mesh
}
pub fn new(_mesh: BilinearPatchMesh, mesh_index: usize, blp_index: usize) -> Self { #[inline(always)]
fn get_vertex_indices(&self) -> [usize; 4] {
unsafe {
let base_ptr = self.mesh.vertex_indices.add((self.blp_index as usize) * 4);
[
*base_ptr.add(0) as usize,
*base_ptr.add(1) as usize,
*base_ptr.add(2) as usize,
*base_ptr.add(3) as usize,
]
}
}
#[inline(always)]
fn get_points(&self) -> [Point3f; 4] {
let [v0, v1, v2, v3] = self.get_vertex_indices();
unsafe {
[
*self.mesh.p.add(v0),
*self.mesh.p.add(v1),
*self.mesh.p.add(v2),
*self.mesh.p.add(v3),
]
}
}
#[inline(always)]
fn get_uvs(&self) -> Option<[Point2f; 4]> {
if self.mesh.uv.is_null() {
return None;
}
let [v0, v1, v2, v3] = self.get_vertex_indices();
unsafe {
Some([
*self.mesh.uv.add(v0),
*self.mesh.uv.add(v1),
*self.mesh.uv.add(v2),
*self.mesh.uv.add(v3),
])
}
}
#[inline(always)]
fn get_shading_normals(&self) -> Option<[Normal3f; 4]> {
if self.mesh.n.is_null() {
return None;
}
let [v0, v1, v2, v3] = self.get_vertex_indices();
unsafe {
Some([
*self.mesh.n.add(v0),
*self.mesh.n.add(v1),
*self.mesh.n.add(v2),
*self.mesh.n.add(v3),
])
}
}
#[cfg(not(target_os = "cuda"))]
pub fn new(mesh: Ptr<DeviceBilinearPatchMesh>, blp_index: i32) -> Self {
let mut bp = BilinearPatchShape { let mut bp = BilinearPatchShape {
mesh_index, mesh,
blp_index, blp_index,
area: 0., area: 0.,
rectangle: false, rectangle: false,
}; };
let (p00, p10, p01, p11) = { let [p00, p10, p01, p11] = bp.get_points();
let data = bp.get_data();
(data.p00, data.p10, data.p01, data.p11)
};
bp.rectangle = bp.is_rectangle(p00, p10, p01, p11); bp.rectangle = bp.is_rectangle(p00, p10, p01, p11);
@ -84,40 +158,6 @@ impl BilinearPatchShape {
bp bp
} }
fn mesh(&self) -> &Arc<BilinearPatchMesh> {
let meshes = BILINEAR_MESHES
.get()
.expect("Mesh has not been initialized");
&meshes[self.mesh_index]
}
fn get_data(&self) -> PatchData<'_> {
let mesh = self.mesh();
let start_index = 4 * self.blp_index;
let v = &mesh.vertex_indices[start_index..start_index + 4];
let p00: Point3f = mesh.p[v[0]];
let p10: Point3f = mesh.p[v[1]];
let p01: Point3f = mesh.p[v[2]];
let p11: Point3f = mesh.p[v[3]];
let n = mesh
.n
.as_ref()
.map(|normals| [normals[v[0]], normals[v[1]], normals[v[2]], normals[v[3]]]);
let uv = mesh
.uv
.as_ref()
.map(|uvs| [uvs[v[0]], uvs[v[1]], uvs[v[2]], uvs[v[3]]]);
PatchData {
mesh,
p00,
p10,
p01,
p11,
n,
uv,
}
}
fn is_rectangle(&self, p00: Point3f, p10: Point3f, p01: Point3f, p11: Point3f) -> bool { fn is_rectangle(&self, p00: Point3f, p10: Point3f, p01: Point3f, p11: Point3f) -> bool {
if p00 == p01 || p01 == p11 || p11 == p10 || p10 == p00 { if p00 == p01 || p01 == p11 || p11 == p10 || p10 == p00 {
return false; return false;
@ -149,25 +189,26 @@ impl BilinearPatchShape {
&self, &self,
ray: &Ray, ray: &Ray,
t_max: Float, t_max: Float,
data: &PatchData, corners: &[Point3f; 4],
) -> Option<BilinearIntersection> { ) -> Option<BilinearIntersection> {
let a = (data.p10 - data.p00).cross(data.p01 - data.p11).dot(ray.d); let &[p00, p01, p10, p11] = corners;
let c = (data.p00 - ray.o).cross(ray.d).dot(data.p01 - data.p00); let a = (p10 - p00).cross(p01 - p11).dot(ray.d);
let b = (data.p10 - ray.o).cross(ray.d).dot(data.p11 - data.p10) - (a + c); let c = (p00 - ray.o).cross(ray.d).dot(p01 - p00);
let b = (p10 - ray.o).cross(ray.d).dot(p11 - p10) - (a + c);
let (u1, u2) = quadratic(a, b, c)?; let (u1, u2) = quadratic(a, b, c)?;
let eps = gamma(10) let eps = gamma(10)
* (ray.o.abs().max_component_value() * (ray.o.abs().max_component_value()
+ ray.d.abs().max_component_value() + ray.d.abs().max_component_value()
+ data.p00.abs().max_component_value() + p00.abs().max_component_value()
+ data.p10.abs().max_component_value() + p10.abs().max_component_value()
+ data.p01.abs().max_component_value() + p01.abs().max_component_value()
+ data.p11.abs().max_component_value()); + p11.abs().max_component_value());
let hit1 = self.check_candidate(u1, ray, data); let hit1 = self.check_candidate(u1, ray, corners);
let hit2 = if u1 != u2 { let hit2 = if u1 != u2 {
self.check_candidate(u2, ray, data) self.check_candidate(u2, ray, corners)
} else { } else {
None None
}; };
@ -183,12 +224,18 @@ impl BilinearPatchShape {
}) })
} }
fn check_candidate(&self, u: Float, ray: &Ray, data: &PatchData) -> Option<IntersectionData> { fn check_candidate(
&self,
u: Float,
ray: &Ray,
corners: &[Point3f; 4],
) -> Option<IntersectionData> {
if !(0.0..=1.0).contains(&u) { if !(0.0..=1.0).contains(&u) {
return None; return None;
} }
let uo: Point3f = lerp(u, data.p00, data.p10); let &[p00, p01, p10, p11] = corners;
let ud: Point3f = Point3f::from(lerp(u, data.p01, data.p11) - uo); let uo: Point3f = lerp(u, p00, p10);
let ud: Point3f = Point3f::from(lerp(u, p01, p11) - uo);
let deltao = uo - ray.o; let deltao = uo - ray.o;
let perp = ray.d.cross(ud.into()); let perp = ray.d.cross(ud.into());
let p2 = perp.norm_squared(); let p2 = perp.norm_squared();
@ -222,26 +269,25 @@ impl BilinearPatchShape {
fn interaction_from_intersection( fn interaction_from_intersection(
&self, &self,
data: &PatchData,
uv: Point2f, uv: Point2f,
time: Float, time: Float,
wo: Vector3f, wo: Vector3f,
) -> SurfaceInteraction { ) -> SurfaceInteraction {
// Base geom and derivatives // Base geom and derivatives
let p = lerp( let corners = self.get_points();
uv[0], let [p00, p01, p10, p11] = corners;
lerp(uv[1], data.p00, data.p01), let p = lerp(uv[0], lerp(uv[1], p00, p01), lerp(uv[1], p10, p11));
lerp(uv[1], data.p10, data.p11), let mut dpdu = lerp(uv[1], p10, p11) - lerp(uv[1], p00, p01);
); let mut dpdv = lerp(uv[0], p01, p11) - lerp(uv[0], p00, p10);
let mut dpdu = lerp(uv[1], data.p10, data.p11) - lerp(uv[1], data.p00, data.p01);
let mut dpdv = lerp(uv[0], data.p01, data.p11) - lerp(uv[0], data.p00, data.p10);
// If textured, apply coordinates // If textured, apply coordinates
let (st, derivatives) = self.apply_texture_coordinates(data, uv, &mut dpdu, &mut dpdv); let patch_uvs = self.get_uvs();
let (st, derivatives) =
self.apply_texture_coordinates(uv, patch_uvs.try_into().unwrap(), &mut dpdu, &mut dpdv);
// Compute second moments // Compute second moments
let n = Normal3f::from(dpdu.cross(dpdv).normalize()); let n = Normal3f::from(dpdu.cross(dpdv).normalize());
let (mut dndu, mut dndv) = self.calculate_surface_curvature(data, &dpdu, &dpdv, n); let (mut dndu, mut dndv) = self.calculate_surface_curvature(&corners, &dpdu, &dpdv, n);
if let Some(ref deriv) = derivatives { if let Some(ref deriv) = derivatives {
let dnds = Normal3f::from(dndu * deriv.duds + dndv * deriv.dvds); let dnds = Normal3f::from(dndu * deriv.duds + dndv * deriv.dvds);
let dndt = Normal3f::from(dndu * deriv.dudt + dndv * deriv.dvdt); let dndt = Normal3f::from(dndu * deriv.dudt + dndv * deriv.dvdt);
@ -249,31 +295,31 @@ impl BilinearPatchShape {
dndv = dndt; dndv = dndt;
} }
let p_abs_sum = data.p00.abs() let p_abs_sum = p00.abs()
+ Vector3f::from(data.p01.abs()) + Vector3f::from(p01.abs())
+ Vector3f::from(data.p10.abs()) + Vector3f::from(p10.abs())
+ Vector3f::from(data.p11.abs()); + Vector3f::from(p11.abs());
let p_error = gamma(6) * Vector3f::from(p_abs_sum); let p_error = gamma(6) * Vector3f::from(p_abs_sum);
let flip_normal = data.mesh.reverse_orientation ^ data.mesh.transform_swaps_handedness; let flip_normal = self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness;
let pi = Point3fi::new_with_error(p, p_error); let pi = Point3fi::new_with_error(p, p_error);
let mut isect = let mut isect =
SurfaceInteraction::new(pi, st, wo, dpdu, dpdv, dndu, dndv, time, flip_normal); SurfaceInteraction::new(pi, st, wo, dpdu, dpdv, dndu, dndv, time, flip_normal);
if data.n.is_some() {
self.apply_shading_normals(&mut isect, data, uv, derivatives); self.apply_shading_normals(&mut isect, self.get_shading_normals(), uv, derivatives);
}
isect isect
} }
#[inline(always)]
fn apply_texture_coordinates( fn apply_texture_coordinates(
&self, &self,
data: &PatchData,
uv: Point2f, uv: Point2f,
patch_uvs: Option<[Point2f; 4]>,
dpdu: &mut Vector3f, dpdu: &mut Vector3f,
dpdv: &mut Vector3f, dpdv: &mut Vector3f,
) -> (Point2f, Option<TextureDerivative>) { ) -> (Point2f, Option<TextureDerivative>) {
let Some(uvs) = data.uv else { let Some(uvs) = patch_uvs else {
return (uv, None); return (uv, Some(TextureDerivative::default()));
}; };
let uv00 = uvs[0]; let uv00 = uvs[0];
let uv01 = uvs[1]; let uv01 = uvs[1];
@ -297,6 +343,7 @@ impl BilinearPatchShape {
if dpdu.cross(*dpdv).dot(dpds.cross(dpdt)) < 0. { if dpdu.cross(*dpdv).dot(dpds.cross(dpdt)) < 0. {
dpdt = -dpdt; dpdt = -dpdt;
} }
*dpdu = dpds; *dpdu = dpds;
*dpdv = dpdt; *dpdv = dpdt;
@ -309,32 +356,33 @@ impl BilinearPatchShape {
(st, Some(factors)) (st, Some(factors))
} }
#[inline(always)]
fn calculate_base_derivatives( fn calculate_base_derivatives(
&self, &self,
data: &PatchData, corners: &[Point3f; 4],
uv: Point2f, uv: Point2f,
) -> (Point3f, Vector3f, Vector3f) { ) -> (Point3f, Vector3f, Vector3f) {
let p = lerp( let (p00, p10, p01, p11) = (corners[0], corners[1], corners[2], corners[3]);
uv[0],
lerp(uv[1], data.p00, data.p01), let p = lerp(uv[0], lerp(uv[1], p00, p01), lerp(uv[1], p10, p11));
lerp(uv[1], data.p10, data.p11), let dpdu = lerp(uv[1], p10, p11) - lerp(uv[1], p00, p01);
); let dpdv = lerp(uv[0], p01, p11) - lerp(uv[0], p00, p10);
let dpdu = lerp(uv[1], data.p10, data.p11) - lerp(uv[1], data.p00, data.p01);
let dpdv = lerp(uv[0], data.p01, data.p11) - lerp(uv[0], data.p00, data.p10);
(p, dpdu, dpdv) (p, dpdu, dpdv)
} }
#[inline(always)]
fn calculate_surface_curvature( fn calculate_surface_curvature(
&self, &self,
data: &PatchData, corners: &[Point3f; 4],
dpdu: &Vector3f, dpdu: &Vector3f,
dpdv: &Vector3f, dpdv: &Vector3f,
n: Normal3f, n: Normal3f,
) -> (Normal3f, Normal3f) { ) -> (Normal3f, Normal3f) {
let (p00, p10, p01, p11) = (corners[0], corners[1], corners[2], corners[3]);
let e = dpdu.dot(*dpdu); let e = dpdu.dot(*dpdu);
let f = dpdu.dot(*dpdv); let f = dpdu.dot(*dpdv);
let g = dpdv.dot(*dpdv); let g = dpdv.dot(*dpdv);
let d2pduv = (data.p00 - data.p01) + (data.p11 - data.p10); let d2pduv = (p00 - p01) + (p11 - p10);
let d2pduu = Vector3f::zero(); let d2pduu = Vector3f::zero();
let d2pdvv = Vector3f::zero(); let d2pdvv = Vector3f::zero();
@ -357,11 +405,13 @@ impl BilinearPatchShape {
fn apply_shading_normals( fn apply_shading_normals(
&self, &self,
isect: &mut SurfaceInteraction, isect: &mut SurfaceInteraction,
data: &PatchData, shading_normals: Option<[Normal3f; 4]>,
uv: Point2f, uv: Point2f,
derivatives: Option<TextureDerivative>, derivatives: Option<TextureDerivative>,
) { ) {
let Some(normals) = data.n else { return }; let Some(normals) = shading_normals else {
return;
};
let n00 = normals[1]; let n00 = normals[1];
let n10 = normals[1]; let n10 = normals[1];
let n01 = normals[2]; let n01 = normals[2];
@ -390,10 +440,7 @@ impl BilinearPatchShape {
fn sample_area_and_pdf(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> { fn sample_area_and_pdf(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
let mut ss = self.sample(u)?; let mut ss = self.sample(u)?;
ss.intr.get_common_mut().time = ctx.time;
let mut intr_clone = (*ss.intr).clone();
intr_clone.common.time = ctx.time;
ss.intr = Arc::new(intr_clone);
let mut wi = ss.intr.p() - ctx.p(); let mut wi = ss.intr.p() - ctx.p();
let dist_sq = wi.norm_squared(); let dist_sq = wi.norm_squared();
@ -412,16 +459,18 @@ impl BilinearPatchShape {
if ss.pdf.is_infinite() { None } else { Some(ss) } if ss.pdf.is_infinite() { None } else { Some(ss) }
} }
fn sample_parametric_coords(&self, data: &PatchData, u: Point2f) -> (Point2f, Float) { fn sample_parametric_coords(&self, corners: &[Point3f; 4], u: Point2f) -> (Point2f, Float) {
if let Some(image_distrib) = &data.mesh.image_distribution { let (p00, p10, p01, p11) = (corners[0], corners[1], corners[2], corners[3]);
if !self.mesh.image_distribution.is_null() {
let image_distrib = self.mesh.image_distribution;
let (uv, pdf, _) = image_distrib.sample(u); let (uv, pdf, _) = image_distrib.sample(u);
(uv, pdf) (uv, pdf)
} else if !self.rectangle { } else if !self.rectangle {
let w = [ let w = [
(data.p10 - data.p00).cross(data.p01 - data.p00).norm(), (p10 - p00).cross(p01 - p00).norm(),
(data.p10 - data.p00).cross(data.p11 - data.p10).norm(), (p10 - p00).cross(p11 - p10).norm(),
(data.p01 - data.p00).cross(data.p11 - data.p01).norm(), (p01 - p00).cross(p11 - p01).norm(),
(data.p11 - data.p10).cross(data.p11 - data.p01).norm(), (p11 - p10).cross(p11 - p01).norm(),
]; ];
let uv = sample_bilinear(u, &w); let uv = sample_bilinear(u, &w);
let pdf = bilinear_pdf(uv, &w); let pdf = bilinear_pdf(uv, &w);
@ -433,11 +482,12 @@ impl BilinearPatchShape {
fn sample_solid_angle( fn sample_solid_angle(
&self, &self,
data: &PatchData, corners: &[Point3f; 4],
ctx: &ShapeSampleContext, ctx: &ShapeSampleContext,
u: Point2f, u: Point2f,
corner_dirs: &[Vector3f; 4], corner_dirs: &[Vector3f; 4],
) -> Option<ShapeSample> { ) -> Option<ShapeSample> {
let (p00, p10, p01, _p11) = (corners[0], corners[1], corners[2], corners[3]);
let mut pdf = 1.; let mut pdf = 1.;
if ctx.ns != Normal3f::zero() { if ctx.ns != Normal3f::zero() {
let w = [ let w = [
@ -450,19 +500,21 @@ impl BilinearPatchShape {
pdf *= bilinear_pdf(u, &w); pdf *= bilinear_pdf(u, &w);
} }
let eu = data.p10 - data.p00; let eu = p10 - p00;
let ev = data.p01 - data.p00; let ev = p01 - p00;
let (p, quad_pdf) = sample_spherical_rectangle(ctx.p(), data.p00, eu, ev, u); let (p, quad_pdf) = sample_spherical_rectangle(ctx.p(), p00, eu, ev, u);
pdf *= quad_pdf?; pdf *= quad_pdf?;
// Compute (u, v) and surface normal for sampled points on rectangle // Compute (u, v) and surface normal for sampled points on rectangle
let uv = Point2f::new( let uv = Point2f::new(
(p - data.p00).dot(eu) / data.p10.distance_squared(data.p00), (p - p00).dot(eu) / p10.distance_squared(p00),
(p - data.p00).dot(ev) / data.p01.distance_squared(data.p00), (p - p00).dot(ev) / p01.distance_squared(p00),
); );
let n = self.compute_sampled_normal(data, &eu, &ev, uv); let patch_uvs = self.get_uvs();
let st = data.uv.map_or(uv, |uvs| { let patch_normals = self.get_shading_normals();
let n = self.compute_sampled_normal(patch_normals, &eu, &ev, uv);
let st = patch_uvs.map_or(uv, |uvs| {
lerp( lerp(
uv[0], uv[0],
lerp(uv[1], uvs[0], uvs[1]), lerp(uv[1], uvs[0], uvs[1]),
@ -474,29 +526,29 @@ impl BilinearPatchShape {
let mut intr = SurfaceInteraction::new_simple(pi, n, st); let mut intr = SurfaceInteraction::new_simple(pi, n, st);
intr.common.time = ctx.time; intr.common.time = ctx.time;
Some(ShapeSample { Some(ShapeSample {
intr: Arc::new(intr), intr: Interaction::Surface(intr),
pdf, pdf,
}) })
} }
fn compute_sampled_normal( fn compute_sampled_normal(
&self, &self,
data: &PatchData, patch_normals: Option<[Normal3f; 4]>,
dpdu: &Vector3f, dpdu: &Vector3f,
dpdv: &Vector3f, dpdv: &Vector3f,
uv: Point2f, uv: Point2f,
) -> Normal3f { ) -> Normal3f {
let mut n = Normal3f::from(dpdu.cross(*dpdv).normalize()); let mut n = Normal3f::from(dpdu.cross(*dpdv).normalize());
if let Some(normals) = data.n { if let Some(normals) = patch_normals {
// Apply interpolated shading normal to orient the geometric normal // Apply interpolated shading normal to orient the geometric normal
let ns = lerp( let ns = lerp(
uv[0], uv[0],
lerp(uv[1], normals[0], normals[2]), lerp(uv[1], normals[0], normals[2]),
lerp(uv[1], normals[1], normals[3]), lerp(uv[1], normals[1], normals[3]),
); );
n = n.face_forward(ns.into()); n = n.face_forward(ns);
} else if data.mesh.reverse_orientation ^ data.mesh.transform_swaps_handedness { } else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness {
n = -n; n = -n;
} }
n n
@ -511,36 +563,33 @@ impl ShapeTrait for BilinearPatchShape {
#[inline] #[inline]
fn normal_bounds(&self) -> DirectionCone { fn normal_bounds(&self) -> DirectionCone {
let data = self.get_data(); let [p00, p01, p10, p11] = self.get_points();
if data.p00 == data.p10 let normals = self.get_shading_normals();
|| data.p10 == data.p11 if p00 == p10 || p10 == p11 || p11 == p01 || p01 == p00 {
|| data.p11 == data.p01 let dpdu = lerp(0.5, p10, p11) - lerp(0.5, p00, p01);
|| data.p01 == data.p00 let dpdv = lerp(0.5, p01, p11) - lerp(0.5, p00, p10);
{
let dpdu = lerp(0.5, data.p10, data.p11) - lerp(0.5, data.p00, data.p01);
let dpdv = lerp(0.5, data.p01, data.p11) - lerp(0.5, data.p00, data.p10);
let mut n = Normal3f::from(dpdu.cross(dpdv).normalize()); let mut n = Normal3f::from(dpdu.cross(dpdv).normalize());
if let Some(normals) = data.n { if let Some(normals) = normals {
let interp_n = (normals[0] + normals[1] + normals[2] + normals[3]) / 4.; let interp_n = (normals[0] + normals[1] + normals[2] + normals[3]) / 4.;
n = n.face_forward(interp_n.into()); n = n.face_forward(interp_n);
} else if data.mesh.reverse_orientation ^ data.mesh.transform_swaps_handedness { } else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness {
n *= -1.; n *= -1.;
} }
return DirectionCone::new_from_vector(Vector3f::from(n)); return DirectionCone::new_from_vector(Vector3f::from(n));
} }
// Compute bilinear patch normals n10, n01, and n11 // Compute bilinear patch normals n10, n01, and n11
let mut n00 = Normal3f::from((data.p10 - data.p00).cross(data.p01 - data.p00).normalize()); let mut n00 = Normal3f::from((p10 - p00).cross(p01 - p00).normalize());
let mut n10 = Normal3f::from((data.p11 - data.p10).cross(data.p00 - data.p10).normalize()); let mut n10 = Normal3f::from((p11 - p10).cross(p00 - p10).normalize());
let mut n01 = Normal3f::from((data.p00 - data.p01).cross(data.p11 - data.p01).normalize()); let mut n01 = Normal3f::from((p00 - p01).cross(p11 - p01).normalize());
let mut n11 = Normal3f::from((data.p01 - data.p11).cross(data.p10 - data.p11).normalize()); let mut n11 = Normal3f::from((p01 - p11).cross(p10 - p11).normalize());
if let Some(normals) = data.n { if let Some(normals) = normals {
n00 = n00.face_forward(normals[0].into()); n00 = n00.face_forward(normals[0]);
n10 = n10.face_forward(normals[1].into()); n10 = n10.face_forward(normals[1]);
n01 = n01.face_forward(normals[2].into()); n01 = n01.face_forward(normals[2]);
n11 = n11.face_forward(normals[3].into()); n11 = n11.face_forward(normals[3]);
} else if data.mesh.reverse_orientation ^ data.mesh.transform_swaps_handedness { } else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness {
n00 = -n00; n00 = -n00;
n10 = -n10; n10 = -n10;
n01 = -n01; n01 = -n01;
@ -559,16 +608,17 @@ impl ShapeTrait for BilinearPatchShape {
#[inline] #[inline]
fn bounds(&self) -> Bounds3f { fn bounds(&self) -> Bounds3f {
let data = self.get_data(); let [p00, p01, p10, p11] = self.get_points();
Bounds3f::from_points(data.p00, data.p01).union(Bounds3f::from_points(data.p10, data.p11)) Bounds3f::from_points(p00, p01).union(Bounds3f::from_points(p10, p11))
} }
#[inline] #[inline]
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> { fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
let t_max_val = t_max?; let t_max_val = t_max?;
let data = self.get_data(); if let Some(bilinear_hit) =
if let Some(bilinear_hit) = self.intersect_bilinear_patch(ray, t_max_val, &data) { self.intersect_bilinear_patch(ray, t_max_val, &self.get_points())
let intr = self.interaction_from_intersection(&data, bilinear_hit.uv, ray.time, -ray.d); {
let intr = self.interaction_from_intersection(bilinear_hit.uv, ray.time, -ray.d);
Some(ShapeIntersection { Some(ShapeIntersection {
intr, intr,
@ -582,25 +632,28 @@ impl ShapeTrait for BilinearPatchShape {
#[inline] #[inline]
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool { fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
let t_max_val = t_max.unwrap_or(Float::INFINITY); let t_max_val = t_max.unwrap_or(Float::INFINITY);
let data = self.get_data(); let corners = self.get_points();
self.intersect_bilinear_patch(ray, t_max_val, &data) self.intersect_bilinear_patch(ray, t_max_val, &corners)
.is_some() .is_some()
} }
#[inline] #[inline]
fn sample(&self, u: Point2f) -> Option<ShapeSample> { fn sample(&self, u: Point2f) -> Option<ShapeSample> {
let data = self.get_data(); let corners = self.get_points();
let [p00, p01, p10, p11] = corners;
// Sample bilinear patch parametric coordinate (u, v) // Sample bilinear patch parametric coordinate (u, v)
let (uv, pdf) = self.sample_parametric_coords(&data, u); let (uv, pdf) = self.sample_parametric_coords(&corners, u);
// Compute bilinear patch geometric quantities at sampled (u, v) // Compute bilinear patch geometric quantities at sampled (u, v)
let (p, dpdu, dpdv) = self.calculate_base_derivatives(&data, uv); let (p, dpdu, dpdv) = self.calculate_base_derivatives(&corners, uv);
if dpdu.norm_squared() == 0. || dpdv.norm_squared() == 0. { if dpdu.norm_squared() == 0. || dpdv.norm_squared() == 0. {
return None; return None;
} }
// Compute surface normal for sampled bilinear patch (u, v) // Compute surface normal for sampled bilinear patch (u, v)
let n = self.compute_sampled_normal(&data, &dpdu, &dpdv, uv); let patch_normals = self.get_shading_normals();
let st = data.uv.map_or(uv, |patch_uvs| { let patch_uvs = self.get_uvs();
let n = self.compute_sampled_normal(patch_normals, &dpdu, &dpdv, uv);
let st = patch_uvs.map_or(uv, |patch_uvs| {
lerp( lerp(
uv[0], uv[0],
lerp(uv[1], patch_uvs[0], patch_uvs[1]), lerp(uv[1], patch_uvs[0], patch_uvs[1]),
@ -608,33 +661,34 @@ impl ShapeTrait for BilinearPatchShape {
) )
}); });
let p_abs_sum = data.p00.abs() let p_abs_sum = p00.abs()
+ Vector3f::from(data.p01.abs()) + Vector3f::from(p01.abs())
+ Vector3f::from(data.p10.abs()) + Vector3f::from(p10.abs())
+ Vector3f::from(data.p11.abs()); + Vector3f::from(p11.abs());
let p_error = gamma(6) * Vector3f::from(p_abs_sum); let p_error = gamma(6) * Vector3f::from(p_abs_sum);
let pi = Point3fi::new_with_error(p, p_error); let pi = Point3fi::new_with_error(p, p_error);
Some(ShapeSample { Some(ShapeSample {
intr: Arc::new(SurfaceInteraction::new_simple(pi, n, st)), intr: Interaction::Surface(SurfaceInteraction::new_simple(pi, n, st)),
pdf: pdf / dpdu.cross(dpdv).norm(), pdf: pdf / dpdu.cross(dpdv).norm(),
}) })
} }
#[inline] #[inline]
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> { fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
let data = self.get_data(); let corners = self.get_points();
let v00 = (data.p00 - ctx.p()).normalize(); let [p00, p01, p10, p11] = corners;
let v10 = (data.p10 - ctx.p()).normalize(); let v00 = (p00 - ctx.p()).normalize();
let v01 = (data.p01 - ctx.p()).normalize(); let v10 = (p10 - ctx.p()).normalize();
let v11 = (data.p11 - ctx.p()).normalize(); let v01 = (p01 - ctx.p()).normalize();
let v11 = (p11 - ctx.p()).normalize();
let use_area_sampling = self.rectangle let use_area_sampling = self.rectangle
|| data.mesh.image_distribution.is_some() || !self.mesh.image_distribution.is_null()
|| spherical_quad_area(v00, v10, v11, v01) <= Self::MIN_SPHERICAL_SAMPLE_AREA; || spherical_quad_area(v00, v10, v11, v01) <= Self::MIN_SPHERICAL_SAMPLE_AREA;
if use_area_sampling { if use_area_sampling {
self.sample_area_and_pdf(ctx, u) self.sample_area_and_pdf(ctx, u)
} else { } else {
self.sample_solid_angle(&data, ctx, u, &[v00, v10, v01, v11]) self.sample_solid_angle(&corners, ctx, u, &[v00, v10, v01, v11])
} }
} }
@ -644,28 +698,30 @@ impl ShapeTrait for BilinearPatchShape {
return 0.0; return 0.0;
}; };
let data = self.get_data(); let corners = self.get_points();
let uv = if let Some(uvs) = &data.mesh.uv { let [p00, p01, p10, p11] = corners;
Point2f::invert_bilinear(si.uv, uvs) let patch_uvs = self.get_uvs();
let uv = if let Some(uvs) = patch_uvs {
Point2f::invert_bilinear(si.common.uv, &uvs)
} else { } else {
si.uv si.common.uv
}; };
let param_pdf = if let Some(image_distrib) = &data.mesh.image_distribution { let param_pdf = if !self.mesh.image_distribution.is_null() {
image_distrib.pdf(uv) self.mesh.image_distribution.pdf(uv)
} else if self.rectangle { } else if self.rectangle {
let w = [ let w = [
(data.p10 - data.p00).cross(data.p01 - data.p00).norm(), (p10 - p00).cross(p01 - p00).norm(),
(data.p10 - data.p00).cross(data.p11 - data.p10).norm(), (p10 - p00).cross(p11 - p10).norm(),
(data.p01 - data.p00).cross(data.p11 - data.p01).norm(), (p01 - p00).cross(p11 - p01).norm(),
(data.p11 - data.p10).cross(data.p11 - data.p01).norm(), (p11 - p10).cross(p11 - p01).norm(),
]; ];
bilinear_pdf(uv, &w) bilinear_pdf(uv, &w)
} else { } else {
1. 1.
}; };
let (_, dpdu, dpdv) = self.calculate_base_derivatives(&data, uv); let (_, dpdu, dpdv) = self.calculate_base_derivatives(&corners, uv);
let cross = dpdu.cross(dpdv).norm(); let cross = dpdu.cross(dpdv).norm();
if cross == 0. { 0. } else { param_pdf / cross } if cross == 0. { 0. } else { param_pdf / cross }
} }
@ -677,15 +733,16 @@ impl ShapeTrait for BilinearPatchShape {
return 0.; return 0.;
}; };
let data = self.get_data(); let corners = self.get_points();
let [p00, p01, p10, p11] = corners;
let v00 = (data.p00 - ctx.p()).normalize(); let v00 = (p00 - ctx.p()).normalize();
let v10 = (data.p10 - ctx.p()).normalize(); let v10 = (p10 - ctx.p()).normalize();
let v01 = (data.p01 - ctx.p()).normalize(); let v01 = (p01 - ctx.p()).normalize();
let v11 = (data.p11 - ctx.p()).normalize(); let v11 = (p11 - ctx.p()).normalize();
let use_area_sampling = !self.rectangle let use_area_sampling = !self.rectangle
|| data.mesh.image_distribution.is_some() || !self.mesh.image_distribution.is_null()
|| spherical_quad_area(v00, v10, v01, v11) <= Self::MIN_SPHERICAL_SAMPLE_AREA; || spherical_quad_area(v00, v10, v01, v11) <= Self::MIN_SPHERICAL_SAMPLE_AREA;
if use_area_sampling { if use_area_sampling {
@ -709,9 +766,9 @@ impl ShapeTrait for BilinearPatchShape {
]; ];
let u = invert_spherical_rectangle_sample( let u = invert_spherical_rectangle_sample(
ctx.p(), ctx.p(),
data.p00, p00,
data.p10 - data.p00, p10 - p00,
data.p01 - data.p00, p01 - p00,
isect.intr.p(), isect.intr.p(),
); );
pdf *= bilinear_pdf(u, &w); pdf *= bilinear_pdf(u, &w);

View file

@ -1,21 +1,95 @@
use crate::core::interaction::InteractionTrait; use crate::Float;
use crate::core::geometry::{
Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
VectorLike,
};
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::shape::{ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait};
use crate::utils::math::{clamp, lerp, square}; use crate::utils::math::{clamp, lerp, square};
use crate::utils::splines::{ use crate::utils::splines::{
bound_cubic_bezier, cubic_bezier_control_points, evaluate_cubic_bezier, subdivide_cubic_bezier, bound_cubic_bezier, cubic_bezier_control_points, evaluate_cubic_bezier, subdivide_cubic_bezier,
}; };
use crate::utils::transform::look_at; use crate::utils::transform::{Transform, look_at};
use super::{ #[repr(C)]
Bounds3f, CurveCommon, CurveShape, CurveType, DirectionCone, Float, Interaction, Normal3f, #[derive(Debug, Clone, Copy, PartialEq)]
Point2f, Point3f, Point3fi, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext, pub enum CurveType {
ShapeTrait, SurfaceInteraction, Transform, Vector2f, Vector3f, VectorLike, Flat,
}; Cylinder,
use std::sync::Arc; Ribbon,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct CurveCommon {
pub curve_type: CurveType,
pub cp_obj: [Point3f; 4],
pub width: [Float; 2],
pub n: [Normal3f; 2],
pub normal_angle: Float,
pub inv_sin_normal_angle: Float,
pub render_from_object: Transform,
pub object_from_render: Transform,
pub reverse_orientation: bool,
pub transform_swap_handedness: bool,
}
impl CurveCommon {
#[allow(clippy::too_many_arguments)]
pub fn new(
c: &[Point3f],
w0: Float,
w1: Float,
curve_type: CurveType,
norm: &[Normal3f],
render_from_object: Transform,
object_from_render: Transform,
reverse_orientation: bool,
) -> Self {
let transform_swap_handedness = render_from_object.swaps_handedness();
let width = [w0, w1];
assert_eq!(c.len(), 4);
let cp_obj: [Point3f; 4] = c[..4].try_into().unwrap();
let mut n = [Normal3f::default(); 2];
let mut normal_angle: Float = 0.;
let mut inv_sin_normal_angle: Float = 0.;
if norm.len() == 2 {
n[0] = norm[0].normalize();
n[1] = norm[1].normalize();
normal_angle = n[0].angle_between(n[1]);
inv_sin_normal_angle = 1. / normal_angle.sin();
}
Self {
curve_type,
cp_obj,
width,
n,
normal_angle,
inv_sin_normal_angle,
render_from_object,
object_from_render,
reverse_orientation,
transform_swap_handedness,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct CurveShape {
pub common: CurveCommon,
pub u_min: Float,
pub u_max: Float,
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
struct IntersectionContext { struct IntersectionContext {
ray: Ray, pub ray: Ray,
object_from_ray: Arc<Transform>, pub object_from_ray: Transform,
common: CurveCommon, pub common: CurveCommon,
} }
impl CurveShape { impl CurveShape {
@ -32,7 +106,6 @@ impl CurveShape {
.common .common
.object_from_render .object_from_render
.apply_to_ray(r, &mut Some(t_max)); .apply_to_ray(r, &mut Some(t_max));
// Get object-space control points for curve segment, cpObj
let cp_obj = cubic_bezier_control_points(&self.common.cp_obj, self.u_min, self.u_max); let cp_obj = cubic_bezier_control_points(&self.common.cp_obj, self.u_min, self.u_max);
// Project curve control points to plane perpendicular to ray // Project curve control points to plane perpendicular to ray
let mut dx = ray.d.cross(cp_obj[3] - cp_obj[0]); let mut dx = ray.d.cross(cp_obj[3] - cp_obj[0]);
@ -43,7 +116,6 @@ impl CurveShape {
let ray_from_object = look_at(ray.o, ray.o + ray.d, dx).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])); let cp = [0; 4].map(|i| ray_from_object.apply_to_point(cp_obj[i]));
// Test ray against bound of projected control points
let max_width = lerp(self.u_min, self.common.width[0], self.common.width[1]).max(lerp( let max_width = lerp(self.u_min, self.common.width[0], self.common.width[1]).max(lerp(
self.u_max, self.u_max,
self.common.width[0], self.common.width[0],
@ -77,7 +149,7 @@ impl CurveShape {
let context = IntersectionContext { let context = IntersectionContext {
ray, ray,
object_from_ray: Arc::new(ray_from_object.inverse()), object_from_ray: ray_from_object.inverse(),
common: self.common.clone(), common: self.common.clone(),
}; };
@ -300,18 +372,18 @@ impl ShapeTrait for CurveShape {
} }
fn pdf(&self, _interaction: &Interaction) -> Float { fn pdf(&self, _interaction: &Interaction) -> Float {
todo!() unimplemented!()
} }
fn pdf_from_context(&self, _ctx: &ShapeSampleContext, _wi: Vector3f) -> Float { fn pdf_from_context(&self, _ctx: &ShapeSampleContext, _wi: Vector3f) -> Float {
todo!() unimplemented!()
} }
fn sample(&self, _u: Point2f) -> Option<ShapeSample> { fn sample(&self, _u: Point2f) -> Option<ShapeSample> {
todo!() unimplemented!()
} }
fn sample_from_context(&self, _ctx: &ShapeSampleContext, _u: Point2f) -> Option<ShapeSample> { fn sample_from_context(&self, _ctx: &ShapeSampleContext, _u: Point2f) -> Option<ShapeSample> {
todo!() unimplemented!()
} }
} }

View file

@ -1,20 +1,39 @@
use super::{ use crate::core::geometry::{
Bounds3f, CylinderShape, DirectionCone, Float, Interaction, Normal3f, PI, Point2f, Point3f, Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
Point3fi, QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext, Vector3fi, VectorLike,
ShapeTrait, SurfaceInteraction, Transform, Vector3f, Vector3fi,
}; };
use crate::core::geometry::{Sqrt, Tuple, VectorLike}; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::interaction::InteractionTrait; use crate::core::shape::{
use crate::core::pbrt::gamma; QuadricIntersection, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait,
};
use crate::utils::splines::{
bound_cubic_bezier, cubic_bezier_control_points, evaluate_cubic_bezier, subdivide_cubic_bezier,
};
use crate::utils::transform::{Transform, look_at};
use crate::{Float, PI, gamma};
use crate::core::geometry::{Sqrt, Tuple};
use crate::utils::interval::Interval; use crate::utils::interval::Interval;
use crate::utils::math::{difference_of_products, lerp, square}; use crate::utils::math::{clamp, difference_of_products, lerp, square};
use std::mem; use std::mem;
use std::sync::Arc;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct CylinderShape {
pub radius: Float,
pub z_min: Float,
pub z_max: Float,
pub phi_max: Float,
pub render_from_object: Transform,
pub object_from_render: Transform,
pub reverse_orientation: bool,
pub transform_swap_handedness: bool,
}
impl CylinderShape { impl CylinderShape {
pub fn new( pub fn new(
render_from_object: Arc<Transform>, render_from_object: Transform,
object_from_render: Arc<Transform>, object_from_render: Transform,
reverse_orientation: bool, reverse_orientation: bool,
radius: Float, radius: Float,
z_min: Float, z_min: Float,
@ -26,7 +45,7 @@ impl CylinderShape {
z_min, z_min,
z_max, z_max,
phi_max, phi_max,
render_from_object: render_from_object.clone(), render_from_object,
object_from_render, object_from_render,
reverse_orientation, reverse_orientation,
transform_swap_handedness: render_from_object.swaps_handedness(), transform_swap_handedness: render_from_object.swaps_handedness(),
@ -247,14 +266,14 @@ impl ShapeTrait for CylinderShape {
(p_obj.z() - self.z_min) / (self.z_max - self.z_min), (p_obj.z() - self.z_min) / (self.z_max - self.z_min),
); );
Some(ShapeSample { Some(ShapeSample {
intr: Arc::new(SurfaceInteraction::new_simple(pi, n, uv)), intr: Interaction::Surface(SurfaceInteraction::new_simple(pi, n, uv)),
pdf: 1. / self.area(), pdf: 1. / self.area(),
}) })
} }
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> { fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
let mut ss = self.sample(u)?; let mut ss = self.sample(u)?;
let intr = Arc::make_mut(&mut ss.intr); let intr = &mut ss.intr;
intr.get_common_mut().time = ctx.time; intr.get_common_mut().time = ctx.time;
let mut wi = ss.intr.p() - ctx.p(); let mut wi = ss.intr.p() - ctx.p();
if wi.norm_squared() == 0. { if wi.norm_squared() == 0. {

View file

@ -1,22 +1,38 @@
use super::{ use crate::core::geometry::{
Bounds3f, DirectionCone, DiskShape, Float, Interaction, Normal3f, PI, Point2f, Point3f, Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
Point3fi, QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext, Vector3fi, VectorLike,
ShapeTrait, SurfaceInteraction, Transform, Vector3f,
}; };
use crate::core::geometry::VectorLike; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::interaction::InteractionTrait; use crate::core::shape::{
QuadricIntersection, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait,
};
use crate::utils::Transform;
use crate::utils::math::square; use crate::utils::math::square;
use crate::utils::sampling::sample_uniform_disk_concentric; use crate::utils::sampling::sample_uniform_disk_concentric;
use crate::{Float, PI};
use std::sync::Arc; use std::sync::Arc;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct DiskShape {
pub radius: Float,
pub inner_radius: Float,
pub height: Float,
pub phi_max: Float,
pub render_from_object: Transform,
pub object_from_render: Transform,
pub reverse_orientation: bool,
pub transform_swap_handedness: bool,
}
impl DiskShape { impl DiskShape {
pub fn new( pub fn new(
radius: Float, radius: Float,
inner_radius: Float, inner_radius: Float,
height: Float, height: Float,
phi_max: Float, phi_max: Float,
render_from_object: Arc<Transform>, render_from_object: Transform,
object_from_render: Arc<Transform>, object_from_render: Transform,
reverse_orientation: bool, reverse_orientation: bool,
) -> Self { ) -> Self {
Self { Self {
@ -157,8 +173,9 @@ impl ShapeTrait for DiskShape {
phi / self.phi_max, phi / self.phi_max,
(self.radius - radius_sample) / (self.radius - self.inner_radius), (self.radius - radius_sample) / (self.radius - self.inner_radius),
); );
Some(ShapeSample { Some(ShapeSample {
intr: Arc::new(SurfaceInteraction::new_simple(pi, n, uv)), intr: Interaction::Surface(SurfaceInteraction::new_simple(pi, n, uv)),
pdf: 1. / self.area(), pdf: 1. / self.area(),
}) })
} }
@ -173,17 +190,18 @@ impl ShapeTrait for DiskShape {
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> { fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
let mut ss = self.sample(u)?; let mut ss = self.sample(u)?;
let intr = Arc::make_mut(&mut ss.intr); ss.intr.get_common_mut().time = ctx.time;
intr.get_common_mut().time = ctx.time;
let mut wi = ss.intr.p() - ctx.p(); let mut wi = ss.intr.p() - ctx.p();
if wi.norm_squared() == 0. { if wi.norm_squared() == 0. {
return None; return None;
} }
wi = wi.normalize(); wi = wi.normalize();
ss.pdf = Vector3f::from(ss.intr.n()).dot(-wi).abs() / ctx.p().distance_squared(ss.intr.p()); ss.pdf = Vector3f::from(ss.intr.n()).dot(-wi).abs() / ctx.p().distance_squared(ss.intr.p());
if ss.pdf.is_infinite() { if ss.pdf.is_infinite() {
return None; return None;
} }
Some(ss) Some(ss)
} }

View file

@ -5,315 +5,9 @@ pub mod disk;
pub mod sphere; pub mod sphere;
pub mod triangle; pub mod triangle;
use crate::core::geometry::{ pub use bilinear::*;
Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f, pub use curves::*;
Vector3fi, VectorLike, pub use cylinder::*;
}; pub use disk::*;
use crate::core::interaction::{ pub use sphere::*;
Interaction, InteractionTrait, MediumInteraction, SurfaceInteraction, pub use triangle::*;
};
use crate::core::material::Material;
use crate::core::medium::{Medium, MediumInterface};
use crate::core::pbrt::{Float, PI};
use crate::lights::Light;
use crate::utils::math::{next_float_down, next_float_up};
use crate::utils::transform::Transform;
use enum_dispatch::enum_dispatch;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone)]
pub struct SphereShape {
radius: Float,
z_min: Float,
z_max: Float,
theta_z_min: Float,
theta_z_max: Float,
phi_max: Float,
render_from_object: Arc<Transform>,
object_from_render: Arc<Transform>,
reverse_orientation: bool,
transform_swap_handedness: bool,
}
impl Default for SphereShape {
fn default() -> Self {
Self::new(
Transform::default().into(),
Transform::default().into(),
false,
1.0,
-1.0,
1.0,
360.0,
)
}
}
#[derive(Debug, Clone)]
pub struct CylinderShape {
radius: Float,
z_min: Float,
z_max: Float,
phi_max: Float,
render_from_object: Arc<Transform>,
object_from_render: Arc<Transform>,
reverse_orientation: bool,
transform_swap_handedness: bool,
}
#[derive(Debug, Clone)]
pub struct DiskShape {
radius: Float,
inner_radius: Float,
height: Float,
phi_max: Float,
render_from_object: Arc<Transform>,
object_from_render: Arc<Transform>,
reverse_orientation: bool,
transform_swap_handedness: bool,
}
#[derive(Debug, Clone)]
pub struct TriangleShape {
pub mesh_ind: usize,
pub tri_index: usize,
}
#[derive(Debug, Clone)]
pub struct BilinearPatchShape {
mesh_index: usize,
blp_index: usize,
area: Float,
rectangle: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CurveType {
Flat,
Cylinder,
Ribbon,
}
#[derive(Debug, Clone)]
pub struct CurveCommon {
curve_type: CurveType,
cp_obj: [Point3f; 4],
width: [Float; 2],
n: [Normal3f; 2],
normal_angle: Float,
inv_sin_normal_angle: Float,
render_from_object: Arc<Transform>,
object_from_render: Arc<Transform>,
reverse_orientation: bool,
transform_swap_handedness: bool,
}
impl CurveCommon {
#[allow(clippy::too_many_arguments)]
pub fn new(
c: &[Point3f],
w0: Float,
w1: Float,
curve_type: CurveType,
norm: &[Vector3f],
render_from_object: Arc<Transform>,
object_from_render: Arc<Transform>,
reverse_orientation: bool,
) -> Self {
let transform_swap_handedness = render_from_object.swaps_handedness();
let width = [w0, w1];
assert_eq!(c.len(), 4);
let cp_obj: [Point3f; 4] = c[..4].try_into().unwrap();
let mut n = [Normal3f::default(); 2];
let mut normal_angle: Float = 0.;
let mut inv_sin_normal_angle: Float = 0.;
if norm.len() == 2 {
n[0] = norm[0].normalize().into();
n[1] = norm[1].normalize().into();
normal_angle = n[0].angle_between(n[1]);
inv_sin_normal_angle = 1. / normal_angle.sin();
}
Self {
curve_type,
cp_obj,
width,
n,
normal_angle,
inv_sin_normal_angle,
render_from_object,
object_from_render,
reverse_orientation,
transform_swap_handedness,
}
}
}
#[derive(Debug, Clone)]
pub struct CurveShape {
common: CurveCommon,
u_min: Float,
u_max: Float,
}
// Define Intersection objects. This only varies for
#[derive(Debug, Clone)]
pub struct ShapeIntersection {
pub intr: SurfaceInteraction,
pub t_hit: Float,
}
impl ShapeIntersection {
pub fn new(intr: SurfaceInteraction, t_hit: Float) -> Self {
Self { intr, t_hit }
}
pub fn t_hit(&self) -> Float {
self.t_hit
}
pub fn set_t_hit(&mut self, new_t: Float) {
self.t_hit = new_t;
}
pub fn set_intersection_properties(
&mut self,
mtl: Arc<Material>,
area: Arc<Light>,
prim_medium_interface: Option<MediumInterface>,
ray_medium: Option<Arc<Medium>>,
) {
let ray_medium = ray_medium.expect("Ray medium must be defined for intersection");
self.intr
.set_intersection_properties(mtl, area, prim_medium_interface, ray_medium);
}
}
#[derive(Debug, Clone)]
pub struct QuadricIntersection {
t_hit: Float,
p_obj: Point3f,
phi: Float,
}
impl QuadricIntersection {
pub fn new(t_hit: Float, p_obj: Point3f, phi: Float) -> Self {
Self { t_hit, p_obj, phi }
}
}
#[derive(Debug, Clone, Copy)]
pub struct TriangleIntersection {
b0: Float,
b1: Float,
b2: Float,
t: Float,
}
impl TriangleIntersection {
pub fn new(b0: Float, b1: Float, b2: Float, t: Float) -> Self {
Self { b0, b1, b2, t }
}
}
#[derive(Debug, Clone)]
pub struct BilinearIntersection {
uv: Point2f,
t: Float,
}
impl BilinearIntersection {
pub fn new(uv: Point2f, t: Float) -> Self {
Self { uv, t }
}
}
#[derive(Clone)]
pub struct ShapeSample {
pub intr: Arc<SurfaceInteraction>,
pub pdf: Float,
}
#[derive(Clone, Debug)]
pub struct ShapeSampleContext {
pub pi: Point3fi,
pub n: Normal3f,
pub ns: Normal3f,
pub time: Float,
}
impl ShapeSampleContext {
pub fn new(pi: Point3fi, n: Normal3f, ns: Normal3f, time: Float) -> Self {
Self { pi, n, ns, time }
}
pub fn new_from_interaction(si: &SurfaceInteraction) -> Self {
Self {
pi: si.pi(),
n: si.n(),
ns: si.shading.n,
time: si.time(),
}
}
pub fn p(&self) -> Point3f {
Point3f::from(self.pi)
}
pub fn offset_ray_origin(&self, w: Vector3f) -> Point3f {
let d = self.n.abs().dot(self.pi.error().into());
let mut offset = d * Vector3f::from(self.n);
if w.dot(self.n.into()) < 0.0 {
offset = -offset;
}
let mut po = Point3f::from(self.pi) + offset;
for i in 0..3 {
if offset[i] > 0.0 {
po[i] = next_float_up(po[i]);
} else {
po[i] = next_float_down(po[i]);
}
}
po
}
pub fn offset_ray_origin_from_point(&self, pt: Point3f) -> Point3f {
self.offset_ray_origin(pt - self.p())
}
pub fn spawn_ray(&self, w: Vector3f) -> Ray {
Ray::new(self.offset_ray_origin(w), w, Some(self.time), None)
}
}
#[enum_dispatch]
pub trait ShapeTrait {
fn bounds(&self) -> Bounds3f;
fn normal_bounds(&self) -> DirectionCone;
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection>;
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool;
fn area(&self) -> Float;
fn pdf(&self, interaction: &Interaction) -> Float;
fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float;
fn sample(&self, u: Point2f) -> Option<ShapeSample>;
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample>;
}
#[derive(Debug, Clone)]
#[enum_dispatch(ShapeTrait)]
pub enum Shape {
Sphere(SphereShape),
Cylinder(CylinderShape),
Disk(DiskShape),
Triangle(TriangleShape),
BilinearPatch(BilinearPatchShape),
Curve(CurveShape),
}
impl Default for Shape {
fn default() -> Self {
Shape::Sphere(SphereShape::default())
}
}

View file

@ -1,22 +1,55 @@
use super::{ use crate::core::geometry::{
Bounds3f, DirectionCone, Float, Interaction, Normal3f, PI, Point2f, Point3f, Point3fi, Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait, Vector3fi, VectorLike,
SphereShape, SurfaceInteraction, Transform, Vector3f, Vector3fi,
}; };
use crate::core::geometry::{Frame, Sqrt, VectorLike, spherical_direction}; use crate::core::geometry::{Frame, Sqrt, spherical_direction};
use crate::core::interaction::InteractionTrait; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::pbrt::gamma; use crate::core::pbrt::gamma;
use crate::core::shape::{
QuadricIntersection, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait,
};
use crate::utils::Transform;
use crate::utils::interval::Interval; use crate::utils::interval::Interval;
use crate::utils::math::{clamp, 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 crate::utils::sampling::sample_uniform_sphere;
use crate::{Float, PI};
use std::mem; use std::mem;
use std::sync::Arc; use std::sync::Arc;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct SphereShape {
pub radius: Float,
pub z_min: Float,
pub z_max: Float,
pub theta_z_min: Float,
pub theta_z_max: Float,
pub phi_max: Float,
pub render_from_object: Transform,
pub object_from_render: Transform,
pub reverse_orientation: bool,
pub transform_swap_handedness: bool,
}
impl Default for SphereShape {
fn default() -> Self {
Self::new(
Transform::default().into(),
Transform::default().into(),
false,
1.0,
-1.0,
1.0,
360.0,
)
}
}
impl SphereShape { impl SphereShape {
pub fn new( pub fn new(
render_from_object: Arc<Transform>, render_from_object: Transform,
object_from_render: Arc<Transform>, object_from_render: Transform,
reverse_orientation: bool, reverse_orientation: bool,
radius: Float, radius: Float,
z_min: Float, z_min: Float,
@ -287,7 +320,7 @@ impl ShapeTrait for SphereShape {
)); ));
let si = SurfaceInteraction::new_simple(pi, n, uv); let si = SurfaceInteraction::new_simple(pi, n, uv);
Some(ShapeSample { Some(ShapeSample {
intr: Arc::new(si), intr: Interaction::Surface(si),
pdf: 1. / self.area(), pdf: 1. / self.area(),
}) })
} }
@ -297,8 +330,7 @@ impl ShapeTrait for SphereShape {
let p_origin = ctx.offset_ray_origin_from_point(p_center); let p_origin = ctx.offset_ray_origin_from_point(p_center);
if p_origin.distance_squared(p_center) <= square(self.radius) { if p_origin.distance_squared(p_center) <= square(self.radius) {
let mut ss = self.sample(u)?; let mut ss = self.sample(u)?;
let intr = Arc::make_mut(&mut ss.intr); ss.intr.get_common_mut().time = ctx.time;
intr.get_common_mut().time = ctx.time;
let mut wi = ss.intr.p() - ctx.p(); let mut wi = ss.intr.p() - ctx.p();
if wi.norm_squared() == 0. { if wi.norm_squared() == 0. {
return None; return None;
@ -349,9 +381,9 @@ impl ShapeTrait for SphereShape {
(theta - self.theta_z_min) / (self.theta_z_max - self.theta_z_min), (theta - self.theta_z_min) / (self.theta_z_max - self.theta_z_min),
); );
let pi = Point3fi::new_with_error(p_obj, p_error); let pi = Point3fi::new_with_error(p_obj, p_error);
let si = SurfaceInteraction::new_simple(pi, n, uv); let intr = SurfaceInteraction::new_simple(pi, n, uv);
Some(ShapeSample { Some(ShapeSample {
intr: Arc::new(si), intr: Interaction::Surface(intr),
pdf: 1. / (2. * PI * one_minus_cos_theta_max), pdf: 1. / (2. * PI * one_minus_cos_theta_max),
}) })
} }

View file

@ -1,80 +1,127 @@
use super::{ use crate::Float;
Bounds3f, DirectionCone, Float, Interaction, Normal3f, Point2f, Point3f, Point3fi, Ray, use crate::core::geometry::{
ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait, SurfaceInteraction, Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3,
TriangleIntersection, TriangleShape, Vector2f, Vector3f, Vector3f,
}; };
use crate::core::geometry::{Sqrt, Tuple, VectorLike, spherical_triangle_area}; use crate::core::geometry::{Sqrt, Tuple, VectorLike, spherical_triangle_area};
use crate::core::interaction::InteractionTrait; use crate::core::interaction::{
Interaction, InteractionBase, InteractionTrait, SimpleInteraction, SurfaceInteraction,
};
use crate::core::pbrt::gamma; use crate::core::pbrt::gamma;
use crate::core::shape::{ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait};
use crate::utils::Ptr;
use crate::utils::math::{difference_of_products, square}; use crate::utils::math::{difference_of_products, square};
use crate::utils::mesh::TriangleMesh; use crate::utils::mesh::DeviceTriangleMesh;
use crate::utils::sampling::{ use crate::utils::sampling::{
bilinear_pdf, invert_spherical_triangle_sample, sample_bilinear, sample_spherical_triangle, bilinear_pdf, invert_spherical_triangle_sample, sample_bilinear, sample_spherical_triangle,
sample_uniform_triangle, sample_uniform_triangle,
}; };
use std::mem;
use std::sync::{Arc, OnceLock};
pub static TRIANGLE_MESHES: OnceLock<Vec<Arc<TriangleMesh>>> = OnceLock::new(); #[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct TriangleIntersection {
b0: Float,
b1: Float,
b2: Float,
t: Float,
}
#[derive(Clone, Copy)] impl TriangleIntersection {
struct TriangleData { pub fn new(b0: Float, b1: Float, b2: Float, t: Float) -> Self {
vertices: [Point3f; 3], Self { b0, b1, b2, t }
uvs: [Point2f; 3], }
normals: Option<[Normal3f; 3]>, }
area: Float,
normal: Normal3f, #[repr(C)]
reverse_orientation: bool, #[derive(Clone, Copy, Debug)]
transform_swaps_handedness: bool, pub struct TriangleShape {
pub mesh: Ptr<DeviceTriangleMesh>,
pub tri_index: i32,
} }
impl TriangleShape { impl TriangleShape {
pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 3e-4; pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 3e-4;
pub const MAX_SPHERICAL_SAMPLE_AREA: Float = 6.22; pub const MAX_SPHERICAL_SAMPLE_AREA: Float = 6.22;
fn mesh(&self) -> &Arc<TriangleMesh> { #[inline(always)]
let meshes = TRIANGLE_MESHES fn get_vertex_indices(&self) -> [usize; 3] {
.get() unsafe {
.expect("Mesh has not been initialized"); let base_ptr = self.mesh.vertex_indices.add((self.tri_index as usize) * 3);
&meshes[self.mesh_ind]
}
fn get_data(&self) -> TriangleData {
let mesh = self.mesh();
let start = 3 * self.tri_index;
let indices = &mesh.vertex_indices[start..start + 3];
let vertices = [mesh.p[indices[0]], mesh.p[indices[1]], mesh.p[indices[2]]];
let uvs = mesh.uv.as_ref().map_or(
[ [
Point2f::zero(), *base_ptr.add(0) as usize,
Point2f::new(1.0, 0.0), *base_ptr.add(1) as usize,
Point2f::new(1.0, 1.0), *base_ptr.add(2) as usize,
], ]
|uv| [uv[indices[0]], uv[indices[1]], uv[indices[2]]],
);
let normals = mesh
.n
.as_ref()
.map(|n| [n[indices[0]], n[indices[1]], n[indices[2]]]);
let dp1 = vertices[1] - vertices[0];
let dp2 = vertices[2] - vertices[0];
let normal = Normal3f::from(dp1.cross(dp2).normalize());
let area = 0.5 * dp1.cross(dp2).norm();
TriangleData {
vertices,
uvs,
normals,
area,
normal,
reverse_orientation: mesh.reverse_orientation,
transform_swaps_handedness: mesh.transform_swaps_handedness,
} }
} }
fn solid_angle(&self, p: Point3f) -> Float { #[inline(always)]
let data = self.get_data(); fn get_points(&self) -> [Point3f; 3] {
let [p0, p1, p2] = data.vertices; let [v0, v1, v2] = self.get_vertex_indices();
unsafe {
[
*self.mesh.p.add(v0),
*self.mesh.p.add(v1),
*self.mesh.p.add(v2),
]
}
}
#[inline(always)]
fn get_uvs(&self) -> Option<[Point2f; 3]> {
if self.mesh.uv.is_null() {
return None;
}
let [v0, v1, v2] = self.get_vertex_indices();
unsafe {
Some([
*self.mesh.uv.add(v0),
*self.mesh.uv.add(v1),
*self.mesh.uv.add(v2),
])
}
}
#[inline(always)]
fn get_tangents(&self) -> Option<[Vector3f; 3]> {
if self.mesh.s.is_null() {
return None;
}
let [v0, v1, v2] = self.get_vertex_indices();
unsafe {
Some([
*self.mesh.s.add(v0),
*self.mesh.s.add(v1),
*self.mesh.s.add(v2),
])
}
}
#[inline(always)]
fn get_shading_normals(&self) -> Option<[Normal3f; 3]> {
if self.mesh.n.is_null() {
return None;
}
let [v0, v1, v2] = self.get_vertex_indices();
unsafe {
Some([
*self.mesh.n.add(v0),
*self.mesh.n.add(v1),
*self.mesh.n.add(v2),
])
}
}
pub fn new(mesh: Ptr<DeviceTriangleMesh>, tri_index: i32) -> Self {
Self { mesh, tri_index }
}
pub fn get_mesh(&self) -> Ptr<DeviceTriangleMesh> {
self.mesh
}
pub fn solid_angle(&self, p: Point3f) -> Float {
let [p0, p1, p2] = self.get_points();
spherical_triangle_area( spherical_triangle_area(
(p0 - p).normalize(), (p0 - p).normalize(),
(p1 - p).normalize(), (p1 - p).normalize(),
@ -82,100 +129,15 @@ impl TriangleShape {
) )
} }
fn intersect_triangle(&self, ray: &Ray, t_max: Float) -> Option<TriangleIntersection> { fn intersect_triangle(
let data = self.get_data(); &self,
let [p0, p1, p2] = data.vertices; _ray: &Ray,
if (p2 - p0).cross(p1 - p0).norm_squared() == 0. { _t_max: Float,
return None; _p0: Point3f,
} _p1: Point3f,
let mut p0t = p0 - Vector3f::from(ray.o); _p2: Point3f,
let mut p1t = p1 - Vector3f::from(ray.o); ) -> Option<TriangleIntersection> {
let mut p2t = p2 - Vector3f::from(ray.o); todo!()
let kz = ray.d.abs().max_component_index();
let kx = if kz == 3 { 0 } else { kz + 1 };
let ky = if kz == 3 { 0 } else { kx + 1 };
let d = ray.d.permute([kx, ky, kz]);
p0t = p0t.permute([kx, ky, kz]);
p1t = p1t.permute([kx, ky, kz]);
p2t = p2t.permute([kx, ky, kz]);
// Apply shear transformation to translated vertex positions
let sx = -d.x() / d.z();
let sy = -d.y() / d.z();
let sz = 1. / d.z();
p0t[0] += sx * p0t.z();
p0t[1] += sy * p0t.z();
p1t[0] += sx * p1t.z();
p1t[1] += sy * p1t.z();
p2t[0] += sx * p2t.z();
p2t[0] += sy * p2t.z();
// Compute edge function coefficients e0, e1, and e2
let mut e0 = difference_of_products(p1t.x(), p2t.y(), p1t.y(), p2t.x());
let mut e1 = difference_of_products(p2t.x(), p0t.y(), p2t.y(), p0t.x());
let mut e2 = difference_of_products(p0t.x(), p1t.y(), p0t.y(), p1t.x());
// if mem::size_of::<Float>() == mem::size_of::<f32>() && (e0 == 0.0 || e1 == 0.0 || e2 == 0.0)
if e0 == 0.0 || e1 == 0.0 || e2 == 0.0 {
let [p0t64, p1t64, p2t64] = [p0t.cast::<f64>(), p1t.cast::<f64>(), p2t.cast::<f64>()];
e0 = (p2t64.y() * p1t64.x() - p2t64.x() * p1t64.y()) as Float;
e1 = (p0t64.y() * p2t64.x() - p0t64.x() * p2t64.y()) as Float;
e2 = (p1t64.y() * p0t64.x() - p1t64.x() * p0t64.y()) as Float;
}
if (e0 < 0. || e1 < 0. || e2 < 0.) && (e0 > 0. || e1 > 0. || e2 > 0.) {
return None;
}
let det = e0 + e1 + e2;
if det == 0. {
return None;
}
// Compute scaled hit distance to triangle and test against ray
p0t[2] *= sz;
p1t[2] *= sz;
p2t[2] *= sz;
let t_scaled = e0 * p0t.z() + e1 * p1t.z() + e2 * p2t.z();
if det < 0. && (t_scaled >= 0. || t_scaled < t_max * det)
|| (det > 0. && (t_scaled <= 0. || t_scaled > t_max * det))
{
return None;
}
// Compute barycentric coordinates and value for triangle intersection
let inv_det = 1. / det;
let b0 = e0 * inv_det;
let b1 = e1 * inv_det;
let b2 = e2 * inv_det;
let t = t_scaled * inv_det;
// Ensure that computed triangle is conservatively greater than zero
let max_z_t = Vector3f::new(p0t.z(), p1t.z(), p2t.z())
.abs()
.max_component_value();
let delta_z = gamma(3) * max_z_t;
let max_x_t = Vector3f::new(p0t.x(), p1t.x(), p2t.x())
.abs()
.max_component_value();
let max_y_t = Vector3f::new(p0t.y(), p1t.y(), p2t.y())
.abs()
.max_component_value();
let delta_x = gamma(5) * (max_x_t + max_z_t);
let delta_y = gamma(5) * (max_y_t + max_z_t);
let delta_e = 2. * (gamma(2) * max_x_t * max_y_t + delta_y * max_x_t + delta_x * max_y_t);
let max_e = Vector3f::new(e0, e1, e2).abs().max_component_value();
let delta_t =
3. * (gamma(3) * max_e * max_z_t + delta_e * max_z_t + delta_z * max_e) * inv_det.abs();
if t <= delta_t {
return None;
}
Some(TriangleIntersection::new(b0, b1, b2, t))
} }
fn interaction_from_intersection( fn interaction_from_intersection(
@ -184,233 +146,255 @@ impl TriangleShape {
time: Float, time: Float,
wo: Vector3f, wo: Vector3f,
) -> SurfaceInteraction { ) -> SurfaceInteraction {
let data = self.get_data(); let [p0, p1, p2] = self.get_points();
let [p0, p1, p2] = data.vertices; let uv = self.get_uvs().unwrap_or([
let [uv0, uv1, uv2] = data.uvs; Point2f::new(0.0, 0.0),
// Compute triangle partial derivatives Point2f::new(1.0, 0.0),
let (dpdu, dpdv, degenerate_uv, det) = self.compute_partials(data); Point2f::new(1.0, 1.0),
// Interpolate (u, v) parametric coordinates and hit point ]);
let p_hit_vec = let duv02 = uv[0] - uv[2];
ti.b0 * Vector3f::from(p0) + ti.b1 * Vector3f::from(p1) + ti.b2 * Vector3f::from(p2); let duv12 = uv[1] - uv[2];
let p_hit = Point3f::from(p_hit_vec); let dp02 = p0 - p2;
let uv_hit_vec = let dp12 = p1 - p2;
ti.b0 * Vector2f::from(uv0) + ti.b1 * Vector2f::from(uv1) + ti.b2 * Vector2f::from(uv2); let determinant = difference_of_products(duv02[0], duv12[1], duv02[1], duv12[0]);
let uv_hit = Point2f::from(uv_hit_vec); let degenerate = determinant.abs() < 1e-9;
let (mut dpdu, mut dpdv) = if !degenerate {
let invdet = 1. / determinant;
let ret0 = difference_of_products(duv12[1], dp02, duv02[1], dp12) * invdet;
let ret1 = difference_of_products(duv02[0], dp12, duv12[0], dp02) * invdet;
(ret0, ret1)
} else {
(Vector3f::zero(), Vector3f::zero())
};
// Return SurfaceInteraction for triangle hit> if degenerate || dpdu.cross(dpdv).norm_squared() == 0. {
let flip_normal = data.reverse_orientation ^ data.transform_swaps_handedness; let mut ng = (p2 - p0).cross(p1 - p0);
let p_abs_sum = (ti.b0 * Vector3f::from(p0)).abs() if ng.norm_squared() == 0. {
+ (ti.b1 * Vector3f::from(p1)).abs() let v1 = p2 - p0;
+ (ti.b2 * Vector3f::from(p2)).abs(); let v2 = p1 - p0;
ng = v1.cast::<f64>().cross(v2.cast::<f64>()).cast::<Float>();
assert!(ng.norm_squared() != 0.);
}
(dpdu, dpdv) = ng.normalize().coordinate_system();
}
let p0_vec = Vector3f::from(p0);
let p1_vec = Vector3f::from(p1);
let p2_vec = Vector3f::from(p2);
let p_hit = Point3f::from(ti.b0 * p0_vec + ti.b1 * p1_vec + ti.b2 * p2_vec);
let uv_hit = Point2f::from(
ti.b0 * Vector2f::from(uv[0])
+ ti.b1 * Vector2f::from(uv[1])
+ ti.b2 * Vector2f::from(uv[2]),
);
let p_abs_sum = (ti.b0 * p0_vec).abs() + (ti.b1 * p1_vec).abs() + (ti.b2 * p2_vec).abs();
let p_error = gamma(7) * p_abs_sum; let p_error = gamma(7) * p_abs_sum;
let mut ng = Normal3f::from(dp02.cross(dp12).normalize());
let flip_normal = self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness;
if flip_normal {
ng = -ng;
}
let mut isect = SurfaceInteraction::new( let mut isect = SurfaceInteraction::new(
Point3fi::new_with_error(p_hit, p_error), Point3fi::new_with_error(p_hit, p_error),
uv_hit, uv_hit,
wo, wo,
dpdu, dpdu,
dpdv, dpdv,
Normal3f::default(), Normal3f::zero(),
Normal3f::default(), Normal3f::zero(),
time, time,
flip_normal, flip_normal,
); );
isect.face_index = self isect.face_index = if !self.mesh.face_indices.is_null() {
.mesh() unsafe { *self.mesh.face_indices.add(self.tri_index as usize) }
.face_indices } else {
.as_ref() 0
.map_or(0, |fi| fi[self.tri_index]); };
isect.common.n = data.normal;
isect.shading.n = isect.n();
if flip_normal {
isect.common.n = -isect.n();
isect.shading.n = -isect.shading.n;
}
if data.normals.is_some() || self.mesh().s.is_some() { isect.common.n = ng;
self.apply_shading_normals(&mut isect, ti, data, degenerate_uv, det); isect.shading.n = ng;
}
if !self.mesh.p.is_null() || !self.mesh.s.is_null() {
self.compute_shading_geometry(&mut isect, &ti, uv, dpdu, determinant, degenerate);
}
isect isect
} }
fn compute_partials(&self, data: TriangleData) -> (Vector3f, Vector3f, bool, Float) { fn compute_shading_geometry(
let [p0, p1, p2] = data.vertices;
let [uv0, uv1, uv2] = data.uvs;
let duv02 = uv0 - uv2;
let duv12 = uv1 - uv2;
let dp02 = p0 - p2;
let dp12 = p1 - p2;
let det = difference_of_products(duv02[0], duv12[1], duv02[1], duv12[0]);
let degenerate_uv = det.abs() < 1e-9;
let (dpdu, dpdv) = if !degenerate_uv {
let inv_det = 1. / det;
(
(dp02 * duv12[1] - dp12 * duv02[1]) * inv_det,
(dp12 * duv02[0] - dp02 * duv12[0]) * inv_det,
)
} else {
let dp20 = p2 - p0;
let dp10 = p1 - p0;
let mut ng = dp20.cross(dp10);
if ng.norm_squared() == 0. {
ng = (dp20.cast::<f64>().cross(dp10.cast::<f64>())).cast();
}
let n = ng.normalize();
n.coordinate_system()
};
(dpdu, dpdv, degenerate_uv, det)
}
fn apply_shading_normals(
&self, &self,
isect: &mut SurfaceInteraction, isect: &mut SurfaceInteraction,
ti: TriangleIntersection, ti: &TriangleIntersection,
data: TriangleData, uv: [Point2f; 3],
dpdu_geom: Vector3f,
determinant: Float,
degenerate_uv: bool, degenerate_uv: bool,
det: Float,
) { ) {
let Some([n0, n1, n2]) = data.normals else { // Interpolate vertex normals if they exist
return; let ns = if let Some(normals) = self.get_shading_normals() {
}; let n = ti.b0 * normals[0] + ti.b1 * normals[1] + ti.b2 * normals[2];
let [uv0, uv1, uv2] = data.uvs; if n.norm_squared() > 0.0 {
let duv02 = uv0 - uv2; n.normalize()
let duv12 = uv1 - uv2; } else {
isect.n()
let ns = ti.b0 * n0 + ti.b1 * n1 + ti.b2 * n2; }
let ns = if ns.norm_squared() > 0. {
ns.normalize()
} else { } else {
isect.n() isect.n()
}; };
let mut ss = self.mesh().s.as_ref().map_or(isect.dpdu, |s| { // Interpolate tangents if they exist
let indices = &self.mesh().vertex_indices[3 * self.tri_index..3 * self.tri_index + 3]; let mut ss = if let Some(tangents) = self.get_tangents() {
let interp_s = ti.b0 * s[indices[0]] + ti.b1 * s[indices[1]] + ti.b2 * s[indices[2]]; let s = ti.b0 * tangents[0] + ti.b1 * tangents[1] + ti.b2 * tangents[2];
if s.norm_squared() > 0.0 {
if interp_s.norm_squared() > 0. { s.normalize()
interp_s
} else { } else {
isect.dpdu dpdu_geom
}
});
let mut ts = Vector3f::from(ns).cross(ss);
if ts.norm_squared() > 0. {
ss = ts.cross(Vector3f::from(ns));
} else {
(ss, ts) = Vector3f::from(ns).coordinate_system();
}
let (dndu, dndv) = if degenerate_uv {
let dn = (n2 - n0).cross(n1 - n0);
if dn.norm_squared() == 0. {
(Normal3f::zero(), Normal3f::zero())
} else {
dn.coordinate_system()
} }
} else { } else {
let inv_det = 1. / det; dpdu_geom
let dn02 = n0 - n2;
let dn12 = n1 - n2;
(
(dn02 * duv12[1] - dn12 * duv02[1]) * inv_det,
(dn12 * duv02[0] - dn02 * duv12[0]) * inv_det,
)
}; };
isect.shading.n = ns; // Ensure shading tangent (ss) is perpendicular to shading normal (ns)
isect.shading.dpdu = ss; let mut ts = ns.cross(ss.into());
isect.shading.dpdv = ts; if ts.norm_squared() > 0.0 {
isect.dndu = dndu; ss = ts.cross(ns.into()).into();
isect.dndv = dndv; } else {
let (s, t) = ns.coordinate_system();
ss = s.into();
ts = t.into();
}
// How does the normal change as we move across UVs?
let (dndu, dndv) = if let Some(normals) = self.get_shading_normals() {
if degenerate_uv {
let dn = (normals[2] - normals[0]).cross(normals[1] - normals[0]);
if dn.norm_squared() == 0.0 {
(Normal3f::zero(), Normal3f::zero())
} else {
let (dnu, dnv) = dn.coordinate_system();
(Normal3f::from(dnu), Normal3f::from(dnv))
}
} else {
let dn1 = normals[0] - normals[2];
let dn2 = normals[1] - normals[2];
let duv02 = uv[0] - uv[2];
let duv12 = uv[1] - uv[2];
let inv_det = 1.0 / determinant;
(
difference_of_products(duv12[1], dn1, duv02[1], dn2) * inv_det,
difference_of_products(duv02[0], dn2, duv12[0], dn1) * inv_det,
)
}
} else {
(Normal3f::zero(), Normal3f::zero())
};
isect.set_shading_geom(ns, ss, ts.into(), dndu, dndv, true);
} }
} }
impl ShapeTrait for TriangleShape { impl ShapeTrait for TriangleShape {
fn bounds(&self) -> Bounds3f { fn bounds(&self) -> Bounds3f {
let [p0, p1, p2] = self.get_data().vertices; let [p0, p1, p2] = self.get_points();
Bounds3f::from_points(p0, p1).union_point(p2) Bounds3f::from_points(p0, p1).union_point(p2)
} }
fn normal_bounds(&self) -> DirectionCone { fn normal_bounds(&self) -> DirectionCone {
let data = self.get_data(); let [p0, p1, p2] = self.get_points();
let mut n = data.normal; let mut n: Normal3f = (p1 - p0).cross(p2 - p0).normalize().into();
if let Some([n0, n1, n2]) = data.normals {
n = n.face_forward((n0 + n1 + n2).into()); if let Some(normals) = self.get_shading_normals() {
} else if data.reverse_orientation ^ data.transform_swaps_handedness { let [n0, n1, n2] = normals;
let ns = n0 + n1 + n2;
n = n.face_forward(ns);
} else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness {
n = -n; n = -n;
} }
DirectionCone::new_from_vector(Vector3f::from(n))
DirectionCone::new_from_vector(n.into())
} }
fn area(&self) -> Float { fn area(&self) -> Float {
self.get_data().area let [p0, p1, p2] = self.get_points();
0.5 * (p1 - p0).cross(p2 - p0).norm()
} }
fn pdf(&self, _interaction: &Interaction) -> Float { fn sample(&self, u: Point2f) -> Option<ShapeSample> {
1. / self.area() let [p0, p1, p2] = self.get_points();
let b = sample_uniform_triangle(u);
let p = p0 + b[1] * (p1 - p0) + b[2] * (p2 - p0);
let mut n: Normal3f = (p1 - p0).cross(p2 - p0).normalize().into();
if let Some(normals) = self.get_shading_normals() {
let [n0, n1, n2] = normals;
let ns = b[0] * n0 + b[1] * n1 + b[2] * n2; // b[2] is (1 - b0 - b1)
n = n.face_forward(ns);
} else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness {
n = -n;
}
let uv_sample = if let Some(uvs) = self.get_uvs() {
let [uv0, uv1, uv2] = uvs;
uv0 + b[1] * (uv1 - uv0) + b[2] * (uv2 - uv0)
} else {
let v = b[0] * Vector2f::new(0.0, 0.0)
+ b[1] * Vector2f::new(1.0, 0.0)
+ b[2] * Vector2f::new(1.0, 1.0);
Point2f::from(v)
};
let p0_v = Vector3f::from(p0);
let p1_v = Vector3f::from(p1);
let p2_v = Vector3f::from(p2);
let p_abs_sum = (b[0] * p0_v).abs() + (b[1] * p1_v).abs() + (b[2] * p2_v).abs();
let p_error = Vector3f::from(p_abs_sum) * gamma(6);
let intr_base = InteractionBase::new_surface_geom(
Point3fi::new_with_error(p, p_error),
n,
uv_sample,
Vector3f::default(),
0.,
);
Some(ShapeSample {
intr: Interaction::Simple(SimpleInteraction::new(intr_base)),
pdf: 1.0 / self.area(),
})
}
fn sample_from_context(&self, ctx: &ShapeSampleContext, mut u: Point2f) -> Option<ShapeSample> {
let [p0, p1, p2] = self.get_points();
let (b, tri_pdf) = sample_spherical_triangle(&[p0, p1, p2], ctx.p(), u)?;
if tri_pdf == 0. {
return None;
} }
fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float {
let solid_angle = self.solid_angle(ctx.p()); let solid_angle = self.solid_angle(ctx.p());
if (Self::MIN_SPHERICAL_SAMPLE_AREA..Self::MAX_SPHERICAL_SAMPLE_AREA).contains(&solid_angle) if solid_angle < Self::MIN_SPHERICAL_SAMPLE_AREA
|| solid_angle > Self::MAX_SPHERICAL_SAMPLE_AREA
{ {
let ray = ctx.spawn_ray(wi); let mut ss = self.sample(u)?;
return self.intersect(&ray, None).map_or(0., |isect| { ss.intr.get_common_mut().time = ctx.time;
let absdot = Vector3f::from(isect.intr.n()).dot(-wi).abs(); let mut wi: Normal3f = (ss.intr.p() - ctx.p()).into();
let d2 = ctx.p().distance_squared(isect.intr.p());
let pdf = 1. / self.area() * (d2 / absdot);
if pdf.is_infinite() { 0. } else { pdf }
});
}
let mut pdf = 1. / solid_angle;
if ctx.ns != Normal3f::zero() {
let [p0, p1, p2] = self.get_data().vertices;
let u = invert_spherical_triangle_sample(&[p0, p1, p2], ctx.p(), wi)
.unwrap_or(Point2f::zero());
let rp = ctx.p();
let wi: [Vector3f; 3] = [
(p0 - rp).normalize(),
(p1 - rp).normalize(),
(p2 - rp).normalize(),
];
let w: [Float; 4] = [
0.01_f32.max(ctx.ns.dot(wi[1].into()).abs()),
0.01_f32.max(ctx.ns.dot(wi[1].into()).abs()),
0.01_f32.max(ctx.ns.dot(wi[0].into()).abs()),
0.01_f32.max(ctx.ns.dot(wi[2].into()).abs()),
];
pdf *= bilinear_pdf(u, &w);
}
pdf
}
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
let data = self.get_data();
let [p0, p1, p2] = data.vertices;
let solid_angle = self.solid_angle(ctx.p());
if (Self::MIN_SPHERICAL_SAMPLE_AREA..Self::MAX_SPHERICAL_SAMPLE_AREA).contains(&solid_angle)
{
// Sample shape by area and compute incident direction wi
return self.sample(u).and_then(|mut ss| {
let mut intr_clone = (*ss.intr).clone();
intr_clone.common.time = ctx.time;
ss.intr = Arc::new(intr_clone);
let wi = (ss.intr.p() - ctx.p()).normalize();
if wi.norm_squared() == 0. { if wi.norm_squared() == 0. {
return None; return None;
} }
let absdot = Vector3f::from(ss.intr.n()).abs_dot(-wi); wi = wi.normalize();
let d2 = ctx.p().distance_squared(ss.intr.p()); ss.pdf /= ss.intr.n().abs_dot(-wi) / ctx.p().distance_squared(ss.intr.p());
ss.pdf /= absdot / d2; if ss.pdf.is_infinite() {
if ss.pdf.is_infinite() { None } else { Some(ss) } return None;
}); }
return Some(ss);
} }
// Sample spherical triangle from reference point
let mut pdf = 1.; let mut pdf = 1.;
if ctx.ns != Normal3f::zero() { if ctx.ns != Normal3f::zero() {
let rp = ctx.p(); let rp = ctx.p();
@ -420,93 +404,112 @@ impl ShapeTrait for TriangleShape {
(p2 - rp).normalize(), (p2 - rp).normalize(),
]; ];
let w: [Float; 4] = [ let w: [Float; 4] = [
0.01_f32.max(ctx.ns.dot(wi[1].into()).abs()), ctx.ns.abs_dot(wi[1].into()).max(0.01),
0.01_f32.max(ctx.ns.dot(wi[1].into()).abs()), ctx.ns.abs_dot(wi[1].into()).max(0.01),
0.01_f32.max(ctx.ns.dot(wi[0].into()).abs()), ctx.ns.abs_dot(wi[0].into()).max(0.01),
0.01_f32.max(ctx.ns.dot(wi[2].into()).abs()), ctx.ns.abs_dot(wi[2].into()).max(0.01),
]; ];
u = sample_bilinear(u, &w);
let u = sample_bilinear(u, &w);
pdf = bilinear_pdf(u, &w); pdf = bilinear_pdf(u, &w);
} }
let (b, tri_pdf) = sample_spherical_triangle(&[p0, p1, p2], ctx.p(), u)?; let p0_v = Vector3f::from(p0);
if tri_pdf == 0. { let p1_v = Vector3f::from(p1);
return None; let p2_v = Vector3f::from(p2);
} let p_abs_sum = (b[0] * p0_v).abs() + (b[1] * p1_v).abs() + (b[2] * p2_v).abs();
pdf *= tri_pdf; let mut n: Normal3f = (p1 - p0).cross(p2 - p0).normalize().into();
let b2 = 1. - b[0] - b[1];
let p_abs_sum = b[0] * Vector3f::from(p0) if let Some(normals) = self.get_shading_normals() {
+ b[1] * Vector3f::from(p1) let [n0, n1, n2] = normals;
+ (1. - b[0] - b[1]) * Vector3f::from(p2); let ns = b[0] * n0 + b[1] * n1 + (1. - b[0] - b[1]) * n2;
let p_error = gamma(6) * p_abs_sum; n = n.face_forward(ns);
// Return ShapeSample for solid angle sampled point on triangle } else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness {
let p_vec =
b[0] * Vector3f::from(p0) + b[1] * Vector3f::from(p1) + b[2] * Vector3f::from(p2);
let p = Point3f::from(p_vec);
let mut n = Normal3f::from((p1 - p0).cross(p2 - p0).normalize());
if let Some([n0, n1, n2]) = data.normals {
let ns = b[0] * n0 + b[1] * n1 + b2 * n2;
n = n.face_forward(ns.into());
} else if data.reverse_orientation ^ data.transform_swaps_handedness {
n = -n; n = -n;
} }
let [uv0, uv1, uv2] = data.uvs; let uv_sample = if let Some(uvs) = self.get_uvs() {
let uv_sample_vec = let [uv0, uv1, uv2] = uvs;
b[0] * Vector2f::from(uv0) + b[1] * Vector2f::from(uv1) + b[2] * Vector2f::from(uv2); uv0 + b[1] * (uv1 - uv0) + b[2] * (uv2 - uv0)
let uv_sample = Point2f::from(uv_sample_vec); } else {
let pi = Point3fi::new_with_error(p, p_error); let v = b[0] * Vector2f::new(0.0, 0.0)
let mut si = SurfaceInteraction::new_simple(pi, n, uv_sample); + b[1] * Vector2f::new(1.0, 0.0)
si.common.time = ctx.time; + b[2] * Vector2f::new(1.0, 1.0);
Point2f::from(v)
};
let p = p0 + b[1] * (p1 - p0) + b[2] * (p2 - p0);
let p_error = Vector3f::from(p_abs_sum) * gamma(6);
let intr_base = InteractionBase::new_surface_geom(
Point3fi::new_with_error(p, p_error),
n,
uv_sample,
Vector3f::default(),
0.,
);
Some(ShapeSample { Some(ShapeSample {
intr: Arc::new(si), intr: Interaction::Simple(SimpleInteraction::new(intr_base)),
pdf, pdf,
}) })
} }
fn sample(&self, u: Point2f) -> Option<ShapeSample> {
let data = self.get_data();
let [p0, p1, p2] = data.vertices;
let [uv0, uv1, uv2] = data.uvs;
let b = sample_uniform_triangle(u);
let p_vec =
b[0] * Vector3f::from(p0) + b[1] * Vector3f::from(p1) + b[2] * Vector3f::from(p2);
let b2 = 1. - b[0] - b[1];
let p = Point3f::from(p_vec);
let mut n = data.normal;
if let Some([n0, n1, n2]) = data.normals {
let interp_n = b[0] * n0 + b[1] * n1 + b2 * n2;
n = n.face_forward(interp_n.into());
} else if data.reverse_orientation ^ data.transform_swaps_handedness {
n = -n;
}
let uv_sample_vec =
b[0] * Vector2f::from(uv0) + b[1] * Vector2f::from(uv1) + b[2] * Vector2f::from(uv2);
let uv_sample = Point2f::from(uv_sample_vec);
let p_abs_sum = (b[0] * Vector3f::from(p0)).abs()
+ (b[1] * Vector3f::from(p1)).abs()
+ ((1. - b[0] - b[1]) * Vector3f::from(p2)).abs();
let p_error = gamma(6) * p_abs_sum;
let pi = Point3fi::new_with_error(p, p_error);
Some(ShapeSample {
intr: Arc::new(SurfaceInteraction::new_simple(pi, n, uv_sample)),
pdf: 1. / self.area(),
})
}
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> { fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
self.intersect_triangle(ray, t_max.unwrap_or(Float::INFINITY)) let [p0, p1, p2] = self.get_points();
.map(|ti| { let tri_isect = self.intersect_triangle(ray, t_max.unwrap_or(0.), p0, p1, p2)?;
let intr = self.interaction_from_intersection(ti, ray.time, -ray.d); let intr = self.interaction_from_intersection(tri_isect, ray.time, -ray.d);
ShapeIntersection { intr, t_hit: ti.t } Some(ShapeIntersection::new(intr, tri_isect.t))
})
} }
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool { fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
self.intersect_triangle(ray, t_max.unwrap_or(Float::INFINITY)) let [p0, p1, p2] = self.get_points();
.is_some() let tri_isect = self.intersect_triangle(ray, t_max.unwrap_or(0.), p0, p1, p2);
tri_isect.is_some()
}
fn pdf(&self, _interaction: &Interaction) -> Float {
1. / self.area()
}
fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float {
let solid_angle = self.solid_angle(ctx.p());
if solid_angle < Self::MIN_SPHERICAL_SAMPLE_AREA
|| solid_angle > Self::MAX_SPHERICAL_SAMPLE_AREA
{
let ray = ctx.spawn_ray(wi);
let Some(isect) = self.intersect(&ray, None) else {
return 0.;
};
let pdf = (1. / self.area())
/ (isect.intr.n().abs_dot(-Normal3f::from(wi))
/ ctx.p().distance_squared(isect.intr.p()));
if pdf.is_infinite() {
return 0.;
}
return pdf;
}
let mut pdf = 1. / solid_angle;
if ctx.ns != Normal3f::zero() {
let [p0, p1, p2] = self.get_points();
let u = invert_spherical_triangle_sample(&[p0, p1, p2], ctx.p(), wi)
.expect("Could not calculate inverse sample");
let rp = ctx.p();
let wi = [
(p0 - rp).normalize(),
(p1 - rp).normalize(),
(p2 - rp).normalize(),
];
let w: [Float; 4] = [
ctx.ns.abs_dot(wi[1].into()).max(0.01),
ctx.ns.abs_dot(wi[1].into()).max(0.01),
ctx.ns.abs_dot(wi[0].into()).max(0.01),
ctx.ns.abs_dot(wi[2].into()).max(0.01),
];
pdf *= bilinear_pdf(u, &w);
}
pdf
} }
} }

View file

@ -1462,7 +1462,7 @@ pub const CIE_D65: [Float; 95] = [
N!(115.392), N!(115.392),
N!(115.923), N!(115.923),
N!(112.367), N!(112.367),
N(108.811), N!(108.811),
N!(109.082), N!(109.082),
N!(109.354), N!(109.354),
N!(108.578), N!(108.578),

View file

@ -3,31 +3,73 @@ use crate::core::geometry::Point2f;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum}; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum};
use crate::utils::math::SquareMatrix3f; use crate::utils::math::SquareMatrix3f;
use crate::utils::ptr::Ptr;
use once_cell::sync::Lazy; use anyhow::{Result, anyhow};
use std::cmp::{Eq, PartialEq}; use std::cmp::{Eq, PartialEq};
use std::error::Error;
use std::sync::Arc;
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone)] #[derive(Copy, Debug, Clone)]
pub struct StandardColorSpaces { pub struct DeviceStandardColorSpaces {
pub srgb: *const RGBColorSpace, pub srgb: Ptr<RGBColorSpace>,
pub dci_p3: *const RGBColorSpace, pub dci_p3: Ptr<RGBColorSpace>,
pub rec2020: *const RGBColorSpace, pub rec2020: Ptr<RGBColorSpace>,
pub aces2065_1: *const RGBColorSpace, pub aces2065_1: Ptr<RGBColorSpace>,
}
impl DeviceStandardColorSpaces {
#[cfg(not(target_arch = "nvptx64"))]
pub fn get_named(&self, name: &str) -> Result<Ptr<RGBColorSpace>> {
match name.to_lowercase().as_str() {
"srgb" => Ok(self.srgb),
"dci-p3" => Ok(self.dci_p3),
"rec2020" => Ok(self.rec2020),
"aces2065-1" => Ok(self.aces2065_1),
_ => Err(anyhow!("No such spectrum")),
}
}
pub fn get_by_id(&self, id: ColorSpaceId) -> Ptr<RGBColorSpace> {
match id {
ColorSpaceId::SRGB => self.srgb,
ColorSpaceId::DciP3 => self.dci_p3,
ColorSpaceId::Rec2020 => self.rec2020,
ColorSpaceId::Aces2065_1 => self.aces2065_1,
}
}
}
#[repr(u8)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ColorSpaceId {
SRGB = 0,
DciP3 = 1,
Rec2020 = 2,
Aces2065_1 = 3,
}
impl ColorSpaceId {
#[cfg(not(target_arch = "nvptx64"))]
pub fn from_name(name: &str) -> Option<Self> {
match name.to_lowercase().as_str() {
"srgb" => Some(Self::SRGB),
"dci-p3" => Some(Self::DciP3),
"rec2020" => Some(Self::Rec2020),
"aces2065-1" => Some(Self::Aces2065_1),
_ => None,
}
}
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub struct RGBColorSpace { pub struct RGBColorSpace {
pub r: Point2f, pub r: Point2f,
pub g: Point2f, pub g: Point2f,
pub b: Point2f, pub b: Point2f,
pub w: Point2f, pub w: Point2f,
pub illuminant: DenselySampledSpectrum, pub illuminant: DenselySampledSpectrum,
pub rgb_to_spectrum_table: *const RGBToSpectrumTable, pub rgb_to_spectrum_table: Ptr<RGBToSpectrumTable>,
pub xyz_from_rgb: SquareMatrix3f, pub xyz_from_rgb: SquareMatrix3f,
pub rgb_from_xyz: SquareMatrix3f, pub rgb_from_xyz: SquareMatrix3f,
} }
@ -45,7 +87,7 @@ impl RGBColorSpace {
} }
pub fn to_rgb_coeffs(&self, rgb: RGB) -> RGBSigmoidPolynomial { pub fn to_rgb_coeffs(&self, rgb: RGB) -> RGBSigmoidPolynomial {
self.rgb_to_spectrum_table.to_polynomial(rgb) self.rgb_to_spectrum_table.evaluate(rgb)
} }
pub fn convert_colorspace(&self, other: &RGBColorSpace) -> SquareMatrix3f { pub fn convert_colorspace(&self, other: &RGBColorSpace) -> SquareMatrix3f {
@ -55,6 +97,14 @@ impl RGBColorSpace {
self.rgb_from_xyz * other.xyz_from_rgb self.rgb_from_xyz * other.xyz_from_rgb
} }
pub fn luminance_vector(&self) -> RGB {
RGB::new(
self.xyz_from_rgb[1][0],
self.xyz_from_rgb[1][1],
self.xyz_from_rgb[1][2],
)
}
} }
impl PartialEq for RGBColorSpace { impl PartialEq for RGBColorSpace {

View file

@ -6,7 +6,7 @@ pub mod simple;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
pub use colorspace::RGBColorSpace; pub use colorspace::{DeviceStandardColorSpaces, RGBColorSpace};
pub use rgb::*; pub use rgb::*;
pub use sampled::{CIE_Y_INTEGRAL, LAMBDA_MAX, LAMBDA_MIN}; pub use sampled::{CIE_Y_INTEGRAL, LAMBDA_MAX, LAMBDA_MIN};
pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths}; pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};

View file

@ -4,6 +4,7 @@ use super::{
}; };
use crate::core::color::{RGB, RGBSigmoidPolynomial, XYZ}; use crate::core::color::{RGB, RGBSigmoidPolynomial, XYZ};
use crate::core::spectrum::SpectrumTrait; use crate::core::spectrum::SpectrumTrait;
use crate::utils::Ptr;
use crate::Float; use crate::Float;
@ -68,13 +69,12 @@ impl SpectrumTrait for UnboundedRGBSpectrum {
pub struct RGBIlluminantSpectrum { pub struct RGBIlluminantSpectrum {
pub scale: Float, pub scale: Float,
pub rsp: RGBSigmoidPolynomial, pub rsp: RGBSigmoidPolynomial,
pub illuminant: DenselySampledSpectrum, pub illuminant: Ptr<DenselySampledSpectrum>,
} }
impl RGBIlluminantSpectrum { impl RGBIlluminantSpectrum {
pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self { pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self {
let illuminant = &cs.illuminant; let illuminant = cs.illuminant;
let densely_sampled = DenselySampledSpectrum::from_spectrum(illuminant);
let m = rgb.max_component_value(); let m = rgb.max_component_value();
let scale = 2. * m; let scale = 2. * m;
let rsp = cs.to_rgb_coeffs(if scale == 1. { let rsp = cs.to_rgb_coeffs(if scale == 1. {
@ -85,33 +85,31 @@ impl RGBIlluminantSpectrum {
Self { Self {
scale, scale,
rsp, rsp,
illuminant, illuminant: Ptr::from(&illuminant),
} }
} }
} }
impl SpectrumTrait for RGBIlluminantSpectrum { impl SpectrumTrait for RGBIlluminantSpectrum {
fn evaluate(&self, lambda: Float) -> Float { fn evaluate(&self, lambda: Float) -> Float {
match &self.illuminant { if self.illuminant.is_null() {
Some(illuminant) => { return 0.;
self.scale * self.rsp.evaluate(lambda) * illuminant.evaluate(lambda)
}
None => 0.0,
} }
self.scale * self.rsp.evaluate(lambda) * self.illuminant.evaluate(lambda)
} }
fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum { fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
if self.illuminant.is_none() { if self.illuminant.is_null() {
return SampledSpectrum::new(0.); return SampledSpectrum::new(0.);
} }
SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i])) SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i]))
} }
fn max_value(&self) -> Float { fn max_value(&self) -> Float {
match &self.illuminant { if self.illuminant.is_null() {
Some(illuminant) => self.scale * self.rsp.max_value() * illuminant.max_value(), return 0.;
None => 0.0,
} }
self.scale * self.rsp.max_value() * self.illuminant.max_value()
} }
} }

View file

@ -1,5 +1,5 @@
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::spectrum::StandardSpectra; use crate::core::spectrum::{SpectrumTrait, StandardSpectra};
use crate::utils::math::{clamp, lerp}; use crate::utils::math::{clamp, lerp};
use std::ops::{ use std::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
@ -32,6 +32,10 @@ impl SampledSpectrum {
} }
} }
pub fn zero() -> Self {
Self::default()
}
#[inline(always)] #[inline(always)]
pub fn from_fn<F>(cb: F) -> Self pub fn from_fn<F>(cb: F) -> Self
where where
@ -118,7 +122,7 @@ impl SampledSpectrum {
} }
pub fn y(&self, lambda: &SampledWavelengths, std: &StandardSpectra) -> Float { pub fn y(&self, lambda: &SampledWavelengths, std: &StandardSpectra) -> Float {
let ys = std.cie_y().sample(lambda); let ys = std.y.sample(lambda);
let pdf = lambda.pdf(); let pdf = lambda.pdf();
SampledSpectrum::safe_div(&(ys * *self), &pdf).average() / CIE_Y_INTEGRAL SampledSpectrum::safe_div(&(ys * *self), &pdf).average() / CIE_Y_INTEGRAL
} }
@ -305,7 +309,7 @@ pub struct SampledWavelengths {
impl SampledWavelengths { impl SampledWavelengths {
pub fn pdf(&self) -> SampledSpectrum { pub fn pdf(&self) -> SampledSpectrum {
SampledSpectrum::from_vector(self.pdf.to_vec()) SampledSpectrum::from_array(&self.pdf)
} }
pub fn secondary_terminated(&self) -> bool { pub fn secondary_terminated(&self) -> bool {

View file

@ -3,6 +3,8 @@ use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
use crate::Float; use crate::Float;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths}; use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};
use crate::utils::find_interval;
use crate::utils::ptr::Ptr;
use core::slice; use core::slice;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::sync::LazyLock; use std::sync::LazyLock;
@ -34,7 +36,7 @@ impl SpectrumTrait for ConstantSpectrum {
pub struct DenselySampledSpectrum { pub struct DenselySampledSpectrum {
pub lambda_min: i32, pub lambda_min: i32,
pub lambda_max: i32, pub lambda_max: i32,
pub values: *const Float, pub values: Ptr<Float>,
} }
unsafe impl Send for DenselySampledSpectrum {} unsafe impl Send for DenselySampledSpectrum {}
@ -42,148 +44,155 @@ unsafe impl Sync for DenselySampledSpectrum {}
impl DenselySampledSpectrum { impl DenselySampledSpectrum {
#[inline(always)] #[inline(always)]
fn as_slice(&self) -> &[Float] { pub fn count(&self) -> usize {
if self.values.is_null() { if self.values.is_null() {
return &[]; 0
}
let len = (self.lambda_max - self.lambda_min + 1).max(0) as usize;
unsafe { slice::from_raw_parts(self.values, len) }
}
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 i32 - self.lambda_min;
let len = (self.lambda_max - self.lambda_min + 1) as i32;
if offset < 0 || offset >= len {
s[i] = 0.0;
} else { } else {
unsafe { s[i] = *self.values.add(offset as usize) }; (self.lambda_max - self.lambda_min + 1) as usize
} }
} }
s
}
pub fn min_component_value(&self) -> Float { #[inline(always)]
self.as_slice() fn get(&self, idx: u32) -> Float {
.iter() unsafe { *self.values.add(idx as usize) }
.fold(Float::INFINITY, |a, &b| a.min(b))
}
pub fn max_component_value(&self) -> Float {
self.as_slice()
.iter()
.fold(Float::NEG_INFINITY, |a, &b| a.max(b))
}
pub fn average(&self) -> Float {
let slice = self.as_slice();
if slice.is_empty() {
return 0.0;
}
slice.iter().sum::<Float>() / (slice.len() 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
} }
} }
impl PartialEq for DenselySampledSpectrum { impl PartialEq for DenselySampledSpectrum {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
if self.lambda_min != other.lambda_min self.lambda_min == other.lambda_min
|| self.lambda_max != other.lambda_max && self.lambda_max == other.lambda_max
|| self.values.len() != other.values.len() && self.values == other.values
{
return false;
}
self.values
.iter()
.zip(&other.values)
.all(|(a, b)| a.to_bits() == b.to_bits())
} }
} }
impl Eq for DenselySampledSpectrum {} impl Eq for DenselySampledSpectrum {}
impl Hash for DenselySampledSpectrum { // impl Hash for DenselySampledSpectrum {
fn hash<H: Hasher>(&self, state: &mut H) { // fn hash<H: Hasher>(&self, state: &mut H) {
self.lambda_min.hash(state); // self.lambda_min.hash(state);
self.lambda_max.hash(state); // self.lambda_max.hash(state);
//
for v in &self.values { // for v in &self.values {
v.to_bits().hash(state); // v.to_bits().hash(state);
} // }
} // }
} // }
impl SpectrumTrait for DenselySampledSpectrum { impl SpectrumTrait for DenselySampledSpectrum {
fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut s = SampledSpectrum::default();
let n = self.count() as i32;
for i in 0..N_SPECTRUM_SAMPLES {
let offset = lambda[i].round() as i32 - self.lambda_min;
if offset < 0 || offset >= n {
s[i] = 0.0;
} else {
unsafe {
s[i] = *self.values.add(offset as usize);
}
}
}
s
}
fn evaluate(&self, lambda: Float) -> Float { fn evaluate(&self, lambda: Float) -> Float {
let offset = (lambda.round() as i32) - self.lambda_min; let offset = (lambda.round() as i32) - self.lambda_min;
if offset < 0 || offset as usize >= self.values.len() { let n = self.count() as i32;
if offset < 0 || offset >= n {
0.0 0.0
} else { } else {
self.values[offset as usize] unsafe { *self.values.add(offset as usize) }
} }
} }
fn max_value(&self) -> Float { fn max_value(&self) -> Float {
self.values.iter().fold(Float::MIN, |a, b| a.max(*b)) if self.values.is_null() {
return 0.;
}
let n = self.count();
let mut max_val = Float::NEG_INFINITY;
for i in 0..n {
unsafe {
let val = *self.values.add(i);
if val > max_val {
max_val = val;
}
}
}
max_val
} }
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct PiecewiseLinearSpectrum { pub struct PiecewiseLinearSpectrum {
pub lambdas: *const Float, pub lambdas: Ptr<Float>,
pub values: *const Float, pub values: Ptr<Float>,
pub count: u32, pub count: u32,
} }
impl PiecewiseLinearSpectrum {
#[inline(always)]
fn lambda(&self, i: u32) -> Float {
unsafe { *self.lambdas.add(i as usize) }
}
#[inline(always)]
fn value(&self, i: u32) -> Float {
unsafe { *self.values.add(i as usize) }
}
}
unsafe impl Send for PiecewiseLinearSpectrum {} unsafe impl Send for PiecewiseLinearSpectrum {}
unsafe impl Sync for PiecewiseLinearSpectrum {} unsafe impl Sync for PiecewiseLinearSpectrum {}
impl SpectrumTrait for PiecewiseLinearSpectrum { impl SpectrumTrait for PiecewiseLinearSpectrum {
fn evaluate(&self, lambda: Float) -> Float { fn evaluate(&self, lambda: Float) -> Float {
if self.lambdas.is_empty() { if self.lambdas.is_null() {
return 0.0; return 0.0;
} }
if lambda <= self.lambdas[0] { if lambda <= self.lambda(0) {
return self.values[0]; return self.value(0);
} }
if lambda >= *self.lambdas.last().unwrap() { if lambda >= self.lambda(self.count - 1) {
return *self.values.last().unwrap(); return self.value(self.count - 1);
} }
let i = self.lambdas.partition_point(|&l| l < lambda); let i = find_interval(self.count, |idx| self.lambda(idx) <= lambda);
let l0 = self.lambdas[i - 1];
let l1 = self.lambdas[i]; let l0 = self.lambda(i);
let v0 = self.values[i - 1]; let l1 = self.lambda(i + 1);
let v1 = self.values[i]; let v0 = self.value(i);
let v1 = self.value(i + 1);
let t = (lambda - l0) / (l1 - l0); let t = (lambda - l0) / (l1 - l0);
v0 + t * (v1 - v0) v0 + t * (v1 - v0)
} }
fn max_value(&self) -> Float { fn max_value(&self) -> Float {
if self.values.is_empty() { if self.values.is_null() {
return 0.0; return 0.;
} }
self.values.iter().fold(0.0, |acc, &v| acc.max(v))
let n = self.count;
let mut max_val = Float::NEG_INFINITY;
for i in 0..n {
unsafe {
let val = *self.values.add(i as usize);
if val > max_val {
max_val = val;
}
}
}
max_val
} }
} }

View file

@ -1,14 +1,18 @@
use crate::Float;
use crate::core::spectrum::Spectrum;
use crate::core::spectrum::SpectrumTrait;
use crate::core::texture::{TextureEvalContext, TextureMapping2D}; use crate::core::texture::{TextureEvalContext, TextureMapping2D};
use crate::spectra::{SampledSpectrum, SampledWavelengths, SpectrumTrait}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Transform; use crate::utils::{Ptr, Transform};
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct FloatBilerpTexture { pub struct FloatBilerpTexture {
mapping: TextureMapping2D, pub mapping: TextureMapping2D,
v00: Float, pub v00: Float,
v01: Float, pub v01: Float,
v10: Float, pub v10: Float,
v11: Float, pub v11: Float,
} }
#[inline(always)] #[inline(always)]
@ -40,22 +44,23 @@ impl FloatBilerpTexture {
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct SpectrumBilerpTexture { pub struct SpectrumBilerpTexture {
pub mapping: TextureMapping2D, pub mapping: TextureMapping2D,
pub v00: Spectrum, pub v00: Ptr<Spectrum>,
pub v01: Spectrum, pub v01: Ptr<Spectrum>,
pub v10: Spectrum, pub v10: Ptr<Spectrum>,
pub v11: Spectrum, pub v11: Ptr<Spectrum>,
} }
impl SpectrumBilerpTexture { impl SpectrumBilerpTexture {
pub fn new( pub fn new(
mapping: TextureMapping2D, mapping: TextureMapping2D,
v00: Spectrum, v00: Ptr<Spectrum>,
v01: Spectrum, v01: Ptr<Spectrum>,
v10: Spectrum, v10: Ptr<Spectrum>,
v11: Spectrum, v11: Ptr<Spectrum>,
) -> Self { ) -> Self {
Self { Self {
mapping, mapping,
@ -69,16 +74,16 @@ impl SpectrumBilerpTexture {
pub fn evaluate( pub fn evaluate(
&self, &self,
ctx: &TextureEvalContext, ctx: &TextureEvalContext,
_lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum {
let c = self.mapping.map(ctx); let c = self.mapping.map(ctx);
bilerp( bilerp(
[c.st[0], c.st[1], c.st[2]], [c.st[0], c.st[1]],
[ [
v00.sample(lambda), self.v00.sample(lambda),
v01.sample(lambda), self.v01.sample(lambda),
v10.sample(lambda), self.v10.sample(lambda),
v11.sample(lambda), self.v11.sample(lambda),
], ],
) )
} }

View file

@ -1,29 +1,105 @@
use crate::core::texture::TextureEvalContext; use crate::Float;
use crate::core::texture::{
GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext, TextureMapping2D, TextureMapping3D,
TextureMapping3DTrait,
};
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::{Ptr, math::square};
// TODO: I have to implement somethign like a TaggedPointer, and change the whole codebase. fn checkerboard(
// Fantastic ctx: &TextureEvalContext,
#[derive(Debug, Clone)] map2d: Ptr<TextureMapping2D>,
map3d: Ptr<TextureMapping3D>,
) -> Float {
let d = |x: Float| -> Float {
let y = x / 2. - (x / 2.).floor() - 0.5;
return x / 2. + y * (1. - 2. * y.abs());
};
let bf = |x: Float, r: Float| -> Float {
if (x.floor() - r) == (x + r).floor() {
return 1. - 2. * (x.floor() as i32 & 1) as Float;
}
(d(x + r) - 2. * d(x) + d(x - r)) / square(r)
};
if !map2d.is_null() {
assert!(map3d.is_null());
let c = map2d.map(&ctx);
let ds = 1.5 * c.dsdx.abs().max(c.dsdy.abs());
let dt = 1.5 * c.dtdx.abs().max(c.dtdy.abs());
// Integrate product of 2D checkerboard function and triangle filter
0.5 - bf(c.st[0], ds) * bf(c.st[1], dt) / 2.
} else {
assert!(!map3d.is_null());
let c = map3d.map(&ctx);
let dx = 1.5 * c.dpdx.x().abs().max(c.dpdy.x().abs());
let dy = 1.5 * c.dpdx.y().abs().max(c.dpdy.y().abs());
let dz = 1.5 * c.dpdx.z().abs().max(c.dpdy.z().abs());
0.5 - bf(c.p.x(), dx) * bf(c.p.y(), dy) * bf(c.p.z(), dz)
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct FloatCheckerboardTexture { pub struct FloatCheckerboardTexture {
pub map_2d: TextureMapping2D, pub map2d: Ptr<TextureMapping2D>,
pub map_3d: TextureMapping3D, pub map3d: Ptr<TextureMapping3D>,
pub tex: [FloatTexture; 2], pub tex: [Ptr<GPUFloatTexture>; 2],
} }
impl FloatCheckerboardTexture { impl FloatCheckerboardTexture {
pub fn evaluate(&self, _ctx: &TextureEvalContext) -> Float { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
todo!() let w = checkerboard(&ctx, self.map2d, self.map3d);
let mut t0 = 0.0;
let mut t1 = 0.0;
if w != 1.0 {
if let Some(tex) = self.tex[0].get() {
t0 = tex.evaluate(ctx);
} }
} }
#[derive(Clone, Debug)] if w != 0.0 {
pub struct SpectrumCheckerboardTexture; if let Some(tex) = self.tex[1].get() {
t1 = tex.evaluate(ctx);
}
}
(1.0 - w) * t0 + w * t1
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct SpectrumCheckerboardTexture {
pub map2d: Ptr<TextureMapping2D>,
pub map3d: Ptr<TextureMapping3D>,
pub tex: [Ptr<GPUSpectrumTexture>; 2],
}
impl SpectrumCheckerboardTexture { impl SpectrumCheckerboardTexture {
pub fn evaluate( pub fn evaluate(
&self, &self,
_ctx: &TextureEvalContext, ctx: &TextureEvalContext,
_lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum {
todo!() let w = checkerboard(ctx, self.map2d, self.map3d);
let mut t0 = SampledSpectrum::new(0.);
let mut t1 = SampledSpectrum::new(0.);
if w != 1.0 {
if let Some(tex) = self.tex[0].get() {
t0 = tex.evaluate(ctx, lambda);
}
}
if w != 0.0 {
if let Some(tex) = self.tex[1].get() {
t1 = tex.evaluate(ctx, lambda);
}
}
t0 * (1.0 - w) + t1 * w
} }
} }

View file

@ -1,8 +1,10 @@
use crate::Float; use crate::Float;
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::TextureEvalContext; use crate::core::texture::TextureEvalContext;
use crate::spectra::{SampledSpectrum, SampledWavelengths, Spectrum}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct FloatConstantTexture { pub struct FloatConstantTexture {
pub value: Float, pub value: Float,
} }
@ -17,7 +19,8 @@ impl FloatConstantTexture {
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct SpectrumConstantTexture { pub struct SpectrumConstantTexture {
pub value: Spectrum, pub value: Spectrum,
} }

View file

@ -1,19 +1,80 @@
#[derive(Debug, Clone)] use crate::Float;
pub struct FloatDotsTexture; use crate::core::geometry::{Point2f, VectorLike};
use crate::core::texture::{
GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext, TextureMapping2D,
};
use crate::spectra::sampled::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::math::square;
use crate::utils::noise::noise_2d;
fn inside_polka_dot(st: Point2f) -> bool {
let s_cell = (st[0] + 0.5).floor();
let t_cell = (st[1] + 0.5).floor();
if noise_2d(s_cell + 0.5, t_cell + 0.5) > 0. {
let radius = 0.35;
let max_shift = 0.5 + radius;
let s_center = s_cell + max_shift * noise_2d(s_cell + 1.5, t_cell + 2.8);
let t_center = t_cell + max_shift * noise_2d(s_cell + 4.5, t_cell + 9.8);
let dst = st - Point2f::new(s_center, t_center);
if dst.norm_squared() < square(radius) {
return true;
}
}
return false;
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct FloatDotsTexture {
pub mapping: TextureMapping2D,
pub outside_dot: Ptr<GPUFloatTexture>,
pub inside_dot: Ptr<GPUFloatTexture>,
}
impl FloatDotsTexture { impl FloatDotsTexture {
pub fn evaluate(&self, _ctx: &TextureEvalContext) -> Float { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
todo!() let c = self.mapping.map(ctx);
let target_texture = if inside_polka_dot(c.st) {
self.inside_dot
} else {
self.outside_dot
};
if !target_texture.is_null() {
target_texture.evaluate(ctx)
} else {
0.0
}
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
pub struct SpectrumDotsTexture; #[derive(Clone, Copy, Debug)]
pub struct SpectrumDotsTexture {
pub mapping: TextureMapping2D,
pub outside_dot: Ptr<GPUSpectrumTexture>,
pub inside_dot: Ptr<GPUSpectrumTexture>,
}
impl SpectrumDotsTexture { impl SpectrumDotsTexture {
pub fn evaluate( pub fn evaluate(
&self, &self,
_ctx: &TextureEvalContext, ctx: &TextureEvalContext,
_lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum {
todo!() let c = self.mapping.map(ctx);
let target_texture = if inside_polka_dot(c.st) {
self.inside_dot
} else {
self.outside_dot
};
if !target_texture.is_null() {
target_texture.evaluate(ctx, lambda)
} else {
SampledSpectrum::new(0.0)
}
} }
} }

View file

@ -1,10 +1,12 @@
use crate::Float;
use crate::core::texture::{TextureEvalContext, TextureMapping3D}; use crate::core::texture::{TextureEvalContext, TextureMapping3D};
use crate::utils::noise::fbm;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub struct FBmTexture { pub struct FBmTexture {
pub mapping: TextureMapping3D, pub mapping: TextureMapping3D,
pub omega: Float, pub omega: Float,
pub octaves: usize, pub octaves: u32,
} }
impl FBmTexture { impl FBmTexture {

View file

@ -1,12 +1,17 @@
use crate::Float; use crate::Float;
use crate::core::color::{RGB, XYZ};
use crate::core::spectrum::SpectrumTrait;
use crate::core::texture::{SpectrumType, TextureEvalContext, TextureMapping2D}; use crate::core::texture::{SpectrumType, TextureEvalContext, TextureMapping2D};
use crate::spectra::RGBColorSpace; use crate::spectra::{
RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
SampledWavelengths,
};
use crate::utils::Ptr;
/* GPU heavy code, dont know if this will ever work the way Im doing things. /* GPU heavy code, dont know if this will ever work the way Im doing things.
* Leaving it here isolated, for careful handling */ * Leaving it here isolated, for careful handling */
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug)] #[derive(Clone, Debug, Copy)]
pub struct GPUSpectrumImageTexture { pub struct GPUSpectrumImageTexture {
pub mapping: TextureMapping2D, pub mapping: TextureMapping2D,
pub tex_obj: u64, pub tex_obj: u64,
@ -18,6 +23,7 @@ pub struct GPUSpectrumImageTexture {
} }
impl GPUSpectrumImageTexture { impl GPUSpectrumImageTexture {
#[allow(unused)]
pub fn evaluate( pub fn evaluate(
&self, &self,
ctx: &TextureEvalContext, ctx: &TextureEvalContext,
@ -32,19 +38,21 @@ impl GPUSpectrumImageTexture {
{ {
use cuda_std::intrinsics; use cuda_std::intrinsics;
let c = self.mapping.map(ctx); let c = self.mapping.map(ctx);
let u = c.st.x; let u = c.st.x();
let v = 1.0 - c.st.y; let v = 1.0 - c.st.y();
let d_p_dx = [c.dsdx, c.dtdx]; let d_p_dx = [c.dsdx, c.dtdx];
let d_p_dy = [c.dsdy, c.dtdy]; let d_p_dy = [c.dsdy, c.dtdy];
let tex_color = if self.is_single_channel { let tex_color = if self.is_single_channel {
let val: Float = let val = 0.;
unsafe { intrinsics::tex2d_grad(self.tex_obj, u, v, d_p_dx, d_p_dy) }; // let val: Float =
// unsafe { intrinsics::tex2d_grad(self.tex_obj, u, v, d_p_dx, d_p_dy) };
RGB::new(val, val, val) RGB::new(val, val, val)
} else { } else {
let val: [Float; 4] = let val = [0., 0., 0., 0.];
unsafe { intrinsics::tex2d_grad(self.tex_obj, u, v, d_p_dx, d_p_dy) }; // let val: [Float; 4] =
// unsafe { intrinsics::tex2d_grad(self.tex_obj, u, v, d_p_dx, d_p_dy) };
RGB::new(val[0], val[1], val[2]) RGB::new(val[0], val[1], val[2])
}; };
@ -53,22 +61,20 @@ impl GPUSpectrumImageTexture {
rgb = (RGB::new(1.0, 1.0, 1.0) - rgb).clamp_zero(); rgb = (RGB::new(1.0, 1.0, 1.0) - rgb).clamp_zero();
} }
let color_space = unsafe { &*self.color_space };
match self.spectrum_type { match self.spectrum_type {
SpectrumType::Unbounded => { SpectrumType::Unbounded => {
RGBUnboundedSpectrum::new(color_space, rgb).sample(lambda) RGBUnboundedSpectrum::new(&self.color_space, rgb).sample(lambda)
} }
SpectrumType::Albedo => { SpectrumType::Albedo => {
RGBAlbedoSpectrum::new(color_space, rgb.clamp(0.0, 1.0)).sample(lambda) RGBAlbedoSpectrum::new(&self.color_space, rgb.clamp(0.0, 1.0)).sample(lambda)
} }
_ => RGBIlluminantSpectrum::new(color_space, rgb).sample(lambda), _ => RGBIlluminantSpectrum::new(&self.color_space, rgb).sample(lambda),
} }
} }
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Copy, Clone)]
pub struct GPUFloatImageTexture { pub struct GPUFloatImageTexture {
pub mapping: TextureMapping2D, pub mapping: TextureMapping2D,
pub tex_obj: u64, pub tex_obj: u64,
@ -77,26 +83,32 @@ pub struct GPUFloatImageTexture {
} }
impl GPUFloatImageTexture { impl GPUFloatImageTexture {
#[allow(unused_variables)]
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
#[cfg(not(feature = "cuda"))] #[cfg(not(feature = "cuda"))]
{ {
return 0.; return 0.;
} }
#[cfg(feature = "cuda")] #[cfg(feature = "cuda")]
#[allow(unused)]
{ {
use cuda_std::intrinsics; use cuda_std::intrinsics;
let c = self.mapping.map(ctx); let c = self.mapping.map(ctx);
let u = c.st.x; let u = c.st.x();
let v = 1.0 - c.st.y; let v = 1.0 - c.st.y();
let d_p_dx = [c.dsdx, c.dtdx]; let d_p_dx = [c.dsdx, c.dtdx];
let d_p_dy = [c.dsdy, c.dtdy]; let d_p_dy = [c.dsdy, c.dtdy];
let val: Float = unsafe { intrinsics::tex2d_grad(self.tex_obj, u, v, d_p_dx, d_p_dy) }; // let val: Float = unsafe { intrinsics::tex2d_grad(self.tex_obj, u, v, d_p_dx, d_p_dy) };
let val: Float = 0.;
if invert { let result = if self.invert {
return (1. - v).max(0.); // Invert the pixel intensity
(1.0 - val).max(0.0)
} else { } else {
return v; val
} };
return result * self.scale;
} }
} }
} }

View file

@ -1,56 +1,67 @@
use crate::core::texture::{TextureEvalContext, TextureMapping3D};
use crate::spectra::{RGBAlbedoSpectrum, SampledSpectrum, SampledWavelengths};
use crate::utils::noise::fbm;
use crate::utils::splines::evaluate_cubic_bezier;
use crate::Float; use crate::Float;
use crate::core::color::RGB;
use crate::core::geometry::{Point3f, Vector3f};
use crate::core::spectrum::SpectrumTrait;
use crate::core::texture::{TextureEvalContext, TextureMapping3D};
use crate::spectra::{RGBAlbedoSpectrum, RGBColorSpace, SampledSpectrum, SampledWavelengths};
use crate::utils::math::clamp;
use crate::utils::noise::fbm;
use crate::utils::ptr::Ptr;
use crate::utils::splines::evaluate_cubic_bezier;
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct MarbleTexture { pub struct MarbleTexture {
pub mapping: TextureMapping3D, pub mapping: TextureMapping3D,
pub octaves: usize, pub octaves: u32,
pub omega: Float, pub omega: Float,
pub scale: Float, pub scale: Float,
pub variation: Float, pub variation: Float,
// TODO: DO not forget to pass StandardColorSpace here!!
pub colorspace: Ptr<RGBColorSpace>,
} }
unsafe impl Send for MarbleTexture {}
unsafe impl Sync for MarbleTexture {}
impl MarbleTexture { impl MarbleTexture {
pub fn evaluate( pub fn evaluate(
&self, &self,
ctx: &TextureEvalContext, ctx: &TextureEvalContext,
_lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum {
let mut c = self.mapping.map(ctx); let mut c = self.mapping.map(ctx);
c.p *= self.scale; c.p = Point3f::from(Vector3f::from(c.p) * self.scale);
let marble = c.p.y() + self.variation * fbm(c.p, self.scale, c.dpdy, omega, self.octaves); let marble = c.p.y()
const COLORS: [RGB; 9] = [ + self.variation
RGB::new(0.58, 0.58, 0.6), * fbm(
RGB::new(0.58, 0.58, 0.6), c.p,
RGB::new(0.58, 0.58, 0.6), self.scale * c.dpdx,
RGB::new(0.5, 0.5, 0.5), self.scale * c.dpdy,
RGB::new(0.6, 0.59, 0.58), self.omega,
RGB::new(0.58, 0.58, 0.6), self.octaves,
RGB::new(0.58, 0.58, 0.6), );
RGB::new(0.2, 0.2, 0.33), let t = 0.5 + 0.5 * marble.sin();
RGB::new(0.58, 0.58, 0.6), let colors: [Point3f; 9] = [
Point3f::new(0.58, 0.58, 0.6),
Point3f::new(0.58, 0.58, 0.6),
Point3f::new(0.58, 0.58, 0.6),
Point3f::new(0.5, 0.5, 0.5),
Point3f::new(0.6, 0.59, 0.58),
Point3f::new(0.58, 0.58, 0.6),
Point3f::new(0.58, 0.58, 0.6),
Point3f::new(0.2, 0.2, 0.33),
Point3f::new(0.58, 0.58, 0.6),
]; ];
const N_SEG: i32 = 6; // (9 - 3) const N_SEG: i32 = 6; // (9 - 3)
let t_clamped = t.clamp(0.0, 1.0); let t_clamped = clamp(t, 0.0, 1.0);
let first = ((t_clamped * N_SEG as Float).floor() as i32).clamp(0, N_SEG - 1); let first = ((t_clamped * N_SEG as Float).floor() as i32).clamp(0, N_SEG - 1);
let t_segment = t_clamped * N_SEG as Float - first as Float; let t_segment = t_clamped * N_SEG as Float - first as Float;
let first_idx = first as usize; let first_idx = first as usize;
let rgb = evaluate_cubic_bezier(&COLORS[first_idx..first_idx + 4], t_segment) * 1.5; let (rgb_vec, _) = evaluate_cubic_bezier(&colors[first_idx..first_idx + 4], t_segment);
let rgb = RGB::new(rgb_vec.x() * 1.5, rgb_vec.y() * 1.5, rgb_vec.z() * 1.5);
let color_space = { RGBAlbedoSpectrum::new(&*self.colorspace, rgb).sample(lambda)
#[cfg(target_os = "cuda")]
{
unsafe { &*RGBColorSpace_sRGB }
}
#[cfg(not(target_os = "cuda"))]
{
RGBColorSpace::srgb()
}
};
RGBAlbedoSpectrum::new(color_space, rgb).sample(lambda)
} }
} }

View file

@ -1,5 +1,7 @@
use crate::core::geometry::Vector3f; use crate::Float;
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture}; use crate::core::geometry::{Vector3f, VectorLike};
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr; use crate::utils::Ptr;
#[repr(C)] #[repr(C)]
@ -7,6 +9,26 @@ use crate::utils::Ptr;
pub struct GPUFloatMixTexture { pub struct GPUFloatMixTexture {
pub tex1: Ptr<GPUFloatTexture>, pub tex1: Ptr<GPUFloatTexture>,
pub tex2: Ptr<GPUFloatTexture>, pub tex2: Ptr<GPUFloatTexture>,
pub amount: Ptr<GPUFloatTexture>,
}
impl GPUFloatMixTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
let amt = self.amount.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0);
let t1 = if amt != 1.0 {
self.tex1.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0)
} else {
0.0
};
let t2 = if amt != 0.0 {
self.tex2.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0)
} else {
0.0
};
(1.0 - amt) * t1 + amt * t2
}
} }
#[repr(C)] #[repr(C)]
@ -17,11 +39,60 @@ pub struct GPUFloatDirectionMixTexture {
pub dir: Vector3f, pub dir: Vector3f,
} }
impl GPUFloatDirectionMixTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
let amt = self.dir.abs_dot(ctx.n.into());
let t1 = if amt != 1.0 {
self.tex1.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0)
} else {
0.0
};
let t2 = if amt != 0.0 {
self.tex2.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0)
} else {
0.0
};
(1.0 - amt) * t1 + amt * t2
}
}
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct GPUSpectrumMixTexture { pub struct GPUSpectrumMixTexture {
pub tex1: Ptr<GPUSpectrumTexture>, pub tex1: Ptr<GPUSpectrumTexture>,
pub tex2: Ptr<GPUSpectrumTexture>, pub tex2: Ptr<GPUSpectrumTexture>,
pub amount: Ptr<GPUFloatTexture>,
}
impl GPUSpectrumMixTexture {
pub fn evaluate(
&self,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum {
let amt = self.amount.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0);
let t1 = if amt != 1.0 {
self.tex1
.get()
.map(|t| t.evaluate(ctx, lambda))
.unwrap_or(SampledSpectrum::new(0.))
} else {
SampledSpectrum::new(0.)
};
let t2 = if amt != 0.0 {
self.tex2
.get()
.map(|t| t.evaluate(ctx, lambda))
.unwrap_or(SampledSpectrum::new(0.))
} else {
SampledSpectrum::new(0.)
};
(1.0 - amt) * t1 + amt * t2
}
} }
#[repr(C)] #[repr(C)]
@ -31,3 +102,32 @@ pub struct GPUSpectrumDirectionMixTexture {
pub tex2: Ptr<GPUSpectrumTexture>, pub tex2: Ptr<GPUSpectrumTexture>,
pub dir: Vector3f, pub dir: Vector3f,
} }
impl GPUSpectrumDirectionMixTexture {
pub fn evaluate(
&self,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum {
let amt = self.dir.abs_dot(ctx.n.into());
let t1 = if amt != 1.0 {
self.tex1
.get()
.map(|t| t.evaluate(ctx, lambda))
.unwrap_or(SampledSpectrum::new(0.))
} else {
SampledSpectrum::new(0.)
};
let t2 = if amt != 0.0 {
self.tex2
.get()
.map(|t| t.evaluate(ctx, lambda))
.unwrap_or(SampledSpectrum::new(0.))
} else {
SampledSpectrum::new(0.)
};
(1.0 - amt) * t1 + amt * t2
}
}

View file

@ -1,29 +1,32 @@
use crate::Float; use crate::Float;
use crate::core::color::{ColorEncoding, RGB};
use crate::core::spectrum::{SpectrumTrait, StandardSpectra};
use crate::core::texture::{SpectrumType, TextureEvalContext}; use crate::core::texture::{SpectrumType, TextureEvalContext};
use crate::spectra::{ use crate::spectra::{
RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, DeviceStandardColorSpaces, RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum,
SampledWavelengths, RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths,
}; };
use crate::utils::Ptr;
/* GPU heavy code, have to see how to best approach this #[repr(C)]
*/ #[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub struct GPUFloatPtexTexture { pub struct GPUFloatPtexTexture {
pub face_values: Vec<Float>, pub face_values: *const Float,
} }
impl GPUFloatPtexTexture { impl GPUFloatPtexTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
self.face_values[ctx.face_index] unsafe { *self.face_values.add(ctx.face_index as usize) }
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct GPUSpectrumPtexTexture { pub struct GPUSpectrumPtexTexture {
pub face_values: *const RGB, pub face_values: Ptr<RGB>,
pub n_faces: usize, pub n_faces: u32,
pub spectrum_type: SpectrumType, pub spectrum_type: SpectrumType,
pub colorspaces: DeviceStandardColorSpaces,
} }
impl GPUSpectrumPtexTexture { impl GPUSpectrumPtexTexture {
@ -32,24 +35,17 @@ impl GPUSpectrumPtexTexture {
ctx: &TextureEvalContext, ctx: &TextureEvalContext,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum {
let index = ctx.face_index.clamp(0, self.n_faces.saturating_sub(1)); let index = (ctx.face_index as u32).clamp(0, self.n_faces.saturating_sub(1));
let rgb = unsafe { *self.face_values.add(index) }; let rgb = unsafe { &*self.face_values.add(index as usize) };
let s_rgb = { let s_rgb = self.colorspaces.srgb;
#[cfg(feature = "cuda")]
unsafe {
&*RGBColorSpace_sRGB
}
#[cfg(not(feature = "cuda"))]
RGBColorSpace::srgb()
};
match self.spectrum_type { match self.spectrum_type {
SpectrumType::Unbounded => RGBUnboundedSpectrum::new(s_rgb, rgb).sample(lambda), SpectrumType::Unbounded => RGBUnboundedSpectrum::new(&s_rgb, *rgb).sample(lambda),
SpectrumType::Albedo => { SpectrumType::Albedo => {
let clamped_rgb = rgb.clamp(0.0, 1.0); let clamped_rgb = rgb.clamp(0.0, 1.0);
RGBAlbedoSpectrum::new(s_rgb, clamped_rgb).sample(lambda) RGBAlbedoSpectrum::new(&s_rgb, clamped_rgb).sample(lambda)
} }
SpectrumType::Illuminant => RGBIlluminantSpectrum::new(s_rgb, rgb).sample(lambda), SpectrumType::Illuminant => RGBIlluminantSpectrum::new(&s_rgb, *rgb).sample(lambda),
} }
} }
} }

View file

@ -1,16 +1,44 @@
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture}; use crate::Float;
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr; use crate::utils::Ptr;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct GPUFloatScaledTexture { pub struct GPUFloatScaledTexture {
tex: Ptr<GPUFloatTexture>, pub tex: Ptr<GPUFloatTexture>,
scale: Ptr<GPUFloatTexture>, pub scale: Ptr<GPUFloatTexture>,
}
impl GPUFloatScaledTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
let sc = self.scale.get().map(|t| t.evaluate(&ctx)).unwrap();
if sc == 0. {
return 0.;
}
self.tex.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0) * sc
}
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct GPUSpectrumScaledTexture { pub struct GPUSpectrumScaledTexture {
tex: Ptr<GPUSpectrumTexture>, pub tex: Ptr<GPUSpectrumTexture>,
scale: Ptr<GPUFloatTexture>, pub scale: Ptr<GPUFloatTexture>,
}
impl GPUSpectrumScaledTexture {
pub fn evaluate(
&self,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum {
let sc = self.scale.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.);
self.tex
.get()
.map(|t| t.evaluate(&ctx, &lambda))
.unwrap_or(SampledSpectrum::new(0.))
* sc
}
} }

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