Compare commits
No commits in common. "arena" and "main" have entirely different histories.
218 changed files with 11820 additions and 17707 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -1,11 +1,7 @@
|
|||
target/
|
||||
/target
|
||||
*.lock
|
||||
*.log
|
||||
*.bak
|
||||
flip.rs
|
||||
.vscode
|
||||
rust-analyzer.json
|
||||
data/
|
||||
src/gpu/
|
||||
src/tests/
|
||||
tests/
|
||||
|
|
|
|||
22
Cargo.toml
22
Cargo.toml
|
|
@ -6,14 +6,13 @@ edition = "2024"
|
|||
[features]
|
||||
default = []
|
||||
use_f64 = []
|
||||
use_gpu = []
|
||||
use_nvtx = []
|
||||
cuda = ["dep:cudarc"]
|
||||
cuda = ["cust", "cuda_builder", "shared/cuda", ]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.100"
|
||||
exr = "1.73.0"
|
||||
flate2 = "1.1.5"
|
||||
gpu = "0.2.3"
|
||||
half = "2.7.1"
|
||||
image_rs = { package = "image", version = "0.25.8" }
|
||||
indicatif = "0.18.3"
|
||||
|
|
@ -31,31 +30,20 @@ unicode-normalization = "0.1.25"
|
|||
wgpu = "27.0.1"
|
||||
|
||||
shared = { path = "shared" }
|
||||
ptex-filter = { path = "crates/ptex-filter" }
|
||||
# kernels = { path = "kernels" }
|
||||
kernels = { path = "kernels" }
|
||||
|
||||
cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true }
|
||||
cust = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, features = ["glam"], optional = true }
|
||||
ptex = "0.3.0"
|
||||
# ptex-sys = "0.3.0"
|
||||
ptex-sys = "0.3.0"
|
||||
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]
|
||||
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 }
|
||||
cc = "1.2.53"
|
||||
|
||||
[workspace]
|
||||
members = ["shared", "crates/ptex-filter"]
|
||||
members = ["kernels", "shared"]
|
||||
|
||||
[lints.clippy]
|
||||
excessive_precision = "allow"
|
||||
|
|
|
|||
43
build.rs
43
build.rs
|
|
@ -1,43 +0,0 @@
|
|||
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);
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
[package]
|
||||
name = "ptex-filter"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0"
|
||||
pkg-config = "0.3"
|
||||
|
||||
[dependencies]
|
||||
ptex = "0.3.0"
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
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");
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
#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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
#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
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +1,447 @@
|
|||
#![cfg_attr(target_arch = "nvptx64", no_std)]
|
||||
#![cfg_attr(target_arch = "nvptx64", feature(abi_ptx))]
|
||||
|
||||
use cuda_std::prelude::*;
|
||||
|
||||
/// Scales each element: data[i] *= scale
|
||||
#[kernel]
|
||||
#[allow(improper_ctypes_definitions)]
|
||||
pub unsafe fn scale_array(data: *mut f32, len: u32, scale: f32) {
|
||||
let idx = thread::index_1d() as u32;
|
||||
if idx >= len {
|
||||
pub mod wavefront;
|
||||
pub mod workitem;
|
||||
|
||||
use cust::context::{CacheConfig, CurrentContext, ResourceLimit};
|
||||
use cust::device::DeviceAttribute;
|
||||
use cust::memory::{DeviceCopy, DeviceMemory};
|
||||
use cust::prelude::*;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use std::error::Error;
|
||||
use std::ffi::c_void;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::Float;
|
||||
use crate::core::geometry::{Normal, Point, Vector};
|
||||
use crate::core::medium::Medium;
|
||||
use crate::core::options::{PBRTOptions, get_options};
|
||||
use crate::impl_gpu_traits;
|
||||
use crate::impl_math_gpu_traits;
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::interval::Interval;
|
||||
|
||||
pub use workitem::{
|
||||
EscapedRayQueue, GetBSSRDFAndProbeRayQueue, HitAreaLightQueue, MaterialEvalQueue,
|
||||
MediumSampleQueue, MediumScatterQueue, PixelSampleStateStorage, RayQueue, ShadowRayQueue,
|
||||
SubsurfaceScatterQueue,
|
||||
};
|
||||
|
||||
#[repr(C, align(16))]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Float4 {
|
||||
pub v: [f32; 4],
|
||||
}
|
||||
|
||||
pub type Vec4 = Vector<Float, 4>;
|
||||
|
||||
impl From<Vec4> for Float4 {
|
||||
#[inline]
|
||||
fn from(vec: Vector<f32, 4>) -> Self {
|
||||
Self { v: vec.0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Float4> for Vec4 {
|
||||
#[inline]
|
||||
fn from(storage: Float4) -> Self {
|
||||
Vector(storage.v)
|
||||
}
|
||||
}
|
||||
|
||||
impl_math_gpu_traits!(Vector);
|
||||
impl_math_gpu_traits!(Normal);
|
||||
impl_math_gpu_traits!(Point);
|
||||
impl_gpu_traits!(Interval);
|
||||
impl_gpu_traits!(Float4);
|
||||
impl_gpu_traits!(SampledSpectrum);
|
||||
impl_gpu_traits!(SampledWavelengths);
|
||||
|
||||
struct KernelStats {
|
||||
description: String,
|
||||
num_launches: usize,
|
||||
sum_ms: f32,
|
||||
min_ms: f32,
|
||||
max_ms: f32,
|
||||
}
|
||||
|
||||
impl KernelStats {
|
||||
fn new(description: &str) -> Self {
|
||||
Self {
|
||||
description: description.to_string(),
|
||||
num_launches: 0,
|
||||
sum_ms: 0.0,
|
||||
min_ms: 0.0,
|
||||
max_ms: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ProfilerEvent {
|
||||
start: Event,
|
||||
stop: Event,
|
||||
active: bool,
|
||||
stats: Option<Arc<Mutex<KernelStats>>>,
|
||||
}
|
||||
|
||||
impl ProfilerEvent {
|
||||
fn new() -> Result<Self, cust::error::CudaError> {
|
||||
let start = Event::new(EventFlags::DEFAULT)?;
|
||||
let stop = Event::new(EventFlags::DEFAULT)?;
|
||||
Ok(Self {
|
||||
start,
|
||||
stop,
|
||||
active: false,
|
||||
stats: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn sync(&mut self) {
|
||||
if !self.active {
|
||||
return;
|
||||
}
|
||||
|
||||
let ptr = unsafe { data.add(idx as usize) };
|
||||
*ptr = *ptr * scale;
|
||||
if self.stop.synchronize().is_ok() {
|
||||
// Check timing between start and stop
|
||||
match self.stop.elapsed_time_f32(&self.start) {
|
||||
Ok(ms) => {
|
||||
if let Some(stats_arc) = &self.stats {
|
||||
let mut stats = stats_arc.lock();
|
||||
stats.num_launches += 1;
|
||||
if stats.num_launches == 1 {
|
||||
stats.sum_ms = ms;
|
||||
stats.min_ms = ms;
|
||||
stats.max_ms = ms;
|
||||
} else {
|
||||
stats.sum_ms += ms;
|
||||
stats.min_ms = stats.min_ms.min(ms);
|
||||
stats.max_ms = stats.max_ms.max(ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => log::error!("Failed to get elapsed time: {:?}", e),
|
||||
}
|
||||
}
|
||||
self.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds two arrays: c[i] = a[i] + b[i]
|
||||
#[kernel]
|
||||
#[allow(improper_ctypes_definitions)]
|
||||
pub unsafe fn add_arrays(a: *const f32, b: *const f32, c: *mut f32, len: u32) {
|
||||
let idx = thread::index_1d() as u32;
|
||||
if idx >= len {
|
||||
// --- Profiler Manager ---
|
||||
|
||||
struct Profiler {
|
||||
kernel_stats: Vec<Arc<Mutex<KernelStats>>>,
|
||||
event_pool: Vec<ProfilerEvent>,
|
||||
pool_offset: usize,
|
||||
}
|
||||
|
||||
impl Profiler {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
kernel_stats: Vec::new(),
|
||||
event_pool: Vec::new(),
|
||||
pool_offset: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepares an event from the pool.
|
||||
/// Returns a mutable reference to the event, valid as long as the borrow of self.
|
||||
fn prepare<'a>(&'a mut self, description: &str) -> &'a mut ProfilerEvent {
|
||||
// Grow pool if empty or needed (simple heuristic)
|
||||
if self.event_pool.is_empty() {
|
||||
for _ in 0..128 {
|
||||
if let Ok(e) = ProfilerEvent::new() {
|
||||
self.event_pool.push(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.pool_offset >= self.event_pool.len() {
|
||||
self.pool_offset = 0;
|
||||
}
|
||||
|
||||
let idx = self.pool_offset;
|
||||
self.pool_offset += 1;
|
||||
|
||||
let pe = &mut self.event_pool[idx];
|
||||
|
||||
if pe.active {
|
||||
pe.sync();
|
||||
}
|
||||
|
||||
pe.active = true;
|
||||
pe.stats = None;
|
||||
|
||||
// Find or create stats
|
||||
let mut found = None;
|
||||
for s in &self.kernel_stats {
|
||||
if s.lock().description == description {
|
||||
found = Some(s.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if found.is_none() {
|
||||
let new_stats = Arc::new(Mutex::new(KernelStats::new(description)));
|
||||
self.kernel_stats.push(new_stats.clone());
|
||||
found = Some(new_stats);
|
||||
}
|
||||
|
||||
pe.stats = found;
|
||||
pe
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GpuState {
|
||||
context: Context,
|
||||
stream: Stream,
|
||||
profiler: Profiler,
|
||||
}
|
||||
|
||||
impl GpuState {
|
||||
fn init(device_index: u32) -> Result<Self, Box<dyn Error>> {
|
||||
cust::init(CudaFlags::empty())?;
|
||||
|
||||
let device = Device::get_device(device_index)?;
|
||||
|
||||
let name = device.name().unwrap_or_else(|_| "Unknown".into());
|
||||
let memory = device.total_memory().unwrap_or(0);
|
||||
let memory_gb = memory as f64 / (1024.0 * 1024.0 * 1024.0);
|
||||
|
||||
let major = device
|
||||
.get_attribute(DeviceAttribute::ComputeCapabilityMajor)
|
||||
.unwrap_or(0);
|
||||
let minor = device
|
||||
.get_attribute(DeviceAttribute::ComputeCapabilityMinor)
|
||||
.unwrap_or(0);
|
||||
|
||||
log::info!(
|
||||
"Selected GPU: {} ({:.2} GB, SM {}.{})",
|
||||
name,
|
||||
memory_gb,
|
||||
major,
|
||||
minor
|
||||
);
|
||||
|
||||
let has_unified = device
|
||||
.get_attribute(DeviceAttribute::UnifiedAddressing)
|
||||
.unwrap_or(0);
|
||||
if has_unified == 0 {
|
||||
panic!("Selected GPU does not support unified addressing.");
|
||||
}
|
||||
|
||||
let context = Context::new(device)?;
|
||||
|
||||
CurrentContext::set_resource_limit(ResourceLimit::StackSize, 8192)?;
|
||||
let stack_size = CurrentContext::get_resource_limit(ResourceLimit::StackSize)?;
|
||||
log::info!("Reset stack size to {}", stack_size);
|
||||
|
||||
CurrentContext::set_resource_limit(ResourceLimit::PrintfFifoSize, 32 * 1024 * 1024)?;
|
||||
CurrentContext::set_cache_config(CacheConfig::PreferL1)?;
|
||||
|
||||
let stream = Stream::new(StreamFlags::DEFAULT, None)?;
|
||||
|
||||
Ok(Self {
|
||||
context,
|
||||
stream,
|
||||
profiler: Profiler::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref GPU_STATE: Mutex<Option<GpuState>> = Mutex::new(None);
|
||||
}
|
||||
|
||||
pub fn gpu_init() {
|
||||
if !get_options().use_gpu {
|
||||
return;
|
||||
}
|
||||
|
||||
let i = idx as usize;
|
||||
*c.add(i) = *a.add(i) + *b.add(i);
|
||||
let device_id = get_options().gpu_device.unwrap_or(0);
|
||||
log::info!("Initializing GPU Device {}", device_id);
|
||||
|
||||
match GpuState::init(device_id) {
|
||||
Ok(state) => {
|
||||
#[cfg(feature = "use_nvtx")]
|
||||
nvtx::name_thread("MAIN_THREAD");
|
||||
*GPU_STATE.lock() = Some(state);
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("Failed to initialize GPU: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gpu_thread_init() {
|
||||
if let Some(state) = GPU_STATE.lock().as_ref() {
|
||||
if let Err(e) = CurrentContext::set_current(&state.context) {
|
||||
log::error!("Failed to set CUDA context for thread: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gpu_wait() {
|
||||
let mut guard = GPU_STATE.lock();
|
||||
if let Some(state) = guard.as_mut() {
|
||||
if let Err(e) = state.stream.synchronize() {
|
||||
log::error!("GPU Wait failed: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Launches a parallel for loop on the GPU.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `description`: Name for profiling.
|
||||
/// * `n_items`: Total items (threads).
|
||||
/// * `function`: Compiled kernel function handle.
|
||||
/// * `params`: Kernel parameters (must be DeviceCopy).
|
||||
pub fn gpu_parallel_for<T: DeviceCopy>(
|
||||
description: &str,
|
||||
n_items: i32,
|
||||
function: &Function,
|
||||
params: &T,
|
||||
) {
|
||||
#[cfg(feature = "use_nvtx")]
|
||||
nvtx::range_push(description);
|
||||
|
||||
let mut guard = GPU_STATE.lock();
|
||||
let state = guard.as_mut().expect("GPU not initialized");
|
||||
|
||||
let (_, block_size) = match function.suggested_launch_configuration(0, 0.into()) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(e) => panic!(
|
||||
"Failed to calculate launch config for {}: {:?}",
|
||||
description, e
|
||||
),
|
||||
};
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
log::debug!("[{}] Block size: {}", description, block_size);
|
||||
|
||||
let grid_size = (n_items as u32 + block_size - 1) / block_size;
|
||||
|
||||
let stream = &state.stream;
|
||||
let profiler = &mut state.profiler;
|
||||
|
||||
// Save the index we are about to use so we can retrieve the STOP event later
|
||||
let event_idx = profiler.pool_offset;
|
||||
|
||||
{
|
||||
let pe = profiler.prepare(description);
|
||||
if let Err(e) = pe.start.record(stream) {
|
||||
log::error!("Failed to record start event: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
let params_ptr = params as *const T as *mut c_void;
|
||||
let n_items_ptr = &n_items as *const i32 as *mut c_void;
|
||||
let args = [params_ptr, n_items_ptr];
|
||||
|
||||
unsafe {
|
||||
if let Err(e) =
|
||||
state
|
||||
.stream
|
||||
.launch(function, (grid_size, 1, 1), (block_size, 1, 1), 0, &args)
|
||||
{
|
||||
panic!("CUDA Launch failed for {}: {:?}", description, e);
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve the specific event we just set up.
|
||||
// Pool_offset was incremented in prepare().
|
||||
// If event_idx was the one used, the event is at event_idx.
|
||||
if event_idx < profiler.event_pool.len() {
|
||||
let pe = &mut profiler.event_pool[event_idx];
|
||||
if let Err(e) = pe.stop.record(stream) {
|
||||
log::error!("Failed to record stop event: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
let _ = state.stream.synchronize();
|
||||
|
||||
#[cfg(feature = "use_nvtx")]
|
||||
nvtx::range_pop();
|
||||
}
|
||||
|
||||
pub fn report_kernel_stats() {
|
||||
let mut guard = GPU_STATE.lock();
|
||||
if let Some(state) = guard.as_mut() {
|
||||
let _ = state.stream.synchronize();
|
||||
|
||||
// Process all pending events
|
||||
for pe in &mut state.profiler.event_pool {
|
||||
if pe.active {
|
||||
pe.sync();
|
||||
}
|
||||
}
|
||||
|
||||
let mut total_ms = 0.0;
|
||||
for s in &state.profiler.kernel_stats {
|
||||
total_ms += s.lock().sum_ms;
|
||||
}
|
||||
|
||||
println!("Wavefront Kernel Profile:");
|
||||
for s in &state.profiler.kernel_stats {
|
||||
let stats = s.lock();
|
||||
let percent = if total_ms > 0.0 {
|
||||
100.0 * stats.sum_ms / total_ms
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
println!(
|
||||
" {:<45} {:5} launches {:9.2} ms / {:5.1}% (avg {:6.3})",
|
||||
stats.description,
|
||||
stats.num_launches,
|
||||
stats.sum_ms,
|
||||
percent,
|
||||
if stats.num_launches > 0 {
|
||||
stats.sum_ms / stats.num_launches as f32
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
);
|
||||
}
|
||||
println!("\nTotal: {:.2} ms", total_ms);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gpu_memset<T: DeviceCopy>(dst: &mut DeviceSlice<T>, value: u8) {
|
||||
unsafe {
|
||||
let ptr = dst.as_raw_ptr(); // Returns CUdeviceptr (u64)
|
||||
let len = dst.len() * std::mem::size_of::<T>();
|
||||
|
||||
// We need the `cust::external::cuda` or equivalent sys crate function
|
||||
|
||||
log::warn!("gpu_memset requested but raw memset not exposed via safe cust API yet.");
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_gpu_traits {
|
||||
($name:ty) => {
|
||||
unsafe impl cust::memory::DeviceCopy for $name {}
|
||||
unsafe impl bytemuck::Zeroable for $name {}
|
||||
unsafe impl bytemuck::Pod for $name {}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_math_gpu_traits {
|
||||
($Struct:ident) => {
|
||||
#[cfg(feature = "use_gpu")]
|
||||
unsafe impl<T, const N: usize> cust::memory::DeviceCopy for $Struct<T, N> where
|
||||
T: cust::memory::DeviceCopy + Copy
|
||||
{
|
||||
}
|
||||
|
||||
unsafe impl<T, const N: usize> bytemuck::Zeroable for $Struct<T, N> where
|
||||
T: bytemuck::Zeroable
|
||||
{
|
||||
}
|
||||
|
||||
unsafe impl<T, const N: usize> bytemuck::Pod for $Struct<T, N> where T: bytemuck::Pod {}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
47
kernels/src/wavefront/mod.rs
Normal file
47
kernels/src/wavefront/mod.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use image_rs::Pixel;
|
||||
|
||||
use crate::camera::Camera;
|
||||
use crate::core::film::Film;
|
||||
use crate::core::filter::Filter;
|
||||
use crate::core::sampler::Sampler;
|
||||
use crate::core::scene::BasicScene;
|
||||
use crate::lights::Light;
|
||||
use crate::lights::LightSampler;
|
||||
use crate::{
|
||||
EscapedRayQueue, GetBSSRDFAndProbeRayQueue, HitAreaLightQueue, MaterialEvalQueue,
|
||||
MediumSampleQueue, MediumScatterQueue, PixelSampleStateStorage, RayQueue, ShadowRayQueue,
|
||||
SubsurfaceScatterQueue,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct WavefrontPathIntegrator {
|
||||
pub film: Film,
|
||||
pub filter: Filter,
|
||||
pub sampler: Sampler,
|
||||
pub camera: Arc<Camera>,
|
||||
pub light_sampler: LightSampler,
|
||||
pub infinite_lights: Option<Vec<Arc<Light>>>,
|
||||
pub max_depth: i32,
|
||||
pub samples_per_pixel: i32,
|
||||
pub regularize: bool,
|
||||
pub scanlines_per_pixel: i32,
|
||||
pub max_queue_size: i32,
|
||||
pub pixel_sample_state: PixelSampleStateStorage,
|
||||
pub ray_queue: [RayQueue; 2],
|
||||
pub hit_area_light_queue: HitAreaLightQueue,
|
||||
pub shadow_ray_queue: ShadowRayQueue,
|
||||
pub escaped_ray_queue: Option<EscapedRayQueue>,
|
||||
pub basic_material_queue: Option<MaterialEvalQueue>,
|
||||
pub universal_material_queue: Option<MaterialEvalQueue>,
|
||||
pub medium_sample_queue: Option<MediumSampleQueue>,
|
||||
pub medium_scatter_queue: Option<MediumScatterQueue>,
|
||||
pub bssrf_queue: Option<GetBSSRDFAndProbeRayQueue>,
|
||||
pub subsurface_queue: Option<SubsurfaceScatterQueue>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "use_gpu")]
|
||||
impl WavefrontPathIntegrator {
|
||||
pub fn new(scene: BasicScene) -> Self {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
535
kernels/src/workitem.rs
Normal file
535
kernels/src/workitem.rs
Normal file
|
|
@ -0,0 +1,535 @@
|
|||
#![allow(clippy::too_many_arguments)]
|
||||
use super::Float4;
|
||||
use crate::Float;
|
||||
use crate::core::geometry::{Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray, Vector3f};
|
||||
use crate::lights::LightSampleContext;
|
||||
use crate::soa_struct;
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use cust::memory::{CopyDestination, DeviceMemory};
|
||||
use cust::prelude::*;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! soa_struct {
|
||||
(
|
||||
$(#[$outer:meta])*
|
||||
pub struct $name:ident {
|
||||
$(
|
||||
pub $field:ident : $type:ty
|
||||
),* $(,)?
|
||||
}
|
||||
) => {
|
||||
#[cfg(feature = "use_gpu")]
|
||||
$(#[$outer])*
|
||||
pub struct $name {
|
||||
capacity: u32,
|
||||
pub count: cust::memory::DeviceBuffer<u32>,
|
||||
$(
|
||||
pub $field: cust::memory::DeviceBuffer<$type>,
|
||||
)*
|
||||
}
|
||||
|
||||
#[cfg(feature = "use_gpu")]
|
||||
impl $name {
|
||||
pub fn new(capacity: usize) -> cust::error::CudaResult<Self> {
|
||||
use cust::memory::DeviceBuffer;
|
||||
Ok(Self {
|
||||
capacity: capacity as u32,
|
||||
count: DeviceBuffer::zeroed(1)?,
|
||||
$(
|
||||
$field: DeviceBuffer::zeroed(capacity)?,
|
||||
)*
|
||||
})
|
||||
}
|
||||
|
||||
pub fn len(&self) -> cust::error::CudaResult<u32> {
|
||||
let mut host_count = [0u32; 1];
|
||||
self.count.copy_to(&mut host_count)?;
|
||||
Ok(host_count[0])
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) -> cust::error::CudaResult<()> {
|
||||
self.count.copy_from(&[0])
|
||||
}
|
||||
|
||||
// Generate the View name
|
||||
pub fn as_view(&mut self) -> paste::paste! { [<$name View>] } {
|
||||
paste::paste! {
|
||||
[<$name View>] {
|
||||
capacity: self.capacity,
|
||||
count: self.count.as_device_ptr().as_mut_ptr(),
|
||||
$(
|
||||
$field: self.$field.as_device_ptr().as_raw() as *mut $type,
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
paste::paste! {
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct [<$name View>] {
|
||||
pub capacity: u32,
|
||||
pub count: *mut u32,
|
||||
$(
|
||||
pub $field: *mut $type,
|
||||
)*
|
||||
}
|
||||
|
||||
unsafe impl cust::memory::DeviceCopy for [<$name View>] {}
|
||||
|
||||
impl [<$name View>] {
|
||||
// The raw push that fills every field
|
||||
#[cfg(feature = "use_gpu")]
|
||||
pub unsafe fn push(&self, $( $field : $type ),* ) -> Option<u32> {
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
let index = unsafe {
|
||||
let counter_ptr = self.count as *mut AtomicU32;
|
||||
(*counter_ptr).fetch_add(1, Ordering::Relaxed)
|
||||
};
|
||||
|
||||
if index >= self.capacity {
|
||||
return None;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
$(
|
||||
*self.$field.add(index as usize) = $field;
|
||||
)*
|
||||
}
|
||||
|
||||
Some(index)
|
||||
}
|
||||
|
||||
#[cfg(feature = "use_gpu")]
|
||||
pub unsafe fn size(&self) -> u32 {
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
unsafe {
|
||||
(*(self.count as *const AtomicU32)).load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
#[cfg(feature = "use_gpu")]
|
||||
pub fn [<$field _ptr>](&self) -> *mut $type {
|
||||
self.$field
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct RaySamplesDirect {
|
||||
pub u: Point2f,
|
||||
pub uc: Float,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct RaySamplesIndirect {
|
||||
pub uc: Float,
|
||||
pub rr: Float,
|
||||
pub u: Point2f,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct RaySamplesSubsurface {
|
||||
pub uc: Float,
|
||||
pub u: Point2f,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct RaySamples {
|
||||
pub direct: RaySamplesDirect,
|
||||
pub indirect: RaySamplesIndirect,
|
||||
pub have_subsurface: bool,
|
||||
pub subsurface: RaySamplesSubsurface,
|
||||
}
|
||||
|
||||
soa_struct! {
|
||||
pub struct RayQueue {
|
||||
pub ray_o: Point3f,
|
||||
pub ray_d: Vector3f,
|
||||
|
||||
pub depth: i32,
|
||||
pub lambda: SampledWavelengths,
|
||||
pub pixel_index: u32,
|
||||
|
||||
pub beta: SampledSpectrum,
|
||||
pub r_u: SampledSpectrum,
|
||||
pub r_l: SampledSpectrum,
|
||||
|
||||
pub ctx_pi: Point3f,
|
||||
pub ctx_n: Normal3f,
|
||||
pub ctx_ns: Normal3f,
|
||||
|
||||
pub eta_scale: Float,
|
||||
pub specular_bounce: u32,
|
||||
pub any_non_specular_bounces: u32,
|
||||
}
|
||||
}
|
||||
|
||||
soa_struct! {
|
||||
pub struct PixelSampleStateStorage {
|
||||
pub p_pixel: Point2i,
|
||||
pub l: SampledSpectrum,
|
||||
pub lambda: SampledWavelengths,
|
||||
pub filter_weight: Float,
|
||||
pub visible_surface: u32,
|
||||
pub camera_ray_weight: SampledSpectrum,
|
||||
|
||||
pub rs_direct_packed: Float4,
|
||||
pub rs_indirect_packed: Float4,
|
||||
pub rs_subsurface_packed: Float4,
|
||||
}
|
||||
}
|
||||
|
||||
soa_struct! {
|
||||
pub struct EscapedRayQueue {
|
||||
pub ray_o: Point3f,
|
||||
pub ray_d: Vector3f,
|
||||
pub depth: i32,
|
||||
pub lambda: SampledWavelengths,
|
||||
pub pixel_index: u32,
|
||||
pub beta: SampledSpectrum,
|
||||
pub specular_bounce: u32,
|
||||
pub r_u: SampledSpectrum,
|
||||
pub r_l: SampledSpectrum,
|
||||
pub ctx_pi: Point3f,
|
||||
pub ctx_n: Normal3f,
|
||||
pub ctx_ns: Normal3f,
|
||||
}
|
||||
}
|
||||
|
||||
soa_struct! {
|
||||
pub struct HitAreaLightQueue {
|
||||
pub area_light_id: u32, // Light ID
|
||||
pub p: Point3f,
|
||||
pub n: Normal3f,
|
||||
pub uv: Point2f,
|
||||
pub wo: Vector3f,
|
||||
pub lambda: SampledWavelengths,
|
||||
pub depth: i32,
|
||||
pub beta: SampledSpectrum,
|
||||
pub r_u: SampledSpectrum,
|
||||
pub r_l: SampledSpectrum,
|
||||
pub ctx_pi: Point3f,
|
||||
pub ctx_n: Normal3f,
|
||||
pub ctx_ns: Normal3f,
|
||||
pub specular_bounce: u32,
|
||||
pub pixel_index: u32,
|
||||
}
|
||||
}
|
||||
|
||||
soa_struct! {
|
||||
pub struct ShadowRayQueue {
|
||||
pub ray_o: Point3f,
|
||||
pub ray_d: Vector3f,
|
||||
pub t_max: Float,
|
||||
pub lambda: SampledWavelengths,
|
||||
pub ld: SampledSpectrum,
|
||||
pub r_u: SampledSpectrum,
|
||||
pub r_l: SampledSpectrum,
|
||||
pub pixel_index: u32,
|
||||
}
|
||||
}
|
||||
|
||||
soa_struct! {
|
||||
pub struct GetBSSRDFAndProbeRayQueue {
|
||||
pub material_id: u32,
|
||||
pub lambda: SampledWavelengths,
|
||||
pub beta: SampledSpectrum,
|
||||
pub r_u: SampledSpectrum,
|
||||
pub p: Point3f,
|
||||
pub wo: Vector3f,
|
||||
pub n: Normal3f,
|
||||
pub ns: Normal3f,
|
||||
pub dpdus: Vector3f,
|
||||
pub uv: Point2f,
|
||||
pub depth: i32,
|
||||
pub mi_inside: u32,
|
||||
pub mi_outside: u32,
|
||||
pub eta_scale: Float,
|
||||
pub pixel_index: u32,
|
||||
}
|
||||
}
|
||||
|
||||
soa_struct! {
|
||||
pub struct SubsurfaceScatterQueue {
|
||||
pub p0: Point3f,
|
||||
pub p1: Point3f,
|
||||
pub depth: i32,
|
||||
pub material_id: u32,
|
||||
pub lambda: SampledWavelengths,
|
||||
pub beta: SampledSpectrum,
|
||||
pub r_u: SampledSpectrum,
|
||||
pub mi_inside: u32,
|
||||
pub mi_outside: u32,
|
||||
pub eta_scale: Float,
|
||||
pub pixel_index: u32,
|
||||
}
|
||||
}
|
||||
|
||||
soa_struct! {
|
||||
pub struct MediumSampleQueue {
|
||||
pub ray_o: Point3f,
|
||||
pub ray_d: Vector3f,
|
||||
pub t_max: Float,
|
||||
pub lambda: SampledWavelengths,
|
||||
pub beta: SampledSpectrum,
|
||||
pub r_u: SampledSpectrum,
|
||||
pub r_l: SampledSpectrum,
|
||||
pub pixel_index: u32,
|
||||
|
||||
pub ctx_pi: Point3f,
|
||||
pub ctx_n: Normal3f,
|
||||
pub ctx_ns: Normal3f,
|
||||
|
||||
pub specular_bounce: u32,
|
||||
pub any_non_specular_bounces: u32,
|
||||
pub eta_scale: Float,
|
||||
|
||||
pub area_light_id: u32,
|
||||
pub pi: Point3fi,
|
||||
pub n: Normal3f,
|
||||
pub dpdu: Vector3f,
|
||||
pub dpdv: Vector3f,
|
||||
pub wo: Vector3f,
|
||||
pub uv: Point2f,
|
||||
pub material_id: u32,
|
||||
pub ns: Normal3f,
|
||||
pub dpdus: Vector3f,
|
||||
pub dpdvs: Vector3f,
|
||||
pub dndus: Normal3f,
|
||||
pub dndvs: Normal3f,
|
||||
pub face_index: i32,
|
||||
pub mi_inside: u32,
|
||||
pub mi_outside: u32,
|
||||
}
|
||||
}
|
||||
|
||||
soa_struct! {
|
||||
pub struct MaterialEvalQueue {
|
||||
pub material_id: u32,
|
||||
pub pi: Point3fi,
|
||||
pub n: Normal3f,
|
||||
pub dpdu: Vector3f,
|
||||
pub dpdv: Vector3f,
|
||||
pub time: Float,
|
||||
pub depth: i32,
|
||||
pub ns: Normal3f,
|
||||
pub dpdus: Vector3f,
|
||||
pub dpdvs: Vector3f,
|
||||
pub dndus: Normal3f,
|
||||
pub dndvs: Normal3f,
|
||||
pub uv: Point2f,
|
||||
pub face_index: i32,
|
||||
pub lambda: SampledWavelengths,
|
||||
pub pixel_index: u32,
|
||||
pub any_non_specular_bounces: u32,
|
||||
pub wo: Vector3f,
|
||||
pub beta: SampledSpectrum,
|
||||
pub r_u: SampledSpectrum,
|
||||
pub eta_scale: Float,
|
||||
pub mi_inside: u32,
|
||||
pub mi_outside: u32,
|
||||
}
|
||||
}
|
||||
|
||||
soa_struct! {
|
||||
pub struct MediumScatterQueue {
|
||||
pub p: Point3f,
|
||||
pub depth: usize,
|
||||
pub lambda: SampledWavelengths,
|
||||
pub beta: SampledSpectrum,
|
||||
pub r_u: SampledSpectrum,
|
||||
pub wo: Vector3f,
|
||||
pub time: Float,
|
||||
pub eta_scale: Float,
|
||||
pub pixel_index: usize,
|
||||
|
||||
// ID
|
||||
pub phase_function: u32,
|
||||
pub medium: u32,
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct RayWorkItem {
|
||||
pub ray: Ray,
|
||||
pub depth: i32,
|
||||
pub lambda: SampledWavelengths,
|
||||
pub pixel_index: u32,
|
||||
pub beta: SampledSpectrum,
|
||||
pub r_u: SampledSpectrum,
|
||||
pub r_l: SampledSpectrum,
|
||||
pub prev_intr_ctx: LightSampleContext,
|
||||
pub eta_scale: Float,
|
||||
pub specular_bounce: bool,
|
||||
pub any_non_specular_bounces: bool,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct EscapedRayWorkItem {
|
||||
pub ray_o: Point3f,
|
||||
pub ray_d: Vector3f,
|
||||
pub depth: i32,
|
||||
pub lambda: SampledWavelengths,
|
||||
pub pixel_index: u32,
|
||||
pub beta: SampledSpectrum,
|
||||
pub specular_bounce: bool,
|
||||
pub r_u: SampledSpectrum,
|
||||
pub r_l: SampledSpectrum,
|
||||
pub prev_intr_ctx: LightSampleContext,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ShadowRayWorkItem {
|
||||
pub ray: Ray,
|
||||
pub t_max: Float,
|
||||
pub lambda: SampledWavelengths,
|
||||
pub ld: SampledSpectrum,
|
||||
pub r_u: SampledSpectrum,
|
||||
pub r_l: SampledSpectrum,
|
||||
pub pixel_index: u32,
|
||||
}
|
||||
|
||||
impl RayQueueView {
|
||||
#[cfg(feature = "use_gpu")]
|
||||
pub unsafe fn push_work_item(&self, item: RayWorkItem) -> Option<u32> {
|
||||
unsafe {
|
||||
self.push(
|
||||
item.ray.o,
|
||||
item.ray.d,
|
||||
item.depth,
|
||||
item.lambda,
|
||||
item.pixel_index,
|
||||
item.beta,
|
||||
item.r_u,
|
||||
item.r_l,
|
||||
item.prev_intr_ctx.pi.into(),
|
||||
item.prev_intr_ctx.n,
|
||||
item.prev_intr_ctx.ns,
|
||||
item.eta_scale,
|
||||
if item.specular_bounce { 1 } else { 0 },
|
||||
if item.any_non_specular_bounces { 1 } else { 0 },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EscapedRayQueueView {
|
||||
#[cfg(feature = "use_gpu")]
|
||||
pub unsafe fn push_work_item(&self, r: &RayWorkItem) -> Option<u32> {
|
||||
unsafe {
|
||||
self.push(
|
||||
r.ray.o,
|
||||
r.ray.d,
|
||||
r.depth,
|
||||
r.lambda,
|
||||
r.pixel_index,
|
||||
r.beta,
|
||||
if r.specular_bounce { 1 } else { 0 },
|
||||
r.r_u,
|
||||
r.r_l,
|
||||
r.prev_intr_ctx.pi.into(),
|
||||
r.prev_intr_ctx.n,
|
||||
r.prev_intr_ctx.ns,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelSampleStateStorageView {
|
||||
#[cfg(feature = "use_gpu")]
|
||||
pub unsafe fn get_samples(&self, index: u32) -> RaySamples {
|
||||
let i = index as usize;
|
||||
|
||||
let (dir, ind, ss) = unsafe {
|
||||
(
|
||||
*self.rs_direct_packed.add(i),
|
||||
*self.rs_indirect_packed.add(i),
|
||||
*self.rs_subsurface_packed.add(i),
|
||||
)
|
||||
};
|
||||
|
||||
let direct_u = Point2f::new(dir.v[0], dir.v[1]);
|
||||
let direct_uc = dir.v[2];
|
||||
let flags = dir.v[3] as i32;
|
||||
let have_subsurface = (flags & 1) != 0;
|
||||
|
||||
let indirect_uc = ind.v[0];
|
||||
let indirect_rr = ind.v[1];
|
||||
let indirect_u = Point2f::new(ind.v[2], ind.v[3]);
|
||||
|
||||
let subsurface_uc = ss.v[0];
|
||||
let subsurface_u = Point2f::new(ss.v[1], ss.v[2]);
|
||||
|
||||
RaySamples {
|
||||
direct: RaySamplesDirect {
|
||||
u: direct_u,
|
||||
uc: direct_uc,
|
||||
},
|
||||
indirect: RaySamplesIndirect {
|
||||
uc: indirect_uc,
|
||||
rr: indirect_rr,
|
||||
u: indirect_u,
|
||||
},
|
||||
have_subsurface,
|
||||
subsurface: RaySamplesSubsurface {
|
||||
uc: subsurface_uc,
|
||||
u: subsurface_u,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "use_gpu")]
|
||||
pub unsafe fn set_samples(&self, index: u32, rs: RaySamples) {
|
||||
if index >= self.capacity {
|
||||
return;
|
||||
}
|
||||
let i = index as usize;
|
||||
|
||||
let flags = if rs.have_subsurface { 1.0 } else { 0.0 };
|
||||
let dir = Float4 {
|
||||
v: [rs.direct.u.0[0], rs.direct.u.0[1], rs.direct.uc, flags],
|
||||
};
|
||||
|
||||
let ind = Float4 {
|
||||
v: [
|
||||
rs.indirect.uc,
|
||||
rs.indirect.rr,
|
||||
rs.indirect.u.0[0],
|
||||
rs.indirect.u.0[1],
|
||||
],
|
||||
};
|
||||
|
||||
unsafe {
|
||||
*self.rs_direct_packed.add(i) = dir;
|
||||
*self.rs_indirect_packed.add(i) = ind;
|
||||
}
|
||||
|
||||
if rs.have_subsurface {
|
||||
let ss = Float4 {
|
||||
v: [
|
||||
rs.subsurface.uc,
|
||||
rs.subsurface.u.0[0],
|
||||
rs.subsurface.u.0[1],
|
||||
0.0,
|
||||
],
|
||||
};
|
||||
unsafe {
|
||||
*self.rs_subsurface_packed.add(i) = ss;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
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];
|
||||
}
|
||||
|
|
@ -15,9 +15,6 @@ num-traits = "0.2.19"
|
|||
once_cell = "1.21.3"
|
||||
smallvec = "1.15.1"
|
||||
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]
|
||||
use_f64 = []
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
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\"))");
|
||||
}
|
||||
|
|
@ -1,468 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,370 +0,0 @@
|
|||
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!()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,644 +0,0 @@
|
|||
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>;
|
||||
|
|
@ -1,240 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
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};
|
||||
|
|
@ -6,4 +6,4 @@ mod spherical;
|
|||
pub use orthographic::OrthographicCamera;
|
||||
pub use perspective::PerspectiveCamera;
|
||||
pub use realistic::RealisticCamera;
|
||||
pub use spherical::{SphericalCamera, Mapping};
|
||||
pub use spherical::SphericalCamera;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use crate::core::geometry::{
|
|||
use crate::core::medium::Medium;
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::core::sampler::CameraSample;
|
||||
use crate::images::ImageMetadata;
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::Transform;
|
||||
use crate::utils::sampling::sample_uniform_disk_concentric;
|
||||
|
|
@ -41,12 +42,11 @@ impl OrthographicCamera {
|
|||
-screen_window.p_max.y(),
|
||||
0.,
|
||||
));
|
||||
|
||||
let mut base_ortho = base;
|
||||
let film = base.film;
|
||||
if film.is_null() {
|
||||
let film_ptr = base.film;
|
||||
if film_ptr.is_null() {
|
||||
panic!("Camera must have a film");
|
||||
}
|
||||
let film = unsafe { &*film_ptr };
|
||||
|
||||
let raster_from_ndc = Transform::scale(
|
||||
film.full_resolution().x() as Float,
|
||||
|
|
@ -60,6 +60,7 @@ impl OrthographicCamera {
|
|||
let camera_from_raster = screen_from_camera.inverse() * screen_from_raster;
|
||||
let dx_camera = camera_from_raster.apply_to_vector(Vector3f::new(1., 0., 0.));
|
||||
let dy_camera = camera_from_raster.apply_to_vector(Vector3f::new(0., 1., 0.));
|
||||
let mut base_ortho = base;
|
||||
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_pos_differential_x = dx_camera;
|
||||
|
|
@ -79,6 +80,11 @@ impl 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 {
|
||||
&self.base
|
||||
}
|
||||
|
|
@ -96,7 +102,7 @@ impl CameraTrait for OrthographicCamera {
|
|||
p_camera,
|
||||
Vector3f::new(0., 0., 1.),
|
||||
Some(self.sample_time(sample.time)),
|
||||
&*self.base().medium,
|
||||
self.base().medium.clone(),
|
||||
);
|
||||
if self.lens_radius > 0. {
|
||||
let p_lens_vec =
|
||||
|
|
@ -127,11 +133,11 @@ impl CameraTrait for OrthographicCamera {
|
|||
let mut rd = RayDifferential::default();
|
||||
if self.lens_radius > 0.0 {
|
||||
let mut sample_x = sample;
|
||||
sample_x.p_film[0] += 1.0;
|
||||
sample_x.p_film.x += 1.0;
|
||||
let rx = self.generate_ray(sample_x, lambda)?;
|
||||
|
||||
let mut sample_y = sample;
|
||||
sample_y.p_film[1] += 1.0;
|
||||
sample_y.p_film.y += 1.0;
|
||||
let ry = self.generate_ray(sample_y, lambda)?;
|
||||
|
||||
rd.rx_origin = rx.ray.o;
|
||||
|
|
@ -155,7 +161,7 @@ impl CameraTrait for OrthographicCamera {
|
|||
rd.rx_direction = central_cam_ray.ray.d;
|
||||
rd.ry_direction = central_cam_ray.ray.d;
|
||||
}
|
||||
central_cam_ray.ray.differential = rd;
|
||||
central_cam_ray.ray.differential = Some(rd);
|
||||
Some(central_cam_ray)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
|
||||
use crate::core::film::Film;
|
||||
use crate::core::filter::FilterTrait;
|
||||
use crate::core::geometry::{
|
||||
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
|
||||
};
|
||||
|
|
@ -11,8 +10,7 @@ use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
|||
use crate::utils::sampling::sample_uniform_disk_concentric;
|
||||
use crate::utils::transform::Transform;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug)]
|
||||
pub struct PerspectiveCamera {
|
||||
pub base: CameraBase,
|
||||
pub screen_from_camera: Transform,
|
||||
|
|
@ -79,7 +77,12 @@ impl PerspectiveCamera {
|
|||
}
|
||||
}
|
||||
|
||||
impl CameraTrait for PerspectiveCamera {
|
||||
impl PerspectiveCamera {
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
|
||||
self.base.init_metadata(metadata)
|
||||
}
|
||||
|
||||
fn base(&self) -> &CameraBase {
|
||||
&self.base
|
||||
}
|
||||
|
|
@ -98,7 +101,7 @@ impl CameraTrait for PerspectiveCamera {
|
|||
Point3f::new(0., 0., 0.),
|
||||
p_vector.normalize(),
|
||||
Some(self.sample_time(sample.time)),
|
||||
&*self.base().medium,
|
||||
self.base().medium.clone(),
|
||||
);
|
||||
// Modify ray for depth of field
|
||||
if self.lens_radius > 0. {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
use crate::PI;
|
||||
use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
|
||||
use crate::core::color::SRGB;
|
||||
use crate::core::film::Film;
|
||||
use crate::core::geometry::{
|
||||
Bounds2f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector2i, Vector3f, VectorLike,
|
||||
};
|
||||
use crate::core::image::{DeviceImage, PixelFormat};
|
||||
use crate::core::medium::Medium;
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::core::sampler::CameraSample;
|
||||
use crate::core::scattering::refract;
|
||||
use crate::images::{Image, PixelFormat};
|
||||
use crate::spectra::color::SRGB;
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::Ptr;
|
||||
use crate::utils::math::{lerp, quadratic, square};
|
||||
|
||||
#[repr(C)]
|
||||
|
|
@ -38,8 +37,8 @@ pub struct RealisticCamera {
|
|||
base: CameraBase,
|
||||
focus_distance: Float,
|
||||
set_aperture_diameter: Float,
|
||||
aperture_image: Ptr<DeviceImage>,
|
||||
element_interfaces: Ptr<LensElementInterface>,
|
||||
aperture_image: *const Image,
|
||||
element_interfaces: *const LensElementInterface,
|
||||
n_elements: usize,
|
||||
physical_extent: Bounds2f,
|
||||
exit_pupil_bounds: [Bounds2f; EXIT_PUPIL_SAMPLES],
|
||||
|
|
@ -52,13 +51,13 @@ impl RealisticCamera {
|
|||
lens_params: &[Float],
|
||||
focus_distance: Float,
|
||||
set_aperture_diameter: Float,
|
||||
aperture_image: Ptr<DeviceImage>,
|
||||
aperture_image: Option<Image>,
|
||||
) -> Self {
|
||||
let film_ptr = base.film;
|
||||
if film_ptr.is_null() {
|
||||
panic!("Camera must have a film");
|
||||
}
|
||||
let film = &*film_ptr;
|
||||
let film = unsafe { &*film_ptr };
|
||||
|
||||
let aspect = film.full_resolution().x() as Float / film.full_resolution().y() as Float;
|
||||
let diagonal = film.diagonal();
|
||||
|
|
@ -91,6 +90,7 @@ impl RealisticCamera {
|
|||
element_interface.push(el_int);
|
||||
}
|
||||
|
||||
let n_samples = 64;
|
||||
let half_diag = film.diagonal() / 2.0;
|
||||
let mut exit_pupil_bounds = [Bounds2f::default(); EXIT_PUPIL_SAMPLES];
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ impl RealisticCamera {
|
|||
Self {
|
||||
base,
|
||||
focus_distance,
|
||||
element_interfaces: Ptr::from(element_interfaces),
|
||||
element_interfaces,
|
||||
n_elements,
|
||||
physical_extent,
|
||||
set_aperture_diameter,
|
||||
|
|
@ -123,16 +123,14 @@ impl RealisticCamera {
|
|||
}
|
||||
|
||||
pub fn compute_thick_lens_approximation(&self) -> ([Float; 2], [Float; 2]) {
|
||||
use crate::utils::Ptr;
|
||||
|
||||
let x = 0.001 * self.get_film().diagonal();
|
||||
let r_scene = Ray::new(
|
||||
Point3f::new(0., x, self.lens_front_z() + 1.),
|
||||
Vector3f::new(0., 0., -1.),
|
||||
None,
|
||||
&Ptr::null(),
|
||||
None,
|
||||
);
|
||||
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!(
|
||||
"Unable to trace ray from scene to film for thick lens approx. Is aperture very small?"
|
||||
)
|
||||
|
|
@ -142,14 +140,14 @@ impl RealisticCamera {
|
|||
Point3f::new(x, 0., self.lens_rear_z() - 1.),
|
||||
Vector3f::new(0., 0., 1.),
|
||||
None,
|
||||
&Ptr::null(),
|
||||
None,
|
||||
);
|
||||
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!(
|
||||
"Unable to trace ray from scene to film for thick lens approx. Is aperture very small?"
|
||||
)
|
||||
};
|
||||
let (pz1, fz1) = Self::compute_cardinal_points(r_film, r_scene);
|
||||
let (pz1, f_1) = Self::compute_cardinal_points(r_film, r_scene);
|
||||
([pz0, pz1], [fz0, fz1])
|
||||
}
|
||||
|
||||
|
|
@ -157,23 +155,19 @@ impl RealisticCamera {
|
|||
let (pz, fz) = self.compute_thick_lens_approximation();
|
||||
let f = fz[0] - pz[0];
|
||||
let z = -focus_distance;
|
||||
let c = (pz[1] - z - pz[0]) * (pz[1] - z - 4. * f - pz[0]);
|
||||
if c <= 0. {
|
||||
let c = (pz[1] - z - pz[0]) * (pz[1] - z - 4 * f - pz[0]);
|
||||
if c <= 0 {
|
||||
panic!(
|
||||
"Coefficient must be positive. It looks focusDistance {} is too short for a given lenses configuration",
|
||||
focus_distance
|
||||
focusDistance
|
||||
);
|
||||
}
|
||||
let delta = (pz[1] - z + pz[0] - c.sqrt()) / 2.;
|
||||
let last_interface = unsafe { self.element_interfaces.add(self.n_elements) };
|
||||
last_interface.thickness + delta
|
||||
self.element_interface.last().thickness + delta
|
||||
}
|
||||
|
||||
pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f {
|
||||
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)
|
||||
Self::compute_exit_pupil_bounds(&self.element_interface, film_x_0, film_x_1)
|
||||
}
|
||||
|
||||
fn compute_exit_pupil_bounds(
|
||||
|
|
@ -209,10 +203,7 @@ impl RealisticCamera {
|
|||
|
||||
// Expand pupil bounds if ray makes it through the lens system
|
||||
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, &Ptr::null()),
|
||||
None,
|
||||
)
|
||||
&& trace_lenses_from_film(Ray::new(p_film, p_rear - p_film, None, None), None)
|
||||
{
|
||||
pupil_bounds = pupil_bounds.union_point(Point2f::new(p_rear.x(), p_rear.y()));
|
||||
}
|
||||
|
|
@ -232,7 +223,7 @@ impl RealisticCamera {
|
|||
|
||||
pub fn sample_exit_pupil(&self, p_film: Point2f, u_lens: Point2f) -> Option<ExitPupilSample> {
|
||||
// Find exit pupil bound for sample distance from film center
|
||||
let film = self.get_film();
|
||||
let film = self.film();
|
||||
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();
|
||||
r_index = (self.exit_pupil_bounds.len() - 1).min(r_index);
|
||||
|
|
@ -274,11 +265,11 @@ impl RealisticCamera {
|
|||
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()),
|
||||
Some(r_camera.time),
|
||||
&Ptr::null(),
|
||||
None,
|
||||
);
|
||||
|
||||
for i in (0..self.n_elements - 1).rev() {
|
||||
let element: &LensElementInterface = unsafe { &self.element_interfaces.add(i) };
|
||||
for i in (0..self.element_interface.len() - 1).rev() {
|
||||
let element: &LensElementInterface = &self.element_interface[i];
|
||||
// Update ray from film accounting for interaction with _element_
|
||||
element_z -= element.thickness;
|
||||
|
||||
|
|
@ -317,9 +308,8 @@ impl RealisticCamera {
|
|||
// Update ray path for element interface interaction
|
||||
if !is_stop {
|
||||
let eta_i = element.eta;
|
||||
let interface_i = unsafe { self.element_interfaces.add(i - 1) };
|
||||
let eta_t = if i > 0 && interface_i.eta != 0. {
|
||||
interface_i.eta
|
||||
let eta_t = if i > 0 && self.element_interface[i - 1].eta != 0. {
|
||||
self.element_interface[i - 1].eta
|
||||
} else {
|
||||
1.
|
||||
};
|
||||
|
|
@ -341,7 +331,7 @@ impl RealisticCamera {
|
|||
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()),
|
||||
Some(r_lens.time),
|
||||
&Ptr::null(),
|
||||
None,
|
||||
);
|
||||
|
||||
Some((weight, r_out))
|
||||
|
|
@ -378,35 +368,49 @@ impl RealisticCamera {
|
|||
}
|
||||
|
||||
pub fn lens_rear_z(&self) -> Float {
|
||||
let last_interface = unsafe { self.element_interfaces.add(self.n_elements - 1) };
|
||||
last_interface.thickness
|
||||
self.element_interface.last().unwrap().thickness
|
||||
}
|
||||
|
||||
pub fn lens_front_z(&self) -> Float {
|
||||
let mut z_sum = 0.;
|
||||
for i in 0..self.n_elements {
|
||||
let element = unsafe { self.element_interfaces.add(i) };
|
||||
for element in &self.element_interface {
|
||||
z_sum += element.thickness;
|
||||
}
|
||||
z_sum
|
||||
}
|
||||
|
||||
pub fn rear_element_radius(&self) -> Float {
|
||||
let last_interface = unsafe { self.element_interfaces.add(self.n_elements - 1) };
|
||||
last_interface.aperture_radius
|
||||
self.element_interface.last().unwrap().aperture_radius
|
||||
}
|
||||
}
|
||||
|
||||
impl CameraTrait for RealisticCamera {
|
||||
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
|
||||
self.base.init_metadata(metadata)
|
||||
}
|
||||
|
||||
fn base(&self) -> &CameraBase {
|
||||
&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(
|
||||
&self,
|
||||
sample: CameraSample,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> Option<CameraRay> {
|
||||
// Find point on film, _pFilm_, corresponding to _sample.pFilm_
|
||||
let film = self.get_film();
|
||||
let s = Point2f::new(
|
||||
sample.p_film.x() / film.full_resolution().x() as Float,
|
||||
|
|
@ -419,7 +423,7 @@ impl CameraTrait for RealisticCamera {
|
|||
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 r_film = Ray::new(p_film, p_pupil - p_film, None, &Ptr::null());
|
||||
let r_film = Ray::new(p_film, p_pupil - p_film, None, None);
|
||||
let (weight, mut ray) = self.trace_lenses_from_film(&r_film)?;
|
||||
if weight == 0. {
|
||||
return None;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
|
||||
use crate::core::camera::{CameraBase, CameraRay, CameraTransform};
|
||||
use crate::core::film::Film;
|
||||
use crate::core::geometry::{Bounds2f, Point2f, Point3f, Ray, Vector3f, spherical_direction};
|
||||
use crate::core::medium::Medium;
|
||||
|
|
@ -6,6 +6,7 @@ use crate::core::pbrt::{Float, PI};
|
|||
use crate::core::sampler::CameraSample;
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
|
|
@ -14,18 +15,36 @@ pub enum Mapping {
|
|||
EqualArea,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct SphericalCamera {
|
||||
pub mapping: Mapping,
|
||||
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 {
|
||||
fn base(&self) -> &CameraBase {
|
||||
&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(
|
||||
&self,
|
||||
sample: CameraSample,
|
||||
|
|
@ -39,6 +58,7 @@ impl CameraTrait for SphericalCamera {
|
|||
);
|
||||
let dir: Vector3f;
|
||||
if self.mapping == Mapping::EquiRectangular {
|
||||
// Compute ray direction using equirectangular mapping
|
||||
let theta = PI * uv[1];
|
||||
let phi = 2. * PI * uv[0];
|
||||
dir = spherical_direction(theta.sin(), theta.cos(), phi);
|
||||
|
|
@ -47,13 +67,13 @@ impl CameraTrait for SphericalCamera {
|
|||
uv = wrap_equal_area_square(&mut uv);
|
||||
dir = equal_area_square_to_sphere(uv);
|
||||
}
|
||||
core::mem::swap(&mut dir.y(), &mut dir.z());
|
||||
std::mem::swap(&mut dir.y(), &mut dir.z());
|
||||
|
||||
let ray = Ray::new(
|
||||
Point3f::new(0., 0., 0.),
|
||||
dir,
|
||||
Some(self.sample_time(sample.time)),
|
||||
&self.base().medium,
|
||||
self.base().medium.clone(),
|
||||
);
|
||||
Some(CameraRay {
|
||||
ray: self.render_from_camera(&ray, &mut None),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use crate::core::geometry::{Bounds3f, Point3f, Ray, Vector3f};
|
||||
use crate::core::pbrt::{Float, find_interval};
|
||||
use crate::core::primitive::PrimitiveTrait;
|
||||
use crate::shapes::ShapeIntersection;
|
||||
use crate::utils::math::encode_morton_3;
|
||||
use crate::utils::math::next_float_down;
|
||||
use crate::utils::partition_slice;
|
||||
use rayon::prelude::*;
|
||||
use shared::Float;
|
||||
use shared::core::geometry::{Bounds3f, Point3f, Ray, Vector3f};
|
||||
use shared::core::primitive::PrimitiveTrait;
|
||||
use shared::core::shape::ShapeIntersection;
|
||||
use shared::utils::math::encode_morton_3;
|
||||
use shared::utils::{find_interval, partition_slice};
|
||||
use std::cmp::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
|
||||
|
|
@ -12,7 +13,7 @@ use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
|
|||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SplitMethod {
|
||||
AH,
|
||||
SAH,
|
||||
Hlbvh,
|
||||
Middle,
|
||||
EqualCounts,
|
||||
|
|
@ -344,6 +345,7 @@ impl BVHAggregate {
|
|||
4,
|
||||
);
|
||||
|
||||
// Add thread-local count to global atomic
|
||||
total_nodes.fetch_add(nodes_created, AtomicOrdering::Relaxed);
|
||||
|
||||
root
|
||||
|
|
@ -1,190 +0,0 @@
|
|||
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()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
use crate::bxdfs::NormalizedFresnelBxDF;
|
||||
use crate::core::bsdf::BSDF;
|
||||
use crate::core::bxdf::BSDF;
|
||||
use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Point3fi, Vector3f};
|
||||
use crate::core::interaction::{InteractionBase, ShadingGeom, SurfaceInteraction};
|
||||
use crate::core::shape::Shape;
|
||||
use crate::core::interaction::{InteractionData, Shadinggeom, SurfaceInteraction};
|
||||
use crate::core::pbrt::{Float, PI};
|
||||
use crate::shapes::Shape;
|
||||
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum};
|
||||
use crate::utils::Ptr;
|
||||
use crate::utils::math::{catmull_rom_weights, square};
|
||||
use crate::utils::sampling::sample_catmull_rom_2d;
|
||||
use crate::{Float, PI};
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
@ -21,13 +19,13 @@ pub struct BSSRDFSample {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SubsurfaceInteraction {
|
||||
pub pi: Point3fi,
|
||||
pub n: Normal3f,
|
||||
pub ns: Normal3f,
|
||||
pub dpdu: Vector3f,
|
||||
pub dpdv: Vector3f,
|
||||
pub dpdus: Vector3f,
|
||||
pub dpdvs: Vector3f,
|
||||
pi: Point3fi,
|
||||
n: Normal3f,
|
||||
ns: Normal3f,
|
||||
dpdu: Vector3f,
|
||||
dpdv: Vector3f,
|
||||
dpdus: Vector3f,
|
||||
dpdvs: Vector3f,
|
||||
}
|
||||
|
||||
impl SubsurfaceInteraction {
|
||||
|
|
@ -65,12 +63,20 @@ impl From<SurfaceInteraction> for SubsurfaceInteraction {
|
|||
impl From<&SubsurfaceInteraction> for SurfaceInteraction {
|
||||
fn from(ssi: &SubsurfaceInteraction) -> SurfaceInteraction {
|
||||
SurfaceInteraction {
|
||||
common: InteractionBase::new_minimal(ssi.pi, ssi.n),
|
||||
common: InteractionData {
|
||||
pi: ssi.pi,
|
||||
n: ssi.n,
|
||||
wo: Vector3f::zero(),
|
||||
time: 0.,
|
||||
medium_interface: None,
|
||||
medium: None,
|
||||
},
|
||||
uv: Point2f::zero(),
|
||||
dpdu: ssi.dpdu,
|
||||
dpdv: ssi.dpdv,
|
||||
dndu: Normal3f::zero(),
|
||||
dndv: Normal3f::zero(),
|
||||
shading: ShadingGeom {
|
||||
shading: Shadinggeom {
|
||||
n: ssi.ns,
|
||||
dpdu: ssi.dpdus,
|
||||
dpdv: ssi.dpdvs,
|
||||
|
|
@ -78,84 +84,70 @@ impl From<&SubsurfaceInteraction> for SurfaceInteraction {
|
|||
dndv: Normal3f::zero(),
|
||||
},
|
||||
face_index: 0,
|
||||
area_light: Ptr::null(),
|
||||
material: Ptr::null(),
|
||||
area_light: None,
|
||||
material: None,
|
||||
dpdx: Vector3f::zero(),
|
||||
dpdy: Vector3f::zero(),
|
||||
dudx: 0.,
|
||||
dvdx: 0.,
|
||||
dudy: 0.,
|
||||
dvdy: 0.,
|
||||
shape: Ptr::from(&Shape::default()),
|
||||
shape: Shape::default().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BSSRDFTable {
|
||||
pub n_rho: u32,
|
||||
pub n_radius: u32,
|
||||
pub rho_samples: Ptr<Float>,
|
||||
pub radius_samples: Ptr<Float>,
|
||||
pub profile: Ptr<Float>,
|
||||
pub rho_eff: Ptr<Float>,
|
||||
pub profile_cdf: Ptr<Float>,
|
||||
rho_samples: Vec<Float>,
|
||||
radius_samples: Vec<Float>,
|
||||
profile: Vec<Float>,
|
||||
rho_eff: Vec<Float>,
|
||||
profile_cdf: Vec<Float>,
|
||||
}
|
||||
|
||||
impl BSSRDFTable {
|
||||
pub fn get_rho(&self) -> &[Float] {
|
||||
unsafe { core::slice::from_raw_parts(self.rho_samples.as_ref(), self.n_rho as usize) }
|
||||
}
|
||||
|
||||
pub fn get_radius(&self) -> &[Float] {
|
||||
unsafe { core::slice::from_raw_parts(self.radius_samples.as_ref(), self.n_radius as usize) }
|
||||
}
|
||||
|
||||
pub fn get_profile(&self) -> &[Float] {
|
||||
let n_profile = (self.n_rho * self.n_radius) as usize;
|
||||
unsafe { core::slice::from_raw_parts(self.profile.as_ref(), n_profile) }
|
||||
}
|
||||
|
||||
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 new(n_rho_samples: usize, n_radius_samples: usize) -> Self {
|
||||
let rho_samples: Vec<Float> = Vec::with_capacity(n_rho_samples);
|
||||
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);
|
||||
let profile_cdf: Vec<Float> = Vec::with_capacity(n_radius_samples * n_rho_samples);
|
||||
Self {
|
||||
rho_samples,
|
||||
radius_samples,
|
||||
profile,
|
||||
rho_eff,
|
||||
profile_cdf,
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
pub fn eval_profile(&self, rho_index: usize, radius_index: usize) -> Float {
|
||||
assert!(rho_index < self.rho_samples.len());
|
||||
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 p0: Point3f,
|
||||
pub p1: Point3f,
|
||||
}
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait BSSRDFTrait {
|
||||
pub trait BSSRDFTrait: Send + Sync + std::fmt::Debug {
|
||||
fn sample_sp(&self, u1: Float, u2: Point2f) -> Option<BSSRDFProbeSegment>;
|
||||
fn probe_intersection_to_sample(
|
||||
&self,
|
||||
si: &SubsurfaceInteraction,
|
||||
bxdf: NormalizedFresnelBxDF,
|
||||
) -> BSSRDFSample;
|
||||
fn probe_intersection_to_sample(&self, si: &SubsurfaceInteraction) -> BSSRDFSample;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[enum_dispatch(BSSRDFTrait)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BSSRDF {
|
||||
Tabulated(TabulatedBSSRDF),
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TabulatedBSSRDF {
|
||||
po: Point3f,
|
||||
wo: Vector3f,
|
||||
|
|
@ -163,7 +155,7 @@ pub struct TabulatedBSSRDF {
|
|||
eta: Float,
|
||||
sigma_t: SampledSpectrum,
|
||||
rho: SampledSpectrum,
|
||||
table: Ptr<BSSRDFTable>,
|
||||
table: Arc<BSSRDFTable>,
|
||||
}
|
||||
|
||||
impl TabulatedBSSRDF {
|
||||
|
|
@ -174,7 +166,7 @@ impl TabulatedBSSRDF {
|
|||
eta: Float,
|
||||
sigma_a: &SampledSpectrum,
|
||||
sigma_s: &SampledSpectrum,
|
||||
table: &BSSRDFTable,
|
||||
table: Arc<BSSRDFTable>,
|
||||
) -> Self {
|
||||
let sigma_t = *sigma_a + *sigma_s;
|
||||
let rho = SampledSpectrum::safe_div(sigma_s, &sigma_t);
|
||||
|
|
@ -183,9 +175,9 @@ impl TabulatedBSSRDF {
|
|||
wo,
|
||||
ns,
|
||||
eta,
|
||||
table,
|
||||
sigma_t,
|
||||
rho,
|
||||
table: Ptr::from(table),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -195,17 +187,16 @@ impl TabulatedBSSRDF {
|
|||
|
||||
pub fn sr(&self, r: Float) -> SampledSpectrum {
|
||||
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 {
|
||||
let r_optical = r * self.sigma_t[i];
|
||||
let (rho_offset, rho_weights) = match catmull_rom_weights(rho_samples, self.rho[i]) {
|
||||
let (rho_offset, rho_weights) =
|
||||
match catmull_rom_weights(&self.table.rho_samples, self.rho[i]) {
|
||||
Some(res) => res,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let (radius_offset, radius_weights) =
|
||||
match catmull_rom_weights(radius_samples, r_optical) {
|
||||
match catmull_rom_weights(&self.table.radius_samples, r_optical) {
|
||||
Some(res) => res,
|
||||
None => continue,
|
||||
};
|
||||
|
|
@ -215,10 +206,7 @@ impl TabulatedBSSRDF {
|
|||
for (k, radius_weight) in radius_weights.iter().enumerate() {
|
||||
let weight = rho_weight * radius_weight;
|
||||
if weight != 0. {
|
||||
sr += weight
|
||||
* self
|
||||
.table
|
||||
.eval_profile(rho_offset + j as u32, radius_offset + k as u32);
|
||||
sr += weight * self.table.eval_profile(rho_offset + j, radius_offset + k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -228,7 +216,7 @@ impl TabulatedBSSRDF {
|
|||
sr_spectrum[i] = sr;
|
||||
}
|
||||
|
||||
sr_spectrum *= square(self.sigma_t);
|
||||
sr_spectrum *= self.sigma_t * self.sigma_t;
|
||||
SampledSpectrum::clamp_zero(&sr_spectrum)
|
||||
}
|
||||
|
||||
|
|
@ -236,30 +224,29 @@ impl TabulatedBSSRDF {
|
|||
if self.sigma_t[0] == 0. {
|
||||
return None;
|
||||
}
|
||||
|
||||
let rho_samples = self.table.get_rho();
|
||||
let radius_samples = self.table.get_radius();
|
||||
let profile = self.table.get_profile();
|
||||
let cdf = self.table.get_cdf();
|
||||
|
||||
let (ret, _, _) =
|
||||
sample_catmull_rom_2d(rho_samples, radius_samples, profile, cdf, self.rho[0], u);
|
||||
let (ret, _, _) = sample_catmull_rom_2d(
|
||||
&self.table.rho_samples,
|
||||
&self.table.radius_samples,
|
||||
&self.table.profile,
|
||||
&self.table.profile_cdf,
|
||||
self.rho[0],
|
||||
u,
|
||||
);
|
||||
Some(ret / self.sigma_t[0])
|
||||
}
|
||||
|
||||
pub fn pdf_sr(&self, r: Float) -> SampledSpectrum {
|
||||
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 {
|
||||
let r_optical = r * self.sigma_t[i];
|
||||
let (rho_offset, rho_weights) = match catmull_rom_weights(rhoeff_samples, self.rho[i]) {
|
||||
let (rho_offset, rho_weights) =
|
||||
match catmull_rom_weights(&self.table.rho_samples, self.rho[i]) {
|
||||
Some(res) => res,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let (radius_offset, radius_weights) =
|
||||
match catmull_rom_weights(radius_samples, r_optical) {
|
||||
match catmull_rom_weights(&self.table.radius_samples, r_optical) {
|
||||
Some(res) => res,
|
||||
None => continue,
|
||||
};
|
||||
|
|
@ -269,14 +256,12 @@ impl TabulatedBSSRDF {
|
|||
for (j, rho_weight) in rho_weights.iter().enumerate() {
|
||||
if *rho_weight != 0. {
|
||||
// Update _rhoEff_ and _sr_ for wavelength
|
||||
rho_eff += rhoeff_samples[rho_offset as usize + j] * rho_weight;
|
||||
rho_eff += self.table.rho_eff[rho_offset + j] * rho_weight;
|
||||
|
||||
// Fix: Use .iter().enumerate() for 'k'
|
||||
for (k, radius_weight) in radius_weights.iter().enumerate() {
|
||||
if *radius_weight != 0. {
|
||||
sr += self
|
||||
.table
|
||||
.eval_profile(rho_offset + j as u32, radius_offset + k as u32)
|
||||
sr += self.table.eval_profile(rho_offset + j, radius_offset + k)
|
||||
* rho_weight
|
||||
* radius_weight;
|
||||
}
|
||||
|
|
@ -336,11 +321,7 @@ impl BSSRDFTrait for TabulatedBSSRDF {
|
|||
})
|
||||
}
|
||||
|
||||
fn probe_intersection_to_sample(
|
||||
&self,
|
||||
_si: &SubsurfaceInteraction,
|
||||
_bxdf: NormalizedFresnelBxDF,
|
||||
) -> BSSRDFSample {
|
||||
fn probe_intersection_to_sample(&self, _si: &SubsurfaceInteraction) -> BSSRDFSample {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -8,9 +8,9 @@ use crate::core::medium::Medium;
|
|||
use crate::core::options::RenderingCoordinateSystem;
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::core::sampler::CameraSample;
|
||||
use crate::images::ImageMetadata;
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::math::lerp;
|
||||
use crate::utils::ptr::Ptr;
|
||||
use crate::utils::transform::{AnimatedTransform, Transform};
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
|
@ -109,17 +109,27 @@ pub struct CameraBase {
|
|||
pub camera_transform: CameraTransform,
|
||||
pub shutter_open: Float,
|
||||
pub shutter_close: Float,
|
||||
pub film: *const Film,
|
||||
pub medium: *const Medium,
|
||||
pub min_pos_differential_x: Vector3f,
|
||||
pub min_pos_differential_y: Vector3f,
|
||||
pub min_dir_differential_x: 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)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[enum_dispatch(CameraTrait)]
|
||||
pub enum Camera {
|
||||
Perspective(PerspectiveCamera),
|
||||
Orthographic(OrthographicCamera),
|
||||
|
|
@ -129,19 +139,19 @@ pub enum Camera {
|
|||
|
||||
#[enum_dispatch]
|
||||
pub trait CameraTrait {
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
fn init_metadata(&self, metadata: &mut ImageMetadata);
|
||||
|
||||
fn base(&self) -> &CameraBase;
|
||||
|
||||
fn generate_ray(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option<CameraRay>;
|
||||
|
||||
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."
|
||||
);
|
||||
fn get_film(&self) -> Result<&Film, String> {
|
||||
if self.film.is_null() {
|
||||
return Err("Camera error: Film pointer is null.".to_string());
|
||||
}
|
||||
}
|
||||
&self.base().film
|
||||
Ok(unsafe { &*self.film })
|
||||
}
|
||||
|
||||
fn sample_time(&self, u: Float) -> Float {
|
||||
|
|
@ -163,6 +173,9 @@ pub trait CameraTrait {
|
|||
sample: CameraSample,
|
||||
lambda: &SampledWavelengths,
|
||||
) -> 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 rd = RayDifferential::default();
|
||||
let mut rx_found = false;
|
||||
|
|
@ -173,10 +186,10 @@ pub trait CameraTrait {
|
|||
s_shift.p_film[0] += eps;
|
||||
|
||||
if let Some(rx_cam_ray) = self.generate_ray(s_shift, lambda) {
|
||||
rd.rx_origin =
|
||||
central_cam_ray.ray.o + (rx_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
|
||||
rd.rx_direction =
|
||||
central_cam_ray.ray.d + (rx_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
|
||||
rd.rx_origin = central_cam_ray.ray.o
|
||||
+ (rx_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
|
||||
rd.rx_direction = central_cam_ray.ray.d
|
||||
+ (rx_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
|
||||
rx_found = true;
|
||||
break;
|
||||
}
|
||||
|
|
@ -187,21 +200,23 @@ pub trait CameraTrait {
|
|||
s_shift.p_film[1] += eps;
|
||||
|
||||
if let Some(ry_cam_ray) = self.generate_ray(s_shift, lambda) {
|
||||
rd.ry_origin =
|
||||
central_cam_ray.ray.o + (ry_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
|
||||
rd.ry_direction =
|
||||
central_cam_ray.ray.d + (ry_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
|
||||
rd.ry_origin = central_cam_ray.ray.o
|
||||
+ (ry_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
|
||||
rd.ry_direction = central_cam_ray.ray.d
|
||||
+ (ry_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
|
||||
ry_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if rx_found && ry_found {
|
||||
central_cam_ray.ray.differential = rd;
|
||||
central_cam_ray.ray.differential = Some(rd);
|
||||
}
|
||||
|
||||
Some(central_cam_ray)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn approximate_dp_dxy(
|
||||
&self,
|
||||
|
|
@ -227,13 +242,13 @@ pub trait CameraTrait {
|
|||
Point3f::new(0., 0., 0.) + self.base().min_pos_differential_x,
|
||||
Vector3f::new(0., 0., 1.) + self.base().min_dir_differential_x,
|
||||
None,
|
||||
&Ptr::default(),
|
||||
None,
|
||||
);
|
||||
let y_ray = Ray::new(
|
||||
Point3f::new(0., 0., 0.) + self.base().min_pos_differential_y,
|
||||
Vector3f::new(0., 0., 1.) + self.base().min_dir_differential_y,
|
||||
None,
|
||||
&Ptr::default(),
|
||||
None,
|
||||
);
|
||||
let n_down = Vector3f::from(n_down_z);
|
||||
let tx = -(n_down.dot(y_ray.o.into())) / n_down.dot(x_ray.d);
|
||||
|
|
|
|||
|
|
@ -4,17 +4,14 @@ use std::ops::{
|
|||
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
|
||||
};
|
||||
|
||||
use crate::Float;
|
||||
use crate::core::geometry::Point2f;
|
||||
use crate::core::pbrt::{Float, find_interval};
|
||||
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 enum_dispatch::enum_dispatch;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct XYZ {
|
||||
pub x: Float,
|
||||
pub y: Float,
|
||||
|
|
@ -27,12 +24,6 @@ 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 {
|
||||
type Item = &'a Float;
|
||||
type IntoIter = std::array::IntoIter<&'a Float, 3>;
|
||||
|
|
@ -90,9 +81,9 @@ impl XYZ {
|
|||
}
|
||||
}
|
||||
|
||||
impl Index<u32> for XYZ {
|
||||
impl Index<usize> for XYZ {
|
||||
type Output = Float;
|
||||
fn index(&self, index: u32) -> &Self::Output {
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
debug_assert!(index < 3);
|
||||
match index {
|
||||
0 => &self.x,
|
||||
|
|
@ -102,8 +93,8 @@ impl Index<u32> for XYZ {
|
|||
}
|
||||
}
|
||||
|
||||
impl IndexMut<u32> for XYZ {
|
||||
fn index_mut(&mut self, index: u32) -> &mut Self::Output {
|
||||
impl IndexMut<usize> for XYZ {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
debug_assert!(index < 3);
|
||||
match index {
|
||||
0 => &mut self.x,
|
||||
|
|
@ -256,8 +247,7 @@ impl fmt::Display for XYZ {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct RGB {
|
||||
pub r: Float,
|
||||
pub g: Float,
|
||||
|
|
@ -292,19 +282,7 @@ impl RGB {
|
|||
self.r.max(self.g).max(self.b)
|
||||
}
|
||||
|
||||
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 {
|
||||
pub fn max_component_index(&self) -> usize {
|
||||
if self.r > self.g {
|
||||
if self.r > self.b { 0 } else { 2 }
|
||||
} else {
|
||||
|
|
@ -312,40 +290,8 @@ impl RGB {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn clamp(&self, min: Float, max: Float) -> Self {
|
||||
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,
|
||||
}
|
||||
pub fn clamp_zero(rgb: Self) -> Self {
|
||||
RGB::new(rgb.r.max(0.), rgb.b.max(0.), rgb.g.max(0.))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -361,28 +307,6 @@ 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 {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
debug_assert!(index < 3);
|
||||
|
|
@ -625,23 +549,10 @@ impl RGBSigmoidPolynomial {
|
|||
}
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait ColorEncodingTrait: 'static + Send + Sync {
|
||||
fn from_linear(&self, vin: &[Float], vout: &mut [u8]);
|
||||
fn to_linear(&self, vin: &[u8], vout: &mut [Float]);
|
||||
pub trait ColorEncodingTrait: 'static + Send + Sync + fmt::Debug + fmt::Display {
|
||||
fn from_linear_slice(&self, vin: &[Float], vout: &mut [u8]);
|
||||
fn to_linear_slice(&self, vin: &[u8], vout: &mut [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 {
|
||||
TypeId::of::<Self>()
|
||||
}
|
||||
|
|
@ -665,29 +576,19 @@ impl fmt::Display for ColorEncoding {
|
|||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct LinearEncoding;
|
||||
impl ColorEncodingTrait for LinearEncoding {
|
||||
fn from_linear(&self, vin: &[Float], vout: &mut [u8]) {
|
||||
fn from_linear_slice(&self, vin: &[Float], vout: &mut [u8]) {
|
||||
for (i, &v) in vin.iter().enumerate() {
|
||||
vout[i] = (v.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
|
||||
}
|
||||
}
|
||||
|
||||
fn to_linear(&self, vin: &[u8], vout: &mut [Float]) {
|
||||
fn to_linear_slice(&self, vin: &[u8], vout: &mut [Float]) {
|
||||
for (i, &v) in vin.iter().enumerate() {
|
||||
vout[i] = v as Float / 255.0;
|
||||
}
|
||||
}
|
||||
|
||||
fn to_float_linear(&self, v: Float) -> Float {
|
||||
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 {
|
||||
|
|
@ -700,7 +601,7 @@ impl fmt::Display for LinearEncoding {
|
|||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct SRGBEncoding;
|
||||
impl ColorEncodingTrait for SRGBEncoding {
|
||||
fn from_linear(&self, vin: &[Float], vout: &mut [u8]) {
|
||||
fn from_linear_slice(&self, vin: &[Float], vout: &mut [u8]) {
|
||||
for (i, &v_linear) in vin.iter().enumerate() {
|
||||
let v = v_linear.clamp(0.0, 1.0);
|
||||
let v_encoded = if v <= 0.0031308 {
|
||||
|
|
@ -712,33 +613,12 @@ impl ColorEncodingTrait for SRGBEncoding {
|
|||
}
|
||||
}
|
||||
|
||||
fn to_linear(&self, vin: &[u8], vout: &mut [Float]) {
|
||||
fn to_linear_slice(&self, vin: &[u8], vout: &mut [Float]) {
|
||||
for (i, &v) in vin.iter().enumerate() {
|
||||
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 {
|
||||
let v = v.clamp(0.0, 1.0);
|
||||
if v <= 0.04045 {
|
||||
|
|
@ -1017,7 +897,7 @@ const SRGB_TO_LINEAR_LUT: [Float; 256] = [
|
|||
1.0000000000,
|
||||
];
|
||||
|
||||
pub const RES: u32 = 64;
|
||||
pub const RES: usize = 64;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
|
|
@ -1039,18 +919,6 @@ 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 {
|
||||
type Output = Self;
|
||||
#[inline(always)]
|
||||
|
|
@ -1066,9 +934,8 @@ impl Mul<Float> for Coeffs {
|
|||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RGBToSpectrumTable {
|
||||
pub z_nodes: Ptr<Float>,
|
||||
pub coeffs: Ptr<Coeffs>,
|
||||
pub n_nodes: u32,
|
||||
pub z_nodes: *const Float,
|
||||
pub coeffs: *const Coeffs,
|
||||
}
|
||||
|
||||
unsafe impl Send for RGBToSpectrumTable {}
|
||||
|
|
@ -1076,16 +943,16 @@ unsafe impl Sync for RGBToSpectrumTable {}
|
|||
|
||||
impl RGBToSpectrumTable {
|
||||
#[inline(always)]
|
||||
fn get_coeffs(&self, bucket: u32, z: u32, y: u32, x: u32) -> Coeffs {
|
||||
fn get_coeffs(&self, bucket: usize, z: usize, y: usize, x: usize) -> Coeffs {
|
||||
let offset = bucket * (RES * RES * RES) + z * (RES * RES) + y * (RES) + x;
|
||||
unsafe { *self.coeffs.add(offset as usize) }
|
||||
unsafe { *self.coeffs.add(offset) }
|
||||
}
|
||||
|
||||
pub fn evaluate(&self, rgb: RGB) -> RGBSigmoidPolynomial {
|
||||
let m = rgb.max_component_value();
|
||||
let min_val = rgb.min_component_value();
|
||||
if m - min_val < 1e-4 {
|
||||
let x: Float = clamp(rgb[0], 1e-4, 0.9999);
|
||||
let x = clamp(rgb[0], 1e-4, 0.9999);
|
||||
let c2 = (0.5 - x) / (x * (1.0 - x)).sqrt();
|
||||
return RGBSigmoidPolynomial::new(0.0, 0.0, c2);
|
||||
}
|
||||
|
|
@ -1114,26 +981,25 @@ impl RGBToSpectrumTable {
|
|||
let x = coord_a / z;
|
||||
let y = coord_b / z;
|
||||
|
||||
let z_nodes_slice =
|
||||
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 z_nodes_slice = unsafe { core::slice::from_raw_parts(self.z_nodes, RES) };
|
||||
let zi = find_interval(RES, |i| z_nodes_slice[i] < z);
|
||||
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 xi = (x_float as u32).min(RES - 2);
|
||||
let xi = (x_float as usize).min(RES - 2);
|
||||
let dx = x_float - xi as Float;
|
||||
|
||||
let y_float = y * (RES - 1) as Float;
|
||||
let yi = (y_float as u32).min(RES - 2);
|
||||
let yi = (y_float as usize).min(RES - 2);
|
||||
let dy = y_float - yi as Float;
|
||||
|
||||
let c000 = self.get_coeffs(c_idx, zi as u32, yi, xi);
|
||||
let c001 = self.get_coeffs(c_idx, zi as u32, yi, xi + 1);
|
||||
let c010 = self.get_coeffs(c_idx, zi as u32, yi + 1, xi);
|
||||
let c011 = self.get_coeffs(c_idx, zi as u32, yi + 1, xi + 1);
|
||||
let c100 = self.get_coeffs(c_idx, zi as u32 + 1, yi, xi);
|
||||
let c101 = self.get_coeffs(c_idx, zi as u32 + 1, yi, xi + 1);
|
||||
let c110 = self.get_coeffs(c_idx, zi as u32 + 1, yi + 1, xi);
|
||||
let c111 = self.get_coeffs(c_idx, zi as u32 + 1, yi + 1, xi + 1);
|
||||
let c000 = self.get_coeffs(c_idx, zi, yi, xi);
|
||||
let c001 = self.get_coeffs(c_idx, zi, yi, xi + 1);
|
||||
let c010 = self.get_coeffs(c_idx, zi, yi + 1, xi);
|
||||
let c011 = self.get_coeffs(c_idx, zi, yi + 1, xi + 1);
|
||||
let c100 = self.get_coeffs(c_idx, zi + 1, yi, xi);
|
||||
let c101 = self.get_coeffs(c_idx, zi + 1, yi, xi + 1);
|
||||
let c110 = self.get_coeffs(c_idx, zi + 1, yi + 1, xi);
|
||||
let c111 = self.get_coeffs(c_idx, zi + 1, yi + 1, xi + 1);
|
||||
let c00 = lerp(dx, c000, c001);
|
||||
let c01 = lerp(dx, c010, c011);
|
||||
let c10 = lerp(dx, c100, c101);
|
||||
|
|
@ -1149,28 +1015,3 @@ 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,84 @@
|
|||
use crate::core::camera::CameraTransform;
|
||||
use crate::core::color::{MatrixMulColor, RGB, SRGB, XYZ, white_balance};
|
||||
use crate::core::filter::{Filter, FilterTrait};
|
||||
use crate::core::filter::Filter;
|
||||
use crate::core::geometry::{
|
||||
Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Tuple, Vector2f, Vector2fi,
|
||||
Vector2i, Vector3f,
|
||||
};
|
||||
use crate::core::image::{DeviceImage, PixelFormat};
|
||||
use crate::core::interaction::SurfaceInteraction;
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait, StandardSpectra};
|
||||
use crate::images::{Image, ImageChannelDesc, ImageChannelValues, ImageMetadata, PixelFormat};
|
||||
use crate::spectra::{
|
||||
ConstantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES,
|
||||
PiecewiseLinearSpectrum, RGBColorSpace, SampledSpectrum, SampledWavelengths, colorspace,
|
||||
get_named_spectrum,
|
||||
};
|
||||
use crate::utils::containers::DeviceArray2D;
|
||||
use crate::utils::AtomicFloat;
|
||||
use crate::utils::containers::Array2D;
|
||||
use crate::utils::math::linear_least_squares;
|
||||
use crate::utils::math::{SquareMatrix, wrap_equal_area_square};
|
||||
use crate::utils::sampling::VarianceEstimator;
|
||||
use crate::utils::transform::AnimatedTransform;
|
||||
use crate::utils::{AtomicFloat, Ptr};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RGBFilm {
|
||||
pub base: FilmBase,
|
||||
pub max_component_value: Float,
|
||||
pub write_fp16: bool,
|
||||
pub filter_integral: Float,
|
||||
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
|
||||
pub pixels: DeviceArray2D<RGBPixel>,
|
||||
pub pixels: Array2D<RGBPixel>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RGBPixel {
|
||||
rgb_sum: [AtomicFloat; 3],
|
||||
weight_sum: AtomicFloat,
|
||||
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 {
|
||||
pub fn base(&self) -> &FilmBase {
|
||||
&self.base
|
||||
|
|
@ -48,16 +88,16 @@ impl RGBFilm {
|
|||
&mut self.base
|
||||
}
|
||||
|
||||
pub fn get_sensor(&self) -> &DevicePixelSensor {
|
||||
pub fn get_sensor(&self) -> &PixelSensor {
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
{
|
||||
if self.base.sensor.is_null() {
|
||||
if self.sensor.is_null() {
|
||||
panic!(
|
||||
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
|
||||
);
|
||||
}
|
||||
}
|
||||
&self.base.sensor
|
||||
unsafe { &*self.sensor }
|
||||
}
|
||||
|
||||
pub fn add_sample(
|
||||
|
|
@ -68,7 +108,7 @@ impl RGBFilm {
|
|||
_vi: Option<&VisibleSurface>,
|
||||
weight: Float,
|
||||
) {
|
||||
let sensor = self.get_sensor();
|
||||
let sensor = unsafe { self.get_sensor() };
|
||||
let mut rgb = sensor.to_sensor_rgb(l, lambda);
|
||||
let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max);
|
||||
if m > self.max_component_value {
|
||||
|
|
@ -77,13 +117,13 @@ impl RGBFilm {
|
|||
|
||||
let pixel = &self.pixels[p_film];
|
||||
for c in 0..3 {
|
||||
pixel.rgb_sum[c].add(weight * rgb[c as u32]);
|
||||
pixel.rgb_sum[c].add((weight * rgb[c]) as f64);
|
||||
}
|
||||
pixel.weight_sum.add(weight);
|
||||
pixel.weight_sum.add(weight as f64);
|
||||
}
|
||||
|
||||
pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) {
|
||||
let sensor = self.get_sensor();
|
||||
let sensor = unsafe { self.get_sensor() };
|
||||
let mut rgb = sensor.to_sensor_rgb(l, lambda);
|
||||
let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max);
|
||||
if m > self.max_component_value {
|
||||
|
|
@ -91,49 +131,48 @@ impl RGBFilm {
|
|||
}
|
||||
|
||||
let p_discrete = p + Vector2f::new(0.5, 0.5);
|
||||
let radius = self.base.filter.radius();
|
||||
let radius = self.get_filter().radius();
|
||||
|
||||
let splat_bounds = Bounds2i::from_points(
|
||||
(p_discrete - radius).floor(),
|
||||
(p_discrete + radius).floor() + Vector2i::new(1, 1),
|
||||
);
|
||||
|
||||
let splat_intersect = splat_bounds.union(self.base().pixel_bounds);
|
||||
let splat_intersect = splat_bounds.union(self.pixel_bounds());
|
||||
for pi in &splat_intersect {
|
||||
let pi_f: Point2f = (*pi).into();
|
||||
let wt = self
|
||||
.base()
|
||||
.filter
|
||||
.get_filter()
|
||||
.evaluate((p - pi_f - Vector2f::new(0.5, 0.5)).into());
|
||||
if wt != 0. {
|
||||
let pixel = &self.pixels[*pi];
|
||||
for i in 0..3 {
|
||||
pixel.rgb_splat[i].add((wt * rgb[i as u32]) as f32);
|
||||
pixel.rgb_splat[i].add((wt * rgb[i]) as f64);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option<Float>) -> RGB {
|
||||
let pixel = &self.pixels.get(p);
|
||||
let pixel = unsafe { &self.pixels.get(p.x(), p.y())[p] };
|
||||
let mut rgb = RGB::new(
|
||||
pixel.rgb_sum[0].get() as Float,
|
||||
pixel.rgb_sum[1].get() as Float,
|
||||
pixel.rgb_sum[2].get() as Float,
|
||||
pixel.rgb_sum[0].load() as Float,
|
||||
pixel.rgb_sum[1].load() as Float,
|
||||
pixel.rgb_sum[2].load() as Float,
|
||||
);
|
||||
let weight_sum = pixel.weight_sum.get();
|
||||
let weight_sum = pixel.weight_sum.load();
|
||||
if weight_sum != 0. {
|
||||
rgb /= weight_sum as Float
|
||||
}
|
||||
|
||||
if let Some(splat) = splat_scale {
|
||||
for c in 0..3 {
|
||||
let splat_val = pixel.rgb_splat[c].get();
|
||||
let splat_val = pixel.rgb_splat[c].load();
|
||||
rgb[c] += splat * splat_val as Float / self.filter_integral;
|
||||
}
|
||||
} else {
|
||||
for c in 0..3 {
|
||||
let splat_val = pixel.rgb_splat[c].get();
|
||||
let splat_val = pixel.rgb_splat[c].load();
|
||||
rgb[c] += splat_val as Float / self.filter_integral;
|
||||
}
|
||||
}
|
||||
|
|
@ -141,8 +180,8 @@ impl RGBFilm {
|
|||
}
|
||||
|
||||
pub fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB {
|
||||
let sensor = self.get_sensor();
|
||||
let sensor_rgb = sensor.to_sensor_rgb(l, lambda);
|
||||
let sensor = unsafe { self.get_sensor() };
|
||||
let mut sensor_rgb = sensor.to_sensor_rgb(l, lambda);
|
||||
self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb)
|
||||
}
|
||||
|
||||
|
|
@ -151,10 +190,8 @@ impl RGBFilm {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default)]
|
||||
#[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
|
||||
pub struct GBufferPixel {
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
struct GBufferPixel {
|
||||
pub rgb_sum: [AtomicFloat; 3],
|
||||
pub weight_sum: AtomicFloat,
|
||||
pub g_bugger_weight_sum: AtomicFloat,
|
||||
|
|
@ -169,19 +206,51 @@ pub struct GBufferPixel {
|
|||
pub rgb_variance: VarianceEstimator,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GBufferFilm {
|
||||
pub base: FilmBase,
|
||||
pub output_from_render: AnimatedTransform,
|
||||
pub apply_inverse: bool,
|
||||
pub pixels: DeviceArray2D<GBufferPixel>,
|
||||
pub colorspace: RGBColorSpace,
|
||||
pub max_component_value: Float,
|
||||
pub write_fp16: bool,
|
||||
pub filter_integral: Float,
|
||||
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
|
||||
output_from_render: AnimatedTransform,
|
||||
apply_inverse: bool,
|
||||
pixels: Array2D<GBufferPixel>,
|
||||
colorspace: RGBColorSpace,
|
||||
max_component_value: Float,
|
||||
write_fp16: bool,
|
||||
filter_integral: Float,
|
||||
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 {
|
||||
|
|
@ -193,31 +262,20 @@ impl GBufferFilm {
|
|||
&mut self.base
|
||||
}
|
||||
|
||||
pub fn get_sensor(&self) -> &DevicePixelSensor {
|
||||
pub fn get_sensor(&self) -> &PixelSensor {
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
{
|
||||
if self.base.sensor.is_null() {
|
||||
if self.sensor.is_null() {
|
||||
panic!(
|
||||
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
|
||||
);
|
||||
}
|
||||
}
|
||||
&self.base.sensor
|
||||
}
|
||||
|
||||
pub fn add_sample(
|
||||
&mut self,
|
||||
_p_film: Point2i,
|
||||
_l: SampledSpectrum,
|
||||
_lambda: &SampledWavelengths,
|
||||
_visible_surface: Option<&VisibleSurface>,
|
||||
_weight: Float,
|
||||
) {
|
||||
todo!()
|
||||
unsafe { &*self.sensor }
|
||||
}
|
||||
|
||||
pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) {
|
||||
let sensor = self.get_sensor();
|
||||
let sensor = unsafe { self.get_sensor() };
|
||||
let mut rgb = sensor.to_sensor_rgb(l, lambda);
|
||||
let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max);
|
||||
if m > self.max_component_value {
|
||||
|
|
@ -225,55 +283,54 @@ impl GBufferFilm {
|
|||
}
|
||||
|
||||
let p_discrete = p + Vector2f::new(0.5, 0.5);
|
||||
let radius = self.base().filter.radius();
|
||||
let radius = self.get_filter().radius();
|
||||
|
||||
let splat_bounds = Bounds2i::from_points(
|
||||
(p_discrete - radius).floor(),
|
||||
(p_discrete + radius).floor() + Vector2i::new(1, 1),
|
||||
);
|
||||
|
||||
let splat_intersect = splat_bounds.union(self.base.pixel_bounds);
|
||||
let splat_intersect = splat_bounds.union(self.pixel_bounds());
|
||||
for pi in &splat_intersect {
|
||||
let pi_f: Point2f = (*pi).into();
|
||||
let wt = self
|
||||
.base
|
||||
.filter
|
||||
.get_filter()
|
||||
.evaluate((p - pi_f - Vector2f::new(0.5, 0.5)).into());
|
||||
if wt != 0. {
|
||||
let pixel = &self.pixels[*pi];
|
||||
for i in 0..3 {
|
||||
pixel.rgb_splat[i].add((wt * rgb[i]) as f32);
|
||||
pixel.rgb_splat[i].add((wt * rgb[i]) as f64);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB {
|
||||
let sensor = self.get_sensor();
|
||||
let sensor = unsafe { self.get_sensor() };
|
||||
let sensor_rgb = sensor.to_sensor_rgb(l, lambda);
|
||||
self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb)
|
||||
}
|
||||
|
||||
pub fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option<Float>) -> RGB {
|
||||
let pixel = &self.pixels.get(p);
|
||||
let pixel = unsafe { &self.pixels.get(p.x(), p.y()) };
|
||||
let mut rgb = RGB::new(
|
||||
pixel.rgb_sum[0].get() as Float,
|
||||
pixel.rgb_sum[1].get() as Float,
|
||||
pixel.rgb_sum[2].get() as Float,
|
||||
pixel.rgb_sum[0].load() as Float,
|
||||
pixel.rgb_sum[1].load() as Float,
|
||||
pixel.rgb_sum[2].load() as Float,
|
||||
);
|
||||
let weight_sum = pixel.weight_sum.get();
|
||||
let weight_sum = pixel.weight_sum.load();
|
||||
if weight_sum != 0. {
|
||||
rgb /= weight_sum as Float
|
||||
}
|
||||
|
||||
if let Some(splat) = splat_scale {
|
||||
for c in 0..3 {
|
||||
let splat_val = pixel.rgb_splat[c].get();
|
||||
let splat_val = pixel.rgb_splat[c].load();
|
||||
rgb[c] += splat * splat_val as Float / self.filter_integral;
|
||||
}
|
||||
} else {
|
||||
for c in 0..3 {
|
||||
let splat_val = pixel.rgb_splat[c].get();
|
||||
let splat_val = pixel.rgb_splat[c].load();
|
||||
rgb[c] += splat_val as Float / self.filter_integral;
|
||||
}
|
||||
}
|
||||
|
|
@ -286,8 +343,7 @@ impl GBufferFilm {
|
|||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default)]
|
||||
#[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct SpectralPixel {
|
||||
pub rgb_sum: [AtomicFloat; 3],
|
||||
pub rgb_weigh_sum: AtomicFloat,
|
||||
|
|
@ -295,9 +351,15 @@ pub struct SpectralPixel {
|
|||
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)]
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct SpectralFilm {
|
||||
pub base: FilmBase,
|
||||
pub colorspace: RGBColorSpace,
|
||||
|
|
@ -307,15 +369,62 @@ pub struct SpectralFilm {
|
|||
pub max_component_value: Float,
|
||||
pub write_fp16: bool,
|
||||
pub filter_integral: Float,
|
||||
pub pixels: DeviceArray2D<SpectralPixel>,
|
||||
pub pixels: Array2D<SpectralPixel>,
|
||||
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
|
||||
pub bucket_sums: *mut f64,
|
||||
pub weight_sums: *mut f64,
|
||||
pub bucket_splats: *mut AtomicFloat,
|
||||
pub bucket_sums: Vec<f64>,
|
||||
pub weight_sums: Vec<f64>,
|
||||
pub bucket_splats: Vec<AtomicFloat>,
|
||||
}
|
||||
|
||||
unsafe impl Send for SpectralFilm {}
|
||||
unsafe impl Sync for SpectralFilm {}
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
impl SpectralFilm {
|
||||
pub fn new(
|
||||
base: &FilmBase,
|
||||
lambda_min: Float,
|
||||
lambda_max: Float,
|
||||
n_buckets: usize,
|
||||
colorspace: &RGBColorSpace,
|
||||
max_component_value: Float,
|
||||
write_fp16: bool,
|
||||
) -> Self {
|
||||
assert!(!base.pixel_bounds.is_empty());
|
||||
let sensor_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 {
|
||||
pub fn base(&self) -> &FilmBase {
|
||||
|
|
@ -330,25 +439,23 @@ impl SpectralFilm {
|
|||
true
|
||||
}
|
||||
|
||||
pub fn add_sample(
|
||||
&mut self,
|
||||
_p_film: Point2i,
|
||||
_l: SampledSpectrum,
|
||||
_lambda: &SampledWavelengths,
|
||||
_visible_surface: Option<&VisibleSurface>,
|
||||
_weight: Float,
|
||||
) {
|
||||
todo!()
|
||||
}
|
||||
pub fn get_pixel_view(&self, p: Point2i) -> SpectralPixelView {
|
||||
let metadata = unsafe { &self.pixels.get(p.x(), p.y()) };
|
||||
let start = metadata.bucket_offset;
|
||||
let end = start + self.n_buckets;
|
||||
|
||||
pub fn add_splat(&mut self, _p: Point2f, _v: SampledSpectrum, _lambda: &SampledWavelengths) {
|
||||
todo!()
|
||||
SpectralPixelView {
|
||||
metadata,
|
||||
bucket_sums: &self.bucket_sums[start..end],
|
||||
weight_sums: &self.weight_sums[start..end],
|
||||
bucket_splats: &self.bucket_splats[start..end],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct DevicePixelSensor {
|
||||
pub struct PixelSensor {
|
||||
pub xyz_from_sensor_rgb: SquareMatrix<Float, 3>,
|
||||
pub r_bar: DenselySampledSpectrum,
|
||||
pub g_bar: DenselySampledSpectrum,
|
||||
|
|
@ -356,7 +463,101 @@ pub struct DevicePixelSensor {
|
|||
pub imaging_ratio: Float,
|
||||
}
|
||||
|
||||
impl DevicePixelSensor {
|
||||
impl PixelSensor {
|
||||
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>(
|
||||
refl: &Spectrum,
|
||||
illum: &Spectrum,
|
||||
|
|
@ -388,9 +589,11 @@ impl DevicePixelSensor {
|
|||
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 {
|
||||
let l_norm = SampledSpectrum::safe_div(&l, &lambda.pdf());
|
||||
self.imaging_ratio
|
||||
|
|
@ -430,27 +633,23 @@ impl VisibleSurface {
|
|||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Default, Copy, Clone)]
|
||||
pub struct FilmBase {
|
||||
pub full_resolution: Point2i,
|
||||
pub pixel_bounds: Bounds2i,
|
||||
pub filter: Filter,
|
||||
pub diagonal: Float,
|
||||
pub sensor: Ptr<DevicePixelSensor>,
|
||||
pub sensor: *const PixelSensor,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Film {
|
||||
RGB(RGBFilm),
|
||||
GBuffer(GBufferFilm),
|
||||
Spectral(SpectralFilm),
|
||||
}
|
||||
|
||||
unsafe impl Send for Film {}
|
||||
unsafe impl Sync for Film {}
|
||||
|
||||
impl Film {
|
||||
pub fn base(&self) -> &FilmBase {
|
||||
match self {
|
||||
|
|
@ -469,7 +668,7 @@ impl Film {
|
|||
}
|
||||
|
||||
pub fn add_sample(
|
||||
&mut self,
|
||||
&self,
|
||||
p_film: Point2i,
|
||||
l: SampledSpectrum,
|
||||
lambda: &SampledWavelengths,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f};
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::filters::*;
|
||||
use crate::utils::containers::DeviceArray2D;
|
||||
use crate::utils::containers::Array2D;
|
||||
use crate::utils::math::{gaussian, gaussian_integral, lerp, sample_tent, windowed_sinc};
|
||||
use crate::utils::sampling::DevicePiecewiseConstant2D;
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
||||
pub struct FilterSample {
|
||||
|
|
@ -11,15 +11,41 @@ pub struct FilterSample {
|
|||
pub weight: Float,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FilterSampler {
|
||||
pub domain: Bounds2f,
|
||||
pub distrib: DevicePiecewiseConstant2D,
|
||||
pub f: DeviceArray2D<Float>,
|
||||
pub distrib: PiecewiseConstant2D,
|
||||
pub f: Array2D<Float>,
|
||||
}
|
||||
|
||||
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 {
|
||||
let (p, pdf, pi) = self.distrib.sample(u);
|
||||
|
||||
|
|
@ -27,13 +53,11 @@ impl FilterSampler {
|
|||
return FilterSample { p, weight: 0.0 };
|
||||
}
|
||||
|
||||
let idx = pi.x() as u32 + self.f.x_size();
|
||||
let weight = *self.f.get_linear(idx as usize) / pdf;
|
||||
let weight = *self.f.get_linear(pi.x() as usize + self.f.x_size()) / pdf;
|
||||
FilterSample { p, weight }
|
||||
}
|
||||
}
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait FilterTrait {
|
||||
fn radius(&self) -> Vector2f;
|
||||
fn evaluate(&self, p: Point2f) -> Float;
|
||||
|
|
@ -43,7 +67,7 @@ pub trait FilterTrait {
|
|||
|
||||
#[repr(C)]
|
||||
#[enum_dispatch(FilterTrait)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Filter {
|
||||
Box(BoxFilter),
|
||||
Gaussian(GaussianFilter),
|
||||
|
|
|
|||
|
|
@ -250,12 +250,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl Bounds2f {
|
||||
pub fn unit() -> Self {
|
||||
Self::from_points(Point2f::new(0.0, 0.0), Point2f::new(1.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounds3f {
|
||||
#[inline(always)]
|
||||
pub fn intersect_p(
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@ pub mod traits;
|
|||
pub use self::bounds::{Bounds, Bounds2f, Bounds2fi, Bounds2i, Bounds3f, Bounds3fi, Bounds3i};
|
||||
pub use self::cone::DirectionCone;
|
||||
pub use self::primitives::{
|
||||
Frame, MulAdd, Normal, Normal3f, Point, Point2f, Point2fi, Point2i, Point3, Point3f, Point3fi,
|
||||
Point3i, Vector, Vector2, Vector2f, Vector2fi, Vector2i, Vector3, Vector3f, Vector3fi,
|
||||
Vector3i,
|
||||
Frame, Normal, Normal3f, Point, Point2f, Point2fi, Point2i, Point3, Point3f, Point3fi, Point3i,
|
||||
Vector, Vector2, Vector2f, Vector2fi, Vector2i, Vector3, Vector3f, Vector3fi, Vector3i,
|
||||
};
|
||||
pub use self::ray::{Ray, RayDifferential};
|
||||
pub use self::traits::{Lerp, Sqrt, Tuple, VectorLike};
|
||||
|
|
|
|||
|
|
@ -9,19 +9,6 @@ use std::ops::{
|
|||
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
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
|
|
@ -273,27 +260,6 @@ 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_rules! impl_float_vector_ops {
|
||||
($Struct:ident) => {
|
||||
|
|
@ -415,10 +381,6 @@ impl_accessors!(Vector);
|
|||
impl_accessors!(Point);
|
||||
impl_accessors!(Normal);
|
||||
|
||||
impl_mul_add!(Vector);
|
||||
impl_mul_add!(Point);
|
||||
impl_mul_add!(Normal);
|
||||
|
||||
// Convert from tuple of Floats, for parsing issues
|
||||
impl_tuple_conversions!(Vector);
|
||||
impl_tuple_conversions!(Point);
|
||||
|
|
@ -816,8 +778,7 @@ impl<T> Normal3<T>
|
|||
where
|
||||
T: Num + PartialOrd + Copy + Neg<Output = T> + Sqrt,
|
||||
{
|
||||
pub fn face_forward(self, v: impl Into<Vector3<T>>) -> Self {
|
||||
let v: Vector3<T> = v.into();
|
||||
pub fn face_forward(self, v: Vector3<T>) -> Self {
|
||||
if Vector3::<T>::from(self).dot(v) < T::zero() {
|
||||
-self
|
||||
} else {
|
||||
|
|
@ -826,8 +787,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
#[repr(C)]
|
||||
pub struct OctahedralVector {
|
||||
x: u16,
|
||||
y: u16,
|
||||
|
|
@ -889,7 +850,6 @@ impl From<OctahedralVector> for Vector3f {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||
pub struct Frame {
|
||||
pub x: Vector3f,
|
||||
|
|
|
|||
|
|
@ -2,18 +2,16 @@ use super::{Normal3f, Point3f, Point3fi, Vector3f, VectorLike};
|
|||
use crate::core::medium::Medium;
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::utils::math::{next_float_down, next_float_up};
|
||||
use crate::utils::ptr::Ptr;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Ray {
|
||||
pub o: Point3f,
|
||||
pub d: Vector3f,
|
||||
pub medium: *const Medium,
|
||||
pub time: Float,
|
||||
pub medium: Ptr<Medium>,
|
||||
// We do this instead of creating a trait for Rayable or some gnarly thing like that
|
||||
pub has_differentials: bool,
|
||||
pub differential: RayDifferential,
|
||||
pub differential: *const RayDifferential,
|
||||
}
|
||||
|
||||
impl Default for Ray {
|
||||
|
|
@ -21,21 +19,20 @@ impl Default for Ray {
|
|||
Self {
|
||||
o: Point3f::new(0.0, 0.0, 0.0),
|
||||
d: Vector3f::new(0.0, 0.0, 0.0),
|
||||
medium: Ptr::null(),
|
||||
medium: None,
|
||||
time: 0.0,
|
||||
has_differentials: false,
|
||||
differential: RayDifferential::default(),
|
||||
differential: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ray {
|
||||
pub fn new(o: Point3f, d: Vector3f, time: Option<Float>, medium: &Medium) -> Self {
|
||||
pub fn new(o: Point3f, d: Vector3f, time: Option<Float>, medium: *const Medium) -> Self {
|
||||
Self {
|
||||
o,
|
||||
d,
|
||||
time: time.unwrap_or_else(|| Self::default().time),
|
||||
medium: Ptr::from(medium),
|
||||
medium,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
|
@ -71,9 +68,8 @@ impl Ray {
|
|||
o: origin,
|
||||
d,
|
||||
time,
|
||||
medium: Ptr::null(),
|
||||
has_differentials: false,
|
||||
differential: RayDifferential::default(),
|
||||
medium: None,
|
||||
differential: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -99,15 +95,13 @@ impl Ray {
|
|||
o: pf,
|
||||
d,
|
||||
time,
|
||||
medium: Ptr::null(),
|
||||
has_differentials: false,
|
||||
differential: RayDifferential::default(),
|
||||
medium: None,
|
||||
differential: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scale_differentials(&mut self, s: Float) {
|
||||
if self.has_differentials {
|
||||
let differential = &mut self.differential;
|
||||
if let Some(differential) = &mut self.differential {
|
||||
differential.rx_origin = self.o + (differential.rx_origin - self.o) * s;
|
||||
differential.ry_origin = self.o + (differential.ry_origin - self.o) * s;
|
||||
differential.rx_direction = self.d + (differential.rx_direction - self.d) * s;
|
||||
|
|
|
|||
|
|
@ -1,206 +0,0 @@
|
|||
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())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,94 +1,41 @@
|
|||
use crate::Float;
|
||||
use crate::bxdfs::DiffuseBxDF;
|
||||
use crate::core::bsdf::BSDF;
|
||||
use crate::core::bssrdf::BSSRDF;
|
||||
use crate::core::bxdf::{BxDF, BxDFFlags};
|
||||
use crate::core::camera::{Camera, CameraTrait};
|
||||
use crate::core::bxdf::{BSDF, BxDF, BxDFFlags, DiffuseBxDF};
|
||||
use crate::core::camera::Camera;
|
||||
use crate::core::geometry::{
|
||||
Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike,
|
||||
};
|
||||
use crate::core::image::DeviceImage;
|
||||
use crate::core::light::{Light, LightTrait};
|
||||
use crate::core::light::Light;
|
||||
use crate::core::material::{
|
||||
Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map,
|
||||
};
|
||||
use crate::core::medium::{Medium, MediumInterface, PhaseFunction};
|
||||
use crate::core::options::get_options;
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::core::sampler::{Sampler, SamplerTrait};
|
||||
use crate::core::shape::Shape;
|
||||
use crate::core::texture::{GPUFloatTexture, UniversalTextureEvaluator};
|
||||
use crate::images::Image;
|
||||
use crate::shapes::Shape;
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::Ptr;
|
||||
use crate::utils::math::{clamp, difference_of_products, square};
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use std::any::Any;
|
||||
use std::default;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default, Copy, Clone, Debug)]
|
||||
pub struct InteractionBase {
|
||||
pub struct InteractionData {
|
||||
pub pi: Point3fi,
|
||||
pub n: Normal3f,
|
||||
pub time: Float,
|
||||
pub wo: Vector3f,
|
||||
pub uv: Point2f,
|
||||
pub medium_interface: MediumInterface,
|
||||
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()
|
||||
}
|
||||
}
|
||||
pub medium: *const Medium,
|
||||
}
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait InteractionTrait {
|
||||
fn get_common(&self) -> &InteractionBase;
|
||||
fn get_common_mut(&mut self) -> &mut InteractionBase;
|
||||
pub trait InteractionTrait: Send + Sync + std::fmt::Debug {
|
||||
fn get_common(&self) -> &InteractionData;
|
||||
fn get_common_mut(&mut self) -> &mut InteractionData;
|
||||
|
||||
fn p(&self) -> Point3f {
|
||||
self.get_common().pi.into()
|
||||
|
|
@ -96,7 +43,6 @@ pub trait InteractionTrait {
|
|||
fn pi(&self) -> Point3fi {
|
||||
self.get_common().pi
|
||||
}
|
||||
|
||||
fn time(&self) -> Float {
|
||||
self.get_common().time
|
||||
}
|
||||
|
|
@ -116,13 +62,13 @@ pub trait InteractionTrait {
|
|||
false
|
||||
}
|
||||
|
||||
fn get_medium(&self, w: Vector3f) -> Ptr<Medium> {
|
||||
fn get_medium(&self, w: Vector3f) -> *const Medium {
|
||||
let data = self.get_common();
|
||||
if !data.medium_interface.inside.is_null() || !data.medium_interface.outside.is_null() {
|
||||
if let Some(mi) = &data.medium_interface {
|
||||
if w.dot(data.n.into()) > 0.0 {
|
||||
data.medium_interface.outside
|
||||
mi.outside
|
||||
} else {
|
||||
data.medium_interface.inside
|
||||
mi.inside
|
||||
}
|
||||
} else {
|
||||
data.medium
|
||||
|
|
@ -144,10 +90,11 @@ pub trait InteractionTrait {
|
|||
ray
|
||||
}
|
||||
|
||||
fn spawn_ray_to_interaction(&self, other: InteractionBase) -> Ray {
|
||||
fn spawn_ray_to_interaction(&self, other: InteractionData) -> Ray {
|
||||
let data = self.get_common();
|
||||
|
||||
let mut ray = Ray::spawn_to_interaction(&data.pi, &data.n, data.time, &other.pi, &other.n);
|
||||
let mut ray =
|
||||
Ray::spawn_to_interaction(&data.pi, &data.n, data.time, &other_data.pi, &other_data.n);
|
||||
ray.medium = self.get_medium(ray.d);
|
||||
ray
|
||||
}
|
||||
|
|
@ -171,40 +118,57 @@ pub enum Interaction {
|
|||
}
|
||||
|
||||
impl Interaction {
|
||||
pub fn set_medium_interface(&mut self, mi: MediumInterface) {
|
||||
pub fn set_medium_interface(&mut self, mi: Option<MediumInterface>) {
|
||||
match self {
|
||||
Interaction::Surface(si) => si.common.medium_interface = mi,
|
||||
Interaction::Medium(_) => {} // Medium interactions don't usually sit on boundaries
|
||||
Interaction::Simple(si) => si.common.medium_interface = mi,
|
||||
Interaction::Medium(_) => {} // Medium interactions don't usually sit on boundaries
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SimpleInteraction {
|
||||
pub common: InteractionBase,
|
||||
pub common: InteractionData,
|
||||
}
|
||||
|
||||
impl SimpleInteraction {
|
||||
pub fn new(common: InteractionBase) -> Self {
|
||||
Self { common }
|
||||
pub fn new(pi: Point3fi, time: Float, medium_interface: Option<MediumInterface>) -> Self {
|
||||
Self {
|
||||
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 {
|
||||
fn get_common(&self) -> &InteractionBase {
|
||||
fn get_common(&self) -> &InteractionData {
|
||||
&self.common
|
||||
}
|
||||
fn get_common_mut(&mut self) -> &mut InteractionBase {
|
||||
|
||||
fn get_common_mut(&mut self) -> &mut InteractionData {
|
||||
&mut self.common
|
||||
}
|
||||
fn is_surface_interaction(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn is_medium_interaction(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
|
@ -220,18 +184,19 @@ pub struct ShadingGeom {
|
|||
#[repr(C)]
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct SurfaceInteraction {
|
||||
pub area_light: Ptr<Light>,
|
||||
pub material: Ptr<Material>,
|
||||
pub shape: Ptr<Shape>,
|
||||
pub common: InteractionBase,
|
||||
pub shading: ShadingGeom,
|
||||
pub common: InteractionData,
|
||||
pub uv: Point2f,
|
||||
pub dpdu: Vector3f,
|
||||
pub dpdv: Vector3f,
|
||||
pub dndu: 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 dpdy: Vector3f,
|
||||
pub face_index: i32,
|
||||
pub dudx: Float,
|
||||
pub dvdx: Float,
|
||||
pub dudy: Float,
|
||||
|
|
@ -243,17 +208,15 @@ unsafe impl Sync for SurfaceInteraction {}
|
|||
|
||||
impl SurfaceInteraction {
|
||||
pub fn le(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
if !self.area_light.is_null() {
|
||||
self.area_light
|
||||
.l(self.p(), self.n(), self.common.uv, w, lambda)
|
||||
if let Some(area_light) = &self.area_light {
|
||||
area_light.l(self.p(), self.n(), self.uv, w, lambda)
|
||||
} else {
|
||||
SampledSpectrum::new(0.)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_differentials(&mut self, r: &Ray, camera: &Camera, samples_per_pixel: i32) {
|
||||
let computed = if !r.has_differentials {
|
||||
let diff = r.differential;
|
||||
let computed = if let Some(diff) = &r.differential {
|
||||
let dot_rx = self.common.n.dot(diff.rx_direction.into());
|
||||
let dot_ry = self.common.n.dot(diff.ry_direction.into());
|
||||
|
||||
|
|
@ -340,8 +303,7 @@ impl SurfaceInteraction {
|
|||
let new_ray = Ray::spawn(&self.pi(), &self.n(), ray.time, ray.d);
|
||||
ray.o = new_ray.o;
|
||||
// Skipping other variables, since they should not change when passing through surface
|
||||
if !ray.has_differentials {
|
||||
let mut diff = ray.differential;
|
||||
if let Some(diff) = &mut ray.differential {
|
||||
diff.rx_origin += diff.rx_direction * t;
|
||||
diff.ry_origin += diff.ry_direction * t;
|
||||
}
|
||||
|
|
@ -358,13 +320,13 @@ impl SurfaceInteraction {
|
|||
self.compute_differentials(r, camera, sampler.samples_per_pixel() as i32);
|
||||
|
||||
let material = {
|
||||
let root_mat = self.material;
|
||||
let mut active_mat: &Material = &*root_mat;
|
||||
let root_mat = self.material.as_deref()?;
|
||||
let mut active_mat: &Material = root_mat;
|
||||
let tex_eval = UniversalTextureEvaluator;
|
||||
while let Material::Mix(mix) = active_mat {
|
||||
// We need a context to evaluate the 'amount' texture
|
||||
let ctx = MaterialEvalContext::from(&*self);
|
||||
active_mat = mix.choose_material(&tex_eval, &ctx)?;
|
||||
active_mat = mix.choose_material(&tex_eval, &ctx);
|
||||
}
|
||||
active_mat.clone()
|
||||
};
|
||||
|
|
@ -372,8 +334,8 @@ impl SurfaceInteraction {
|
|||
let ctx = MaterialEvalContext::from(&*self);
|
||||
let tex_eval = UniversalTextureEvaluator;
|
||||
let displacement = material.get_displacement();
|
||||
let normal_map = Ptr::from(material.get_normal_map().unwrap());
|
||||
if !displacement.is_null() || !normal_map.is_null() {
|
||||
let normal_map = material.get_normal_map();
|
||||
if displacement.is_some() || normal_map.is_some() {
|
||||
// This calls the function defined above
|
||||
self.compute_bump_geom(&tex_eval, displacement, normal_map);
|
||||
}
|
||||
|
|
@ -382,7 +344,7 @@ impl SurfaceInteraction {
|
|||
if get_options().force_diffuse {
|
||||
let r = bsdf.rho_wo(self.common.wo, &[sampler.get1d()], &[sampler.get2d()]);
|
||||
let diff_bxdf = BxDF::Diffuse(DiffuseBxDF::new(r));
|
||||
bsdf = BSDF::new(self.shading.n, self.shading.dpdu, Ptr::from(&diff_bxdf));
|
||||
bsdf = BSDF::new(self.shading.n, self.shading.dpdu, Some(diff_bxdf));
|
||||
}
|
||||
Some(bsdf)
|
||||
}
|
||||
|
|
@ -395,12 +357,13 @@ impl SurfaceInteraction {
|
|||
_camera: &Camera,
|
||||
) -> Option<BSSRDF> {
|
||||
let material = {
|
||||
let mut active_mat = unsafe { self.material.as_ref() };
|
||||
let root_mat = self.material.as_deref()?;
|
||||
let mut active_mat: &Material = root_mat;
|
||||
let tex_eval = UniversalTextureEvaluator;
|
||||
while let Material::Mix(mix) = active_mat {
|
||||
// We need a context to evaluate the 'amount' texture
|
||||
let ctx = MaterialEvalContext::from(self);
|
||||
active_mat = mix.choose_material(&tex_eval, &ctx)?;
|
||||
active_mat = mix.choose_material(&tex_eval, &ctx);
|
||||
}
|
||||
active_mat.clone()
|
||||
};
|
||||
|
|
@ -413,15 +376,14 @@ impl SurfaceInteraction {
|
|||
fn compute_bump_geom(
|
||||
&mut self,
|
||||
tex_eval: &UniversalTextureEvaluator,
|
||||
displacement: Ptr<GPUFloatTexture>,
|
||||
normal_image: Ptr<DeviceImage>,
|
||||
displacement: *const GPUFloatTexture,
|
||||
normal_image: *const Image,
|
||||
) {
|
||||
let ctx = NormalBumpEvalContext::from(&*self);
|
||||
let (dpdu, dpdv) = if !displacement.is_null() {
|
||||
bump_map(tex_eval, &displacement, &ctx)
|
||||
} else if !normal_image.is_null() {
|
||||
let map = unsafe { normal_image.as_ref() };
|
||||
normal_map(map, &ctx)
|
||||
let (dpdu, dpdv) = if let Some(disp) = displacement {
|
||||
bump_map(tex_eval, &disp, &ctx)
|
||||
} else if let Some(map) = normal_image {
|
||||
normal_map(map.as_ref(), &ctx)
|
||||
} else {
|
||||
(self.shading.dpdu, self.shading.dpdv)
|
||||
};
|
||||
|
|
@ -443,8 +405,7 @@ impl SurfaceInteraction {
|
|||
) -> Ray {
|
||||
let mut rd = self.spawn_ray(wi);
|
||||
|
||||
if ray_i.has_differentials {
|
||||
let diff_i = ray_i.differential;
|
||||
if let Some(diff_i) = &ray_i.differential {
|
||||
let mut n = self.shading.n;
|
||||
|
||||
let mut dndx = self.shading.dndu * self.dudx + self.shading.dndv * self.dvdx;
|
||||
|
|
@ -514,14 +475,14 @@ impl SurfaceInteraction {
|
|||
|| Vector3f::from(new_diff_rx_origin).norm_squared() > threshold
|
||||
|| Vector3f::from(new_diff_ry_origin).norm_squared() > threshold
|
||||
{
|
||||
rd.differential = RayDifferential::default();
|
||||
rd.differential = None;
|
||||
} else {
|
||||
rd.differential = RayDifferential {
|
||||
rd.differential = Some(RayDifferential {
|
||||
rx_origin: new_diff_rx_origin,
|
||||
ry_origin: new_diff_ry_origin,
|
||||
rx_direction: new_diff_rx_dir,
|
||||
ry_direction: new_diff_ry_dir,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -531,21 +492,22 @@ impl SurfaceInteraction {
|
|||
}
|
||||
|
||||
impl InteractionTrait for SurfaceInteraction {
|
||||
fn get_common(&self) -> &InteractionBase {
|
||||
fn get_common(&self) -> &InteractionData {
|
||||
&self.common
|
||||
}
|
||||
|
||||
fn get_common_mut(&mut self) -> &mut InteractionBase {
|
||||
fn get_common_mut(&mut self) -> &mut InteractionData {
|
||||
&mut self.common
|
||||
}
|
||||
|
||||
fn get_medium(&self, w: Vector3f) -> Ptr<Medium> {
|
||||
let interface = self.common.medium_interface;
|
||||
fn get_medium(&self, w: Vector3f) -> *const Medium {
|
||||
self.common.medium_interface.as_ref().and_then(|interface| {
|
||||
if self.n().dot(w.into()) > 0.0 {
|
||||
interface.outside
|
||||
} else {
|
||||
interface.inside
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn is_surface_interaction(&self) -> bool {
|
||||
|
|
@ -574,7 +536,15 @@ impl SurfaceInteraction {
|
|||
}
|
||||
|
||||
Self {
|
||||
common: InteractionBase::new_surface_geom(pi, n, uv, wo, time),
|
||||
common: InteractionData {
|
||||
pi,
|
||||
n,
|
||||
time,
|
||||
wo,
|
||||
medium_interface: None,
|
||||
medium: None,
|
||||
},
|
||||
uv,
|
||||
dpdu,
|
||||
dpdv,
|
||||
dndu,
|
||||
|
|
@ -586,16 +556,16 @@ impl SurfaceInteraction {
|
|||
dndu,
|
||||
dndv,
|
||||
},
|
||||
material: Ptr::null(),
|
||||
material: None,
|
||||
face_index: 0,
|
||||
area_light: Ptr::null(),
|
||||
area_light: None,
|
||||
dpdx: Vector3f::zero(),
|
||||
dpdy: Vector3f::zero(),
|
||||
dudx: 0.0,
|
||||
dudy: 0.0,
|
||||
dvdx: 0.0,
|
||||
dvdy: 0.0,
|
||||
shape: Ptr::null(),
|
||||
shape: core::ptr::null(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -609,7 +579,7 @@ impl SurfaceInteraction {
|
|||
dndv: Normal3f,
|
||||
time: Float,
|
||||
flip: bool,
|
||||
face_index: i32,
|
||||
face_index: usize,
|
||||
) -> Self {
|
||||
let mut si = Self::new(pi, uv, wo, dpdu, dpdv, dndu, dndv, time, flip);
|
||||
si.face_index = face_index;
|
||||
|
|
@ -627,7 +597,7 @@ impl SurfaceInteraction {
|
|||
) {
|
||||
self.shading.n = ns;
|
||||
if orientation {
|
||||
self.common.n = self.n().face_forward(self.shading.n);
|
||||
self.common.n = self.n().face_forward(self.shading.n.into());
|
||||
}
|
||||
self.shading.dpdu = dpdus;
|
||||
self.shading.dpdv = dpdvs;
|
||||
|
|
@ -637,18 +607,26 @@ impl SurfaceInteraction {
|
|||
|
||||
pub fn new_simple(pi: Point3fi, n: Normal3f, uv: Point2f) -> Self {
|
||||
Self {
|
||||
common: InteractionBase::new_surface_geom(pi, n, uv, Vector3f::zero(), 0.),
|
||||
common: InteractionData {
|
||||
pi,
|
||||
n,
|
||||
time: 0.,
|
||||
wo: Vector3f::zero(),
|
||||
medium_interface: None,
|
||||
medium: None,
|
||||
},
|
||||
uv,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_minimal(pi: Point3fi, uv: Point2f) -> Self {
|
||||
Self {
|
||||
common: InteractionBase {
|
||||
common: InteractionData {
|
||||
pi,
|
||||
uv,
|
||||
..Default::default()
|
||||
},
|
||||
uv,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
|
@ -656,18 +634,18 @@ impl SurfaceInteraction {
|
|||
#[cfg(not(target_os = "cuda"))]
|
||||
pub fn set_intersection_properties(
|
||||
&mut self,
|
||||
mtl: &Material,
|
||||
area: &Light,
|
||||
ray_medium: &Medium,
|
||||
mtl: *const Material,
|
||||
area: *const Light,
|
||||
prim_medium_interface: MediumInterface,
|
||||
ray_medium: *const Medium,
|
||||
) {
|
||||
self.material = Ptr::from(mtl);
|
||||
self.area_light = Ptr::from(area);
|
||||
self.material = mtl;
|
||||
self.area_light = area;
|
||||
|
||||
if prim_medium_interface.is_medium_transition() {
|
||||
self.common.medium_interface = prim_medium_interface;
|
||||
self.common.medium_interface = *prim_medium_interface;
|
||||
} else {
|
||||
self.common.medium = Ptr::from(ray_medium);
|
||||
self.common.medium = ray_medium;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -675,8 +653,10 @@ impl SurfaceInteraction {
|
|||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct MediumInteraction {
|
||||
pub common: InteractionBase,
|
||||
pub common: InteractionData,
|
||||
pub medium: *const Medium,
|
||||
pub phase: PhaseFunction,
|
||||
pub medium_interface: MediumInterface,
|
||||
}
|
||||
|
||||
impl MediumInteraction {
|
||||
|
|
@ -684,12 +664,21 @@ impl MediumInteraction {
|
|||
p: Point3f,
|
||||
wo: Vector3f,
|
||||
time: Float,
|
||||
medium: Ptr<Medium>,
|
||||
medium: *const Medium,
|
||||
phase: PhaseFunction,
|
||||
) -> Self {
|
||||
Self {
|
||||
common: InteractionBase::new_medium(p, wo, time, medium),
|
||||
common: InteractionData {
|
||||
pi: Point3fi::new_from_point(p),
|
||||
n: Normal3f::default(),
|
||||
time,
|
||||
wo: wo.normalize(),
|
||||
medium_interface: None,
|
||||
medium,
|
||||
},
|
||||
medium,
|
||||
phase,
|
||||
medium_interface: MediumInterface::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -699,11 +688,11 @@ impl InteractionTrait for MediumInteraction {
|
|||
true
|
||||
}
|
||||
|
||||
fn get_common(&self) -> &InteractionBase {
|
||||
fn get_common(&self) -> &InteractionData {
|
||||
&self.common
|
||||
}
|
||||
|
||||
fn get_common_mut(&mut self) -> &mut InteractionBase {
|
||||
fn get_common_mut(&mut self) -> &mut InteractionData {
|
||||
&mut self.common
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ use crate::core::geometry::{
|
|||
Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray,
|
||||
Vector3f, VectorLike, cos_theta,
|
||||
};
|
||||
use crate::core::image::DeviceImage;
|
||||
use crate::core::interaction::{
|
||||
Interaction, InteractionBase, InteractionTrait, MediumInteraction, SimpleInteraction,
|
||||
Interaction, InteractionData, InteractionTrait, MediumInteraction, SimpleInteraction,
|
||||
SurfaceInteraction,
|
||||
};
|
||||
use crate::core::medium::MediumInterface;
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||
use crate::images::Image;
|
||||
use crate::lights::*;
|
||||
use crate::spectra::{
|
||||
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBColorSpace, RGBIlluminantSpectrum,
|
||||
|
|
@ -17,7 +17,7 @@ use crate::spectra::{
|
|||
};
|
||||
use crate::utils::Transform;
|
||||
use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square};
|
||||
use crate::utils::sampling::DevicePiecewiseConstant2D;
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
use crate::{Float, PI};
|
||||
use bitflags::bitflags;
|
||||
|
||||
|
|
@ -49,9 +49,9 @@ impl LightType {
|
|||
pub struct LightLeSample {
|
||||
pub l: SampledSpectrum,
|
||||
pub ray: Ray,
|
||||
pub intr: *const InteractionData,
|
||||
pub pdf_pos: Float,
|
||||
pub pdf_dir: Float,
|
||||
pub intr: Interaction,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
|
@ -60,12 +60,12 @@ pub struct LightLiSample {
|
|||
pub l: SampledSpectrum,
|
||||
pub wi: Vector3f,
|
||||
pub pdf: Float,
|
||||
pub p_light: Interaction,
|
||||
pub p_light: InteractionData,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
impl LightLiSample {
|
||||
pub fn new(l: SampledSpectrum, wi: Vector3f, pdf: Float, p_light: Interaction) -> Self {
|
||||
pub fn new(l: SampledSpectrum, wi: Vector3f, pdf: Float, p_light: InteractionData) -> Self {
|
||||
Self {
|
||||
l,
|
||||
wi,
|
||||
|
|
@ -140,23 +140,32 @@ impl From<&Interaction> for LightSampleContext {
|
|||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LightBase {
|
||||
pub render_from_light: Transform,
|
||||
pub light_type: LightType,
|
||||
pub light_type: u32,
|
||||
pub medium_interface: MediumInterface,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
impl LightBase {
|
||||
pub fn new(
|
||||
light_type: LightType,
|
||||
render_from_light: Transform,
|
||||
medium_interface: MediumInterface,
|
||||
render_from_light: &Transform,
|
||||
medium_interface: &MediumInterface,
|
||||
) -> Self {
|
||||
Self {
|
||||
light_type,
|
||||
render_from_light,
|
||||
medium_interface,
|
||||
render_from_light: *render_from_light,
|
||||
medium_interface: medium_interface.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
&self,
|
||||
_p: Point3f,
|
||||
|
|
@ -339,9 +348,9 @@ pub enum Light {
|
|||
DiffuseArea(DiffuseAreaLight),
|
||||
Distant(DistantLight),
|
||||
Goniometric(GoniometricLight),
|
||||
InfiniteUniform(UniformInfiniteLight),
|
||||
InfiniteImage(ImageInfiniteLight),
|
||||
InfinitePortal(PortalInfiniteLight),
|
||||
InfiniteUniform(InfiniteUniformLight),
|
||||
InfiniteImage(InfiniteImageLight),
|
||||
InfinitePortal(InfinitePortalLight),
|
||||
Point(PointLight),
|
||||
Projection(ProjectionLight),
|
||||
Spot(SpotLight),
|
||||
|
|
|
|||
|
|
@ -1,23 +1,21 @@
|
|||
use crate::materials::*;
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::Float;
|
||||
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::bxdf::{
|
||||
BSDF, BxDF, CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF,
|
||||
};
|
||||
use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike};
|
||||
use crate::core::image::{DeviceImage, WrapMode, WrapMode2D};
|
||||
use crate::core::interaction::{Interaction, InteractionTrait, ShadingGeom, SurfaceInteraction};
|
||||
use crate::core::interaction::InteractionTrait;
|
||||
use crate::core::interaction::{Interaction, ShadingGeom, SurfaceInteraction};
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::core::scattering::TrowbridgeReitzDistribution;
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||
use crate::core::texture::{
|
||||
GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext, TextureEvaluator,
|
||||
};
|
||||
use crate::materials::*;
|
||||
use crate::images::{Image, WrapMode, WrapMode2D};
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::Ptr;
|
||||
use crate::utils::hash::hash_float;
|
||||
|
|
@ -65,14 +63,14 @@ pub struct NormalBumpEvalContext {
|
|||
pub dudy: Float,
|
||||
pub dvdx: Float,
|
||||
pub dvdy: Float,
|
||||
pub face_index: i32,
|
||||
pub face_index: usize,
|
||||
}
|
||||
|
||||
impl From<&SurfaceInteraction> for NormalBumpEvalContext {
|
||||
fn from(si: &SurfaceInteraction) -> Self {
|
||||
Self {
|
||||
p: si.p(),
|
||||
uv: si.common.uv,
|
||||
uv: si.uv,
|
||||
n: si.n(),
|
||||
shading: si.shading.clone(),
|
||||
dudx: si.dudx,
|
||||
|
|
@ -103,7 +101,7 @@ impl From<&NormalBumpEvalContext> for TextureEvalContext {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn normal_map(normal_map: &DeviceImage, ctx: &NormalBumpEvalContext) -> (Vector3f, Vector3f) {
|
||||
pub fn normal_map(normal_map: &Image, ctx: &NormalBumpEvalContext) -> (Vector3f, Vector3f) {
|
||||
let wrap = WrapMode2D::from(WrapMode::Repeat);
|
||||
let uv = Point2f::new(ctx.uv[0], 1. - ctx.uv[1]);
|
||||
let r = normal_map.bilerp_channel_with_wrap(uv, 0, wrap);
|
||||
|
|
@ -125,7 +123,7 @@ pub fn bump_map<T: TextureEvaluator>(
|
|||
displacement: &GPUFloatTexture,
|
||||
ctx: &NormalBumpEvalContext,
|
||||
) -> (Vector3f, Vector3f) {
|
||||
debug_assert!(tex_eval.can_evaluate(&[Ptr::from(displacement)], &[]));
|
||||
debug_assert!(tex_eval.can_evaluate(&[displacement], &[]));
|
||||
let mut du = 0.5 * (ctx.dudx.abs() + ctx.dudy.abs());
|
||||
if du == 0.0 {
|
||||
du = 0.0005;
|
||||
|
|
@ -157,7 +155,7 @@ pub fn bump_map<T: TextureEvaluator>(
|
|||
}
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait MaterialTrait {
|
||||
pub trait MaterialTrait: Send + Sync + std::fmt::Debug {
|
||||
fn get_bsdf<T: TextureEvaluator>(
|
||||
&self,
|
||||
tex_eval: &T,
|
||||
|
|
@ -173,9 +171,9 @@ pub trait MaterialTrait {
|
|||
) -> Option<BSSRDF>;
|
||||
|
||||
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool;
|
||||
fn get_normal_map(&self) -> Option<&DeviceImage>;
|
||||
fn get_displacement(&self) -> Ptr<GPUFloatTexture>;
|
||||
fn has_subsurface_scattering(&self) -> bool;
|
||||
fn get_normal_map(&self) -> *const Image;
|
||||
fn get_displacement(&self) -> Option<GPUFloatTexture>;
|
||||
fn has_surface_scattering(&self) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
|
@ -193,3 +191,729 @@ pub enum Material {
|
|||
ThinDielectric(ThinDielectricMaterial),
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,16 +5,14 @@ use crate::core::geometry::{
|
|||
Bounds3f, Frame, Point2f, Point3f, Point3i, Ray, Vector3f, VectorLike, spherical_direction,
|
||||
};
|
||||
use crate::core::pbrt::{Float, INV_4_PI, PI};
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||
use crate::spectra::{
|
||||
BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum,
|
||||
RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths,
|
||||
};
|
||||
use crate::utils::containers::SampledGrid;
|
||||
use crate::utils::math::{clamp, square};
|
||||
use crate::utils::ptr::Ptr;
|
||||
use crate::utils::rng::Rng;
|
||||
use crate::utils::transform::Transform;
|
||||
use crate::utils::transform::TransformGeneric;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -39,7 +37,7 @@ pub trait PhaseFunctionTrait {
|
|||
|
||||
#[repr(C)]
|
||||
#[enum_dispatch(PhaseFunctionTrait)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PhaseFunction {
|
||||
HenyeyGreenstein(HGPhaseFunction),
|
||||
}
|
||||
|
|
@ -90,30 +88,26 @@ impl PhaseFunctionTrait for HGPhaseFunction {
|
|||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MajorantGrid {
|
||||
pub bounds: Bounds3f,
|
||||
pub res: Point3i,
|
||||
pub voxels: *mut Float,
|
||||
pub n_voxels: u32,
|
||||
pub voxels: *const Float,
|
||||
}
|
||||
|
||||
unsafe impl Send for MajorantGrid {}
|
||||
unsafe impl Sync for MajorantGrid {}
|
||||
|
||||
impl MajorantGrid {
|
||||
// #[cfg(not(target_os = "cuda"))]
|
||||
// pub fn new(bounds: Bounds3f, res: Point3i) -> Self {
|
||||
// let n_voxels = (res.x() * res.y() * res.z()) as usize;
|
||||
// let voxels = Vec::with_capacity(n_voxels);
|
||||
// Self {
|
||||
// bounds,
|
||||
// res,
|
||||
// voxels: voxels.as_ptr(),
|
||||
// n_voxels: n_voxels as u32,
|
||||
// }
|
||||
// }
|
||||
//
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
pub fn new(bounds: Bounds3f, res: Point3i) -> Self {
|
||||
Self {
|
||||
bounds,
|
||||
res,
|
||||
voxels: Vec::with_capacity((res.x() * res.y() * res.z()) as usize),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_valid(&self) -> bool {
|
||||
!self.voxels.is_null()
|
||||
|
|
@ -127,7 +121,7 @@ impl MajorantGrid {
|
|||
|
||||
let idx = z * self.res.x() * self.res.y() + y * self.res.x() + x;
|
||||
|
||||
if idx >= 0 && (idx as u32) < self.n_voxels {
|
||||
if idx >= 0 && (idx as usize) < self.voxels.len() {
|
||||
unsafe { *self.voxels.add(idx as usize) }
|
||||
} else {
|
||||
0.0
|
||||
|
|
@ -135,7 +129,7 @@ impl MajorantGrid {
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn set(&mut self, x: i32, y: i32, z: i32, v: Float) {
|
||||
pub fn set(&self, x: i32, y: i32, z: i32, v: Float) {
|
||||
if !self.is_valid() {
|
||||
return;
|
||||
}
|
||||
|
|
@ -268,7 +262,7 @@ impl DDAMajorantIterator {
|
|||
|
||||
let p_grid_start = grid.bounds.offset(&ray.at(t_min));
|
||||
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 {
|
||||
iter.voxel[axis] = clamp(
|
||||
|
|
@ -439,8 +433,7 @@ pub trait MediumTrait: Send + Sync + std::fmt::Debug {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[enum_dispatch(MediumTrait)]
|
||||
pub enum Medium {
|
||||
Homogeneous(HomogeneousMedium),
|
||||
|
|
@ -450,13 +443,38 @@ pub enum Medium {
|
|||
NanoVDB(NanoVDBMedium),
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HomogeneousMedium {
|
||||
pub sigma_a_spec: DenselySampledSpectrum,
|
||||
pub sigma_s_spec: DenselySampledSpectrum,
|
||||
pub le_spec: DenselySampledSpectrum,
|
||||
pub phase: HGPhaseFunction,
|
||||
sigma_a_spec: DenselySampledSpectrum,
|
||||
sigma_s_spec: DenselySampledSpectrum,
|
||||
le_spec: DenselySampledSpectrum,
|
||||
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 {
|
||||
|
|
@ -495,17 +513,70 @@ impl MediumTrait for HomogeneousMedium {
|
|||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GridMedium {
|
||||
pub bounds: Bounds3f,
|
||||
pub render_from_medium: Transform,
|
||||
pub sigma_a_spec: DenselySampledSpectrum,
|
||||
pub sigma_s_spec: DenselySampledSpectrum,
|
||||
pub density_grid: SampledGrid<Float>,
|
||||
pub phase: HGPhaseFunction,
|
||||
pub temperature_grid: Option<SampledGrid<Float>>,
|
||||
pub le_spec: DenselySampledSpectrum,
|
||||
pub le_scale: SampledGrid<Float>,
|
||||
pub is_emissive: bool,
|
||||
pub majorant_grid: MajorantGrid,
|
||||
bounds: Bounds3f,
|
||||
render_from_medium: TransformGeneric<Float>,
|
||||
sigma_a_spec: DenselySampledSpectrum,
|
||||
sigma_s_spec: DenselySampledSpectrum,
|
||||
density_grid: SampledGrid<Float>,
|
||||
phase: HGPhaseFunction,
|
||||
temperature_grid: SampledGrid<Float>,
|
||||
le_spec: DenselySampledSpectrum,
|
||||
le_scale: SampledGrid<Float>,
|
||||
is_emissive: bool,
|
||||
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 {
|
||||
|
|
@ -528,11 +599,12 @@ impl MediumTrait for GridMedium {
|
|||
};
|
||||
|
||||
let le = if scale > 0.0 {
|
||||
let raw_emission = if let Some(temp_grid) = &self.temperature_grid {
|
||||
let temp = temp_grid.lookup(p);
|
||||
let raw_emission = match &self.temperature_grid {
|
||||
Some(grid) => {
|
||||
let temp = grid.lookup(p);
|
||||
BlackbodySpectrum::new(temp).sample(lambda)
|
||||
} else {
|
||||
self.le_spec.sample(lambda)
|
||||
}
|
||||
None => self.le_spec.sample(lambda),
|
||||
};
|
||||
|
||||
raw_emission * scale
|
||||
|
|
@ -588,15 +660,59 @@ impl MediumTrait for GridMedium {
|
|||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RGBGridMedium {
|
||||
pub bounds: Bounds3f,
|
||||
pub render_from_medium: Transform,
|
||||
pub phase: HGPhaseFunction,
|
||||
pub le_scale: Float,
|
||||
pub sigma_scale: Float,
|
||||
pub sigma_a_grid: SampledGrid<RGBUnboundedSpectrum>,
|
||||
pub sigma_s_grid: SampledGrid<RGBUnboundedSpectrum>,
|
||||
pub le_grid: SampledGrid<RGBIlluminantSpectrum>,
|
||||
pub majorant_grid: MajorantGrid,
|
||||
bounds: Bounds3f,
|
||||
render_from_medium: Transform,
|
||||
phase: HGPhaseFunction,
|
||||
le_scale: Float,
|
||||
sigma_scale: Float,
|
||||
sigma_a_grid: SampledGrid<RGBUnboundedSpectrum>,
|
||||
sigma_s_grid: SampledGrid<RGBUnboundedSpectrum>,
|
||||
le_grid: SampledGrid<RGBIlluminantSpectrum>,
|
||||
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 {
|
||||
|
|
@ -669,8 +785,7 @@ impl MediumTrait for RGBGridMedium {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CloudMedium;
|
||||
impl MediumTrait for CloudMedium {
|
||||
fn is_emissive(&self) -> bool {
|
||||
|
|
@ -688,9 +803,7 @@ impl MediumTrait for CloudMedium {
|
|||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NanoVDBMedium;
|
||||
impl MediumTrait for NanoVDBMedium {
|
||||
fn is_emissive(&self) -> bool {
|
||||
|
|
@ -712,8 +825,8 @@ impl MediumTrait for NanoVDBMedium {
|
|||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct MediumInterface {
|
||||
pub inside: Ptr<Medium>,
|
||||
pub outside: Ptr<Medium>,
|
||||
pub inside: *const Medium,
|
||||
pub outside: *const Medium,
|
||||
}
|
||||
|
||||
unsafe impl Send for MediumInterface {}
|
||||
|
|
@ -722,37 +835,22 @@ unsafe impl Sync for MediumInterface {}
|
|||
impl Default for MediumInterface {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inside: Ptr::null(),
|
||||
outside: Ptr::null(),
|
||||
inside: core::ptr::null(),
|
||||
outside: core::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 {
|
||||
pub fn new(inside: &Medium, outside: &Medium) -> Self {
|
||||
Self {
|
||||
inside: Ptr::from(inside),
|
||||
outside: Ptr::from(outside),
|
||||
}
|
||||
pub fn new(inside: *const Medium, outside: *const Medium) -> Self {
|
||||
Self { inside, outside }
|
||||
}
|
||||
|
||||
pub fn empty() -> Self {
|
||||
Self::default()
|
||||
Self {
|
||||
inside: core::ptr::null(),
|
||||
outside: core::ptr::null(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_medium_transition(&self) -> bool {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
pub mod bsdf;
|
||||
pub mod aggregates;
|
||||
pub mod bssrdf;
|
||||
pub mod bxdf;
|
||||
pub mod camera;
|
||||
|
|
@ -6,7 +6,6 @@ pub mod color;
|
|||
pub mod film;
|
||||
pub mod filter;
|
||||
pub mod geometry;
|
||||
pub mod image;
|
||||
pub mod interaction;
|
||||
pub mod light;
|
||||
pub mod material;
|
||||
|
|
@ -16,6 +15,5 @@ pub mod pbrt;
|
|||
pub mod primitive;
|
||||
pub mod sampler;
|
||||
pub mod scattering;
|
||||
pub mod shape;
|
||||
pub mod spectrum;
|
||||
pub mod texture;
|
||||
|
|
|
|||
|
|
@ -1,54 +1,12 @@
|
|||
use crate::core::geometry::Lerp;
|
||||
use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
|
||||
use num_traits::{Num, PrimInt};
|
||||
use std::hash::Hash;
|
||||
use std::ops::{Add, Mul};
|
||||
use std::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
|
||||
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;
|
||||
|
||||
// #[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"))]
|
||||
pub type FloatBits = u32;
|
||||
|
||||
|
|
@ -142,18 +100,50 @@ pub const PI_OVER_2: Float = 1.570_796_326_794_896_619_23;
|
|||
pub const PI_OVER_4: Float = 0.785_398_163_397_448_309_61;
|
||||
pub const SQRT_2: Float = 1.414_213_562_373_095_048_80;
|
||||
|
||||
#[inline]
|
||||
pub fn 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]
|
||||
pub fn gamma(n: i32) -> Float {
|
||||
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_CONDITION_MET: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! check_rare {
|
||||
($frequency_threshold:expr, $condition:expr) => {
|
||||
use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
|
||||
const CHECK_INTERVAL: u64 = 4096;
|
||||
|
||||
let total_calls = RARE_EVENT_TOTAL_CALLS.fetch_add(1, SyncOrdering::Relaxed);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
use crate::core::aggregates::LinearBVHNode;
|
||||
use crate::core::geometry::{Bounds3f, Ray};
|
||||
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
||||
use crate::core::light::Light;
|
||||
use crate::core::material::Material;
|
||||
use crate::core::medium::{Medium, MediumInterface};
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::core::shape::{Shape, ShapeIntersection, ShapeTrait};
|
||||
use crate::core::texture::{GPUFloatTexture, TextureEvalContext};
|
||||
use crate::utils::Ptr;
|
||||
use crate::shapes::{Shape, ShapeIntersection, ShapeTrait};
|
||||
use crate::utils::hash::hash_float;
|
||||
use crate::utils::transform::{AnimatedTransform, Transform};
|
||||
use crate::utils::transform::{AnimatedTransform, TransformGeneric};
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use std::sync::Arc;
|
||||
|
|
@ -23,11 +23,11 @@ pub trait PrimitiveTrait {
|
|||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GeometricPrimitive {
|
||||
pub shape: Ptr<Shape>,
|
||||
pub material: Ptr<Material>,
|
||||
pub area_light: Ptr<Light>,
|
||||
pub medium_interface: MediumInterface,
|
||||
pub alpha: Ptr<GPUFloatTexture>,
|
||||
shape: *const Shape,
|
||||
material: *const Material,
|
||||
area_light: *const Light,
|
||||
medium_interface: MediumInterface,
|
||||
alpha: *const GPUFloatTexture,
|
||||
}
|
||||
|
||||
unsafe impl Send for GeometricPrimitive {}
|
||||
|
|
@ -40,8 +40,7 @@ impl PrimitiveTrait for GeometricPrimitive {
|
|||
|
||||
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
|
||||
let mut si = self.shape.intersect(r, t_max)?;
|
||||
if !self.alpha.is_null() {
|
||||
let alpha = unsafe { &self.alpha.as_ref() };
|
||||
if let Some(ref alpha) = self.alpha {
|
||||
let ctx = TextureEvalContext::from(&si.intr);
|
||||
let a = alpha.evaluate(&ctx);
|
||||
if a < 1.0 {
|
||||
|
|
@ -66,21 +65,18 @@ impl PrimitiveTrait for GeometricPrimitive {
|
|||
}
|
||||
}
|
||||
|
||||
if r.medium.is_null() {
|
||||
return None;
|
||||
}
|
||||
si.set_intersection_properties(
|
||||
self.material,
|
||||
self.area_light,
|
||||
self.medium_interface.clone(),
|
||||
r.medium,
|
||||
self.material.clone(),
|
||||
self.area_light.clone(),
|
||||
Some(self.medium_interface.clone()),
|
||||
Some(r.medium.clone().expect("Medium not set")),
|
||||
);
|
||||
|
||||
Some(si)
|
||||
}
|
||||
|
||||
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool {
|
||||
if !self.alpha.is_null() {
|
||||
if self.alpha.is_some() {
|
||||
self.intersect(r, t_max).is_some()
|
||||
} else {
|
||||
self.shape.intersect_p(r, t_max)
|
||||
|
|
@ -91,28 +87,14 @@ impl PrimitiveTrait for GeometricPrimitive {
|
|||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct SimplePrimitive {
|
||||
pub shape: Ptr<Shape>,
|
||||
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!()
|
||||
}
|
||||
shape: Arc<Shape>,
|
||||
material: Arc<Material>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TransformedPrimitive {
|
||||
pub primitive: Ptr<Primitive>,
|
||||
pub render_from_primitive: Transform,
|
||||
primitive: Arc<dyn PrimitiveTrait>,
|
||||
render_from_primitive: TransformGeneric<Float>,
|
||||
}
|
||||
|
||||
impl PrimitiveTrait for TransformedPrimitive {
|
||||
|
|
@ -143,10 +125,9 @@ impl PrimitiveTrait for TransformedPrimitive {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AnimatedPrimitive {
|
||||
primitive: Ptr<Primitive>,
|
||||
primitive: Arc<dyn PrimitiveTrait>,
|
||||
render_from_primitive: AnimatedTransform,
|
||||
}
|
||||
|
||||
|
|
@ -177,38 +158,31 @@ impl PrimitiveTrait for AnimatedPrimitive {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LinearBVHNode {
|
||||
bounds: Bounds3f,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BVHAggregatePrimitive {
|
||||
max_prims_in_node: u32,
|
||||
primitives: *const Ptr<Primitive>,
|
||||
nodes: Ptr<LinearBVHNode>,
|
||||
max_prims_in_node: usize,
|
||||
primitives: Vec<Arc<dyn PrimitiveTrait>>,
|
||||
nodes: Vec<LinearBVHNode>,
|
||||
}
|
||||
|
||||
impl PrimitiveTrait for BVHAggregatePrimitive {
|
||||
fn bounds(&self) -> Bounds3f {
|
||||
if !self.nodes.is_null() {
|
||||
self.nodes.bounds
|
||||
if !self.nodes.is_empty() {
|
||||
self.nodes[0].bounds
|
||||
} else {
|
||||
Bounds3f::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
|
||||
if !self.nodes.is_null() {
|
||||
if self.nodes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
self.intersect(r, t_max)
|
||||
}
|
||||
|
||||
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool {
|
||||
if !self.nodes.is_null() {
|
||||
if self.nodes.is_empty() {
|
||||
return false;
|
||||
}
|
||||
self.intersect_p(r, t_max)
|
||||
|
|
@ -235,7 +209,6 @@ impl PrimitiveTrait for KdTreeAggregate {
|
|||
#[derive(Clone, Debug)]
|
||||
#[enum_dispatch(PrimitiveTrait)]
|
||||
pub enum Primitive {
|
||||
Simple(SimplePrimitive),
|
||||
Geometric(GeometricPrimitive),
|
||||
Transformed(TransformedPrimitive),
|
||||
Animated(AnimatedPrimitive),
|
||||
|
|
|
|||
|
|
@ -1,23 +1,27 @@
|
|||
use crate::core::filter::FilterTrait;
|
||||
use crate::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f};
|
||||
use crate::core::options::{PBRTOptions, get_options};
|
||||
use crate::core::pbrt::{Float, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4};
|
||||
use crate::utils::Ptr;
|
||||
use crate::utils::containers::DeviceArray2D;
|
||||
use crate::utils::math::{
|
||||
BinaryPermuteScrambler, DeviceDigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler,
|
||||
PRIME_TABLE_SIZE, Scrambler, clamp, encode_morton_2, inverse_radical_inverse, lerp, log2_int,
|
||||
owen_scrambled_radical_inverse, permutation_element, radical_inverse, round_up_pow2,
|
||||
scrambled_radical_inverse, sobol_interval_to_index, sobol_sample,
|
||||
};
|
||||
use crate::utils::rng::Rng;
|
||||
use crate::utils::sobol::N_SOBOL_DIMENSIONS;
|
||||
use crate::utils::{hash::*, sobol};
|
||||
use std::ops::RangeFull;
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use rand::seq::index::sample;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
use crate::core::filter::FilterTrait;
|
||||
use crate::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f};
|
||||
use crate::core::options::{PBRTOptions, get_options};
|
||||
use crate::core::pbrt::{Float, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4, find_interval};
|
||||
use crate::utils::containers::Array2D;
|
||||
use crate::utils::error::FileLoc;
|
||||
use crate::utils::math::{
|
||||
BinaryPermuteScrambler, DigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler,
|
||||
PRIME_TABLE_SIZE, Scrambler, clamp, compute_radical_inverse_permutations, encode_morton_2,
|
||||
inverse_radical_inverse, lerp, log2_int, owen_scrambled_radical_inverse, permutation_element,
|
||||
radical_inverse, round_up_pow2, scrambled_radical_inverse, sobol_interval_to_index,
|
||||
sobol_sample,
|
||||
};
|
||||
use crate::utils::parameters::ParameterDictionary;
|
||||
use crate::utils::rng::Rng;
|
||||
use crate::utils::sobol::N_SOBOL_DIMENSIONS;
|
||||
use crate::utils::{hash::*, sobol};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CameraSample {
|
||||
pub p_film: Point2f,
|
||||
pub p_lens: Point2f,
|
||||
|
|
@ -25,6 +29,17 @@ pub struct CameraSample {
|
|||
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
|
||||
where
|
||||
S: SamplerTrait,
|
||||
|
|
@ -39,29 +54,43 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct IndependentSampler {
|
||||
pub samples_per_pixel: i32,
|
||||
pub seed: u64,
|
||||
pub rng: Rng,
|
||||
samples_per_pixel: usize,
|
||||
seed: u64,
|
||||
rng: Rng,
|
||||
}
|
||||
|
||||
impl IndependentSampler {
|
||||
pub fn new(samples_per_pixel: i32, seed: u64) -> Self {
|
||||
pub fn new(samples_per_pixel: usize, seed: u64) -> Self {
|
||||
Self {
|
||||
samples_per_pixel,
|
||||
seed,
|
||||
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 {
|
||||
fn samples_per_pixel(&self) -> i32 {
|
||||
fn samples_per_pixel(&self) -> usize {
|
||||
self.samples_per_pixel
|
||||
}
|
||||
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
|
||||
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>) {
|
||||
let hash_input = [p.x() as u64, p.y() as u64, self.seed];
|
||||
let sequence_index = hash_buffer(&hash_input, 0);
|
||||
self.rng.set_sequence(sequence_index);
|
||||
|
|
@ -80,10 +109,9 @@ impl SamplerTrait for IndependentSampler {
|
|||
}
|
||||
}
|
||||
|
||||
pub const MAX_HALTON_RESOLUTION: i32 = 128;
|
||||
const MAX_HALTON_RESOLUTION: i32 = 128;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Copy)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub enum RandomizeStrategy {
|
||||
#[default]
|
||||
None,
|
||||
|
|
@ -92,26 +120,75 @@ pub enum RandomizeStrategy {
|
|||
Owen,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct HaltonSampler {
|
||||
pub samples_per_pixel: i32,
|
||||
pub randomize: RandomizeStrategy,
|
||||
pub base_scales: [u64; 2],
|
||||
pub base_exponents: [u64; 2],
|
||||
pub mult_inverse: [u64; 2],
|
||||
pub halton_index: u64,
|
||||
pub dim: u32,
|
||||
pub digit_permutations: Ptr<DeviceDigitPermutation>,
|
||||
samples_per_pixel: usize,
|
||||
randomize: RandomizeStrategy,
|
||||
digit_permutations: Vec<DigitPermutation>,
|
||||
base_scales: [u64; 2],
|
||||
base_exponents: [u64; 2],
|
||||
mult_inverse: [u64; 2],
|
||||
halton_index: u64,
|
||||
dim: usize,
|
||||
}
|
||||
|
||||
impl HaltonSampler {
|
||||
pub fn sample_dimension(&self, dimension: u32) -> Float {
|
||||
pub fn new(
|
||||
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 {
|
||||
radical_inverse(dimension, self.halton_index)
|
||||
} else if self.randomize == RandomizeStrategy::PermuteDigits {
|
||||
let digit_perm = unsafe { &*self.digit_permutations.add(dimension as usize) };
|
||||
scrambled_radical_inverse(dimension, self.halton_index, digit_perm)
|
||||
scrambled_radical_inverse(
|
||||
dimension,
|
||||
self.halton_index,
|
||||
&self.digit_permutations[dimension],
|
||||
)
|
||||
} else {
|
||||
owen_scrambled_radical_inverse(
|
||||
dimension,
|
||||
|
|
@ -121,12 +198,12 @@ impl HaltonSampler {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn multiplicative_inverse(a: i64, n: i64) -> u64 {
|
||||
fn multiplicative_inverse(a: i64, n: i64) -> u64 {
|
||||
let (x, _) = Self::extended_gcd(a as u64, n as u64);
|
||||
x.rem_euclid(n) as u64
|
||||
}
|
||||
|
||||
pub fn extended_gcd(a: u64, b: u64) -> (i64, i64) {
|
||||
fn extended_gcd(a: u64, b: u64) -> (i64, i64) {
|
||||
if b == 0 {
|
||||
return (1, 0);
|
||||
}
|
||||
|
|
@ -137,14 +214,45 @@ impl HaltonSampler {
|
|||
|
||||
(yp, xp - d * yp)
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
params: &ParameterDictionary,
|
||||
full_res: Point2i,
|
||||
loc: &FileLoc,
|
||||
) -> Result<Self, String> {
|
||||
let options = get_options();
|
||||
let nsamp = options
|
||||
.quick_render
|
||||
.then_some(1)
|
||||
.or(options.pixel_samples)
|
||||
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
|
||||
let seed = params.get_one_int("seed", options.seed);
|
||||
let s = match params
|
||||
.get_one_string("randomization", "permutedigits")
|
||||
.as_str()
|
||||
{
|
||||
"none" => RandomizeStrategy::None,
|
||||
"permutedigits" => RandomizeStrategy::PermuteDigits,
|
||||
"fastowen" => RandomizeStrategy::FastOwen,
|
||||
"owen" => RandomizeStrategy::Owen,
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"{}: Unknown randomization strategy for Halton",
|
||||
loc
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(HaltonSampler::new(nsamp as usize, full_res, s, seed as u64))
|
||||
}
|
||||
}
|
||||
|
||||
impl SamplerTrait for HaltonSampler {
|
||||
fn samples_per_pixel(&self) -> i32 {
|
||||
fn samples_per_pixel(&self) -> usize {
|
||||
self.samples_per_pixel
|
||||
}
|
||||
|
||||
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
|
||||
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>) {
|
||||
self.halton_index = 0;
|
||||
|
||||
let sample_stride = self.base_scales[0] * self.base_scales[1];
|
||||
|
|
@ -179,14 +287,14 @@ impl SamplerTrait for HaltonSampler {
|
|||
}
|
||||
|
||||
fn get1d(&mut self) -> Float {
|
||||
if self.dim > PRIME_TABLE_SIZE as u32 {
|
||||
if self.dim > PRIME_TABLE_SIZE {
|
||||
self.dim = 2;
|
||||
}
|
||||
self.sample_dimension(self.dim)
|
||||
}
|
||||
|
||||
fn get2d(&mut self) -> Point2f {
|
||||
if self.dim > PRIME_TABLE_SIZE as u32 {
|
||||
if self.dim > PRIME_TABLE_SIZE {
|
||||
self.dim = 2;
|
||||
}
|
||||
let dim = self.dim;
|
||||
|
|
@ -202,23 +310,22 @@ impl SamplerTrait for HaltonSampler {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct StratifiedSampler {
|
||||
x_pixel_samples: i32,
|
||||
y_pixel_samples: i32,
|
||||
x_pixel_samples: usize,
|
||||
y_pixel_samples: usize,
|
||||
jitter: bool,
|
||||
seed: u64,
|
||||
rng: Rng,
|
||||
pixel: Point2i,
|
||||
sample_index: i32,
|
||||
dim: u32,
|
||||
sample_index: usize,
|
||||
dim: usize,
|
||||
}
|
||||
|
||||
impl StratifiedSampler {
|
||||
pub fn new(
|
||||
x_pixel_samples: i32,
|
||||
y_pixel_samples: i32,
|
||||
x_pixel_samples: usize,
|
||||
y_pixel_samples: usize,
|
||||
seed: Option<u64>,
|
||||
jitter: bool,
|
||||
) -> Self {
|
||||
|
|
@ -233,14 +340,43 @@ impl StratifiedSampler {
|
|||
dim: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
params: &ParameterDictionary,
|
||||
_full_res: Point2i,
|
||||
_loc: &FileLoc,
|
||||
) -> Result<Self, String> {
|
||||
let options = get_options();
|
||||
let jitter = params.get_one_bool("jitter", true);
|
||||
let (x_samples, y_samples) = if options.quick_render {
|
||||
(1, 1)
|
||||
} else if let Some(n) = options.pixel_samples {
|
||||
let div = (n as f64).sqrt() as i32;
|
||||
let y = (1..=div).rev().find(|d| n % d == 0).unwrap();
|
||||
|
||||
(n / y, y)
|
||||
} else {
|
||||
(
|
||||
params.get_one_int("xsamples", 4),
|
||||
params.get_one_int("ysamples", 4),
|
||||
)
|
||||
};
|
||||
let seed = params.get_one_int("seed", options.seed);
|
||||
Ok(Self::new(
|
||||
x_samples as usize,
|
||||
y_samples as usize,
|
||||
Some(seed as u64),
|
||||
jitter,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl SamplerTrait for StratifiedSampler {
|
||||
fn samples_per_pixel(&self) -> i32 {
|
||||
fn samples_per_pixel(&self) -> usize {
|
||||
self.x_pixel_samples * self.y_pixel_samples
|
||||
}
|
||||
|
||||
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
|
||||
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>) {
|
||||
self.pixel = p;
|
||||
self.sample_index = sample_index;
|
||||
let hash_input = [p.x() as u64, p.y() as u64, self.seed];
|
||||
|
|
@ -310,19 +446,18 @@ impl SamplerTrait for StratifiedSampler {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct PaddedSobolSampler {
|
||||
samples_per_pixel: i32,
|
||||
samples_per_pixel: usize,
|
||||
seed: u64,
|
||||
randomize: RandomizeStrategy,
|
||||
pixel: Point2i,
|
||||
sample_index: i32,
|
||||
dim: u32,
|
||||
sample_index: usize,
|
||||
dim: usize,
|
||||
}
|
||||
|
||||
impl PaddedSobolSampler {
|
||||
pub fn new(samples_per_pixel: i32, randomize: RandomizeStrategy, seed: Option<u64>) -> Self {
|
||||
pub fn new(samples_per_pixel: usize, randomize: RandomizeStrategy, seed: Option<u64>) -> Self {
|
||||
Self {
|
||||
samples_per_pixel,
|
||||
seed: seed.unwrap_or(0),
|
||||
|
|
@ -333,7 +468,7 @@ impl PaddedSobolSampler {
|
|||
}
|
||||
}
|
||||
|
||||
fn sample_dimension(&self, dimension: u32, a: u32, hash: u32) -> Float {
|
||||
fn sample_dimension(&self, dimension: usize, a: u32, hash: u32) -> Float {
|
||||
if self.randomize == RandomizeStrategy::None {
|
||||
return sobol_sample(a as u64, dimension, NoRandomizer);
|
||||
}
|
||||
|
|
@ -348,13 +483,41 @@ impl PaddedSobolSampler {
|
|||
RandomizeStrategy::None => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
params: &ParameterDictionary,
|
||||
_full_res: Point2i,
|
||||
loc: &FileLoc,
|
||||
) -> Result<Self, String> {
|
||||
let options = get_options();
|
||||
let nsamp = options
|
||||
.quick_render
|
||||
.then_some(1)
|
||||
.or(options.pixel_samples)
|
||||
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
|
||||
let seed = params.get_one_int("seed", options.seed);
|
||||
let s = match params.get_one_string("randomization", "fastowen").as_str() {
|
||||
"none" => RandomizeStrategy::None,
|
||||
"permutedigits" => RandomizeStrategy::PermuteDigits,
|
||||
"fastowen" => RandomizeStrategy::FastOwen,
|
||||
"owen" => RandomizeStrategy::Owen,
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"{}: Unknown randomization strategy for ZSobol",
|
||||
loc
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self::new(nsamp as usize, s, Some(seed as u64)))
|
||||
}
|
||||
}
|
||||
|
||||
impl SamplerTrait for PaddedSobolSampler {
|
||||
fn samples_per_pixel(&self) -> i32 {
|
||||
fn samples_per_pixel(&self) -> usize {
|
||||
self.samples_per_pixel
|
||||
}
|
||||
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
|
||||
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>) {
|
||||
self.pixel = p;
|
||||
self.sample_index = sample_index;
|
||||
self.dim = dim.unwrap_or(0);
|
||||
|
|
@ -367,13 +530,13 @@ impl SamplerTrait for PaddedSobolSampler {
|
|||
self.dim as u64,
|
||||
self.seed,
|
||||
];
|
||||
let hash = hash_buffer(&hash_input, 0);
|
||||
let hash = hash_buffer(&hash_input, 0) as u32;
|
||||
let index = permutation_element(
|
||||
self.sample_index as u32,
|
||||
self.samples_per_pixel as u32,
|
||||
hash as u32,
|
||||
hash,
|
||||
);
|
||||
self.sample_dimension(0, index, (hash >> 32) as u32)
|
||||
self.sample_dimension(0, index, hash >> 32)
|
||||
}
|
||||
fn get2d(&mut self) -> Point2f {
|
||||
let hash_input = [
|
||||
|
|
@ -382,16 +545,16 @@ impl SamplerTrait for PaddedSobolSampler {
|
|||
self.dim as u64,
|
||||
self.seed,
|
||||
];
|
||||
let hash = hash_buffer(&hash_input, 0);
|
||||
let hash = hash_buffer(&hash_input, 0) as u32;
|
||||
let index = permutation_element(
|
||||
self.sample_index as u32,
|
||||
self.samples_per_pixel as u32,
|
||||
hash as u32,
|
||||
hash,
|
||||
);
|
||||
self.dim += 2;
|
||||
Point2f::new(
|
||||
self.sample_dimension(0, index, hash as u32),
|
||||
self.sample_dimension(1, index, (hash >> 32) as u32),
|
||||
self.sample_dimension(0, index, hash),
|
||||
self.sample_dimension(1, index, hash >> 32),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -402,18 +565,18 @@ impl SamplerTrait for PaddedSobolSampler {
|
|||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct SobolSampler {
|
||||
samples_per_pixel: i32,
|
||||
samples_per_pixel: usize,
|
||||
scale: i32,
|
||||
seed: u64,
|
||||
randomize: RandomizeStrategy,
|
||||
pixel: Point2i,
|
||||
dim: u32,
|
||||
dim: usize,
|
||||
sobol_index: u64,
|
||||
}
|
||||
|
||||
impl SobolSampler {
|
||||
pub fn new(
|
||||
samples_per_pixel: i32,
|
||||
samples_per_pixel: usize,
|
||||
full_resolution: Point2i,
|
||||
randomize: RandomizeStrategy,
|
||||
seed: Option<u64>,
|
||||
|
|
@ -430,7 +593,7 @@ impl SobolSampler {
|
|||
}
|
||||
}
|
||||
|
||||
fn sample_dimension(&self, dimension: u32) -> Float {
|
||||
fn sample_dimension(&self, dimension: usize) -> Float {
|
||||
if self.randomize == RandomizeStrategy::None {
|
||||
return sobol_sample(self.sobol_index, dimension, NoRandomizer);
|
||||
}
|
||||
|
|
@ -451,13 +614,41 @@ impl SobolSampler {
|
|||
RandomizeStrategy::None => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
params: &ParameterDictionary,
|
||||
full_res: Point2i,
|
||||
loc: &FileLoc,
|
||||
) -> Result<Self, String> {
|
||||
let options = get_options();
|
||||
let nsamp = options
|
||||
.quick_render
|
||||
.then_some(1)
|
||||
.or(options.pixel_samples)
|
||||
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
|
||||
let seed = params.get_one_int("seed", options.seed);
|
||||
let s = match params.get_one_string("randomization", "fastowen").as_str() {
|
||||
"none" => RandomizeStrategy::None,
|
||||
"permutedigits" => RandomizeStrategy::PermuteDigits,
|
||||
"fastowen" => RandomizeStrategy::FastOwen,
|
||||
"owen" => RandomizeStrategy::Owen,
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"{}: Unknown randomization strategy for ZSobol",
|
||||
loc
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self::new(nsamp as usize, full_res, s, Some(seed as u64)))
|
||||
}
|
||||
}
|
||||
|
||||
impl SamplerTrait for SobolSampler {
|
||||
fn samples_per_pixel(&self) -> i32 {
|
||||
fn samples_per_pixel(&self) -> usize {
|
||||
self.samples_per_pixel
|
||||
}
|
||||
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
|
||||
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>) {
|
||||
self.pixel = p;
|
||||
self.dim = 2.max(dim.unwrap_or(0));
|
||||
self.sobol_index =
|
||||
|
|
@ -465,7 +656,7 @@ impl SamplerTrait for SobolSampler {
|
|||
}
|
||||
|
||||
fn get1d(&mut self) -> Float {
|
||||
if self.dim >= N_SOBOL_DIMENSIONS as u32 {
|
||||
if self.dim >= N_SOBOL_DIMENSIONS {
|
||||
self.dim = 2;
|
||||
}
|
||||
|
||||
|
|
@ -475,7 +666,7 @@ impl SamplerTrait for SobolSampler {
|
|||
}
|
||||
|
||||
fn get2d(&mut self) -> Point2f {
|
||||
if self.dim >= N_SOBOL_DIMENSIONS as u32 {
|
||||
if self.dim >= N_SOBOL_DIMENSIONS {
|
||||
self.dim = 2;
|
||||
}
|
||||
let u = Point2f::new(
|
||||
|
|
@ -505,20 +696,19 @@ impl SamplerTrait for SobolSampler {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default, Copy, Debug, Clone)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct ZSobolSampler {
|
||||
randomize: RandomizeStrategy,
|
||||
seed: u64,
|
||||
log2_samples_per_pixel: i32,
|
||||
log2_samples_per_pixel: u32,
|
||||
n_base4_digits: u32,
|
||||
morton_index: u64,
|
||||
dim: u32,
|
||||
dim: usize,
|
||||
}
|
||||
|
||||
impl ZSobolSampler {
|
||||
pub fn new(
|
||||
samples_per_pixel: i32,
|
||||
samples_per_pixel: u32,
|
||||
full_resolution: Point2i,
|
||||
randomize: RandomizeStrategy,
|
||||
seed: Option<u64>,
|
||||
|
|
@ -526,11 +716,11 @@ impl ZSobolSampler {
|
|||
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 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 as u32;
|
||||
let n_base4_digits = log2_int(res as Float) as u32 + log4_samples_per_pixel;
|
||||
Self {
|
||||
randomize,
|
||||
seed: seed.unwrap_or(0),
|
||||
log2_samples_per_pixel: log2_samples_per_pixel as i32,
|
||||
log2_samples_per_pixel,
|
||||
n_base4_digits,
|
||||
morton_index: 0,
|
||||
dim: 0,
|
||||
|
|
@ -591,13 +781,46 @@ impl ZSobolSampler {
|
|||
|
||||
sample_index
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
params: &ParameterDictionary,
|
||||
full_res: Point2i,
|
||||
loc: &FileLoc,
|
||||
) -> Result<Self, String> {
|
||||
let options = get_options();
|
||||
let nsamp = options
|
||||
.quick_render
|
||||
.then_some(1)
|
||||
.or(options.pixel_samples)
|
||||
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
|
||||
let seed = params.get_one_int("seed", options.seed);
|
||||
let s = match params.get_one_string("randomization", "fastowen").as_str() {
|
||||
"none" => RandomizeStrategy::None,
|
||||
"permutedigits" => RandomizeStrategy::PermuteDigits,
|
||||
"fastowen" => RandomizeStrategy::FastOwen,
|
||||
"owen" => RandomizeStrategy::Owen,
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"{}: Unknown randomization strategy for ZSobol",
|
||||
loc
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ZSobolSampler::new(
|
||||
nsamp as u32,
|
||||
full_res,
|
||||
s,
|
||||
Some(seed as u64),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl SamplerTrait for ZSobolSampler {
|
||||
fn samples_per_pixel(&self) -> i32 {
|
||||
fn samples_per_pixel(&self) -> usize {
|
||||
todo!()
|
||||
}
|
||||
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
|
||||
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>) {
|
||||
self.dim = dim.unwrap_or(0);
|
||||
self.morton_index = (encode_morton_2(p.x() as u32, p.y() as u32)
|
||||
<< self.log2_samples_per_pixel)
|
||||
|
|
@ -663,10 +886,10 @@ impl SamplerTrait for ZSobolSampler {
|
|||
#[derive(Default, Debug, Clone)]
|
||||
pub struct MLTSampler;
|
||||
impl SamplerTrait for MLTSampler {
|
||||
fn samples_per_pixel(&self) -> i32 {
|
||||
fn samples_per_pixel(&self) -> usize {
|
||||
todo!()
|
||||
}
|
||||
fn start_pixel_sample(&mut self, _p: Point2i, _sample_index: i32, _dim: Option<u32>) {
|
||||
fn start_pixel_sample(&mut self, _p: Point2i, _sample_index: usize, _dim: Option<usize>) {
|
||||
todo!()
|
||||
}
|
||||
fn get1d(&mut self) -> Float {
|
||||
|
|
@ -682,8 +905,8 @@ impl SamplerTrait for MLTSampler {
|
|||
|
||||
#[enum_dispatch]
|
||||
pub trait SamplerTrait {
|
||||
fn samples_per_pixel(&self) -> i32;
|
||||
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>);
|
||||
fn samples_per_pixel(&self) -> usize;
|
||||
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>);
|
||||
fn get1d(&mut self) -> Float;
|
||||
fn get2d(&mut self) -> Point2f;
|
||||
fn get_pixel2d(&mut self) -> Point2f;
|
||||
|
|
@ -700,3 +923,40 @@ pub enum Sampler {
|
|||
ZSobol(ZSobolSampler),
|
||||
MLT(MLTSampler),
|
||||
}
|
||||
|
||||
impl Sampler {
|
||||
pub fn create(
|
||||
name: &str,
|
||||
params: &ParameterDictionary,
|
||||
full_res: Point2i,
|
||||
loc: &FileLoc,
|
||||
) -> Result<Self, String> {
|
||||
match name {
|
||||
"zsobol" => {
|
||||
let sampler = ZSobolSampler::create(params, full_res, loc)?;
|
||||
Ok(Sampler::ZSobol(sampler))
|
||||
}
|
||||
"paddedsobol" => {
|
||||
let sampler = PaddedSobolSampler::create(params, full_res, loc)?;
|
||||
Ok(Sampler::PaddedSobol(sampler))
|
||||
}
|
||||
"halton" => {
|
||||
let sampler = HaltonSampler::create(params, full_res, loc)?;
|
||||
Ok(Sampler::Halton(sampler))
|
||||
}
|
||||
"sobol" => {
|
||||
let sampler = SobolSampler::create(params, full_res, loc)?;
|
||||
Ok(Sampler::Sobol(sampler))
|
||||
}
|
||||
"Independent" => {
|
||||
let sampler = IndependentSampler::create(params, full_res, loc)?;
|
||||
Ok(Sampler::Independent(sampler))
|
||||
}
|
||||
"stratified" => {
|
||||
let sampler = StratifiedSampler::create(params, full_res, loc)?;
|
||||
Ok(Sampler::Stratified(sampler))
|
||||
}
|
||||
_ => Err(format!("Film type '{}' unknown at {}", name, loc)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ use crate::utils::sampling::sample_uniform_disk_polar;
|
|||
|
||||
use num::complex::Complex;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct TrowbridgeReitzDistribution {
|
||||
alpha_x: Float,
|
||||
|
|
@ -171,40 +170,3 @@ pub fn fr_complex_from_spectrum(
|
|||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,154 +0,0 @@
|
|||
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())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
use crate::Float;
|
||||
use crate::core::color::{RGB, XYZ};
|
||||
use crate::spectra::*;
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
||||
pub use crate::spectra::*;
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait SpectrumTrait: Copy {
|
||||
fn evaluate(&self, lambda: Float) -> Float;
|
||||
|
|
@ -44,14 +43,14 @@ impl Spectrum {
|
|||
}
|
||||
|
||||
pub fn to_xyz(&self, std: &StandardSpectra) -> XYZ {
|
||||
let x = self.inner_product(&Spectrum::Dense(std.x));
|
||||
let y = self.inner_product(&Spectrum::Dense(std.y));
|
||||
let z = self.inner_product(&Spectrum::Dense(std.z));
|
||||
let x = self.inner_product(&std.x());
|
||||
let y = self.inner_product(&std.y());
|
||||
let z = self.inner_product(&std.z());
|
||||
|
||||
XYZ::new(x, y, z) / CIE_Y_INTEGRAL
|
||||
}
|
||||
|
||||
pub fn to_rgb(&self, cs: &RGBColorSpace, std: &StandardSpectra) -> RGB {
|
||||
fn to_rgb(&self, cs: &RGBColorSpace, std: &StandardSpectra) -> RGB {
|
||||
let xyz = self.to_xyz(std);
|
||||
cs.to_rgb(xyz)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,18 @@ use crate::core::color::ColorEncoding;
|
|||
use crate::core::geometry::{
|
||||
Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike, spherical_phi, spherical_theta,
|
||||
};
|
||||
use crate::core::image::WrapMode;
|
||||
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
||||
use crate::images::WrapMode;
|
||||
use crate::spectra::{
|
||||
RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
|
||||
SampledWavelengths,
|
||||
};
|
||||
|
||||
use crate::utils::Ptr;
|
||||
use crate::textures::*;
|
||||
use crate::utils::Transform;
|
||||
use crate::utils::math::square;
|
||||
use crate::{Float, INV_2_PI, INV_PI, PI};
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
||||
pub use crate::textures::*;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct TexCoord2D {
|
||||
|
|
@ -261,7 +258,7 @@ impl PointTransformMapping {
|
|||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Default, Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TextureEvalContext {
|
||||
pub p: Point3f,
|
||||
pub dpdx: Vector3f,
|
||||
|
|
@ -272,7 +269,7 @@ pub struct TextureEvalContext {
|
|||
pub dudy: Float,
|
||||
pub dvdx: Float,
|
||||
pub dvdy: Float,
|
||||
pub face_index: i32,
|
||||
pub face_index: usize,
|
||||
}
|
||||
|
||||
impl TextureEvalContext {
|
||||
|
|
@ -287,7 +284,7 @@ impl TextureEvalContext {
|
|||
dudy: Float,
|
||||
dvdx: Float,
|
||||
dvdy: Float,
|
||||
face_index: i32,
|
||||
face_index: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
p,
|
||||
|
|
@ -311,7 +308,7 @@ impl From<&SurfaceInteraction> for TextureEvalContext {
|
|||
dpdx: si.dpdx,
|
||||
dpdy: si.dpdy,
|
||||
n: si.common.n,
|
||||
uv: si.common.uv,
|
||||
uv: si.uv,
|
||||
dudx: si.dudx,
|
||||
dudy: si.dudy,
|
||||
dvdx: si.dvdx,
|
||||
|
|
@ -350,6 +347,7 @@ pub enum GPUFloatTexture {
|
|||
FBm(FBmTexture),
|
||||
Windy(WindyTexture),
|
||||
Wrinkled(WrinkledTexture),
|
||||
Ptex(GPUFloatPtexTexture),
|
||||
Image(GPUFloatImageTexture),
|
||||
Mix(GPUFloatMixTexture),
|
||||
}
|
||||
|
|
@ -365,7 +363,8 @@ impl GPUFloatTexture {
|
|||
GPUFloatTexture::Dots(t) => t.evaluate(ctx),
|
||||
GPUFloatTexture::FBm(t) => t.evaluate(ctx),
|
||||
GPUFloatTexture::Windy(t) => t.evaluate(ctx),
|
||||
GPUFloatTexture::Wrinkled(t) => t.evaluate(ctx),
|
||||
GPUFloatTexture::Wrinkle(t) => t.evaluate(ctx),
|
||||
GPUFloatTexture::Ptex(t) => t.evaluate(ctx),
|
||||
GPUFloatTexture::Image(t) => t.evaluate(ctx),
|
||||
GPUFloatTexture::Mix(t) => t.evaluate(ctx),
|
||||
}
|
||||
|
|
@ -397,11 +396,7 @@ pub enum GPUSpectrumTexture {
|
|||
}
|
||||
|
||||
impl GPUSpectrumTexture {
|
||||
pub fn evaluate(
|
||||
&self,
|
||||
ctx: &TextureEvalContext,
|
||||
lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
pub fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> Float {
|
||||
match self {
|
||||
GPUSpectrumTexture::Constant(t) => t.evaluate(ctx, lambda),
|
||||
GPUSpectrumTexture::Bilerp(t) => t.evaluate(ctx, lambda),
|
||||
|
|
@ -426,11 +421,7 @@ pub trait TextureEvaluator: Send + Sync {
|
|||
lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum;
|
||||
|
||||
fn can_evaluate(
|
||||
&self,
|
||||
_ftex: &[Ptr<GPUFloatTexture>],
|
||||
_stex: &[Ptr<GPUSpectrumTexture>],
|
||||
) -> bool;
|
||||
fn can_evaluate(&self, _ftex: &[&GPUFloatTexture], _stex: &[&GPUSpectrumTexture]) -> bool;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
|
@ -453,8 +444,8 @@ impl TextureEvaluator for UniversalTextureEvaluator {
|
|||
|
||||
fn can_evaluate(
|
||||
&self,
|
||||
_float_textures: &[Ptr<GPUFloatTexture>],
|
||||
_spectrum_textures: &[Ptr<GPUSpectrumTexture>],
|
||||
_float_textures: &[&GPUFloatTexture],
|
||||
_spectrum_textures: &[&GPUSpectrumTexture],
|
||||
) -> bool {
|
||||
true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,39 +2,55 @@ use crate::Float;
|
|||
use bytemuck::cast_slice;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
#[repr(C, align(16))]
|
||||
struct AlignedData<const N: usize>(pub [u8; N]);
|
||||
static SRGB_SCALE_BYTES: &[u8] = include_bytes!("../../data/srgb_scale.dat");
|
||||
static SRGB_COEFFS_BYTES: &[u8] = include_bytes!("../../data/srgb_coeffs.dat");
|
||||
|
||||
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_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(SRGB_SCALE_BYTES));
|
||||
|
||||
unsafe {
|
||||
let bytes = &RAW_DATA.0;
|
||||
|
||||
let stride = core::mem::size_of::<Float>();
|
||||
let len = bytes.len() / stride;
|
||||
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)
|
||||
pub static SRGB_COEFFS: Lazy<&[Float]> =
|
||||
Lazy::new(|| match bytemuck::try_cast_slice(SRGB_COEFFS_BYTES) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
let v: Vec<Float> = bytemuck::pod_collect_to_vec(SRGB_COEFFS_BYTES);
|
||||
Box::leak(v.into_boxed_slice())
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
static DCI_P3_SCALE_BYTES: &[u8] = include_bytes!("../../data/dcip3_scale.dat");
|
||||
static DCI_P3_COEFFS_BYTES: &[u8] = include_bytes!("../../data/dcip3_coeffs.dat");
|
||||
pub static DCI_P3_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(DCI_P3_SCALE_BYTES));
|
||||
pub static DCI_P3_COEFFS: Lazy<&[Float]> =
|
||||
Lazy::new(|| match bytemuck::try_cast_slice(DCI_P3_COEFFS_BYTES) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
let v: Vec<Float> = bytemuck::pod_collect_to_vec(DCI_P3_COEFFS_BYTES);
|
||||
Box::leak(v.into_boxed_slice())
|
||||
}
|
||||
});
|
||||
|
||||
load_static_table!(SRGB_SCALE, "../../data/srgb_scale.dat");
|
||||
load_static_table!(SRGB_COEFFS, "../../data/srgb_coeffs.dat");
|
||||
static ACES_SCALE_BYTES: &[u8] = include_bytes!("../../data/aces_scale.dat");
|
||||
static ACES_COEFFS_BYTES: &[u8] = include_bytes!("../../data/aces_coeffs.dat");
|
||||
|
||||
load_static_table!(DCI_P3_SCALE, "../../data/dcip3_scale.dat");
|
||||
load_static_table!(DCI_P3_COEFFS, "../../data/dcip3_coeffs.dat");
|
||||
pub static ACES_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(ACES_SCALE_BYTES));
|
||||
|
||||
load_static_table!(ACES_SCALE, "../../data/aces_scale.dat");
|
||||
load_static_table!(ACES_COEFFS, "../../data/aces_coeffs.dat");
|
||||
pub static ACES_COEFFS: Lazy<&[Float]> =
|
||||
Lazy::new(|| match bytemuck::try_cast_slice(ACES_COEFFS_BYTES) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
let v: Vec<Float> = bytemuck::pod_collect_to_vec(ACES_COEFFS_BYTES);
|
||||
Box::leak(v.into_boxed_slice())
|
||||
}
|
||||
});
|
||||
|
||||
load_static_table!(REC2020_SCALE, "../../data/rec2020_scale.dat");
|
||||
load_static_table!(REC2020_COEFFS, "../../data/rec2020_coeffs.dat");
|
||||
static REC2020_SCALE_BYTES: &[u8] = include_bytes!("../../data/rec2020_scale.dat");
|
||||
static REC2020_COEFFS_BYTES: &[u8] = include_bytes!("../../data/rec2020_coeffs.dat");
|
||||
|
||||
pub static REC2020_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(REC2020_SCALE_BYTES));
|
||||
pub static REC2020_COEFFS: Lazy<&[Float]> =
|
||||
Lazy::new(|| match bytemuck::try_cast_slice(REC2020_COEFFS_BYTES) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
let v: Vec<Float> = bytemuck::pod_collect_to_vec(REC2020_COEFFS_BYTES);
|
||||
Box::leak(v.into_boxed_slice())
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
use crate::Float;
|
||||
use crate::core::filter::{FilterSample, FilterTrait};
|
||||
use crate::core::filter::FilterSample;
|
||||
use crate::core::geometry::{Point2f, Vector2f};
|
||||
use crate::utils::math::lerp;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BoxFilter {
|
||||
pub radius: Vector2f,
|
||||
}
|
||||
|
|
@ -13,13 +11,12 @@ impl BoxFilter {
|
|||
pub fn new(radius: Vector2f) -> Self {
|
||||
Self { radius }
|
||||
}
|
||||
}
|
||||
impl FilterTrait for BoxFilter {
|
||||
fn radius(&self) -> Vector2f {
|
||||
|
||||
pub fn radius(&self) -> Vector2f {
|
||||
self.radius
|
||||
}
|
||||
|
||||
fn evaluate(&self, p: Point2f) -> Float {
|
||||
pub fn evaluate(&self, p: Point2f) -> Float {
|
||||
if p.x().abs() <= self.radius.x() && p.y().abs() <= self.radius.y() {
|
||||
1.
|
||||
} else {
|
||||
|
|
@ -27,11 +24,11 @@ impl FilterTrait for BoxFilter {
|
|||
}
|
||||
}
|
||||
|
||||
fn integral(&self) -> Float {
|
||||
pub fn integral(&self) -> Float {
|
||||
(2.0 * self.radius.x()) * (2.0 * self.radius.y())
|
||||
}
|
||||
|
||||
fn sample(&self, u: Point2f) -> FilterSample {
|
||||
pub fn sample(&self, u: Point2f) -> FilterSample {
|
||||
let p = Point2f::new(
|
||||
lerp(u[0], -self.radius.x(), self.radius.x()),
|
||||
lerp(u[1], -self.radius.y(), self.radius.y()),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,4 @@
|
|||
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)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GaussianFilter {
|
||||
pub radius: Vector2f,
|
||||
pub sigma: Float,
|
||||
|
|
@ -13,24 +7,43 @@ pub struct GaussianFilter {
|
|||
pub sampler: FilterSampler,
|
||||
}
|
||||
|
||||
impl FilterTrait for GaussianFilter {
|
||||
fn radius(&self) -> Vector2f {
|
||||
impl GaussianFilter {
|
||||
pub fn new(radius: Vector2f, sigma: Float) -> Self {
|
||||
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
|
||||
}
|
||||
|
||||
fn evaluate(&self, p: Point2f) -> Float {
|
||||
pub fn evaluate(&self, p: Point2f) -> Float {
|
||||
(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)
|
||||
}
|
||||
|
||||
fn integral(&self) -> Float {
|
||||
pub fn integral(&self) -> Float {
|
||||
(gaussian_integral(-self.radius.x(), self.radius.x(), 0.0, self.sigma)
|
||||
- 2.0 * self.radius.x() * self.exp_x)
|
||||
* (gaussian_integral(-self.radius.y(), self.radius.y(), 0.0, self.sigma)
|
||||
- 2.0 * self.radius.y() * self.exp_y)
|
||||
}
|
||||
|
||||
fn sample(&self, u: Point2f) -> FilterSample {
|
||||
pub fn sample(&self, u: Point2f) -> FilterSample {
|
||||
self.sampler.sample(u)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,33 @@
|
|||
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)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LanczosSincFilter {
|
||||
pub radius: Vector2f,
|
||||
pub tau: Float,
|
||||
pub sampler: FilterSampler,
|
||||
}
|
||||
|
||||
impl FilterTrait for LanczosSincFilter {
|
||||
fn radius(&self) -> Vector2f {
|
||||
impl LanczosSincFilter {
|
||||
pub fn new(radius: Vector2f, tau: Float) -> Self {
|
||||
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
|
||||
}
|
||||
|
||||
fn evaluate(&self, p: Point2f) -> Float {
|
||||
pub fn evaluate(&self, p: Point2f) -> Float {
|
||||
windowed_sinc(p.x(), self.radius.x(), self.tau)
|
||||
* windowed_sinc(p.y(), self.radius.y(), self.tau)
|
||||
}
|
||||
|
||||
fn integral(&self) -> Float {
|
||||
pub fn integral(&self) -> Float {
|
||||
let sqrt_samples = 64;
|
||||
let n_samples = sqrt_samples * sqrt_samples;
|
||||
let area = (2.0 * self.radius.x()) * (2.0 * self.radius.y());
|
||||
|
|
@ -45,7 +50,7 @@ impl FilterTrait for LanczosSincFilter {
|
|||
sum / n_samples as Float * area
|
||||
}
|
||||
|
||||
fn sample(&self, u: Point2f) -> FilterSample {
|
||||
pub fn sample(&self, u: Point2f) -> FilterSample {
|
||||
self.sampler.sample(u)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use crate::Float;
|
||||
use crate::core::filter::{FilterSample, FilterSampler, FilterTrait};
|
||||
use crate::core::filter::FilterSampler;
|
||||
use crate::core::geometry::{Point2f, Vector2f};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MitchellFilter {
|
||||
pub radius: Vector2f,
|
||||
pub b: Float,
|
||||
|
|
@ -12,7 +11,22 @@ pub struct MitchellFilter {
|
|||
}
|
||||
|
||||
impl MitchellFilter {
|
||||
pub fn mitchell_1d_eval(b: Float, c: Float, x: Float) -> Float {
|
||||
pub fn new(radius: Vector2f, b: Float, c: Float) -> Self {
|
||||
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();
|
||||
if x <= 1.0 {
|
||||
((12.0 - 9.0 * b - 6.0 * c) * x.powi(3)
|
||||
|
|
@ -33,23 +47,21 @@ impl MitchellFilter {
|
|||
fn mitchell_1d(&self, x: Float) -> Float {
|
||||
Self::mitchell_1d_eval(self.b, self.c, x)
|
||||
}
|
||||
}
|
||||
|
||||
impl FilterTrait for MitchellFilter {
|
||||
fn radius(&self) -> Vector2f {
|
||||
pub fn radius(&self) -> Vector2f {
|
||||
self.radius
|
||||
}
|
||||
|
||||
fn evaluate(&self, p: Point2f) -> Float {
|
||||
pub fn evaluate(&self, p: Point2f) -> Float {
|
||||
self.mitchell_1d(2.0 * p.x() / self.radius.x())
|
||||
* self.mitchell_1d(2.0 * p.y() / self.radius.y())
|
||||
}
|
||||
|
||||
fn integral(&self) -> Float {
|
||||
pub fn integral(&self) -> Float {
|
||||
self.radius.x() * self.radius.y() / 4.0
|
||||
}
|
||||
|
||||
fn sample(&self, u: Point2f) -> FilterSample {
|
||||
pub fn sample(&self, u: Point2f) -> FilterSample {
|
||||
self.sampler.sample(u)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,4 @@
|
|||
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)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TriangleFilter {
|
||||
pub radius: Vector2f,
|
||||
}
|
||||
|
|
@ -13,22 +7,20 @@ impl TriangleFilter {
|
|||
pub fn new(radius: Vector2f) -> Self {
|
||||
Self { radius }
|
||||
}
|
||||
}
|
||||
|
||||
impl FilterTrait for TriangleFilter {
|
||||
fn radius(&self) -> Vector2f {
|
||||
pub fn radius(&self) -> Vector2f {
|
||||
self.radius
|
||||
}
|
||||
|
||||
fn evaluate(&self, p: Point2f) -> Float {
|
||||
pub fn evaluate(&self, p: Point2f) -> Float {
|
||||
(self.radius.x() - p.x().abs()).max(0.0) * (self.radius.y() - p.y().abs()).max(0.0)
|
||||
}
|
||||
|
||||
fn integral(&self) -> Float {
|
||||
pub fn integral(&self) -> Float {
|
||||
self.radius.x().powi(2) * self.radius.y().powi(2)
|
||||
}
|
||||
|
||||
fn sample(&self, u: Point2f) -> FilterSample {
|
||||
pub fn sample(&self, u: Point2f) -> FilterSample {
|
||||
let p = Point2f::new(
|
||||
sample_tent(u[0], self.radius.x()),
|
||||
sample_tent(u[1], self.radius.y()),
|
||||
|
|
|
|||
118
shared/src/images/metadata.rs
Normal file
118
shared/src/images/metadata.rs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
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>>,
|
||||
}
|
||||
443
shared/src/images/mod.rs
Normal file
443
shared/src/images/mod.rs
Normal file
|
|
@ -0,0 +1,443 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,10 @@
|
|||
use super::Image;
|
||||
use crate::core::image::PixelStorage;
|
||||
use crate::core::image::pixel::PixelStorageTrait;
|
||||
// use rayon::prelude::*;
|
||||
use crate::core::geometry::{Bounds2i, Point2i};
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::image::pixel::PixelStorage;
|
||||
use crate::image::{Image, PixelData, PixelFormat, WrapMode, WrapMode2D};
|
||||
use crate::utils::math::windowed_sinc;
|
||||
use rayon::prelude::*;
|
||||
use shared::Float;
|
||||
use shared::core::color::ColorEncoding;
|
||||
use shared::core::geometry::{Bounds2i, Point2i};
|
||||
use shared::core::image::{PixelFormat, WrapMode, WrapMode2D};
|
||||
use shared::utils::Ptr;
|
||||
use shared::utils::math::windowed_sinc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -18,41 +15,38 @@ pub struct ResampleWeight {
|
|||
|
||||
impl Image {
|
||||
pub fn flip_y(&mut self) {
|
||||
let res = self.resolution();
|
||||
let nc = self.n_channels() as usize;
|
||||
let res = self.resolution;
|
||||
let nc = self.n_channels();
|
||||
|
||||
match &mut self.pixels {
|
||||
PixelStorage::U8(d) => flip_y_kernel(d, res, nc),
|
||||
PixelStorage::F16(d) => flip_y_kernel(d, res, nc),
|
||||
PixelStorage::F32(d) => flip_y_kernel(d, res, nc),
|
||||
PixelData::U8(d) => flip_y_kernel(d, res, nc),
|
||||
PixelData::F16(d) => flip_y_kernel(d, res, nc),
|
||||
PixelData::F32(d) => flip_y_kernel(d, res, nc),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn crop(&self, bounds: Bounds2i) -> Image {
|
||||
let res = self.resolution();
|
||||
let n_channels = self.n_channels() as usize;
|
||||
|
||||
let new_res = Point2i::new(
|
||||
bounds.p_max.x() - bounds.p_min.x(),
|
||||
bounds.p_max.y() - bounds.p_min.y(),
|
||||
);
|
||||
|
||||
let mut new_image = Image::new(
|
||||
self.format(),
|
||||
let mut new_image = Image::from_vector(
|
||||
self.format,
|
||||
new_res,
|
||||
&self.channel_names,
|
||||
self.encoding().into(),
|
||||
self.channel_names.clone(),
|
||||
self.encoding,
|
||||
);
|
||||
|
||||
match (&self.pixels, &mut new_image.pixels) {
|
||||
(PixelStorage::U8(src), PixelStorage::U8(dst)) => {
|
||||
crop_kernel(src, dst, res, bounds, n_channels)
|
||||
(PixelData::U8(src), PixelData::U8(dst)) => {
|
||||
crop_kernel(src, dst, self.resolution, bounds, self.n_channels())
|
||||
}
|
||||
(PixelStorage::F16(src), PixelStorage::F16(dst)) => {
|
||||
crop_kernel(src, dst, res, bounds, n_channels)
|
||||
(PixelData::F16(src), PixelData::F16(dst)) => {
|
||||
crop_kernel(src, dst, self.resolution, bounds, self.n_channels())
|
||||
}
|
||||
(PixelStorage::F32(src), PixelStorage::F32(dst)) => {
|
||||
crop_kernel(src, dst, res, bounds, n_channels)
|
||||
(PixelData::F32(src), PixelData::F32(dst)) => {
|
||||
crop_kernel(src, dst, self.resolution, bounds, self.n_channels())
|
||||
}
|
||||
_ => panic!("Format mismatch in crop"),
|
||||
}
|
||||
|
|
@ -62,41 +56,46 @@ impl Image {
|
|||
|
||||
pub fn copy_rect_out(&self, extent: Bounds2i, buf: &mut [Float], wrap: WrapMode2D) {
|
||||
match &self.pixels {
|
||||
PixelStorage::U8(d) => copy_rect_out_kernel(d, self, extent, buf, wrap),
|
||||
PixelStorage::F16(d) => copy_rect_out_kernel(d, self, extent, buf, wrap),
|
||||
PixelStorage::F32(d) => copy_rect_out_kernel(d, self, extent, buf, wrap),
|
||||
PixelData::U8(d) => copy_rect_out_kernel(d, self, extent, buf, wrap),
|
||||
PixelData::F16(d) => copy_rect_out_kernel(d, self, extent, buf, wrap),
|
||||
PixelData::F32(d) => copy_rect_out_kernel(d, self, extent, buf, wrap),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_rect_in(&mut self, extent: Bounds2i, buf: &[Float]) {
|
||||
let res = self.resolution();
|
||||
let n_channels = self.n_channels() as usize;
|
||||
let encoding = self.encoding();
|
||||
let resolution = self.resolution;
|
||||
let n_channels = self.n_channels();
|
||||
let encoding = self.encoding;
|
||||
|
||||
match &mut self.pixels {
|
||||
PixelStorage::U8(d) => copy_rect_in_kernel(d, res, n_channels, encoding, extent, buf),
|
||||
PixelStorage::F16(d) => copy_rect_in_kernel(d, res, n_channels, encoding, extent, buf),
|
||||
PixelStorage::F32(d) => copy_rect_in_kernel(d, res, n_channels, encoding, extent, buf),
|
||||
PixelData::U8(d) => {
|
||||
copy_rect_in_kernel(d, resolution, n_channels, encoding, extent, buf)
|
||||
}
|
||||
PixelData::F16(d) => {
|
||||
copy_rect_in_kernel(d, resolution, n_channels, encoding, extent, buf)
|
||||
}
|
||||
PixelData::F32(d) => {
|
||||
copy_rect_in_kernel(d, resolution, n_channels, encoding, extent, buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn float_resize_up(&self, new_res: Point2i, wrap_mode: WrapMode2D) -> Image {
|
||||
let res = self.resolution();
|
||||
assert!(new_res.x() >= res.x() && new_res.y() >= res.y());
|
||||
assert!(new_res.x() >= self.resolution.x() && new_res.y() >= self.resolution.y());
|
||||
assert!(
|
||||
matches!(self.format(), PixelFormat::F32),
|
||||
matches!(self.format, PixelFormat::F32),
|
||||
"ResizeUp requires Float format"
|
||||
);
|
||||
|
||||
let resampled_image = Arc::new(Mutex::new(Image::new(
|
||||
PixelFormat::F32,
|
||||
let resampled_image = Arc::new(Mutex::new(Image::from_vector(
|
||||
PixelFormat::F32, // Force float output
|
||||
new_res,
|
||||
&self.channel_names,
|
||||
self.encoding().into(),
|
||||
self.channel_names.clone(),
|
||||
self.encoding,
|
||||
)));
|
||||
|
||||
let x_weights = resample_weights(res.x() as usize, new_res.x() as usize);
|
||||
let y_weights = resample_weights(res.y() as usize, new_res.y() as usize);
|
||||
let x_weights = resample_weights(self.resolution.x() as usize, new_res.x() as usize);
|
||||
let y_weights = resample_weights(self.resolution.y() as usize, new_res.y() as usize);
|
||||
let n_channels = self.n_channels();
|
||||
|
||||
let tile_size = 16;
|
||||
|
|
@ -111,14 +110,14 @@ impl Image {
|
|||
let in_extent =
|
||||
Bounds2i::from_points(Point2i::new(x_start, y_start), Point2i::new(x_end, y_end));
|
||||
|
||||
let mut in_buf = vec![0.0; in_extent.area() as usize * n_channels as usize];
|
||||
let mut in_buf = vec![0.0; in_extent.area() as usize * n_channels];
|
||||
self.copy_rect_out(in_extent, &mut in_buf, wrap_mode);
|
||||
|
||||
let out_buf = compute_resize_tile(
|
||||
&in_buf,
|
||||
in_extent,
|
||||
*out_extent,
|
||||
n_channels.try_into().unwrap(),
|
||||
n_channels,
|
||||
&x_weights,
|
||||
&y_weights,
|
||||
);
|
||||
|
|
@ -141,23 +140,23 @@ impl Image {
|
|||
|
||||
loop {
|
||||
let prev = levels.last().unwrap();
|
||||
let old = prev.resolution();
|
||||
let old = prev.resolution;
|
||||
if old.x() == 1 && old.y() == 1 {
|
||||
break;
|
||||
}
|
||||
|
||||
let new_res = Point2i::new((old.x() / 2).max(1), (old.y() / 2).max(1));
|
||||
let mut next = Image::new(
|
||||
prev.format(),
|
||||
let mut next = Image::from_vector(
|
||||
prev.format,
|
||||
new_res,
|
||||
&prev.channel_names,
|
||||
prev.encoding().into(),
|
||||
prev.channel_names.clone(),
|
||||
prev.encoding,
|
||||
);
|
||||
|
||||
match &mut next.pixels {
|
||||
PixelStorage::U8(d) => downsample_kernel(d, new_res, prev, internal_wrap),
|
||||
PixelStorage::F16(d) => downsample_kernel(d, new_res, prev, internal_wrap),
|
||||
PixelStorage::F32(d) => downsample_kernel(d, new_res, prev, internal_wrap),
|
||||
PixelData::U8(d) => downsample_kernel(d, new_res, prev, internal_wrap),
|
||||
PixelData::F16(d) => downsample_kernel(d, new_res, prev, internal_wrap),
|
||||
PixelData::F32(d) => downsample_kernel(d, new_res, prev, internal_wrap),
|
||||
}
|
||||
levels.push(next);
|
||||
}
|
||||
|
|
@ -165,7 +164,7 @@ impl Image {
|
|||
}
|
||||
}
|
||||
|
||||
fn flip_y_kernel<T: PixelStorageTrait>(pixels: &mut [T], res: Point2i, channels: usize) {
|
||||
fn flip_y_kernel<T: PixelStorage>(pixels: &mut [T], res: Point2i, channels: usize) {
|
||||
let w = res.x() as usize;
|
||||
let h = res.y() as usize;
|
||||
let stride = w * channels;
|
||||
|
|
@ -178,7 +177,7 @@ fn flip_y_kernel<T: PixelStorageTrait>(pixels: &mut [T], res: Point2i, channels:
|
|||
}
|
||||
}
|
||||
|
||||
fn crop_kernel<T: PixelStorageTrait>(
|
||||
fn crop_kernel<T: PixelStorage>(
|
||||
src: &[T],
|
||||
dst: &mut [T],
|
||||
src_res: Point2i,
|
||||
|
|
@ -201,7 +200,7 @@ fn crop_kernel<T: PixelStorageTrait>(
|
|||
});
|
||||
}
|
||||
|
||||
fn copy_rect_out_kernel<T: PixelStorageTrait>(
|
||||
fn copy_rect_out_kernel<T: PixelStorage>(
|
||||
src: &[T],
|
||||
image: &Image,
|
||||
extent: Bounds2i,
|
||||
|
|
@ -209,9 +208,9 @@ fn copy_rect_out_kernel<T: PixelStorageTrait>(
|
|||
wrap: WrapMode2D,
|
||||
) {
|
||||
let w = (extent.p_max.x() - extent.p_min.x()) as usize;
|
||||
let channels = image.n_channels() as usize;
|
||||
let enc = image.encoding();
|
||||
let res = image.resolution();
|
||||
let channels = image.n_channels();
|
||||
let enc = image.encoding;
|
||||
let res = image.resolution;
|
||||
|
||||
buf.par_chunks_mut(w * channels)
|
||||
.enumerate()
|
||||
|
|
@ -220,6 +219,7 @@ fn copy_rect_out_kernel<T: PixelStorageTrait>(
|
|||
for x_rel in 0..w {
|
||||
let x = extent.p_min.x() + x_rel as i32;
|
||||
|
||||
// This allows us to use 'src' directly (Fast Path).
|
||||
if x >= 0 && x < res.x() && y >= 0 && y < res.y() {
|
||||
let offset = (y as usize * res.x() as usize + x as usize) * channels;
|
||||
|
||||
|
|
@ -227,22 +227,22 @@ fn copy_rect_out_kernel<T: PixelStorageTrait>(
|
|||
row_buf[x_rel * channels + c] = T::to_linear(src[offset + c], enc);
|
||||
}
|
||||
} else {
|
||||
// Slow path: Out of bounds, requires Wrap Mode logic.
|
||||
// We fall back to get_channel which handles the wrapping math.
|
||||
let p = Point2i::new(x, y);
|
||||
for c in 0..channels {
|
||||
row_buf[x_rel * channels + c] =
|
||||
image.get_channel_with_wrap(p, c.try_into().unwrap(), wrap);
|
||||
row_buf[x_rel * channels + c] = image.get_channel_with_wrap(p, c, wrap);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn copy_rect_in_kernel<T: PixelStorageTrait>(
|
||||
fn copy_rect_in_kernel<T: PixelStorage>(
|
||||
dst: &mut [T],
|
||||
res: Point2i,
|
||||
channels: usize,
|
||||
enc: ColorEncoding,
|
||||
enc: crate::spectra::color::ColorEncoding,
|
||||
extent: Bounds2i,
|
||||
buf: &[Float],
|
||||
) {
|
||||
|
|
@ -271,18 +271,18 @@ fn copy_rect_in_kernel<T: PixelStorageTrait>(
|
|||
}
|
||||
}
|
||||
|
||||
fn downsample_kernel<T: PixelStorageTrait>(
|
||||
dst: &mut Ptr<T>,
|
||||
fn downsample_kernel<T: PixelStorage>(
|
||||
dst: &mut [T],
|
||||
dst_res: Point2i,
|
||||
prev: &Image,
|
||||
wrap: WrapMode2D,
|
||||
) {
|
||||
let w = dst_res.x() as usize;
|
||||
let channels = prev.n_channels();
|
||||
let enc = prev.encoding();
|
||||
let old_res = prev.resolution();
|
||||
let enc = prev.encoding;
|
||||
let old_res = prev.resolution;
|
||||
|
||||
dst.par_chunks_mut(w * channels as usize)
|
||||
dst.par_chunks_mut(w * channels)
|
||||
.enumerate()
|
||||
.for_each(|(y, row)| {
|
||||
let src_y = y * 2;
|
||||
|
|
@ -304,7 +304,7 @@ fn downsample_kernel<T: PixelStorageTrait>(
|
|||
}
|
||||
|
||||
let avg = if count > 0.0 { sum / count } else { 0.0 };
|
||||
row[x * channels as usize + c as usize] = T::from_linear(avg, enc);
|
||||
row[x * channels + c] = T::from_linear(avg, enc);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
use crate::core::pbrt::Float;
|
||||
use crate::spectra::color::{ColorEncoding, ColorEncodingTrait};
|
||||
use half::f16;
|
||||
use shared::Float;
|
||||
use shared::core::color::{ColorEncoding, ColorEncodingTrait};
|
||||
|
||||
// Allows writing generic algorithms that work on any image format.
|
||||
pub trait PixelStorageTrait: Copy + Send + Sync + 'static + PartialEq {
|
||||
pub trait PixelStorage: Copy + Send + Sync + 'static + PartialEq {
|
||||
fn from_linear(val: Float, encoding: ColorEncoding) -> Self;
|
||||
fn to_linear(self, encoding: ColorEncoding) -> Float;
|
||||
}
|
||||
|
||||
impl PixelStorageTrait for f32 {
|
||||
impl PixelStorage for f32 {
|
||||
#[inline(always)]
|
||||
fn from_linear(val: Float, _enc: ColorEncoding) -> Self {
|
||||
val
|
||||
|
|
@ -19,7 +19,7 @@ impl PixelStorageTrait for f32 {
|
|||
}
|
||||
}
|
||||
|
||||
impl PixelStorageTrait for f16 {
|
||||
impl PixelStorage for f16 {
|
||||
#[inline(always)]
|
||||
fn from_linear(val: Float, _enc: ColorEncoding) -> Self {
|
||||
f16::from_f32(val)
|
||||
|
|
@ -30,18 +30,18 @@ impl PixelStorageTrait for f16 {
|
|||
}
|
||||
}
|
||||
|
||||
impl PixelStorageTrait for u8 {
|
||||
impl PixelStorage for u8 {
|
||||
#[inline(always)]
|
||||
fn from_linear(val: Float, enc: ColorEncoding) -> Self {
|
||||
let mut out = [0u8];
|
||||
enc.from_linear(&[val], &mut out);
|
||||
enc.from_linear_slice(&[val], &mut out);
|
||||
out[0]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn to_linear(self, enc: ColorEncoding) -> Float {
|
||||
let mut out = [0.0];
|
||||
enc.to_linear(&[self], &mut out);
|
||||
enc.to_linear_slice(&[self], &mut out);
|
||||
out[0]
|
||||
}
|
||||
}
|
||||
1182
shared/src/integrators/mod.rs
Normal file
1182
shared/src/integrators/mod.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,23 +1,11 @@
|
|||
use super::RayIntegratorTrait;
|
||||
use super::base::IntegratorBase;
|
||||
use crate::Arena;
|
||||
use crate::core::camera::InitMetadata;
|
||||
use crate::core::film::FilmTrait;
|
||||
use crate::core::image::{Image, ImageIO, ImageMetadata};
|
||||
use crate::spectra::get_spectra_context;
|
||||
use crate::core::{options::PBRTOptions, sampler::get_camera_sample};
|
||||
use crate::image::{Image, ImageMetadata};
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use shared::Float;
|
||||
use shared::core::camera::{Camera, CameraTrait};
|
||||
use shared::core::film::Film;
|
||||
use shared::core::geometry::{Bounds2i, Point2i, VectorLike};
|
||||
use shared::core::options::get_options;
|
||||
use shared::core::sampler::get_camera_sample;
|
||||
use shared::core::sampler::{Sampler, SamplerTrait};
|
||||
use shared::spectra::SampledSpectrum;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use super::*;
|
||||
|
||||
struct PbrtProgress {
|
||||
bar: ProgressBar,
|
||||
}
|
||||
|
|
@ -78,16 +66,16 @@ pub fn render<T>(
|
|||
_base: &IntegratorBase,
|
||||
camera: &Camera,
|
||||
sampler_prototype: &Sampler,
|
||||
arena: &mut Arena,
|
||||
) where
|
||||
T: RayIntegratorTrait,
|
||||
{
|
||||
let options = get_options();
|
||||
if let Some((p_pixel, sample_index)) = options.debug_start {
|
||||
let s_index = sample_index as usize;
|
||||
let scratch = Bump::new();
|
||||
let mut tile_sampler = sampler_prototype.clone();
|
||||
|
||||
tile_sampler.start_pixel_sample(p_pixel, s_index as i32, None);
|
||||
tile_sampler.start_pixel_sample(p_pixel, s_index, None);
|
||||
|
||||
evaluate_pixel_sample(
|
||||
integrator,
|
||||
|
|
@ -95,7 +83,7 @@ pub fn render<T>(
|
|||
&mut tile_sampler,
|
||||
p_pixel,
|
||||
s_index,
|
||||
arena,
|
||||
&scratch,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -157,19 +145,23 @@ pub fn render<T>(
|
|||
let tiles = generate_tiles(pixel_bounds);
|
||||
while wave_start < spp {
|
||||
tiles.par_iter().for_each(|tile_bounds| {
|
||||
let mut arena = Bump::with_capacity(65 * 1024);
|
||||
let mut sampler = sampler_prototype.clone();
|
||||
|
||||
for p_pixel in tile_bounds {
|
||||
for sample_index in wave_start..wave_end {
|
||||
sampler.start_pixel_sample(*p_pixel, sample_index, None);
|
||||
|
||||
evaluate_pixel_sample(
|
||||
integrator,
|
||||
camera,
|
||||
&mut sampler,
|
||||
*p_pixel,
|
||||
sample_index.try_into().unwrap(),
|
||||
arena,
|
||||
sample_index,
|
||||
&arena,
|
||||
);
|
||||
|
||||
arena.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -206,8 +198,7 @@ pub fn render<T>(
|
|||
let splat_scale = 1.0 / (wave_start as Float);
|
||||
|
||||
let film_metadata = ImageMetadata::default();
|
||||
let film = *camera.get_film();
|
||||
let film_image = film.get_image(&film_metadata, splat_scale);
|
||||
let film_image = camera.get_film().get_image(&film_metadata, splat_scale);
|
||||
|
||||
let (mse_values, _mse_debug_img) =
|
||||
film_image.mse(film_image.all_channels_desc(), ref_img, false);
|
||||
|
|
@ -230,15 +221,14 @@ pub fn evaluate_pixel_sample<T: RayIntegratorTrait>(
|
|||
sampler: &mut Sampler,
|
||||
pixel: Point2i,
|
||||
_sample_index: usize,
|
||||
arena: &mut Arena,
|
||||
scratch: &Bump,
|
||||
) {
|
||||
let mut lu = sampler.get1d();
|
||||
if get_options().disable_wavelength_jitter {
|
||||
lu = 0.5;
|
||||
}
|
||||
|
||||
let lambda = camera.get_film().sample_wavelengths(lu);
|
||||
let mut film: &mut Film = camera.get_film();
|
||||
let film = camera.get_film();
|
||||
let filter = film.get_filter();
|
||||
let camera_sample = get_camera_sample(sampler, pixel, filter);
|
||||
if let Some(mut camera_ray) = camera.generate_ray_differential(camera_sample, &lambda) {
|
||||
|
|
@ -254,13 +244,12 @@ pub fn evaluate_pixel_sample<T: RayIntegratorTrait>(
|
|||
camera_ray.ray,
|
||||
&lambda,
|
||||
sampler,
|
||||
scratch,
|
||||
initialize_visible_surface,
|
||||
arena,
|
||||
);
|
||||
l *= camera_ray.weight;
|
||||
|
||||
let std_spectra = get_spectra_context();
|
||||
if l.has_nans() || l.y(&lambda, &std_spectra).is_infinite() {
|
||||
if l.has_nans() || l.y(&lambda).is_infinite() {
|
||||
l = SampledSpectrum::new(0.);
|
||||
}
|
||||
|
||||
|
|
@ -1,18 +1,17 @@
|
|||
#![allow(unused_imports, dead_code)]
|
||||
#![feature(float_erf)]
|
||||
#![feature(f16)]
|
||||
#![feature(associated_type_defaults)]
|
||||
|
||||
pub mod bxdfs;
|
||||
pub mod cameras;
|
||||
pub mod core;
|
||||
pub mod data;
|
||||
pub mod filters;
|
||||
pub mod lights;
|
||||
pub mod materials;
|
||||
pub mod shapes;
|
||||
pub mod spectra;
|
||||
pub mod textures;
|
||||
pub mod utils;
|
||||
mod cameras;
|
||||
mod core;
|
||||
mod data;
|
||||
mod filters;
|
||||
mod images;
|
||||
mod integrators;
|
||||
mod lights;
|
||||
mod shapes;
|
||||
mod spectra;
|
||||
mod textures;
|
||||
mod utils;
|
||||
|
||||
pub use core::pbrt::*;
|
||||
|
|
|
|||
|
|
@ -1,54 +1,114 @@
|
|||
use crate::PI;
|
||||
use crate::core::color::{RGB, XYZ};
|
||||
use crate::core::geometry::*;
|
||||
use crate::core::image::{DeviceImage, ImageAccess};
|
||||
use crate::core::interaction::{
|
||||
Interaction, InteractionTrait, MediumInteraction, SurfaceInteraction,
|
||||
};
|
||||
use crate::core::interaction::{Interaction, MediumInteraction, SurfaceInteraction};
|
||||
use crate::core::light::{
|
||||
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
|
||||
};
|
||||
use crate::core::medium::MediumInterface;
|
||||
use crate::core::shape::{Shape, ShapeSampleContext, ShapeTrait};
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||
use crate::core::texture::{
|
||||
GPUFloatTexture, TextureEvalContext, TextureEvaluator, UniversalTextureEvaluator,
|
||||
};
|
||||
use crate::core::texture::{GPUFloatTexture, TextureEvalContext, UniversalTextureEvaluator};
|
||||
use crate::images::Image;
|
||||
use crate::shapes::{Shape, ShapeSampleContext};
|
||||
use crate::spectra::*;
|
||||
use crate::utils::Transform;
|
||||
use crate::utils::hash::hash_float;
|
||||
use crate::utils::{Ptr, Transform};
|
||||
use crate::{Float, PI};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DiffuseAreaLight {
|
||||
pub base: LightBase,
|
||||
pub shape: Ptr<Shape>,
|
||||
pub alpha: Ptr<GPUFloatTexture>,
|
||||
pub colorspace: Ptr<RGBColorSpace>,
|
||||
pub lemit: Ptr<DenselySampledSpectrum>,
|
||||
pub image: Ptr<DeviceImage>,
|
||||
pub shape: *const Shape,
|
||||
pub alpha: *const GPUFloatTexture,
|
||||
pub area: Float,
|
||||
pub two_sided: bool,
|
||||
pub lemit: DenselySampledSpectrum,
|
||||
pub scale: Float,
|
||||
pub image: *const Image,
|
||||
pub image_color_space: RGBColorSpace,
|
||||
}
|
||||
|
||||
unsafe impl Send for DiffuseAreaLight {}
|
||||
unsafe impl Sync for DiffuseAreaLight {}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
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 {
|
||||
if !self.two_sided && n.dot(wo.into()) <= 0.0 {
|
||||
if !self.two_sided && n.dot(wo) <= 0.0 {
|
||||
return SampledSpectrum::new(0.0);
|
||||
}
|
||||
self.lemit.sample(lambda) * self.scale
|
||||
let spec = DenselySampledSpectrum::from_array(&self.lemit_coeffs);
|
||||
spec.sample(lambda) * self.scale
|
||||
}
|
||||
|
||||
fn alpha_masked(&self, intr: &Interaction) -> bool {
|
||||
if self.alpha.is_null() {
|
||||
let Some(alpha_tex) = &self.alpha else {
|
||||
return false;
|
||||
};
|
||||
let ctx = TextureEvalContext::from(intr);
|
||||
let a = UniversalTextureEvaluator.evaluate_float(&self.alpha, &ctx);
|
||||
let a = UniversalTextureEvaluator.evaluate_float(alpha_tex, &ctx);
|
||||
if a >= 1.0 {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -73,14 +133,15 @@ impl LightTrait for DiffuseAreaLight {
|
|||
) -> Option<LightLiSample> {
|
||||
let shape_ctx = ShapeSampleContext::new(ctx.pi, ctx.n, ctx.ns, 0.0);
|
||||
let ss = self.shape.sample_from_context(&shape_ctx, u)?;
|
||||
let mut intr = ss.intr;
|
||||
intr.set_medium_interface(self.base.medium_interface);
|
||||
let mut intr: SurfaceInteraction = ss.intr.as_ref().clone();
|
||||
|
||||
intr.common.medium_interface = Some(self.base.medium_interface.clone());
|
||||
let p = intr.p();
|
||||
let n = intr.n();
|
||||
let uv = intr.get_common().uv;
|
||||
let uv = intr.uv;
|
||||
|
||||
// let generic_intr = Interaction::Surface(intr);
|
||||
if self.alpha_masked(&intr) {
|
||||
let generic_intr = Interaction::Surface(intr);
|
||||
if self.alpha_masked(&generic_intr) {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +152,7 @@ impl LightTrait for DiffuseAreaLight {
|
|||
return None;
|
||||
}
|
||||
|
||||
Some(LightLiSample::new(le, wi, ss.pdf, intr))
|
||||
Some(LightLiSample::new(le, wi, ss.pdf, generic_intr))
|
||||
}
|
||||
|
||||
fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, _allow_incomplete_pdf: bool) -> Float {
|
||||
|
|
@ -118,15 +179,17 @@ impl LightTrait for DiffuseAreaLight {
|
|||
if self.alpha_masked(&intr) {
|
||||
return SampledSpectrum::new(0.);
|
||||
}
|
||||
if !self.image.is_null() {
|
||||
if let Some(image) = &self.image {
|
||||
let mut rgb = RGB::default();
|
||||
uv[1] = 1. - uv[1];
|
||||
for c in 0..3 {
|
||||
rgb[c] = self.image.bilerp_channel(uv, c as i32);
|
||||
rgb[c] = image.bilerp_channel(uv, c);
|
||||
}
|
||||
|
||||
let cs_ref = unsafe { self.colorspace.as_ref() };
|
||||
let spec = RGBIlluminantSpectrum::new(cs_ref, rgb.clamp_zero());
|
||||
let spec = RGBIlluminantSpectrum::new(
|
||||
self.image_color_space.as_ref().unwrap(),
|
||||
RGB::clamp_zero(rgb),
|
||||
);
|
||||
|
||||
self.scale * spec.sample(lambda)
|
||||
} else {
|
||||
|
|
@ -141,19 +204,21 @@ impl LightTrait for DiffuseAreaLight {
|
|||
#[cfg(not(target_os = "cuda"))]
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
let mut l = SampledSpectrum::new(0.);
|
||||
if !self.image.is_null() {
|
||||
for y in 0..self.image.resolution().y() {
|
||||
for x in 0..self.image.resolution().x() {
|
||||
if let Some(image) = &self.image {
|
||||
for y in 0..image.resolution().y() {
|
||||
for x in 0..image.resolution().x() {
|
||||
let mut rgb = RGB::default();
|
||||
for c in 0..3 {
|
||||
rgb[c] = self.image.get_channel(Point2i::new(x, y), c as i32);
|
||||
rgb[c] = image.get_channel(Point2i::new(x, y), c);
|
||||
}
|
||||
|
||||
let cs_ref = unsafe { self.colorspace.as_ref() };
|
||||
l += RGBIlluminantSpectrum::new(cs_ref, rgb.clamp_zero()).sample(&lambda);
|
||||
l += RGBIlluminantSpectrum::new(
|
||||
self.image_color_space.as_ref().unwrap(),
|
||||
RGB::clamp_zero(rgb),
|
||||
)
|
||||
.sample(&lambda);
|
||||
}
|
||||
}
|
||||
l *= self.scale / (self.image.resolution().x() * self.image.resolution().y()) as Float;
|
||||
l *= self.scale / (image.resolution().x() * image.resolution().y()) as Float;
|
||||
} else {
|
||||
l = self.lemit.sample(&lambda) * self.scale;
|
||||
}
|
||||
|
|
@ -169,11 +234,11 @@ impl LightTrait for DiffuseAreaLight {
|
|||
#[cfg(not(target_os = "cuda"))]
|
||||
fn bounds(&self) -> Option<LightBounds> {
|
||||
let mut phi = 0.;
|
||||
if !self.image.is_null() {
|
||||
for y in 0..self.image.base.resolution.y() {
|
||||
for x in 0..self.image.base.resolution.x() {
|
||||
if let Some(image) = &self.image {
|
||||
for y in 0..image.resolution.y() {
|
||||
for x in 0..image.resolution.x() {
|
||||
for c in 0..3 {
|
||||
phi += self.image.get_channel(Point2i::new(x, y), c);
|
||||
phi += image.get_channel(Point2i::new(x, y), c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,14 @@
|
|||
use crate::core::geometry::{
|
||||
Bounds3f, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector3f, VectorLike,
|
||||
};
|
||||
use crate::core::interaction::{Interaction, InteractionBase, SimpleInteraction};
|
||||
use crate::core::geometry::{Bounds3f, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector3f};
|
||||
use crate::core::interaction::{Interaction, InteractionData};
|
||||
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)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct DistantLight {
|
||||
pub base: LightBase,
|
||||
pub lemit: Ptr<DenselySampledSpectrum>,
|
||||
pub lemit_coeffs: [Float; 32],
|
||||
pub scale: Float,
|
||||
pub scene_center: Point3f,
|
||||
pub scene_radius: Float,
|
||||
|
|
@ -30,7 +26,8 @@ impl DistantLight {
|
|||
.apply_to_vector(Vector3f::new(0., 0., 1.))
|
||||
.normalize();
|
||||
let p_outside = ctx_p + wi * 2. * self.scene_radius;
|
||||
let li = self.scale * self.lemit.sample(lambda);
|
||||
let spectrum = DenselySampledSpectrum::from_array(&self.lemit_coeffs);
|
||||
let li = self.scale * spectrum.sample(lambda);
|
||||
(li, wi, 1.0, p_outside)
|
||||
}
|
||||
}
|
||||
|
|
@ -59,8 +56,11 @@ impl LightTrait for DistantLight {
|
|||
let p_outside = ctx.p() + wi * 2. * self.scene_radius;
|
||||
|
||||
let li = self.scale * self.lemit.sample(lambda);
|
||||
let base = InteractionBase::new_boundary(p_outside, 0.0, self.base.medium_interface);
|
||||
let intr = SimpleInteraction::new(base);
|
||||
let intr = SimpleInteraction::new(
|
||||
Point3fi::new_from_point(p_outside),
|
||||
0.0,
|
||||
Some(self.base.medium_interface.clone()),
|
||||
);
|
||||
|
||||
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,46 @@
|
|||
use crate::core::geometry::{Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f};
|
||||
use crate::core::image::{DeviceImage, ImageAccess};
|
||||
use crate::core::light::{
|
||||
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
|
||||
};
|
||||
use crate::Float;
|
||||
use crate::core::geometry::{Bounds3f, Normal3f, Point2f, Point3f, Vector3f};
|
||||
use crate::core::light::{LightBase, LightBounds, LightLiSample, LightTrait};
|
||||
use crate::core::medium::MediumInterface;
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::math::equal_area_sphere_to_square;
|
||||
use crate::utils::sampling::DevicePiecewiseConstant2D;
|
||||
use crate::utils::{Ptr, Transform};
|
||||
use crate::{Float, PI};
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::Transform;
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GoniometricLight {
|
||||
pub base: LightBase,
|
||||
pub iemit: Ptr<DenselySampledSpectrum>,
|
||||
pub scale: Float,
|
||||
pub image: Ptr<DeviceImage>,
|
||||
pub distrib: Ptr<DevicePiecewiseConstant2D>,
|
||||
iemit: DenselySampledSpectrum,
|
||||
scale: Float,
|
||||
image: Image,
|
||||
distrib: PiecewiseConstant2D,
|
||||
}
|
||||
|
||||
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 {
|
||||
let uv = equal_area_sphere_to_square(w);
|
||||
self.scale * self.iemit.sample(lambda) * self.image.lookup_nearest_channel(uv, 0)
|
||||
|
|
@ -77,14 +97,13 @@ impl LightTrait for GoniometricLight {
|
|||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
let resolution = self.image.resolution();
|
||||
let mut sum_y = 0.;
|
||||
for y in 0..resolution.y() {
|
||||
for x in 0..resolution.x() {
|
||||
for y in 0..self.image.resolution.y() {
|
||||
for x in 0..self.image.resolution.x() {
|
||||
sum_y += self.image.get_channel(Point2i::new(x, y), 0);
|
||||
}
|
||||
}
|
||||
self.scale * self.iemit.sample(&lambda) * 4. * PI * sum_y
|
||||
/ (resolution.x() * resolution.y()) as Float
|
||||
/ (self.image.resolution.x() * self.image.resolution.y()) as Float
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +1,48 @@
|
|||
use crate::core::color::RGB;
|
||||
use crate::core::geometry::{
|
||||
Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector3f,
|
||||
use crate::{
|
||||
core::geometry::Frame,
|
||||
core::medium::Medium,
|
||||
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::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;
|
||||
|
||||
use crate::images::{PixelFormat, WrapMode};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct UniformInfiniteLight {
|
||||
pub struct InfiniteUniformLight {
|
||||
pub base: LightBase,
|
||||
pub lemit: Ptr<DenselySampledSpectrum>,
|
||||
pub lemit: u32,
|
||||
pub scale: Float,
|
||||
pub scene_center: Point3f,
|
||||
pub scene_radius: Float,
|
||||
}
|
||||
|
||||
unsafe impl Send for UniformInfiniteLight {}
|
||||
unsafe impl Sync for UniformInfiniteLight {}
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
impl InfiniteUniformLight {
|
||||
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 UniformInfiniteLight {
|
||||
impl LightTrait for InfiniteUniformLight {
|
||||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
|
|
@ -51,12 +58,10 @@ impl LightTrait for UniformInfiniteLight {
|
|||
}
|
||||
let wi = sample_uniform_sphere(u);
|
||||
let pdf = uniform_sphere_pdf();
|
||||
let base = InteractionBase::new_boundary(
|
||||
let intr_simple = SimpleInteraction::new_interface(
|
||||
ctx.p() + wi * (2. * self.scene_radius),
|
||||
0.,
|
||||
MediumInterface::default(),
|
||||
Some(MediumInterface::default()),
|
||||
);
|
||||
let intr_simple = SimpleInteraction::new(base);
|
||||
|
||||
let intr = Interaction::Simple(intr_simple);
|
||||
Some(LightLiSample::new(
|
||||
|
|
@ -93,40 +98,91 @@ impl LightTrait for UniformInfiniteLight {
|
|||
fn le(&self, _ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
self.scale * self.lemit.sample(lambda)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
fn bounds(&self) -> Option<LightBounds> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
4. * PI * PI * square(self.scene_radius) * self.scale * self.lemit.sample(&lambda)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ImageInfiniteLight {
|
||||
pub base: LightBase,
|
||||
pub image: Ptr<DeviceImage>,
|
||||
pub image_color_space: Ptr<RGBColorSpace>,
|
||||
pub distrib: Ptr<DevicePiecewiseConstant2D>,
|
||||
pub compensated_distrib: Ptr<DevicePiecewiseConstant2D>,
|
||||
pub scale: Float,
|
||||
pub scene_radius: Float,
|
||||
pub scene_center: Point3f,
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct InfiniteImageLight {
|
||||
base: LightBase,
|
||||
image: Image,
|
||||
image_color_space: u32,
|
||||
scale: Float,
|
||||
scene_radius: Float,
|
||||
scene_center: Point3f,
|
||||
distrib: PiecewiseConstant2D,
|
||||
compensated_distrib: PiecewiseConstant2D,
|
||||
}
|
||||
|
||||
unsafe impl Send for ImageInfiniteLight {}
|
||||
unsafe impl Sync for ImageInfiniteLight {}
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
impl InfiniteImageLight {
|
||||
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 {
|
||||
let mut rgb = RGB::default();
|
||||
for c in 0..3 {
|
||||
|
|
@ -136,12 +192,13 @@ impl ImageInfiniteLight {
|
|||
WrapMode::OctahedralSphere.into(),
|
||||
);
|
||||
}
|
||||
let spec = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero());
|
||||
let spec =
|
||||
RGBIlluminantSpectrum::new(self.image_color_space.as_ref(), RGB::clamp_zero(rgb));
|
||||
self.scale * spec.sample(lambda)
|
||||
}
|
||||
}
|
||||
|
||||
impl LightTrait for ImageInfiniteLight {
|
||||
impl LightTrait for InfiniteImageLight {
|
||||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
|
|
@ -168,14 +225,13 @@ impl LightTrait for ImageInfiniteLight {
|
|||
let pdf = map_pdf / (4. * PI);
|
||||
|
||||
// Return radiance value for infinite light direction
|
||||
let base = InteractionBase::new_boundary(
|
||||
let mut simple_intr = SimpleInteraction::new_interface(
|
||||
ctx.p() + wi * (2. * self.scene_radius),
|
||||
0.,
|
||||
self.base.medium_interface,
|
||||
Some(MediumInterface::default()),
|
||||
);
|
||||
let simple_intr = SimpleInteraction::new(base);
|
||||
let intr = Interaction::Simple(simple_intr);
|
||||
|
||||
simple_intr.common.medium_interface = Some(self.base.medium_interface.clone());
|
||||
let intr = Interaction::Simple(simple_intr);
|
||||
Some(LightLiSample::new(self.image_le(uv, lambda), wi, pdf, intr))
|
||||
}
|
||||
|
||||
|
|
@ -214,8 +270,8 @@ impl LightTrait for ImageInfiniteLight {
|
|||
#[cfg(not(target_os = "cuda"))]
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
let mut sum_l = SampledSpectrum::new(0.);
|
||||
let width = self.image.resolution().x();
|
||||
let height = self.image.resolution().y();
|
||||
let width = self.image.resolution.x();
|
||||
let height = self.image.resolution.y();
|
||||
for v in 0..height {
|
||||
for u in 0..width {
|
||||
let mut rgb = RGB::default();
|
||||
|
|
@ -226,7 +282,10 @@ impl LightTrait for ImageInfiniteLight {
|
|||
WrapMode::OctahedralSphere.into(),
|
||||
);
|
||||
}
|
||||
sum_l += RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero())
|
||||
sum_l += RGBIlluminantSpectrum::new(
|
||||
self.image_color_space.as_ref(),
|
||||
RGB::clamp_zero(rgb),
|
||||
)
|
||||
.sample(&lambda);
|
||||
}
|
||||
}
|
||||
|
|
@ -248,25 +307,157 @@ impl LightTrait for ImageInfiniteLight {
|
|||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct PortalInfiniteLight {
|
||||
pub struct InfinitePortalLight {
|
||||
pub base: LightBase,
|
||||
pub image: Ptr<DeviceImage>,
|
||||
pub image_color_space: Ptr<RGBColorSpace>,
|
||||
pub image: Image,
|
||||
pub image_color_space: RGBColorSpace,
|
||||
pub scale: Float,
|
||||
pub filename: String,
|
||||
pub portal: [Point3f; 4],
|
||||
pub portal_frame: Frame,
|
||||
pub distribution: DeviceWindowedPiecewiseConstant2D,
|
||||
pub distribution: WindowedPiecewiseConstant2D,
|
||||
pub scene_center: Point3f,
|
||||
pub scene_radius: Float,
|
||||
}
|
||||
|
||||
impl PortalInfiniteLight {
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
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 {
|
||||
let mut rgb = RGB::default();
|
||||
for c in 0..3 {
|
||||
rgb[c] = self.image.lookup_nearest_channel(uv, c)
|
||||
}
|
||||
let spec = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero());
|
||||
let spec =
|
||||
RGBIlluminantSpectrum::new(self.image_color_space.as_ref(), RGB::clamp_zero(rgb));
|
||||
self.scale * spec.sample(lambda)
|
||||
}
|
||||
|
||||
|
|
@ -317,7 +508,7 @@ impl PortalInfiniteLight {
|
|||
}
|
||||
}
|
||||
|
||||
impl LightTrait for PortalInfiniteLight {
|
||||
impl LightTrait for InfinitePortalLight {
|
||||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
|
|
@ -338,8 +529,7 @@ impl LightTrait for PortalInfiniteLight {
|
|||
let pdf = map_pdf / duv_dw;
|
||||
let l = self.image_lookup(uv, lambda);
|
||||
let pl = ctx.p() + 2. * self.scene_radius * wi;
|
||||
let base = InteractionBase::new_boundary(pl, 0., self.base.medium_interface);
|
||||
let sintr = SimpleInteraction::new(base);
|
||||
let sintr = SimpleInteraction::new_interface(pl, Some(self.base.medium_interface.clone()));
|
||||
let intr = Interaction::Simple(sintr);
|
||||
Some(LightLiSample::new(l, wi, pdf, intr))
|
||||
}
|
||||
|
|
@ -381,8 +571,8 @@ impl LightTrait for PortalInfiniteLight {
|
|||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
fn preprocess(&mut self, scene_bounds: &Bounds3f) {
|
||||
(self.scene_center, self.scene_radius) = scene_bounds.bounding_sphere();
|
||||
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ pub mod spot;
|
|||
pub use diffuse::DiffuseAreaLight;
|
||||
pub use distant::DistantLight;
|
||||
pub use goniometric::GoniometricLight;
|
||||
pub use infinite::{ImageInfiniteLight, PortalInfiniteLight, UniformInfiniteLight};
|
||||
pub use infinite::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight};
|
||||
pub use point::PointLight;
|
||||
pub use projection::ProjectionLight;
|
||||
pub use spot::SpotLight;
|
||||
|
|
|
|||
|
|
@ -1,24 +1,33 @@
|
|||
use crate::core::geometry::{
|
||||
Bounds3f, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector3f, VectorLike,
|
||||
};
|
||||
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};
|
||||
use crate::Float;
|
||||
use crate::core::light::LightBaseData;
|
||||
use crate::spectra::SampledSpectrum;
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PointLight {
|
||||
pub base: LightBase,
|
||||
pub base: LightBasea,
|
||||
pub i:
|
||||
pub scale: Float,
|
||||
pub i: Ptr<DenselySampledSpectrum>,
|
||||
}
|
||||
|
||||
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 {
|
||||
&self.base
|
||||
}
|
||||
|
|
@ -37,8 +46,7 @@ impl LightTrait for PointLight {
|
|||
let p: Point3f = pi.into();
|
||||
let wi = (p - ctx.p()).normalize();
|
||||
let li = self.scale * self.i.sample(lambda) / p.distance_squared(ctx.p());
|
||||
let base = InteractionBase::new_boundary(p, 0., self.base.medium_interface);
|
||||
let intr = SimpleInteraction::new(base);
|
||||
let intr = SimpleInteraction::new(pi, 0.0, Some(self.base.medium_interface.clone()));
|
||||
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,6 @@
|
|||
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::{
|
||||
spectra::{RGBColorSpace, RGBIlluminantSpectrum},
|
||||
utils::{Ptr, Transform, sampling::DevicePiecewiseConstant2D},
|
||||
spectra::{RGB, RGBColorSpace},
|
||||
utils::{Transform, sampling::PiecewiseConstant2D},
|
||||
};
|
||||
|
||||
#[repr(C)]
|
||||
|
|
@ -25,13 +12,66 @@ pub struct ProjectionLight {
|
|||
pub screen_bounds: Bounds2f,
|
||||
pub screen_from_light: Transform,
|
||||
pub light_from_screen: Transform,
|
||||
pub image_id: u32,
|
||||
pub a: Float,
|
||||
pub image: Ptr<DeviceImage>,
|
||||
pub distrib: Ptr<DevicePiecewiseConstant2D>,
|
||||
pub image_color_space: Ptr<RGBColorSpace>,
|
||||
pub distrib: PiecewiseConstant2D,
|
||||
pub image_color_space: *const RGBColorSpace,
|
||||
}
|
||||
|
||||
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 {
|
||||
if w.z() < self.hither {
|
||||
return SampledSpectrum::new(0.);
|
||||
|
|
@ -43,9 +83,9 @@ impl ProjectionLight {
|
|||
let uv = Point2f::from(self.screen_bounds.offset(&Point2f::new(ps.x(), ps.y())));
|
||||
let mut rgb = RGB::default();
|
||||
for c in 0..3 {
|
||||
rgb[c] = self.image.lookup_nearest_channel(uv, c as i32);
|
||||
rgb[c] = self.image.lookup_nearest_channel(uv, c);
|
||||
}
|
||||
let s = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero());
|
||||
let s = RGBIlluminantSpectrum::new(self.image_color_space.as_ref(), RGB::clamp_zero(rgb));
|
||||
self.scale * s.sample(&lambda)
|
||||
}
|
||||
}
|
||||
|
|
@ -91,12 +131,11 @@ impl LightTrait for ProjectionLight {
|
|||
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
let mut sum = SampledSpectrum::new(0.);
|
||||
let res = self.image.resolution();
|
||||
for y in 0..res.y() {
|
||||
for x in 0..res.x() {
|
||||
for y in 0..self.image.resolution.y() {
|
||||
for x in 0..self.image.resolution.x() {
|
||||
let ps = self.screen_bounds.lerp(Point2f::new(
|
||||
(x as Float + 0.5) / res.x() as Float,
|
||||
(y as Float + 0.5) / res.y() as Float,
|
||||
(x as Float + 0.5) / self.image.resolution.x() as Float,
|
||||
(y as Float + 0.5) / self.image.resolution.y() as Float,
|
||||
));
|
||||
let w_raw = Vector3f::from(self.light_from_screen.apply_to_point(Point3f::new(
|
||||
ps.x(),
|
||||
|
|
@ -107,14 +146,17 @@ impl LightTrait for ProjectionLight {
|
|||
let dwda = cos_theta(w).powi(3);
|
||||
let mut rgb = RGB::default();
|
||||
for c in 0..3 {
|
||||
rgb[c] = self.image.get_channel(Point2i::new(x, y), c as i32);
|
||||
rgb[c] = self.image.get_channel(Point2i::new(x, y), c);
|
||||
}
|
||||
|
||||
let s = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero());
|
||||
let s = RGBIlluminantSpectrum::new(
|
||||
self.image_color_space.as_ref(),
|
||||
RGB::clamp_zero(rgb),
|
||||
);
|
||||
sum += s.sample(&lambda) * dwda;
|
||||
}
|
||||
}
|
||||
self.scale * self.a * sum / (res.x() * res.y()) as Float
|
||||
self.scale * self.a * sum / (self.image.resolution.x() * self.image.resolution.y()) as Float
|
||||
}
|
||||
|
||||
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
use crate::core::geometry::primitives::OctahedralVector;
|
||||
use crate::core::geometry::{Bounds3f, Normal3f, Point3f, Vector3f, VectorLike};
|
||||
use crate::core::geometry::{DirectionCone, Normal};
|
||||
use crate::core::light::Light;
|
||||
use crate::utils::math::{clamp, lerp, sample_discrete};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::core::light::{LightBounds, LightSampleContext};
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::math::{clamp, lerp, sample_discrete};
|
||||
use crate::utils::math::{safe_sqrt, square};
|
||||
use crate::utils::ptr::Ptr;
|
||||
use crate::utils::sampling::AliasTable;
|
||||
use crate::{Float, ONE_MINUS_EPSILON, PI};
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
|
@ -154,25 +155,22 @@ impl CompactLightBounds {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SampledLight {
|
||||
pub light: Ptr<Light>,
|
||||
pub light: Arc<Light>,
|
||||
pub p: Float,
|
||||
}
|
||||
|
||||
impl SampledLight {
|
||||
pub fn new(light: Light, p: Float) -> Self {
|
||||
Self {
|
||||
light: Ptr::from(&light),
|
||||
p,
|
||||
}
|
||||
pub fn new(light: Arc<Light>, p: Float) -> Self {
|
||||
Self { light, p }
|
||||
}
|
||||
}
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait LightSamplerTrait {
|
||||
pub trait LightSamplerTrait: Send + Sync + std::fmt::Debug {
|
||||
fn sample_with_context(&self, ctx: &LightSampleContext, u: Float) -> Option<SampledLight>;
|
||||
fn pmf_with_context(&self, ctx: &LightSampleContext, light: &Light) -> Float;
|
||||
fn pmf_with_context(&self, ctx: &LightSampleContext, light: &Arc<Light>) -> Float;
|
||||
fn sample(&self, u: Float) -> Option<SampledLight>;
|
||||
fn pmf(&self, light: &Light) -> Float;
|
||||
fn pmf(&self, light: &Arc<Light>) -> Float;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -185,18 +183,14 @@ pub enum LightSampler {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UniformLightSampler {
|
||||
lights: *const Light,
|
||||
lights_len: u32,
|
||||
lights: Vec<Arc<Light>>,
|
||||
}
|
||||
|
||||
impl UniformLightSampler {
|
||||
pub fn new(lights: *const Light, lights_len: u32) -> Self {
|
||||
Self { lights, lights_len }
|
||||
pub fn new(lights: &[Arc<Light>]) -> Self {
|
||||
Self {
|
||||
lights: lights.to_vec(),
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn light(&self, idx: usize) -> Light {
|
||||
unsafe { *self.lights.add(idx) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -204,52 +198,77 @@ impl LightSamplerTrait for UniformLightSampler {
|
|||
fn sample_with_context(&self, _ctx: &LightSampleContext, u: Float) -> Option<SampledLight> {
|
||||
self.sample(u)
|
||||
}
|
||||
fn pmf_with_context(&self, _ctx: &LightSampleContext, light: &Light) -> Float {
|
||||
fn pmf_with_context(&self, _ctx: &LightSampleContext, light: &Arc<Light>) -> Float {
|
||||
self.pmf(light)
|
||||
}
|
||||
fn sample(&self, u: Float) -> Option<SampledLight> {
|
||||
if self.lights_len == 0 {
|
||||
if self.lights.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let light_index = (u as u32 * self.lights_len).min(self.lights_len - 1) as usize;
|
||||
let light_index = (u as usize * self.lights.len()).min(self.lights.len() - 1);
|
||||
Some(SampledLight {
|
||||
light: Ptr::from(&self.light(light_index)),
|
||||
p: 1. / self.lights_len as Float,
|
||||
light: self.lights[light_index].clone(),
|
||||
p: 1. / self.lights.len() as Float,
|
||||
})
|
||||
}
|
||||
fn pmf(&self, _light: &Light) -> Float {
|
||||
if self.lights_len == 0 {
|
||||
fn pmf(&self, _light: &Arc<Light>) -> Float {
|
||||
if self.lights.is_empty() {
|
||||
return 0.;
|
||||
}
|
||||
1. / self.lights_len as Float
|
||||
1. / self.lights.len() as Float
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Alias {
|
||||
pub q: Float,
|
||||
pub alias: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PowerLightSampler {
|
||||
pub lights: Ptr<Light>,
|
||||
pub lights_len: u32,
|
||||
pub alias_table: AliasTable,
|
||||
lights: Vec<Arc<Light>>,
|
||||
light_to_index: HashMap<usize, usize>,
|
||||
alias_table: AliasTable,
|
||||
}
|
||||
|
||||
unsafe impl Send for PowerLightSampler {}
|
||||
unsafe impl Sync for PowerLightSampler {}
|
||||
impl PowerLightSampler {
|
||||
pub fn new(lights: &[Arc<Light>]) -> Self {
|
||||
if lights.is_empty() {
|
||||
return Self {
|
||||
lights: Vec::new(),
|
||||
light_to_index: HashMap::new(),
|
||||
alias_table: AliasTable::new(&[]),
|
||||
};
|
||||
}
|
||||
|
||||
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 {
|
||||
fn sample_with_context(&self, _ctx: &LightSampleContext, u: Float) -> Option<SampledLight> {
|
||||
self.sample(u)
|
||||
}
|
||||
|
||||
fn pmf_with_context(&self, _ctx: &LightSampleContext, light: &Light) -> Float {
|
||||
fn pmf_with_context(&self, _ctx: &LightSampleContext, light: &Arc<Light>) -> Float {
|
||||
self.pmf(light)
|
||||
}
|
||||
|
||||
|
|
@ -260,25 +279,24 @@ impl LightSamplerTrait for PowerLightSampler {
|
|||
|
||||
let (light_index, pmf, _) = self.alias_table.sample(u);
|
||||
|
||||
let light_ref = unsafe { self.lights.add(light_index as usize) };
|
||||
Some(SampledLight {
|
||||
light: light_ref,
|
||||
light: self.lights[light_index].clone(),
|
||||
p: pmf,
|
||||
})
|
||||
}
|
||||
|
||||
fn pmf(&self, light: &Light) -> Float {
|
||||
let array_start = self.lights.as_raw();
|
||||
let target = light as *const Light as *mut Light;
|
||||
|
||||
unsafe {
|
||||
let index = target.offset_from(array_start);
|
||||
|
||||
if index >= 0 && index < self.lights_len as isize {
|
||||
return self.alias_table.pmf(index as u32);
|
||||
fn pmf(&self, light: &Arc<Light>) -> Float {
|
||||
if self.alias_table.size() == 0 {
|
||||
return 0.;
|
||||
}
|
||||
|
||||
let ptr = Arc::as_ptr(light) as usize;
|
||||
|
||||
if let Some(&index) = self.light_to_index.get(&ptr) {
|
||||
self.alias_table.pmf(index)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
0.
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -286,6 +304,7 @@ impl LightSamplerTrait for PowerLightSampler {
|
|||
#[repr(C, align(32))]
|
||||
pub struct LightBVHNode {
|
||||
pub light_bounds: CompactLightBounds,
|
||||
|
||||
// Bit 31 (MSB) : isLeaf (1 bit)
|
||||
// Bits 0..31 : childOrLightIndex (31 bits)
|
||||
packed_data: u32,
|
||||
|
|
@ -354,40 +373,14 @@ impl LightBVHNode {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BVHLightSampler {
|
||||
pub nodes: *const LightBVHNode,
|
||||
pub lights: *const Light,
|
||||
pub infinite_lights: *const Light,
|
||||
pub bit_trails: *const u64,
|
||||
pub nodes_len: u32,
|
||||
pub lights_len: u32,
|
||||
pub infinite_lights_len: u32,
|
||||
pub all_light_bounds: Bounds3f,
|
||||
lights: Vec<Arc<Light>>,
|
||||
infinite_lights: Vec<Arc<Light>>,
|
||||
all_light_bounds: Bounds3f,
|
||||
nodes: Vec<LightBVHNode>,
|
||||
light_to_bit_trail: HashMap<usize, usize>,
|
||||
}
|
||||
|
||||
unsafe impl Send for BVHLightSampler {}
|
||||
unsafe impl Sync for 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 {
|
||||
let theta_o = b.cos_theta_o.acos();
|
||||
let theta_e = b.cos_theta_e.acos();
|
||||
|
|
@ -404,9 +397,9 @@ impl BVHLightSampler {
|
|||
|
||||
impl LightSamplerTrait for BVHLightSampler {
|
||||
fn sample_with_context(&self, ctx: &LightSampleContext, mut u: Float) -> Option<SampledLight> {
|
||||
let empty_nodes = if self.nodes_len == 0 { 0. } else { 1. };
|
||||
let inf_size = self.infinite_lights_len as Float;
|
||||
let light_size = self.lights_len as Float;
|
||||
let empty_nodes = if self.nodes.is_empty() { 0. } else { 1. };
|
||||
let inf_size = self.infinite_lights.len() as Float;
|
||||
let light_size = self.lights.len() as Float;
|
||||
|
||||
let p_inf = inf_size / (inf_size + empty_nodes);
|
||||
|
||||
|
|
@ -414,10 +407,9 @@ impl LightSamplerTrait for BVHLightSampler {
|
|||
u /= p_inf;
|
||||
let ind = (u * light_size).min(light_size - 1.) as usize;
|
||||
let pmf = p_inf / inf_size;
|
||||
return Some(SampledLight::new(self.infinite_light(ind), pmf));
|
||||
}
|
||||
|
||||
if self.nodes_len == 0 {
|
||||
Some(SampledLight::new(self.infinite_lights[ind].clone(), pmf))
|
||||
} else {
|
||||
if self.nodes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let p = ctx.p();
|
||||
|
|
@ -427,16 +419,19 @@ impl LightSamplerTrait for BVHLightSampler {
|
|||
let mut pmf = 1. - p_inf;
|
||||
|
||||
loop {
|
||||
let node = self.node(node_ind);
|
||||
let node = self.nodes[node_ind];
|
||||
if !node.is_leaf() {
|
||||
let child0_idx = node_ind + 1;
|
||||
let child1_idx = node.child_or_light_index() as usize;
|
||||
let child0 = self.node(child0_idx);
|
||||
let child1 = self.node(child1_idx);
|
||||
|
||||
let children: [LightBVHNode; 2] = [
|
||||
self.nodes[node_ind + 1],
|
||||
self.nodes[node.child_or_light_index() as usize],
|
||||
];
|
||||
let ci: [Float; 2] = [
|
||||
child0.light_bounds.importance(p, n, &self.all_light_bounds),
|
||||
child1.light_bounds.importance(p, n, &self.all_light_bounds),
|
||||
children[0]
|
||||
.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. {
|
||||
|
|
@ -446,96 +441,81 @@ impl LightSamplerTrait for BVHLightSampler {
|
|||
let mut node_pmf: Float = 0.;
|
||||
let child = sample_discrete(&ci, u, Some(&mut node_pmf), Some(&mut u));
|
||||
pmf *= node_pmf;
|
||||
node_ind = if child == 0 { child0_idx } else { child1_idx };
|
||||
node_ind = if child == 0 {
|
||||
node_ind + 1
|
||||
} else {
|
||||
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;
|
||||
return Some(SampledLight::new(self.light(light_idx), pmf));
|
||||
node.child_or_light_index() as usize
|
||||
};
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
let finite_start = self.lights;
|
||||
let finite_end = unsafe { self.lights.add(self.lights_len as usize) };
|
||||
|
||||
if light_ptr < finite_start || light_ptr >= finite_end {
|
||||
return 0.0;
|
||||
fn pmf_with_context(&self, ctx: &LightSampleContext, light: &Arc<Light>) -> Float {
|
||||
let ptr = Arc::as_ptr(light) as usize;
|
||||
let empty_nodes = if self.nodes.is_empty() { 0. } else { 1. };
|
||||
if self.light_to_bit_trail.contains_key(&ptr) {
|
||||
return 1. / (self.infinite_lights.len() as Float + empty_nodes);
|
||||
}
|
||||
|
||||
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 mut bit_trail = self.light_to_bit_trail[&ptr];
|
||||
let p = ctx.p();
|
||||
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 {
|
||||
let node = self.node(node_ind);
|
||||
let node = self.nodes[node_ind];
|
||||
if node.is_leaf() {
|
||||
return pmf;
|
||||
}
|
||||
let child0 = self.node(node_ind + 1);
|
||||
let child1 = self.node(node.child_or_light_index() as usize);
|
||||
let child0 = self.nodes[node_ind + 1];
|
||||
let child1 = self.nodes[node.child_or_light_index() as usize];
|
||||
let ci = [
|
||||
child0.light_bounds.importance(p, n, &self.all_light_bounds),
|
||||
child1.light_bounds.importance(p, n, &self.all_light_bounds),
|
||||
];
|
||||
|
||||
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 {
|
||||
pmf *= ci[bit_trail & 1] / (ci[0] + ci[1]);
|
||||
node_ind = if (bit_trail & 1) != 0 {
|
||||
node.child_or_light_index() as usize
|
||||
} else {
|
||||
node_ind + 1
|
||||
};
|
||||
|
||||
bit_trail >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn sample(&self, u: Float) -> Option<SampledLight> {
|
||||
if self.lights_len == 0 {
|
||||
if self.lights.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let light_ind = (u * self.lights_len as Float).min(self.lights_len as Float - 1.) as usize;
|
||||
let light_ind =
|
||||
(u * self.lights.len() as Float).min(self.lights.len() as Float - 1.) as usize;
|
||||
|
||||
Some(SampledLight::new(
|
||||
self.light(light_ind),
|
||||
1. / self.lights_len as Float,
|
||||
self.lights[light_ind].clone(),
|
||||
1. / self.lights.len() as Float,
|
||||
))
|
||||
}
|
||||
|
||||
fn pmf(&self, _light: &Light) -> Float {
|
||||
if self.lights_len == 0 {
|
||||
fn pmf(&self, _light: &Arc<Light>) -> Float {
|
||||
if self.lights.is_empty() {
|
||||
return 0.;
|
||||
}
|
||||
|
||||
1. / self.lights_len as Float
|
||||
1. / self.lights.len() as Float
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,16 @@
|
|||
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};
|
||||
use crate::core::light::{LightBase, LightLiSample, LightSampleContext, LightTrait};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SpotLight {
|
||||
pub base: LightBase,
|
||||
pub iemit: Ptr<DenselySampledSpectrum>,
|
||||
pub iemit_coeffs: [Float; 32],
|
||||
pub scale: Float,
|
||||
pub cos_falloff_start: Float,
|
||||
pub cos_falloff_end: Float,
|
||||
}
|
||||
|
||||
impl SpotLight {
|
||||
impl SpotLightData {
|
||||
pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
let cos_theta = w.z(); // assuming normalized in light space
|
||||
let falloff = crate::utils::math::smooth_step(
|
||||
|
|
@ -26,7 +18,8 @@ impl SpotLight {
|
|||
self.cos_falloff_end,
|
||||
self.cos_falloff_start,
|
||||
);
|
||||
falloff * self.scale * self.iemit.sample(lambda)
|
||||
let spectrum = DenselySampledSpectrum::from_array(&self.iemit_coeffs);
|
||||
falloff * self.scale * spectrum.sample(lambda)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -49,10 +42,9 @@ impl LightTrait for SpotLight {
|
|||
let p: Point3f = pi.into();
|
||||
let wi = (p - ctx.p()).normalize();
|
||||
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 base = InteractionBase::new_boundary(p, 0., self.base.medium_interface);
|
||||
let intr = SimpleInteraction::new(base);
|
||||
let intr = SimpleInteraction::new(pi, 0.0, Some(self.base.medium_interface.clone()));
|
||||
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
|
||||
}
|
||||
|
||||
|
|
@ -86,7 +78,7 @@ impl LightTrait for SpotLight {
|
|||
* self.iemit.sample(&lambda)
|
||||
* 2.
|
||||
* PI
|
||||
* ((1. - self.cos_falloff_start) + (self.cos_falloff_start - self.cos_falloff_end) / 2.)
|
||||
* ((1. - self.cos_fallof_start) + (self.cos_fallof_start - self.cos_fallof_end) / 2.)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
|
|
@ -106,12 +98,12 @@ impl LightTrait for SpotLight {
|
|||
.apply_to_vector(Vector3f::new(0., 0., 1.))
|
||||
.normalize();
|
||||
let phi = self.scale * self.iemit.max_value() * 4. * PI;
|
||||
let cos_theta_e = (self.cos_falloff_end.acos() - self.cos_falloff_start.acos()).cos();
|
||||
let cos_theta_e = (self.cos_fallof_end.acos() - self.cos_fallof_start.acos()).cos();
|
||||
Some(LightBounds::new(
|
||||
&Bounds3f::from_points(p, p),
|
||||
w,
|
||||
phi,
|
||||
self.cos_falloff_start,
|
||||
self.cos_fallof_start,
|
||||
cos_theta_e,
|
||||
false,
|
||||
))
|
||||
|
|
|
|||
|
|
@ -1,335 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
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!()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
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::*;
|
||||
|
|
@ -1,132 +1,58 @@
|
|||
use crate::core::geometry::{
|
||||
Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Tuple, Vector3f,
|
||||
VectorLike, spherical_quad_area,
|
||||
use super::{
|
||||
BilinearIntersection, BilinearPatchShape, Bounds3f, DirectionCone, Interaction, Normal3f,
|
||||
Point2f, Point3f, Point3fi, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext,
|
||||
ShapeTrait, SurfaceInteraction, Transform, Vector3f,
|
||||
};
|
||||
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
||||
use crate::core::geometry::{Tuple, VectorLike, spherical_quad_area};
|
||||
use crate::core::interaction::InteractionTrait;
|
||||
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::mesh::DeviceBilinearPatchMesh;
|
||||
use crate::utils::mesh::BilinearPatchMesh;
|
||||
use crate::utils::sampling::{
|
||||
bilinear_pdf, invert_spherical_rectangle_sample, sample_bilinear, sample_spherical_rectangle,
|
||||
};
|
||||
use core::ops::Add;
|
||||
use std::sync::Arc;
|
||||
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 {
|
||||
pub t: Float,
|
||||
pub u: Float,
|
||||
pub v: Float,
|
||||
t: Float,
|
||||
u: Float,
|
||||
v: Float,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
struct TextureDerivative {
|
||||
pub duds: Float,
|
||||
pub dvds: Float,
|
||||
pub dudt: 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,
|
||||
duds: Float,
|
||||
dvds: Float,
|
||||
dudt: Float,
|
||||
dvdt: Float,
|
||||
}
|
||||
|
||||
static BILINEAR_MESHES: OnceLock<Vec<Arc<BilinearPatchMesh>>> = OnceLock::new();
|
||||
impl BilinearPatchShape {
|
||||
pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 1e-4;
|
||||
fn mesh(&self) -> Ptr<DeviceBilinearPatchMesh> {
|
||||
self.mesh
|
||||
}
|
||||
|
||||
#[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 {
|
||||
pub fn new(_mesh: BilinearPatchMesh, mesh_index: usize, blp_index: usize) -> Self {
|
||||
let mut bp = BilinearPatchShape {
|
||||
mesh,
|
||||
mesh_index,
|
||||
blp_index,
|
||||
area: 0.,
|
||||
rectangle: false,
|
||||
};
|
||||
|
||||
let [p00, p10, p01, p11] = bp.get_points();
|
||||
let (p00, p10, p01, p11) = {
|
||||
let data = bp.get_data();
|
||||
(data.p00, data.p10, data.p01, data.p11)
|
||||
};
|
||||
|
||||
bp.rectangle = bp.is_rectangle(p00, p10, p01, p11);
|
||||
|
||||
|
|
@ -158,6 +84,40 @@ impl BilinearPatchShape {
|
|||
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 {
|
||||
if p00 == p01 || p01 == p11 || p11 == p10 || p10 == p00 {
|
||||
return false;
|
||||
|
|
@ -189,26 +149,25 @@ impl BilinearPatchShape {
|
|||
&self,
|
||||
ray: &Ray,
|
||||
t_max: Float,
|
||||
corners: &[Point3f; 4],
|
||||
data: &PatchData,
|
||||
) -> Option<BilinearIntersection> {
|
||||
let &[p00, p01, p10, p11] = corners;
|
||||
let a = (p10 - p00).cross(p01 - p11).dot(ray.d);
|
||||
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 a = (data.p10 - data.p00).cross(data.p01 - data.p11).dot(ray.d);
|
||||
let c = (data.p00 - ray.o).cross(ray.d).dot(data.p01 - data.p00);
|
||||
let b = (data.p10 - ray.o).cross(ray.d).dot(data.p11 - data.p10) - (a + c);
|
||||
|
||||
let (u1, u2) = quadratic(a, b, c)?;
|
||||
|
||||
let eps = gamma(10)
|
||||
* (ray.o.abs().max_component_value()
|
||||
+ ray.d.abs().max_component_value()
|
||||
+ p00.abs().max_component_value()
|
||||
+ p10.abs().max_component_value()
|
||||
+ p01.abs().max_component_value()
|
||||
+ p11.abs().max_component_value());
|
||||
+ data.p00.abs().max_component_value()
|
||||
+ data.p10.abs().max_component_value()
|
||||
+ data.p01.abs().max_component_value()
|
||||
+ data.p11.abs().max_component_value());
|
||||
|
||||
let hit1 = self.check_candidate(u1, ray, corners);
|
||||
let hit1 = self.check_candidate(u1, ray, data);
|
||||
let hit2 = if u1 != u2 {
|
||||
self.check_candidate(u2, ray, corners)
|
||||
self.check_candidate(u2, ray, data)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
@ -224,18 +183,12 @@ impl BilinearPatchShape {
|
|||
})
|
||||
}
|
||||
|
||||
fn check_candidate(
|
||||
&self,
|
||||
u: Float,
|
||||
ray: &Ray,
|
||||
corners: &[Point3f; 4],
|
||||
) -> Option<IntersectionData> {
|
||||
fn check_candidate(&self, u: Float, ray: &Ray, data: &PatchData) -> Option<IntersectionData> {
|
||||
if !(0.0..=1.0).contains(&u) {
|
||||
return None;
|
||||
}
|
||||
let &[p00, p01, p10, p11] = corners;
|
||||
let uo: Point3f = lerp(u, p00, p10);
|
||||
let ud: Point3f = Point3f::from(lerp(u, p01, p11) - uo);
|
||||
let uo: Point3f = lerp(u, data.p00, data.p10);
|
||||
let ud: Point3f = Point3f::from(lerp(u, data.p01, data.p11) - uo);
|
||||
let deltao = uo - ray.o;
|
||||
let perp = ray.d.cross(ud.into());
|
||||
let p2 = perp.norm_squared();
|
||||
|
|
@ -269,25 +222,26 @@ impl BilinearPatchShape {
|
|||
|
||||
fn interaction_from_intersection(
|
||||
&self,
|
||||
data: &PatchData,
|
||||
uv: Point2f,
|
||||
time: Float,
|
||||
wo: Vector3f,
|
||||
) -> SurfaceInteraction {
|
||||
// Base geom and derivatives
|
||||
let corners = self.get_points();
|
||||
let [p00, p01, p10, p11] = corners;
|
||||
let p = lerp(uv[0], lerp(uv[1], p00, p01), lerp(uv[1], p10, 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 p = lerp(
|
||||
uv[0],
|
||||
lerp(uv[1], data.p00, data.p01),
|
||||
lerp(uv[1], data.p10, data.p11),
|
||||
);
|
||||
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
|
||||
let patch_uvs = self.get_uvs();
|
||||
let (st, derivatives) =
|
||||
self.apply_texture_coordinates(uv, patch_uvs.try_into().unwrap(), &mut dpdu, &mut dpdv);
|
||||
let (st, derivatives) = self.apply_texture_coordinates(data, uv, &mut dpdu, &mut dpdv);
|
||||
|
||||
// Compute second moments
|
||||
let n = Normal3f::from(dpdu.cross(dpdv).normalize());
|
||||
let (mut dndu, mut dndv) = self.calculate_surface_curvature(&corners, &dpdu, &dpdv, n);
|
||||
let (mut dndu, mut dndv) = self.calculate_surface_curvature(data, &dpdu, &dpdv, n);
|
||||
if let Some(ref deriv) = derivatives {
|
||||
let dnds = Normal3f::from(dndu * deriv.duds + dndv * deriv.dvds);
|
||||
let dndt = Normal3f::from(dndu * deriv.dudt + dndv * deriv.dvdt);
|
||||
|
|
@ -295,31 +249,31 @@ impl BilinearPatchShape {
|
|||
dndv = dndt;
|
||||
}
|
||||
|
||||
let p_abs_sum = p00.abs()
|
||||
+ Vector3f::from(p01.abs())
|
||||
+ Vector3f::from(p10.abs())
|
||||
+ Vector3f::from(p11.abs());
|
||||
let p_abs_sum = data.p00.abs()
|
||||
+ Vector3f::from(data.p01.abs())
|
||||
+ Vector3f::from(data.p10.abs())
|
||||
+ Vector3f::from(data.p11.abs());
|
||||
let p_error = gamma(6) * Vector3f::from(p_abs_sum);
|
||||
|
||||
let flip_normal = self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness;
|
||||
let flip_normal = data.mesh.reverse_orientation ^ data.mesh.transform_swaps_handedness;
|
||||
let pi = Point3fi::new_with_error(p, p_error);
|
||||
let mut isect =
|
||||
SurfaceInteraction::new(pi, st, wo, dpdu, dpdv, dndu, dndv, time, flip_normal);
|
||||
|
||||
self.apply_shading_normals(&mut isect, self.get_shading_normals(), uv, derivatives);
|
||||
if data.n.is_some() {
|
||||
self.apply_shading_normals(&mut isect, data, uv, derivatives);
|
||||
}
|
||||
isect
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn apply_texture_coordinates(
|
||||
&self,
|
||||
data: &PatchData,
|
||||
uv: Point2f,
|
||||
patch_uvs: Option<[Point2f; 4]>,
|
||||
dpdu: &mut Vector3f,
|
||||
dpdv: &mut Vector3f,
|
||||
) -> (Point2f, Option<TextureDerivative>) {
|
||||
let Some(uvs) = patch_uvs else {
|
||||
return (uv, Some(TextureDerivative::default()));
|
||||
let Some(uvs) = data.uv else {
|
||||
return (uv, None);
|
||||
};
|
||||
let uv00 = uvs[0];
|
||||
let uv01 = uvs[1];
|
||||
|
|
@ -343,7 +297,6 @@ impl BilinearPatchShape {
|
|||
if dpdu.cross(*dpdv).dot(dpds.cross(dpdt)) < 0. {
|
||||
dpdt = -dpdt;
|
||||
}
|
||||
|
||||
*dpdu = dpds;
|
||||
*dpdv = dpdt;
|
||||
|
||||
|
|
@ -356,33 +309,32 @@ impl BilinearPatchShape {
|
|||
(st, Some(factors))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn calculate_base_derivatives(
|
||||
&self,
|
||||
corners: &[Point3f; 4],
|
||||
data: &PatchData,
|
||||
uv: Point2f,
|
||||
) -> (Point3f, Vector3f, Vector3f) {
|
||||
let (p00, p10, p01, p11) = (corners[0], corners[1], corners[2], corners[3]);
|
||||
|
||||
let p = lerp(uv[0], lerp(uv[1], p00, p01), lerp(uv[1], p10, 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 p = lerp(
|
||||
uv[0],
|
||||
lerp(uv[1], data.p00, data.p01),
|
||||
lerp(uv[1], data.p10, data.p11),
|
||||
);
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn calculate_surface_curvature(
|
||||
&self,
|
||||
corners: &[Point3f; 4],
|
||||
data: &PatchData,
|
||||
dpdu: &Vector3f,
|
||||
dpdv: &Vector3f,
|
||||
n: Normal3f,
|
||||
) -> (Normal3f, Normal3f) {
|
||||
let (p00, p10, p01, p11) = (corners[0], corners[1], corners[2], corners[3]);
|
||||
let e = dpdu.dot(*dpdu);
|
||||
let f = dpdu.dot(*dpdv);
|
||||
let g = dpdv.dot(*dpdv);
|
||||
let d2pduv = (p00 - p01) + (p11 - p10);
|
||||
let d2pduv = (data.p00 - data.p01) + (data.p11 - data.p10);
|
||||
let d2pduu = Vector3f::zero();
|
||||
let d2pdvv = Vector3f::zero();
|
||||
|
||||
|
|
@ -405,13 +357,11 @@ impl BilinearPatchShape {
|
|||
fn apply_shading_normals(
|
||||
&self,
|
||||
isect: &mut SurfaceInteraction,
|
||||
shading_normals: Option<[Normal3f; 4]>,
|
||||
data: &PatchData,
|
||||
uv: Point2f,
|
||||
derivatives: Option<TextureDerivative>,
|
||||
) {
|
||||
let Some(normals) = shading_normals else {
|
||||
return;
|
||||
};
|
||||
let Some(normals) = data.n else { return };
|
||||
let n00 = normals[1];
|
||||
let n10 = normals[1];
|
||||
let n01 = normals[2];
|
||||
|
|
@ -440,7 +390,10 @@ impl BilinearPatchShape {
|
|||
|
||||
fn sample_area_and_pdf(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
|
||||
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 dist_sq = wi.norm_squared();
|
||||
|
|
@ -459,18 +412,16 @@ impl BilinearPatchShape {
|
|||
if ss.pdf.is_infinite() { None } else { Some(ss) }
|
||||
}
|
||||
|
||||
fn sample_parametric_coords(&self, corners: &[Point3f; 4], u: Point2f) -> (Point2f, Float) {
|
||||
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;
|
||||
fn sample_parametric_coords(&self, data: &PatchData, u: Point2f) -> (Point2f, Float) {
|
||||
if let Some(image_distrib) = &data.mesh.image_distribution {
|
||||
let (uv, pdf, _) = image_distrib.sample(u);
|
||||
(uv, pdf)
|
||||
} else if !self.rectangle {
|
||||
let w = [
|
||||
(p10 - p00).cross(p01 - p00).norm(),
|
||||
(p10 - p00).cross(p11 - p10).norm(),
|
||||
(p01 - p00).cross(p11 - p01).norm(),
|
||||
(p11 - p10).cross(p11 - p01).norm(),
|
||||
(data.p10 - data.p00).cross(data.p01 - data.p00).norm(),
|
||||
(data.p10 - data.p00).cross(data.p11 - data.p10).norm(),
|
||||
(data.p01 - data.p00).cross(data.p11 - data.p01).norm(),
|
||||
(data.p11 - data.p10).cross(data.p11 - data.p01).norm(),
|
||||
];
|
||||
let uv = sample_bilinear(u, &w);
|
||||
let pdf = bilinear_pdf(uv, &w);
|
||||
|
|
@ -482,12 +433,11 @@ impl BilinearPatchShape {
|
|||
|
||||
fn sample_solid_angle(
|
||||
&self,
|
||||
corners: &[Point3f; 4],
|
||||
data: &PatchData,
|
||||
ctx: &ShapeSampleContext,
|
||||
u: Point2f,
|
||||
corner_dirs: &[Vector3f; 4],
|
||||
) -> Option<ShapeSample> {
|
||||
let (p00, p10, p01, _p11) = (corners[0], corners[1], corners[2], corners[3]);
|
||||
let mut pdf = 1.;
|
||||
if ctx.ns != Normal3f::zero() {
|
||||
let w = [
|
||||
|
|
@ -500,21 +450,19 @@ impl BilinearPatchShape {
|
|||
pdf *= bilinear_pdf(u, &w);
|
||||
}
|
||||
|
||||
let eu = p10 - p00;
|
||||
let ev = p01 - p00;
|
||||
let (p, quad_pdf) = sample_spherical_rectangle(ctx.p(), p00, eu, ev, u);
|
||||
let eu = data.p10 - data.p00;
|
||||
let ev = data.p01 - data.p00;
|
||||
let (p, quad_pdf) = sample_spherical_rectangle(ctx.p(), data.p00, eu, ev, u);
|
||||
pdf *= quad_pdf?;
|
||||
|
||||
// Compute (u, v) and surface normal for sampled points on rectangle
|
||||
let uv = Point2f::new(
|
||||
(p - p00).dot(eu) / p10.distance_squared(p00),
|
||||
(p - p00).dot(ev) / p01.distance_squared(p00),
|
||||
(p - data.p00).dot(eu) / data.p10.distance_squared(data.p00),
|
||||
(p - data.p00).dot(ev) / data.p01.distance_squared(data.p00),
|
||||
);
|
||||
|
||||
let patch_uvs = self.get_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| {
|
||||
let n = self.compute_sampled_normal(data, &eu, &ev, uv);
|
||||
let st = data.uv.map_or(uv, |uvs| {
|
||||
lerp(
|
||||
uv[0],
|
||||
lerp(uv[1], uvs[0], uvs[1]),
|
||||
|
|
@ -526,29 +474,29 @@ impl BilinearPatchShape {
|
|||
let mut intr = SurfaceInteraction::new_simple(pi, n, st);
|
||||
intr.common.time = ctx.time;
|
||||
Some(ShapeSample {
|
||||
intr: Interaction::Surface(intr),
|
||||
intr: Arc::new(intr),
|
||||
pdf,
|
||||
})
|
||||
}
|
||||
|
||||
fn compute_sampled_normal(
|
||||
&self,
|
||||
patch_normals: Option<[Normal3f; 4]>,
|
||||
data: &PatchData,
|
||||
dpdu: &Vector3f,
|
||||
dpdv: &Vector3f,
|
||||
uv: Point2f,
|
||||
) -> Normal3f {
|
||||
let mut n = Normal3f::from(dpdu.cross(*dpdv).normalize());
|
||||
|
||||
if let Some(normals) = patch_normals {
|
||||
if let Some(normals) = data.n {
|
||||
// Apply interpolated shading normal to orient the geometric normal
|
||||
let ns = lerp(
|
||||
uv[0],
|
||||
lerp(uv[1], normals[0], normals[2]),
|
||||
lerp(uv[1], normals[1], normals[3]),
|
||||
);
|
||||
n = n.face_forward(ns);
|
||||
} else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness {
|
||||
n = n.face_forward(ns.into());
|
||||
} else if data.mesh.reverse_orientation ^ data.mesh.transform_swaps_handedness {
|
||||
n = -n;
|
||||
}
|
||||
n
|
||||
|
|
@ -563,33 +511,36 @@ impl ShapeTrait for BilinearPatchShape {
|
|||
|
||||
#[inline]
|
||||
fn normal_bounds(&self) -> DirectionCone {
|
||||
let [p00, p01, p10, p11] = self.get_points();
|
||||
let normals = self.get_shading_normals();
|
||||
if p00 == p10 || p10 == p11 || p11 == p01 || p01 == p00 {
|
||||
let dpdu = lerp(0.5, p10, p11) - lerp(0.5, p00, p01);
|
||||
let dpdv = lerp(0.5, p01, p11) - lerp(0.5, p00, p10);
|
||||
let data = self.get_data();
|
||||
if data.p00 == data.p10
|
||||
|| data.p10 == data.p11
|
||||
|| data.p11 == data.p01
|
||||
|| data.p01 == data.p00
|
||||
{
|
||||
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());
|
||||
if let Some(normals) = normals {
|
||||
if let Some(normals) = data.n {
|
||||
let interp_n = (normals[0] + normals[1] + normals[2] + normals[3]) / 4.;
|
||||
n = n.face_forward(interp_n);
|
||||
} else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness {
|
||||
n = n.face_forward(interp_n.into());
|
||||
} else if data.mesh.reverse_orientation ^ data.mesh.transform_swaps_handedness {
|
||||
n *= -1.;
|
||||
}
|
||||
return DirectionCone::new_from_vector(Vector3f::from(n));
|
||||
}
|
||||
|
||||
// Compute bilinear patch normals n10, n01, and n11
|
||||
let mut n00 = Normal3f::from((p10 - p00).cross(p01 - p00).normalize());
|
||||
let mut n10 = Normal3f::from((p11 - p10).cross(p00 - p10).normalize());
|
||||
let mut n01 = Normal3f::from((p00 - p01).cross(p11 - p01).normalize());
|
||||
let mut n11 = Normal3f::from((p01 - p11).cross(p10 - p11).normalize());
|
||||
let mut n00 = Normal3f::from((data.p10 - data.p00).cross(data.p01 - data.p00).normalize());
|
||||
let mut n10 = Normal3f::from((data.p11 - data.p10).cross(data.p00 - data.p10).normalize());
|
||||
let mut n01 = Normal3f::from((data.p00 - data.p01).cross(data.p11 - data.p01).normalize());
|
||||
let mut n11 = Normal3f::from((data.p01 - data.p11).cross(data.p10 - data.p11).normalize());
|
||||
|
||||
if let Some(normals) = normals {
|
||||
n00 = n00.face_forward(normals[0]);
|
||||
n10 = n10.face_forward(normals[1]);
|
||||
n01 = n01.face_forward(normals[2]);
|
||||
n11 = n11.face_forward(normals[3]);
|
||||
} else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness {
|
||||
if let Some(normals) = data.n {
|
||||
n00 = n00.face_forward(normals[0].into());
|
||||
n10 = n10.face_forward(normals[1].into());
|
||||
n01 = n01.face_forward(normals[2].into());
|
||||
n11 = n11.face_forward(normals[3].into());
|
||||
} else if data.mesh.reverse_orientation ^ data.mesh.transform_swaps_handedness {
|
||||
n00 = -n00;
|
||||
n10 = -n10;
|
||||
n01 = -n01;
|
||||
|
|
@ -608,17 +559,16 @@ impl ShapeTrait for BilinearPatchShape {
|
|||
|
||||
#[inline]
|
||||
fn bounds(&self) -> Bounds3f {
|
||||
let [p00, p01, p10, p11] = self.get_points();
|
||||
Bounds3f::from_points(p00, p01).union(Bounds3f::from_points(p10, p11))
|
||||
let data = self.get_data();
|
||||
Bounds3f::from_points(data.p00, data.p01).union(Bounds3f::from_points(data.p10, data.p11))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
|
||||
let t_max_val = t_max?;
|
||||
if let Some(bilinear_hit) =
|
||||
self.intersect_bilinear_patch(ray, t_max_val, &self.get_points())
|
||||
{
|
||||
let intr = self.interaction_from_intersection(bilinear_hit.uv, ray.time, -ray.d);
|
||||
let data = self.get_data();
|
||||
if let Some(bilinear_hit) = self.intersect_bilinear_patch(ray, t_max_val, &data) {
|
||||
let intr = self.interaction_from_intersection(&data, bilinear_hit.uv, ray.time, -ray.d);
|
||||
|
||||
Some(ShapeIntersection {
|
||||
intr,
|
||||
|
|
@ -632,28 +582,25 @@ impl ShapeTrait for BilinearPatchShape {
|
|||
#[inline]
|
||||
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
|
||||
let t_max_val = t_max.unwrap_or(Float::INFINITY);
|
||||
let corners = self.get_points();
|
||||
self.intersect_bilinear_patch(ray, t_max_val, &corners)
|
||||
let data = self.get_data();
|
||||
self.intersect_bilinear_patch(ray, t_max_val, &data)
|
||||
.is_some()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample(&self, u: Point2f) -> Option<ShapeSample> {
|
||||
let corners = self.get_points();
|
||||
let [p00, p01, p10, p11] = corners;
|
||||
let data = self.get_data();
|
||||
// Sample bilinear patch parametric coordinate (u, v)
|
||||
let (uv, pdf) = self.sample_parametric_coords(&corners, u);
|
||||
let (uv, pdf) = self.sample_parametric_coords(&data, u);
|
||||
// Compute bilinear patch geometric quantities at sampled (u, v)
|
||||
let (p, dpdu, dpdv) = self.calculate_base_derivatives(&corners, uv);
|
||||
let (p, dpdu, dpdv) = self.calculate_base_derivatives(&data, uv);
|
||||
if dpdu.norm_squared() == 0. || dpdv.norm_squared() == 0. {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Compute surface normal for sampled bilinear patch (u, v)
|
||||
let patch_normals = self.get_shading_normals();
|
||||
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| {
|
||||
let n = self.compute_sampled_normal(&data, &dpdu, &dpdv, uv);
|
||||
let st = data.uv.map_or(uv, |patch_uvs| {
|
||||
lerp(
|
||||
uv[0],
|
||||
lerp(uv[1], patch_uvs[0], patch_uvs[1]),
|
||||
|
|
@ -661,34 +608,33 @@ impl ShapeTrait for BilinearPatchShape {
|
|||
)
|
||||
});
|
||||
|
||||
let p_abs_sum = p00.abs()
|
||||
+ Vector3f::from(p01.abs())
|
||||
+ Vector3f::from(p10.abs())
|
||||
+ Vector3f::from(p11.abs());
|
||||
let p_abs_sum = data.p00.abs()
|
||||
+ Vector3f::from(data.p01.abs())
|
||||
+ Vector3f::from(data.p10.abs())
|
||||
+ Vector3f::from(data.p11.abs());
|
||||
let p_error = gamma(6) * Vector3f::from(p_abs_sum);
|
||||
let pi = Point3fi::new_with_error(p, p_error);
|
||||
Some(ShapeSample {
|
||||
intr: Interaction::Surface(SurfaceInteraction::new_simple(pi, n, st)),
|
||||
intr: Arc::new(SurfaceInteraction::new_simple(pi, n, st)),
|
||||
pdf: pdf / dpdu.cross(dpdv).norm(),
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
|
||||
let corners = self.get_points();
|
||||
let [p00, p01, p10, p11] = corners;
|
||||
let v00 = (p00 - ctx.p()).normalize();
|
||||
let v10 = (p10 - ctx.p()).normalize();
|
||||
let v01 = (p01 - ctx.p()).normalize();
|
||||
let v11 = (p11 - ctx.p()).normalize();
|
||||
let data = self.get_data();
|
||||
let v00 = (data.p00 - ctx.p()).normalize();
|
||||
let v10 = (data.p10 - ctx.p()).normalize();
|
||||
let v01 = (data.p01 - ctx.p()).normalize();
|
||||
let v11 = (data.p11 - ctx.p()).normalize();
|
||||
|
||||
let use_area_sampling = self.rectangle
|
||||
|| !self.mesh.image_distribution.is_null()
|
||||
|| data.mesh.image_distribution.is_some()
|
||||
|| spherical_quad_area(v00, v10, v11, v01) <= Self::MIN_SPHERICAL_SAMPLE_AREA;
|
||||
if use_area_sampling {
|
||||
self.sample_area_and_pdf(ctx, u)
|
||||
} else {
|
||||
self.sample_solid_angle(&corners, ctx, u, &[v00, v10, v01, v11])
|
||||
self.sample_solid_angle(&data, ctx, u, &[v00, v10, v01, v11])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -698,30 +644,28 @@ impl ShapeTrait for BilinearPatchShape {
|
|||
return 0.0;
|
||||
};
|
||||
|
||||
let corners = self.get_points();
|
||||
let [p00, p01, p10, p11] = corners;
|
||||
let patch_uvs = self.get_uvs();
|
||||
let uv = if let Some(uvs) = patch_uvs {
|
||||
Point2f::invert_bilinear(si.common.uv, &uvs)
|
||||
let data = self.get_data();
|
||||
let uv = if let Some(uvs) = &data.mesh.uv {
|
||||
Point2f::invert_bilinear(si.uv, uvs)
|
||||
} else {
|
||||
si.common.uv
|
||||
si.uv
|
||||
};
|
||||
|
||||
let param_pdf = if !self.mesh.image_distribution.is_null() {
|
||||
self.mesh.image_distribution.pdf(uv)
|
||||
let param_pdf = if let Some(image_distrib) = &data.mesh.image_distribution {
|
||||
image_distrib.pdf(uv)
|
||||
} else if self.rectangle {
|
||||
let w = [
|
||||
(p10 - p00).cross(p01 - p00).norm(),
|
||||
(p10 - p00).cross(p11 - p10).norm(),
|
||||
(p01 - p00).cross(p11 - p01).norm(),
|
||||
(p11 - p10).cross(p11 - p01).norm(),
|
||||
(data.p10 - data.p00).cross(data.p01 - data.p00).norm(),
|
||||
(data.p10 - data.p00).cross(data.p11 - data.p10).norm(),
|
||||
(data.p01 - data.p00).cross(data.p11 - data.p01).norm(),
|
||||
(data.p11 - data.p10).cross(data.p11 - data.p01).norm(),
|
||||
];
|
||||
bilinear_pdf(uv, &w)
|
||||
} else {
|
||||
1.
|
||||
};
|
||||
|
||||
let (_, dpdu, dpdv) = self.calculate_base_derivatives(&corners, uv);
|
||||
let (_, dpdu, dpdv) = self.calculate_base_derivatives(&data, uv);
|
||||
let cross = dpdu.cross(dpdv).norm();
|
||||
if cross == 0. { 0. } else { param_pdf / cross }
|
||||
}
|
||||
|
|
@ -733,16 +677,15 @@ impl ShapeTrait for BilinearPatchShape {
|
|||
return 0.;
|
||||
};
|
||||
|
||||
let corners = self.get_points();
|
||||
let [p00, p01, p10, p11] = corners;
|
||||
let data = self.get_data();
|
||||
|
||||
let v00 = (p00 - ctx.p()).normalize();
|
||||
let v10 = (p10 - ctx.p()).normalize();
|
||||
let v01 = (p01 - ctx.p()).normalize();
|
||||
let v11 = (p11 - ctx.p()).normalize();
|
||||
let v00 = (data.p00 - ctx.p()).normalize();
|
||||
let v10 = (data.p10 - ctx.p()).normalize();
|
||||
let v01 = (data.p01 - ctx.p()).normalize();
|
||||
let v11 = (data.p11 - ctx.p()).normalize();
|
||||
|
||||
let use_area_sampling = !self.rectangle
|
||||
|| !self.mesh.image_distribution.is_null()
|
||||
|| data.mesh.image_distribution.is_some()
|
||||
|| spherical_quad_area(v00, v10, v01, v11) <= Self::MIN_SPHERICAL_SAMPLE_AREA;
|
||||
|
||||
if use_area_sampling {
|
||||
|
|
@ -766,9 +709,9 @@ impl ShapeTrait for BilinearPatchShape {
|
|||
];
|
||||
let u = invert_spherical_rectangle_sample(
|
||||
ctx.p(),
|
||||
p00,
|
||||
p10 - p00,
|
||||
p01 - p00,
|
||||
data.p00,
|
||||
data.p10 - data.p00,
|
||||
data.p01 - data.p00,
|
||||
isect.intr.p(),
|
||||
);
|
||||
pdf *= bilinear_pdf(u, &w);
|
||||
|
|
|
|||
|
|
@ -1,95 +1,21 @@
|
|||
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::core::interaction::InteractionTrait;
|
||||
use crate::utils::math::{clamp, lerp, square};
|
||||
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::utils::transform::look_at;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum CurveType {
|
||||
Flat,
|
||||
Cylinder,
|
||||
Ribbon,
|
||||
}
|
||||
use super::{
|
||||
Bounds3f, CurveCommon, CurveShape, CurveType, DirectionCone, Float, Interaction, Normal3f,
|
||||
Point2f, Point3f, Point3fi, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext,
|
||||
ShapeTrait, SurfaceInteraction, Transform, Vector2f, Vector3f, VectorLike,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[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 {
|
||||
pub ray: Ray,
|
||||
pub object_from_ray: Transform,
|
||||
pub common: CurveCommon,
|
||||
ray: Ray,
|
||||
object_from_ray: Arc<Transform>,
|
||||
common: CurveCommon,
|
||||
}
|
||||
|
||||
impl CurveShape {
|
||||
|
|
@ -106,6 +32,7 @@ impl CurveShape {
|
|||
.common
|
||||
.object_from_render
|
||||
.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);
|
||||
// Project curve control points to plane perpendicular to ray
|
||||
let mut dx = ray.d.cross(cp_obj[3] - cp_obj[0]);
|
||||
|
|
@ -116,6 +43,7 @@ impl CurveShape {
|
|||
let ray_from_object = look_at(ray.o, ray.o + ray.d, dx).expect("Inversion error");
|
||||
let cp = [0; 4].map(|i| ray_from_object.apply_to_point(cp_obj[i]));
|
||||
|
||||
// Test ray against bound of projected control points
|
||||
let max_width = lerp(self.u_min, self.common.width[0], self.common.width[1]).max(lerp(
|
||||
self.u_max,
|
||||
self.common.width[0],
|
||||
|
|
@ -149,7 +77,7 @@ impl CurveShape {
|
|||
|
||||
let context = IntersectionContext {
|
||||
ray,
|
||||
object_from_ray: ray_from_object.inverse(),
|
||||
object_from_ray: Arc::new(ray_from_object.inverse()),
|
||||
common: self.common.clone(),
|
||||
};
|
||||
|
||||
|
|
@ -372,18 +300,18 @@ impl ShapeTrait for CurveShape {
|
|||
}
|
||||
|
||||
fn pdf(&self, _interaction: &Interaction) -> Float {
|
||||
unimplemented!()
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn pdf_from_context(&self, _ctx: &ShapeSampleContext, _wi: Vector3f) -> Float {
|
||||
unimplemented!()
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn sample(&self, _u: Point2f) -> Option<ShapeSample> {
|
||||
unimplemented!()
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn sample_from_context(&self, _ctx: &ShapeSampleContext, _u: Point2f) -> Option<ShapeSample> {
|
||||
unimplemented!()
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +1,20 @@
|
|||
use crate::core::geometry::{
|
||||
Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
|
||||
Vector3fi, VectorLike,
|
||||
use super::{
|
||||
Bounds3f, CylinderShape, DirectionCone, Float, Interaction, Normal3f, PI, Point2f, Point3f,
|
||||
Point3fi, QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext,
|
||||
ShapeTrait, SurfaceInteraction, Transform, Vector3f, Vector3fi,
|
||||
};
|
||||
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
||||
use crate::core::shape::{
|
||||
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::core::geometry::{Sqrt, Tuple, VectorLike};
|
||||
use crate::core::interaction::InteractionTrait;
|
||||
use crate::core::pbrt::gamma;
|
||||
use crate::utils::interval::Interval;
|
||||
use crate::utils::math::{clamp, difference_of_products, lerp, square};
|
||||
use crate::utils::math::{difference_of_products, lerp, square};
|
||||
use std::mem;
|
||||
|
||||
#[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,
|
||||
}
|
||||
use std::sync::Arc;
|
||||
|
||||
impl CylinderShape {
|
||||
pub fn new(
|
||||
render_from_object: Transform,
|
||||
object_from_render: Transform,
|
||||
render_from_object: Arc<Transform>,
|
||||
object_from_render: Arc<Transform>,
|
||||
reverse_orientation: bool,
|
||||
radius: Float,
|
||||
z_min: Float,
|
||||
|
|
@ -45,7 +26,7 @@ impl CylinderShape {
|
|||
z_min,
|
||||
z_max,
|
||||
phi_max,
|
||||
render_from_object,
|
||||
render_from_object: render_from_object.clone(),
|
||||
object_from_render,
|
||||
reverse_orientation,
|
||||
transform_swap_handedness: render_from_object.swaps_handedness(),
|
||||
|
|
@ -266,14 +247,14 @@ impl ShapeTrait for CylinderShape {
|
|||
(p_obj.z() - self.z_min) / (self.z_max - self.z_min),
|
||||
);
|
||||
Some(ShapeSample {
|
||||
intr: Interaction::Surface(SurfaceInteraction::new_simple(pi, n, uv)),
|
||||
intr: Arc::new(SurfaceInteraction::new_simple(pi, n, uv)),
|
||||
pdf: 1. / self.area(),
|
||||
})
|
||||
}
|
||||
|
||||
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
|
||||
let mut ss = self.sample(u)?;
|
||||
let intr = &mut ss.intr;
|
||||
let intr = Arc::make_mut(&mut ss.intr);
|
||||
intr.get_common_mut().time = ctx.time;
|
||||
let mut wi = ss.intr.p() - ctx.p();
|
||||
if wi.norm_squared() == 0. {
|
||||
|
|
|
|||
|
|
@ -1,38 +1,22 @@
|
|||
use crate::core::geometry::{
|
||||
Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
|
||||
Vector3fi, VectorLike,
|
||||
use super::{
|
||||
Bounds3f, DirectionCone, DiskShape, Float, Interaction, Normal3f, PI, Point2f, Point3f,
|
||||
Point3fi, QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext,
|
||||
ShapeTrait, SurfaceInteraction, Transform, Vector3f,
|
||||
};
|
||||
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
||||
use crate::core::shape::{
|
||||
QuadricIntersection, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait,
|
||||
};
|
||||
use crate::utils::Transform;
|
||||
use crate::core::geometry::VectorLike;
|
||||
use crate::core::interaction::InteractionTrait;
|
||||
use crate::utils::math::square;
|
||||
use crate::utils::sampling::sample_uniform_disk_concentric;
|
||||
use crate::{Float, PI};
|
||||
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 {
|
||||
pub fn new(
|
||||
radius: Float,
|
||||
inner_radius: Float,
|
||||
height: Float,
|
||||
phi_max: Float,
|
||||
render_from_object: Transform,
|
||||
object_from_render: Transform,
|
||||
render_from_object: Arc<Transform>,
|
||||
object_from_render: Arc<Transform>,
|
||||
reverse_orientation: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
|
|
@ -173,9 +157,8 @@ impl ShapeTrait for DiskShape {
|
|||
phi / self.phi_max,
|
||||
(self.radius - radius_sample) / (self.radius - self.inner_radius),
|
||||
);
|
||||
|
||||
Some(ShapeSample {
|
||||
intr: Interaction::Surface(SurfaceInteraction::new_simple(pi, n, uv)),
|
||||
intr: Arc::new(SurfaceInteraction::new_simple(pi, n, uv)),
|
||||
pdf: 1. / self.area(),
|
||||
})
|
||||
}
|
||||
|
|
@ -190,18 +173,17 @@ impl ShapeTrait for DiskShape {
|
|||
|
||||
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
|
||||
let mut ss = self.sample(u)?;
|
||||
ss.intr.get_common_mut().time = ctx.time;
|
||||
let intr = Arc::make_mut(&mut ss.intr);
|
||||
intr.get_common_mut().time = ctx.time;
|
||||
let mut wi = ss.intr.p() - ctx.p();
|
||||
if wi.norm_squared() == 0. {
|
||||
return None;
|
||||
}
|
||||
wi = wi.normalize();
|
||||
|
||||
ss.pdf = Vector3f::from(ss.intr.n()).dot(-wi).abs() / ctx.p().distance_squared(ss.intr.p());
|
||||
if ss.pdf.is_infinite() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(ss)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,315 @@ pub mod disk;
|
|||
pub mod sphere;
|
||||
pub mod triangle;
|
||||
|
||||
pub use bilinear::*;
|
||||
pub use curves::*;
|
||||
pub use cylinder::*;
|
||||
pub use disk::*;
|
||||
pub use sphere::*;
|
||||
pub use triangle::*;
|
||||
use crate::core::geometry::{
|
||||
Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
|
||||
Vector3fi, VectorLike,
|
||||
};
|
||||
use crate::core::interaction::{
|
||||
Interaction, InteractionTrait, MediumInteraction, SurfaceInteraction,
|
||||
};
|
||||
use crate::core::material::Material;
|
||||
use crate::core::medium::{Medium, MediumInterface};
|
||||
use crate::core::pbrt::{Float, PI};
|
||||
use crate::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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,55 +1,22 @@
|
|||
use crate::core::geometry::{
|
||||
Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
|
||||
Vector3fi, VectorLike,
|
||||
use super::{
|
||||
Bounds3f, DirectionCone, Float, Interaction, Normal3f, PI, Point2f, Point3f, Point3fi,
|
||||
QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait,
|
||||
SphereShape, SurfaceInteraction, Transform, Vector3f, Vector3fi,
|
||||
};
|
||||
use crate::core::geometry::{Frame, Sqrt, spherical_direction};
|
||||
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
||||
use crate::core::geometry::{Frame, Sqrt, VectorLike, spherical_direction};
|
||||
use crate::core::interaction::InteractionTrait;
|
||||
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::math::{clamp, difference_of_products, radians, safe_acos, safe_sqrt, square};
|
||||
use crate::utils::sampling::sample_uniform_sphere;
|
||||
use crate::{Float, PI};
|
||||
|
||||
use std::mem;
|
||||
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 {
|
||||
pub fn new(
|
||||
render_from_object: Transform,
|
||||
object_from_render: Transform,
|
||||
render_from_object: Arc<Transform>,
|
||||
object_from_render: Arc<Transform>,
|
||||
reverse_orientation: bool,
|
||||
radius: Float,
|
||||
z_min: Float,
|
||||
|
|
@ -320,7 +287,7 @@ impl ShapeTrait for SphereShape {
|
|||
));
|
||||
let si = SurfaceInteraction::new_simple(pi, n, uv);
|
||||
Some(ShapeSample {
|
||||
intr: Interaction::Surface(si),
|
||||
intr: Arc::new(si),
|
||||
pdf: 1. / self.area(),
|
||||
})
|
||||
}
|
||||
|
|
@ -330,7 +297,8 @@ impl ShapeTrait for SphereShape {
|
|||
let p_origin = ctx.offset_ray_origin_from_point(p_center);
|
||||
if p_origin.distance_squared(p_center) <= square(self.radius) {
|
||||
let mut ss = self.sample(u)?;
|
||||
ss.intr.get_common_mut().time = ctx.time;
|
||||
let intr = Arc::make_mut(&mut ss.intr);
|
||||
intr.get_common_mut().time = ctx.time;
|
||||
let mut wi = ss.intr.p() - ctx.p();
|
||||
if wi.norm_squared() == 0. {
|
||||
return None;
|
||||
|
|
@ -381,9 +349,9 @@ impl ShapeTrait for SphereShape {
|
|||
(theta - self.theta_z_min) / (self.theta_z_max - self.theta_z_min),
|
||||
);
|
||||
let pi = Point3fi::new_with_error(p_obj, p_error);
|
||||
let intr = SurfaceInteraction::new_simple(pi, n, uv);
|
||||
let si = SurfaceInteraction::new_simple(pi, n, uv);
|
||||
Some(ShapeSample {
|
||||
intr: Interaction::Surface(intr),
|
||||
intr: Arc::new(si),
|
||||
pdf: 1. / (2. * PI * one_minus_cos_theta_max),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,127 +1,80 @@
|
|||
use crate::Float;
|
||||
use crate::core::geometry::{
|
||||
Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3,
|
||||
Vector3f,
|
||||
use super::{
|
||||
Bounds3f, DirectionCone, Float, Interaction, Normal3f, Point2f, Point3f, Point3fi, Ray,
|
||||
ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait, SurfaceInteraction,
|
||||
TriangleIntersection, TriangleShape, Vector2f, Vector3f,
|
||||
};
|
||||
use crate::core::geometry::{Sqrt, Tuple, VectorLike, spherical_triangle_area};
|
||||
use crate::core::interaction::{
|
||||
Interaction, InteractionBase, InteractionTrait, SimpleInteraction, SurfaceInteraction,
|
||||
};
|
||||
use crate::core::interaction::InteractionTrait;
|
||||
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::mesh::DeviceTriangleMesh;
|
||||
use crate::utils::mesh::TriangleMesh;
|
||||
use crate::utils::sampling::{
|
||||
bilinear_pdf, invert_spherical_triangle_sample, sample_bilinear, sample_spherical_triangle,
|
||||
sample_uniform_triangle,
|
||||
};
|
||||
use std::mem;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TriangleIntersection {
|
||||
b0: Float,
|
||||
b1: Float,
|
||||
b2: Float,
|
||||
t: Float,
|
||||
}
|
||||
pub static TRIANGLE_MESHES: OnceLock<Vec<Arc<TriangleMesh>>> = OnceLock::new();
|
||||
|
||||
impl TriangleIntersection {
|
||||
pub fn new(b0: Float, b1: Float, b2: Float, t: Float) -> Self {
|
||||
Self { b0, b1, b2, t }
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TriangleShape {
|
||||
pub mesh: Ptr<DeviceTriangleMesh>,
|
||||
pub tri_index: i32,
|
||||
#[derive(Clone, Copy)]
|
||||
struct TriangleData {
|
||||
vertices: [Point3f; 3],
|
||||
uvs: [Point2f; 3],
|
||||
normals: Option<[Normal3f; 3]>,
|
||||
area: Float,
|
||||
normal: Normal3f,
|
||||
reverse_orientation: bool,
|
||||
transform_swaps_handedness: bool,
|
||||
}
|
||||
|
||||
impl TriangleShape {
|
||||
pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 3e-4;
|
||||
pub const MAX_SPHERICAL_SAMPLE_AREA: Float = 6.22;
|
||||
|
||||
#[inline(always)]
|
||||
fn get_vertex_indices(&self) -> [usize; 3] {
|
||||
unsafe {
|
||||
let base_ptr = self.mesh.vertex_indices.add((self.tri_index as usize) * 3);
|
||||
fn mesh(&self) -> &Arc<TriangleMesh> {
|
||||
let meshes = TRIANGLE_MESHES
|
||||
.get()
|
||||
.expect("Mesh has not been initialized");
|
||||
&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(
|
||||
[
|
||||
*base_ptr.add(0) as usize,
|
||||
*base_ptr.add(1) as usize,
|
||||
*base_ptr.add(2) as usize,
|
||||
]
|
||||
Point2f::zero(),
|
||||
Point2f::new(1.0, 0.0),
|
||||
Point2f::new(1.0, 1.0),
|
||||
],
|
||||
|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,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_points(&self) -> [Point3f; 3] {
|
||||
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();
|
||||
fn solid_angle(&self, p: Point3f) -> Float {
|
||||
let data = self.get_data();
|
||||
let [p0, p1, p2] = data.vertices;
|
||||
spherical_triangle_area(
|
||||
(p0 - p).normalize(),
|
||||
(p1 - p).normalize(),
|
||||
|
|
@ -129,15 +82,100 @@ impl TriangleShape {
|
|||
)
|
||||
}
|
||||
|
||||
fn intersect_triangle(
|
||||
&self,
|
||||
_ray: &Ray,
|
||||
_t_max: Float,
|
||||
_p0: Point3f,
|
||||
_p1: Point3f,
|
||||
_p2: Point3f,
|
||||
) -> Option<TriangleIntersection> {
|
||||
todo!()
|
||||
fn intersect_triangle(&self, ray: &Ray, t_max: Float) -> Option<TriangleIntersection> {
|
||||
let data = self.get_data();
|
||||
let [p0, p1, p2] = data.vertices;
|
||||
if (p2 - p0).cross(p1 - p0).norm_squared() == 0. {
|
||||
return None;
|
||||
}
|
||||
let mut p0t = p0 - Vector3f::from(ray.o);
|
||||
let mut p1t = p1 - Vector3f::from(ray.o);
|
||||
let mut p2t = p2 - Vector3f::from(ray.o);
|
||||
|
||||
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(
|
||||
|
|
@ -146,255 +184,233 @@ impl TriangleShape {
|
|||
time: Float,
|
||||
wo: Vector3f,
|
||||
) -> SurfaceInteraction {
|
||||
let [p0, p1, p2] = self.get_points();
|
||||
let uv = self.get_uvs().unwrap_or([
|
||||
Point2f::new(0.0, 0.0),
|
||||
Point2f::new(1.0, 0.0),
|
||||
Point2f::new(1.0, 1.0),
|
||||
]);
|
||||
let duv02 = uv[0] - uv[2];
|
||||
let duv12 = uv[1] - uv[2];
|
||||
let dp02 = p0 - p2;
|
||||
let dp12 = p1 - p2;
|
||||
let determinant = difference_of_products(duv02[0], duv12[1], duv02[1], duv12[0]);
|
||||
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())
|
||||
};
|
||||
let data = self.get_data();
|
||||
let [p0, p1, p2] = data.vertices;
|
||||
let [uv0, uv1, uv2] = data.uvs;
|
||||
// Compute triangle partial derivatives
|
||||
let (dpdu, dpdv, degenerate_uv, det) = self.compute_partials(data);
|
||||
// Interpolate (u, v) parametric coordinates and hit point
|
||||
let p_hit_vec =
|
||||
ti.b0 * Vector3f::from(p0) + ti.b1 * Vector3f::from(p1) + ti.b2 * Vector3f::from(p2);
|
||||
let p_hit = Point3f::from(p_hit_vec);
|
||||
let uv_hit_vec =
|
||||
ti.b0 * Vector2f::from(uv0) + ti.b1 * Vector2f::from(uv1) + ti.b2 * Vector2f::from(uv2);
|
||||
let uv_hit = Point2f::from(uv_hit_vec);
|
||||
|
||||
if degenerate || dpdu.cross(dpdv).norm_squared() == 0. {
|
||||
let mut ng = (p2 - p0).cross(p1 - p0);
|
||||
if ng.norm_squared() == 0. {
|
||||
let v1 = p2 - p0;
|
||||
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();
|
||||
// Return SurfaceInteraction for triangle hit>
|
||||
let flip_normal = data.reverse_orientation ^ data.transform_swaps_handedness;
|
||||
let p_abs_sum = (ti.b0 * Vector3f::from(p0)).abs()
|
||||
+ (ti.b1 * Vector3f::from(p1)).abs()
|
||||
+ (ti.b2 * Vector3f::from(p2)).abs();
|
||||
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(
|
||||
Point3fi::new_with_error(p_hit, p_error),
|
||||
uv_hit,
|
||||
wo,
|
||||
dpdu,
|
||||
dpdv,
|
||||
Normal3f::zero(),
|
||||
Normal3f::zero(),
|
||||
Normal3f::default(),
|
||||
Normal3f::default(),
|
||||
time,
|
||||
flip_normal,
|
||||
);
|
||||
|
||||
isect.face_index = if !self.mesh.face_indices.is_null() {
|
||||
unsafe { *self.mesh.face_indices.add(self.tri_index as usize) }
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
isect.common.n = ng;
|
||||
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.face_index = self
|
||||
.mesh()
|
||||
.face_indices
|
||||
.as_ref()
|
||||
.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() {
|
||||
self.apply_shading_normals(&mut isect, ti, data, degenerate_uv, det);
|
||||
}
|
||||
|
||||
isect
|
||||
}
|
||||
|
||||
fn compute_shading_geometry(
|
||||
fn compute_partials(&self, data: TriangleData) -> (Vector3f, Vector3f, bool, Float) {
|
||||
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,
|
||||
isect: &mut SurfaceInteraction,
|
||||
ti: &TriangleIntersection,
|
||||
uv: [Point2f; 3],
|
||||
dpdu_geom: Vector3f,
|
||||
determinant: Float,
|
||||
ti: TriangleIntersection,
|
||||
data: TriangleData,
|
||||
degenerate_uv: bool,
|
||||
det: Float,
|
||||
) {
|
||||
// Interpolate vertex normals if they exist
|
||||
let ns = if let Some(normals) = self.get_shading_normals() {
|
||||
let n = ti.b0 * normals[0] + ti.b1 * normals[1] + ti.b2 * normals[2];
|
||||
if n.norm_squared() > 0.0 {
|
||||
n.normalize()
|
||||
} else {
|
||||
isect.n()
|
||||
}
|
||||
let Some([n0, n1, n2]) = data.normals else {
|
||||
return;
|
||||
};
|
||||
let [uv0, uv1, uv2] = data.uvs;
|
||||
let duv02 = uv0 - uv2;
|
||||
let duv12 = uv1 - uv2;
|
||||
|
||||
let ns = ti.b0 * n0 + ti.b1 * n1 + ti.b2 * n2;
|
||||
let ns = if ns.norm_squared() > 0. {
|
||||
ns.normalize()
|
||||
} else {
|
||||
isect.n()
|
||||
};
|
||||
|
||||
// Interpolate tangents if they exist
|
||||
let mut ss = if let Some(tangents) = self.get_tangents() {
|
||||
let s = ti.b0 * tangents[0] + ti.b1 * tangents[1] + ti.b2 * tangents[2];
|
||||
if s.norm_squared() > 0.0 {
|
||||
s.normalize()
|
||||
} else {
|
||||
dpdu_geom
|
||||
}
|
||||
} else {
|
||||
dpdu_geom
|
||||
};
|
||||
let mut ss = self.mesh().s.as_ref().map_or(isect.dpdu, |s| {
|
||||
let indices = &self.mesh().vertex_indices[3 * self.tri_index..3 * self.tri_index + 3];
|
||||
let interp_s = ti.b0 * s[indices[0]] + ti.b1 * s[indices[1]] + ti.b2 * s[indices[2]];
|
||||
|
||||
// Ensure shading tangent (ss) is perpendicular to shading normal (ns)
|
||||
let mut ts = ns.cross(ss.into());
|
||||
if ts.norm_squared() > 0.0 {
|
||||
ss = ts.cross(ns.into()).into();
|
||||
if interp_s.norm_squared() > 0. {
|
||||
interp_s
|
||||
} else {
|
||||
let (s, t) = ns.coordinate_system();
|
||||
ss = s.into();
|
||||
ts = t.into();
|
||||
isect.dpdu
|
||||
}
|
||||
});
|
||||
|
||||
// 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 {
|
||||
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 {
|
||||
let (dnu, dnv) = dn.coordinate_system();
|
||||
(Normal3f::from(dnu), Normal3f::from(dnv))
|
||||
dn.coordinate_system()
|
||||
}
|
||||
} 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;
|
||||
let inv_det = 1. / det;
|
||||
let dn02 = n0 - n2;
|
||||
let dn12 = n1 - n2;
|
||||
(
|
||||
difference_of_products(duv12[1], dn1, duv02[1], dn2) * inv_det,
|
||||
difference_of_products(duv02[0], dn2, duv12[0], dn1) * inv_det,
|
||||
(dn02 * duv12[1] - dn12 * duv02[1]) * inv_det,
|
||||
(dn12 * duv02[0] - dn02 * duv12[0]) * inv_det,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
(Normal3f::zero(), Normal3f::zero())
|
||||
};
|
||||
|
||||
isect.set_shading_geom(ns, ss, ts.into(), dndu, dndv, true);
|
||||
isect.shading.n = ns;
|
||||
isect.shading.dpdu = ss;
|
||||
isect.shading.dpdv = ts;
|
||||
isect.dndu = dndu;
|
||||
isect.dndv = dndv;
|
||||
}
|
||||
}
|
||||
|
||||
impl ShapeTrait for TriangleShape {
|
||||
fn bounds(&self) -> Bounds3f {
|
||||
let [p0, p1, p2] = self.get_points();
|
||||
let [p0, p1, p2] = self.get_data().vertices;
|
||||
Bounds3f::from_points(p0, p1).union_point(p2)
|
||||
}
|
||||
|
||||
fn normal_bounds(&self) -> DirectionCone {
|
||||
let [p0, p1, p2] = self.get_points();
|
||||
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 = n0 + n1 + n2;
|
||||
n = n.face_forward(ns);
|
||||
} else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness {
|
||||
let data = self.get_data();
|
||||
let mut n = data.normal;
|
||||
if let Some([n0, n1, n2]) = data.normals {
|
||||
n = n.face_forward((n0 + n1 + n2).into());
|
||||
} else if data.reverse_orientation ^ data.transform_swaps_handedness {
|
||||
n = -n;
|
||||
}
|
||||
|
||||
DirectionCone::new_from_vector(n.into())
|
||||
DirectionCone::new_from_vector(Vector3f::from(n))
|
||||
}
|
||||
|
||||
fn area(&self) -> Float {
|
||||
let [p0, p1, p2] = self.get_points();
|
||||
0.5 * (p1 - p0).cross(p2 - p0).norm()
|
||||
self.get_data().area
|
||||
}
|
||||
|
||||
fn sample(&self, u: Point2f) -> Option<ShapeSample> {
|
||||
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(&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
|
||||
if (Self::MIN_SPHERICAL_SAMPLE_AREA..Self::MAX_SPHERICAL_SAMPLE_AREA).contains(&solid_angle)
|
||||
{
|
||||
let mut ss = self.sample(u)?;
|
||||
ss.intr.get_common_mut().time = ctx.time;
|
||||
let mut wi: Normal3f = (ss.intr.p() - ctx.p()).into();
|
||||
let ray = ctx.spawn_ray(wi);
|
||||
return self.intersect(&ray, None).map_or(0., |isect| {
|
||||
let absdot = Vector3f::from(isect.intr.n()).dot(-wi).abs();
|
||||
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. {
|
||||
return None;
|
||||
}
|
||||
wi = wi.normalize();
|
||||
ss.pdf /= ss.intr.n().abs_dot(-wi) / ctx.p().distance_squared(ss.intr.p());
|
||||
if ss.pdf.is_infinite() {
|
||||
return None;
|
||||
}
|
||||
return Some(ss);
|
||||
let absdot = Vector3f::from(ss.intr.n()).abs_dot(-wi);
|
||||
let d2 = ctx.p().distance_squared(ss.intr.p());
|
||||
ss.pdf /= absdot / d2;
|
||||
if ss.pdf.is_infinite() { None } else { Some(ss) }
|
||||
});
|
||||
}
|
||||
|
||||
// Sample spherical triangle from reference point
|
||||
let mut pdf = 1.;
|
||||
if ctx.ns != Normal3f::zero() {
|
||||
let rp = ctx.p();
|
||||
|
|
@ -404,112 +420,93 @@ impl ShapeTrait for TriangleShape {
|
|||
(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),
|
||||
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()),
|
||||
];
|
||||
u = sample_bilinear(u, &w);
|
||||
|
||||
let u = sample_bilinear(u, &w);
|
||||
pdf = bilinear_pdf(u, &w);
|
||||
}
|
||||
|
||||
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 mut n: Normal3f = (p1 - p0).cross(p2 - p0).normalize().into();
|
||||
let (b, tri_pdf) = sample_spherical_triangle(&[p0, p1, p2], ctx.p(), u)?;
|
||||
if tri_pdf == 0. {
|
||||
return None;
|
||||
}
|
||||
pdf *= tri_pdf;
|
||||
let b2 = 1. - b[0] - b[1];
|
||||
|
||||
if let Some(normals) = self.get_shading_normals() {
|
||||
let [n0, n1, n2] = normals;
|
||||
let ns = b[0] * n0 + b[1] * n1 + (1. - b[0] - b[1]) * n2;
|
||||
n = n.face_forward(ns);
|
||||
} else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness {
|
||||
let p_abs_sum = b[0] * Vector3f::from(p0)
|
||||
+ b[1] * Vector3f::from(p1)
|
||||
+ (1. - b[0] - b[1]) * Vector3f::from(p2);
|
||||
let p_error = gamma(6) * p_abs_sum;
|
||||
// Return ShapeSample for solid angle sampled point on triangle
|
||||
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;
|
||||
}
|
||||
|
||||
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 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.,
|
||||
);
|
||||
|
||||
let [uv0, uv1, uv2] = data.uvs;
|
||||
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 pi = Point3fi::new_with_error(p, p_error);
|
||||
let mut si = SurfaceInteraction::new_simple(pi, n, uv_sample);
|
||||
si.common.time = ctx.time;
|
||||
Some(ShapeSample {
|
||||
intr: Interaction::Simple(SimpleInteraction::new(intr_base)),
|
||||
intr: Arc::new(si),
|
||||
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> {
|
||||
let [p0, p1, p2] = self.get_points();
|
||||
let tri_isect = self.intersect_triangle(ray, t_max.unwrap_or(0.), p0, p1, p2)?;
|
||||
let intr = self.interaction_from_intersection(tri_isect, ray.time, -ray.d);
|
||||
Some(ShapeIntersection::new(intr, tri_isect.t))
|
||||
self.intersect_triangle(ray, t_max.unwrap_or(Float::INFINITY))
|
||||
.map(|ti| {
|
||||
let intr = self.interaction_from_intersection(ti, ray.time, -ray.d);
|
||||
ShapeIntersection { intr, t_hit: ti.t }
|
||||
})
|
||||
}
|
||||
|
||||
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
|
||||
let [p0, p1, p2] = self.get_points();
|
||||
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
|
||||
self.intersect_triangle(ray, t_max.unwrap_or(Float::INFINITY))
|
||||
.is_some()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1462,7 +1462,7 @@ pub const CIE_D65: [Float; 95] = [
|
|||
N!(115.392),
|
||||
N!(115.923),
|
||||
N!(112.367),
|
||||
N!(108.811),
|
||||
N(108.811),
|
||||
N!(109.082),
|
||||
N!(109.354),
|
||||
N!(108.578),
|
||||
|
|
|
|||
|
|
@ -3,73 +3,31 @@ use crate::core::geometry::Point2f;
|
|||
use crate::core::pbrt::Float;
|
||||
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum};
|
||||
use crate::utils::math::SquareMatrix3f;
|
||||
use crate::utils::ptr::Ptr;
|
||||
use anyhow::{Result, anyhow};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use std::cmp::{Eq, PartialEq};
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Debug, Clone)]
|
||||
pub struct DeviceStandardColorSpaces {
|
||||
pub srgb: Ptr<RGBColorSpace>,
|
||||
pub dci_p3: Ptr<RGBColorSpace>,
|
||||
pub rec2020: Ptr<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,
|
||||
}
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct StandardColorSpaces {
|
||||
pub srgb: *const RGBColorSpace,
|
||||
pub dci_p3: *const RGBColorSpace,
|
||||
pub rec2020: *const RGBColorSpace,
|
||||
pub aces2065_1: *const RGBColorSpace,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RGBColorSpace {
|
||||
pub r: Point2f,
|
||||
pub g: Point2f,
|
||||
pub b: Point2f,
|
||||
pub w: Point2f,
|
||||
pub illuminant: DenselySampledSpectrum,
|
||||
pub rgb_to_spectrum_table: Ptr<RGBToSpectrumTable>,
|
||||
pub rgb_to_spectrum_table: *const RGBToSpectrumTable,
|
||||
pub xyz_from_rgb: SquareMatrix3f,
|
||||
pub rgb_from_xyz: SquareMatrix3f,
|
||||
}
|
||||
|
|
@ -87,7 +45,7 @@ impl RGBColorSpace {
|
|||
}
|
||||
|
||||
pub fn to_rgb_coeffs(&self, rgb: RGB) -> RGBSigmoidPolynomial {
|
||||
self.rgb_to_spectrum_table.evaluate(rgb)
|
||||
self.rgb_to_spectrum_table.to_polynomial(rgb)
|
||||
}
|
||||
|
||||
pub fn convert_colorspace(&self, other: &RGBColorSpace) -> SquareMatrix3f {
|
||||
|
|
@ -97,14 +55,6 @@ impl RGBColorSpace {
|
|||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ pub mod simple;
|
|||
|
||||
use crate::core::pbrt::Float;
|
||||
|
||||
pub use colorspace::{DeviceStandardColorSpaces, RGBColorSpace};
|
||||
pub use colorspace::RGBColorSpace;
|
||||
pub use rgb::*;
|
||||
pub use sampled::{CIE_Y_INTEGRAL, LAMBDA_MAX, LAMBDA_MIN};
|
||||
pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ use super::{
|
|||
};
|
||||
use crate::core::color::{RGB, RGBSigmoidPolynomial, XYZ};
|
||||
use crate::core::spectrum::SpectrumTrait;
|
||||
use crate::utils::Ptr;
|
||||
|
||||
use crate::Float;
|
||||
|
||||
|
|
@ -69,12 +68,13 @@ impl SpectrumTrait for UnboundedRGBSpectrum {
|
|||
pub struct RGBIlluminantSpectrum {
|
||||
pub scale: Float,
|
||||
pub rsp: RGBSigmoidPolynomial,
|
||||
pub illuminant: Ptr<DenselySampledSpectrum>,
|
||||
pub illuminant: DenselySampledSpectrum,
|
||||
}
|
||||
|
||||
impl RGBIlluminantSpectrum {
|
||||
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 scale = 2. * m;
|
||||
let rsp = cs.to_rgb_coeffs(if scale == 1. {
|
||||
|
|
@ -85,31 +85,33 @@ impl RGBIlluminantSpectrum {
|
|||
Self {
|
||||
scale,
|
||||
rsp,
|
||||
illuminant: Ptr::from(&illuminant),
|
||||
illuminant,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpectrumTrait for RGBIlluminantSpectrum {
|
||||
fn evaluate(&self, lambda: Float) -> Float {
|
||||
if self.illuminant.is_null() {
|
||||
return 0.;
|
||||
match &self.illuminant {
|
||||
Some(illuminant) => {
|
||||
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 {
|
||||
if self.illuminant.is_null() {
|
||||
if self.illuminant.is_none() {
|
||||
return SampledSpectrum::new(0.);
|
||||
}
|
||||
SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i]))
|
||||
}
|
||||
|
||||
fn max_value(&self) -> Float {
|
||||
if self.illuminant.is_null() {
|
||||
return 0.;
|
||||
match &self.illuminant {
|
||||
Some(illuminant) => self.scale * self.rsp.max_value() * illuminant.max_value(),
|
||||
None => 0.0,
|
||||
}
|
||||
self.scale * self.rsp.max_value() * self.illuminant.max_value()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::core::pbrt::Float;
|
||||
use crate::core::spectrum::{SpectrumTrait, StandardSpectra};
|
||||
use crate::core::spectrum::StandardSpectra;
|
||||
use crate::utils::math::{clamp, lerp};
|
||||
use std::ops::{
|
||||
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
|
||||
|
|
@ -32,10 +32,6 @@ impl SampledSpectrum {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn from_fn<F>(cb: F) -> Self
|
||||
where
|
||||
|
|
@ -122,7 +118,7 @@ impl SampledSpectrum {
|
|||
}
|
||||
|
||||
pub fn y(&self, lambda: &SampledWavelengths, std: &StandardSpectra) -> Float {
|
||||
let ys = std.y.sample(lambda);
|
||||
let ys = std.cie_y().sample(lambda);
|
||||
let pdf = lambda.pdf();
|
||||
SampledSpectrum::safe_div(&(ys * *self), &pdf).average() / CIE_Y_INTEGRAL
|
||||
}
|
||||
|
|
@ -309,7 +305,7 @@ pub struct SampledWavelengths {
|
|||
|
||||
impl SampledWavelengths {
|
||||
pub fn pdf(&self) -> SampledSpectrum {
|
||||
SampledSpectrum::from_array(&self.pdf)
|
||||
SampledSpectrum::from_vector(self.pdf.to_vec())
|
||||
}
|
||||
|
||||
pub fn secondary_terminated(&self) -> bool {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
|
|||
use crate::Float;
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::find_interval;
|
||||
use crate::utils::ptr::Ptr;
|
||||
use core::slice;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::LazyLock;
|
||||
|
|
@ -36,7 +34,7 @@ impl SpectrumTrait for ConstantSpectrum {
|
|||
pub struct DenselySampledSpectrum {
|
||||
pub lambda_min: i32,
|
||||
pub lambda_max: i32,
|
||||
pub values: Ptr<Float>,
|
||||
pub values: *const Float,
|
||||
}
|
||||
|
||||
unsafe impl Send for DenselySampledSpectrum {}
|
||||
|
|
@ -44,155 +42,148 @@ unsafe impl Sync for DenselySampledSpectrum {}
|
|||
|
||||
impl DenselySampledSpectrum {
|
||||
#[inline(always)]
|
||||
pub fn count(&self) -> usize {
|
||||
fn as_slice(&self) -> &[Float] {
|
||||
if self.values.is_null() {
|
||||
0
|
||||
} else {
|
||||
(self.lambda_max - self.lambda_min + 1) as usize
|
||||
return &[];
|
||||
}
|
||||
let len = (self.lambda_max - self.lambda_min + 1).max(0) as usize;
|
||||
unsafe { slice::from_raw_parts(self.values, len) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get(&self, idx: u32) -> Float {
|
||||
unsafe { *self.values.add(idx as usize) }
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for DenselySampledSpectrum {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.lambda_min == other.lambda_min
|
||||
&& self.lambda_max == other.lambda_max
|
||||
&& self.values == other.values
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for DenselySampledSpectrum {}
|
||||
|
||||
// impl Hash for DenselySampledSpectrum {
|
||||
// fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
// self.lambda_min.hash(state);
|
||||
// self.lambda_max.hash(state);
|
||||
//
|
||||
// for v in &self.values {
|
||||
// v.to_bits().hash(state);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl SpectrumTrait for DenselySampledSpectrum {
|
||||
fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
pub 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;
|
||||
let len = (self.lambda_max - self.lambda_min + 1) as i32;
|
||||
|
||||
if offset < 0 || offset >= n {
|
||||
if offset < 0 || offset >= len {
|
||||
s[i] = 0.0;
|
||||
} else {
|
||||
unsafe {
|
||||
s[i] = *self.values.add(offset as usize);
|
||||
}
|
||||
unsafe { s[i] = *self.values.add(offset as usize) };
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
pub fn min_component_value(&self) -> Float {
|
||||
self.as_slice()
|
||||
.iter()
|
||||
.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 {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
if self.lambda_min != other.lambda_min
|
||||
|| self.lambda_max != other.lambda_max
|
||||
|| self.values.len() != other.values.len()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
self.values
|
||||
.iter()
|
||||
.zip(&other.values)
|
||||
.all(|(a, b)| a.to_bits() == b.to_bits())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for DenselySampledSpectrum {}
|
||||
|
||||
impl Hash for DenselySampledSpectrum {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.lambda_min.hash(state);
|
||||
self.lambda_max.hash(state);
|
||||
|
||||
for v in &self.values {
|
||||
v.to_bits().hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpectrumTrait for DenselySampledSpectrum {
|
||||
fn evaluate(&self, lambda: Float) -> Float {
|
||||
let offset = (lambda.round() as i32) - self.lambda_min;
|
||||
let n = self.count() as i32;
|
||||
if offset < 0 || offset >= n {
|
||||
if offset < 0 || offset as usize >= self.values.len() {
|
||||
0.0
|
||||
} else {
|
||||
unsafe { *self.values.add(offset as usize) }
|
||||
self.values[offset as usize]
|
||||
}
|
||||
}
|
||||
|
||||
fn max_value(&self) -> Float {
|
||||
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
|
||||
self.values.iter().fold(Float::MIN, |a, b| a.max(*b))
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PiecewiseLinearSpectrum {
|
||||
pub lambdas: Ptr<Float>,
|
||||
pub values: Ptr<Float>,
|
||||
pub lambdas: *const Float,
|
||||
pub values: *const Float,
|
||||
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 Sync for PiecewiseLinearSpectrum {}
|
||||
|
||||
impl SpectrumTrait for PiecewiseLinearSpectrum {
|
||||
fn evaluate(&self, lambda: Float) -> Float {
|
||||
if self.lambdas.is_null() {
|
||||
if self.lambdas.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
if lambda <= self.lambda(0) {
|
||||
return self.value(0);
|
||||
if lambda <= self.lambdas[0] {
|
||||
return self.values[0];
|
||||
}
|
||||
|
||||
if lambda >= self.lambda(self.count - 1) {
|
||||
return self.value(self.count - 1);
|
||||
if lambda >= *self.lambdas.last().unwrap() {
|
||||
return *self.values.last().unwrap();
|
||||
}
|
||||
|
||||
let i = find_interval(self.count, |idx| self.lambda(idx) <= lambda);
|
||||
|
||||
let l0 = self.lambda(i);
|
||||
let l1 = self.lambda(i + 1);
|
||||
let v0 = self.value(i);
|
||||
let v1 = self.value(i + 1);
|
||||
let i = self.lambdas.partition_point(|&l| l < lambda);
|
||||
let l0 = self.lambdas[i - 1];
|
||||
let l1 = self.lambdas[i];
|
||||
let v0 = self.values[i - 1];
|
||||
let v1 = self.values[i];
|
||||
|
||||
let t = (lambda - l0) / (l1 - l0);
|
||||
|
||||
v0 + t * (v1 - v0)
|
||||
}
|
||||
|
||||
fn max_value(&self) -> Float {
|
||||
if self.values.is_null() {
|
||||
return 0.;
|
||||
if self.values.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
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
|
||||
self.values.iter().fold(0.0, |acc, &v| acc.max(v))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,14 @@
|
|||
use crate::Float;
|
||||
use crate::core::spectrum::Spectrum;
|
||||
use crate::core::spectrum::SpectrumTrait;
|
||||
use crate::core::texture::{TextureEvalContext, TextureMapping2D};
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::{Ptr, Transform};
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths, SpectrumTrait};
|
||||
use crate::utils::Transform;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FloatBilerpTexture {
|
||||
pub mapping: TextureMapping2D,
|
||||
pub v00: Float,
|
||||
pub v01: Float,
|
||||
pub v10: Float,
|
||||
pub v11: Float,
|
||||
mapping: TextureMapping2D,
|
||||
v00: Float,
|
||||
v01: Float,
|
||||
v10: Float,
|
||||
v11: Float,
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
|
@ -44,23 +40,22 @@ impl FloatBilerpTexture {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpectrumBilerpTexture {
|
||||
pub mapping: TextureMapping2D,
|
||||
pub v00: Ptr<Spectrum>,
|
||||
pub v01: Ptr<Spectrum>,
|
||||
pub v10: Ptr<Spectrum>,
|
||||
pub v11: Ptr<Spectrum>,
|
||||
pub v00: Spectrum,
|
||||
pub v01: Spectrum,
|
||||
pub v10: Spectrum,
|
||||
pub v11: Spectrum,
|
||||
}
|
||||
|
||||
impl SpectrumBilerpTexture {
|
||||
pub fn new(
|
||||
mapping: TextureMapping2D,
|
||||
v00: Ptr<Spectrum>,
|
||||
v01: Ptr<Spectrum>,
|
||||
v10: Ptr<Spectrum>,
|
||||
v11: Ptr<Spectrum>,
|
||||
v00: Spectrum,
|
||||
v01: Spectrum,
|
||||
v10: Spectrum,
|
||||
v11: Spectrum,
|
||||
) -> Self {
|
||||
Self {
|
||||
mapping,
|
||||
|
|
@ -74,16 +69,16 @@ impl SpectrumBilerpTexture {
|
|||
pub fn evaluate(
|
||||
&self,
|
||||
ctx: &TextureEvalContext,
|
||||
lambda: &SampledWavelengths,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
let c = self.mapping.map(ctx);
|
||||
bilerp(
|
||||
[c.st[0], c.st[1]],
|
||||
[c.st[0], c.st[1], c.st[2]],
|
||||
[
|
||||
self.v00.sample(lambda),
|
||||
self.v01.sample(lambda),
|
||||
self.v10.sample(lambda),
|
||||
self.v11.sample(lambda),
|
||||
v00.sample(lambda),
|
||||
v01.sample(lambda),
|
||||
v10.sample(lambda),
|
||||
v11.sample(lambda),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,105 +1,29 @@
|
|||
use crate::Float;
|
||||
use crate::core::texture::{
|
||||
GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext, TextureMapping2D, TextureMapping3D,
|
||||
TextureMapping3DTrait,
|
||||
};
|
||||
use crate::core::texture::TextureEvalContext;
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::{Ptr, math::square};
|
||||
|
||||
fn checkerboard(
|
||||
ctx: &TextureEvalContext,
|
||||
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)]
|
||||
// TODO: I have to implement somethign like a TaggedPointer, and change the whole codebase.
|
||||
// Fantastic
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FloatCheckerboardTexture {
|
||||
pub map2d: Ptr<TextureMapping2D>,
|
||||
pub map3d: Ptr<TextureMapping3D>,
|
||||
pub tex: [Ptr<GPUFloatTexture>; 2],
|
||||
pub map_2d: TextureMapping2D,
|
||||
pub map_3d: TextureMapping3D,
|
||||
pub tex: [FloatTexture; 2],
|
||||
}
|
||||
|
||||
impl FloatCheckerboardTexture {
|
||||
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
|
||||
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);
|
||||
pub fn evaluate(&self, _ctx: &TextureEvalContext) -> Float {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
if w != 0.0 {
|
||||
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],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpectrumCheckerboardTexture;
|
||||
impl SpectrumCheckerboardTexture {
|
||||
pub fn evaluate(
|
||||
&self,
|
||||
ctx: &TextureEvalContext,
|
||||
lambda: &SampledWavelengths,
|
||||
_ctx: &TextureEvalContext,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
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
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
use crate::Float;
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||
use crate::core::texture::TextureEvalContext;
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths, Spectrum};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FloatConstantTexture {
|
||||
pub value: Float,
|
||||
}
|
||||
|
|
@ -19,8 +17,7 @@ impl FloatConstantTexture {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpectrumConstantTexture {
|
||||
pub value: Spectrum,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,80 +1,19 @@
|
|||
use crate::Float;
|
||||
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>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FloatDotsTexture;
|
||||
impl FloatDotsTexture {
|
||||
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
|
||||
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
|
||||
}
|
||||
pub fn evaluate(&self, _ctx: &TextureEvalContext) -> Float {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SpectrumDotsTexture {
|
||||
pub mapping: TextureMapping2D,
|
||||
pub outside_dot: Ptr<GPUSpectrumTexture>,
|
||||
pub inside_dot: Ptr<GPUSpectrumTexture>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpectrumDotsTexture;
|
||||
impl SpectrumDotsTexture {
|
||||
pub fn evaluate(
|
||||
&self,
|
||||
ctx: &TextureEvalContext,
|
||||
lambda: &SampledWavelengths,
|
||||
_ctx: &TextureEvalContext,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
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)
|
||||
}
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
use crate::Float;
|
||||
use crate::core::texture::{TextureEvalContext, TextureMapping3D};
|
||||
use crate::utils::noise::fbm;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FBmTexture {
|
||||
pub mapping: TextureMapping3D,
|
||||
pub omega: Float,
|
||||
pub octaves: u32,
|
||||
pub octaves: usize,
|
||||
}
|
||||
|
||||
impl FBmTexture {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,12 @@
|
|||
use crate::Float;
|
||||
use crate::core::color::{RGB, XYZ};
|
||||
use crate::core::spectrum::SpectrumTrait;
|
||||
use crate::core::texture::{SpectrumType, TextureEvalContext, TextureMapping2D};
|
||||
use crate::spectra::{
|
||||
RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
|
||||
SampledWavelengths,
|
||||
};
|
||||
use crate::utils::Ptr;
|
||||
use crate::spectra::RGBColorSpace;
|
||||
|
||||
/* GPU heavy code, dont know if this will ever work the way Im doing things.
|
||||
* Leaving it here isolated, for careful handling */
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GPUSpectrumImageTexture {
|
||||
pub mapping: TextureMapping2D,
|
||||
pub tex_obj: u64,
|
||||
|
|
@ -23,7 +18,6 @@ pub struct GPUSpectrumImageTexture {
|
|||
}
|
||||
|
||||
impl GPUSpectrumImageTexture {
|
||||
#[allow(unused)]
|
||||
pub fn evaluate(
|
||||
&self,
|
||||
ctx: &TextureEvalContext,
|
||||
|
|
@ -38,21 +32,19 @@ impl GPUSpectrumImageTexture {
|
|||
{
|
||||
use cuda_std::intrinsics;
|
||||
let c = self.mapping.map(ctx);
|
||||
let u = c.st.x();
|
||||
let v = 1.0 - c.st.y();
|
||||
let u = c.st.x;
|
||||
let v = 1.0 - c.st.y;
|
||||
|
||||
let d_p_dx = [c.dsdx, c.dtdx];
|
||||
let d_p_dy = [c.dsdy, c.dtdy];
|
||||
|
||||
let tex_color = if self.is_single_channel {
|
||||
let val = 0.;
|
||||
// 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) };
|
||||
RGB::new(val, val, val)
|
||||
} else {
|
||||
let val = [0., 0., 0., 0.];
|
||||
// let val: [Float; 4] =
|
||||
// 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])
|
||||
};
|
||||
|
||||
|
|
@ -61,20 +53,22 @@ impl GPUSpectrumImageTexture {
|
|||
rgb = (RGB::new(1.0, 1.0, 1.0) - rgb).clamp_zero();
|
||||
}
|
||||
|
||||
let color_space = unsafe { &*self.color_space };
|
||||
|
||||
match self.spectrum_type {
|
||||
SpectrumType::Unbounded => {
|
||||
RGBUnboundedSpectrum::new(&self.color_space, rgb).sample(lambda)
|
||||
RGBUnboundedSpectrum::new(color_space, rgb).sample(lambda)
|
||||
}
|
||||
SpectrumType::Albedo => {
|
||||
RGBAlbedoSpectrum::new(&self.color_space, rgb.clamp(0.0, 1.0)).sample(lambda)
|
||||
RGBAlbedoSpectrum::new(color_space, rgb.clamp(0.0, 1.0)).sample(lambda)
|
||||
}
|
||||
_ => RGBIlluminantSpectrum::new(&self.color_space, rgb).sample(lambda),
|
||||
_ => RGBIlluminantSpectrum::new(color_space, rgb).sample(lambda),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GPUFloatImageTexture {
|
||||
pub mapping: TextureMapping2D,
|
||||
pub tex_obj: u64,
|
||||
|
|
@ -83,32 +77,26 @@ pub struct GPUFloatImageTexture {
|
|||
}
|
||||
|
||||
impl GPUFloatImageTexture {
|
||||
#[allow(unused_variables)]
|
||||
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
|
||||
#[cfg(not(feature = "cuda"))]
|
||||
{
|
||||
return 0.;
|
||||
}
|
||||
#[cfg(feature = "cuda")]
|
||||
#[allow(unused)]
|
||||
{
|
||||
use cuda_std::intrinsics;
|
||||
let c = self.mapping.map(ctx);
|
||||
let u = c.st.x();
|
||||
let v = 1.0 - c.st.y();
|
||||
let u = c.st.x;
|
||||
let v = 1.0 - c.st.y;
|
||||
let d_p_dx = [c.dsdx, c.dtdx];
|
||||
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 = 0.;
|
||||
let val: Float = unsafe { intrinsics::tex2d_grad(self.tex_obj, u, v, d_p_dx, d_p_dy) };
|
||||
|
||||
let result = if self.invert {
|
||||
// Invert the pixel intensity
|
||||
(1.0 - val).max(0.0)
|
||||
if invert {
|
||||
return (1. - v).max(0.);
|
||||
} else {
|
||||
val
|
||||
};
|
||||
|
||||
return result * self.scale;
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue