Compare commits

..

No commits in common. "ed2caf094d522bcc9e6c2384b3b89f042a3047e7" and "f7c47be077bacb4ed6afb653d0582ca5ab468f45" have entirely different histories.

233 changed files with 12172 additions and 20138 deletions

View file

@ -1,4 +0,0 @@
[build]
# Enable the host's AVX2/FMA. Measured ~12% on the CPU wavefront (13700H).
# NOTE: pins binaries to this CPU family. Use "x86-64-v3" if you ship them.
rustflags = ["-C", "target-cpu=native"]

17
.gitignore vendored
View file

@ -4,20 +4,5 @@ target/
*.bak *.bak
flip.rs flip.rs
.vscode .vscode
rust-analyzer.toml rust-analyzer.json
data/ data/
src/gpu/
src/tests/
tests/
*.spv
*.json
*.txt
scenes/
compile.sh
output/
!README.md
!INSTALL.md
docs/
GTAGS
GPATH
GRTAGS

View file

@ -6,24 +6,20 @@ edition = "2024"
[features] [features]
default = [] default = []
use_f64 = [] use_f64 = []
use_gpu = ["dep:wgpu"] cuda = ["cust", "cuda_builder", "shared/cuda", ]
use_nvtx = ["dep:nvtx"]
cuda = ["dep:cudarc", "dep:cust", "dep:cust_raw", "dep:cuda-runtime-sys"]
vulkan = ["dep:ash", "dep:gpu-allocator", "shared/vulkan"]
ash = ["dep:ash"]
gpu-allocator = ["dep:gpu-allocator"]
jemalloc = ["jemallocator"]
[dependencies] [dependencies]
anyhow = "1.0.100" anyhow = "1.0.100"
exr = "1.73.0" exr = "1.73.0"
flate2 = "1.1.5" flate2 = "1.1.5"
gpu = "0.2.3"
half = "2.7.1" half = "2.7.1"
image_rs = { package = "image", version = "0.25.8" } image_rs = { package = "image", version = "0.25.8" }
indicatif = "0.18.3" indicatif = "0.18.3"
lazy_static = "1.5.0" lazy_static = "1.5.0"
log = "0.4.29" log = "0.4.29"
memmap2 = "0.9.9" memmap2 = "0.9.9"
nvtx = "1.3.0"
parking_lot = "0.12.5" parking_lot = "0.12.5"
paste = "1.0.15" paste = "1.0.15"
qoi = "0.4.1" qoi = "0.4.1"
@ -31,62 +27,29 @@ rand = "0.9.2"
rayon = "1.11.0" rayon = "1.11.0"
thiserror = "2.0.17" thiserror = "2.0.17"
unicode-normalization = "0.1.25" unicode-normalization = "0.1.25"
wgpu = "27.0.1"
shared = { path = "shared" }
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 = "0.3.0"
ptex-sys = "0.3.0"
slice = "0.0.4" slice = "0.0.4"
crossbeam-channel = "0.5.15" crossbeam-channel = "0.5.15"
num_cpus = "1.17.0" num_cpus = "1.17.0"
ply-rs = "0.1.3" ply-rs = "0.1.3"
enum_dispatch = "0.3.13"
bytemuck = "1.24.0"
once_cell = "1.21.3"
smallvec = "1.15.1"
shared = { path = "shared" }
ptex-filter = { path = "crates/ptex-filter" }
# kernels = { path = "kernels" }
nvtx = { version = "1.3.0", optional = true }
wgpu = { version = "27.0.1", optional = true }
ash = { version = "0.38", optional = true }
gpu-allocator = { version = "0.28", features = ["vulkan"], optional = true }
cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true }
cust = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, features = ["glam"], optional = true }
cust_raw = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true }
cuda-runtime-sys = { version = "0.3.0-alpha.1", optional = true}
cudarc = { version = "0.18.2", features = ["cuda-13000"], optional = true }
jemallocator = { version = "0.5", optional = true }
syn = "2.0.117"
[build-dependencies] [build-dependencies]
spirv-builder = { git = "https://github.com/rust-gpu/rust-gpu", branch = "main", optional = true } spirv-builder = { git = "https://github.com/rust-gpu/rust-gpu", branch = "main", optional = true }
cuda_builder = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", optional = true } cuda_builder = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", optional = true }
cc = "1.2.53"
[dev-dependencies]
sysinfo = "0.30"
[workspace] [workspace]
members = ["shared"] members = ["kernels", "shared"]
exclude = ["crates/ptex-filter", "kernels"]
[lints.clippy] [lints.clippy]
excessive_precision = "allow" excessive_precision = "allow"
approx_constant = "allow" approx_constant = "allow"
upper_case_acronyms = "allow" upper_case_acronyms = "allow"
wrong_self_convention = "allow" wrong_self_convention = "allow"
[profile.release]
debug = true
# Renders run through `cargo test`, whose profile inherits from `dev`. Cargo's
# default there is opt-level = 0, which costs ~15x on ray throughput.
[profile.dev]
opt-level = 1
# Applies to dependencies only -- Cargo excludes workspace members from "*".
[profile.dev.package."*"]
opt-level = 3
# `shared` is a workspace member, so it needs naming explicitly. It holds the
# geometry/BSDF/sampling math, so it wants full optimisation.
[profile.dev.package.shared]
opt-level = 3

View file

@ -1,10 +1,10 @@
# PBRusT # PBRust
## Description ## Description
A Rust implementation of the physically based renderer described in the tremendous book *Physically Based Rendering: From Theory to Implementation* by Matt Pharr, Wenzel Jakob, and Greg Humphreys. This project aims to explore modern Rust features, and create a performant and stable rendering engine. A Rust implementation of the physically based renderer described in the tremendous book *Physically Based Rendering: From Theory to Implementation* by Matt Pharr, Wenzel Jakob, and Greg Humphreys. This project aims to explore modern Rust features, and create a performant and stable rendering engine.
This implementation is currently under development and serves as a learning exercise for both advanced rendering techniques and Rust programming. This implementation is currently under development and serves as a learning exercise for both advanced rendering techniques and cutting-edge Rust programming.
## Getting Started ## Getting Started
@ -16,11 +16,11 @@ rustup toolchain install nightly
rustup default nightly rustup default nightly
``` ```
To get a local copy up and running: To get a local copy up and running, follow these simple steps.
1. **Clone the repository:** 1. **Clone the repository:**
```sh ```sh
git clone <repository> git clone <your-repository-url>
cd pbrt cd pbrt
``` ```
@ -43,7 +43,6 @@ This project relies on the following external crates:
* [**once_cell**](https://crates.io/crates/once_cell) * [**once_cell**](https://crates.io/crates/once_cell)
* [**rand**](https://crates.io/crates/rand) * [**rand**](https://crates.io/crates/rand)
* [**thiserror**](https://crates.io/crates/thiserror) * [**thiserror**](https://crates.io/crates/thiserror)
* TODO: Generate docs with cargo docs. There are a lot more crates.
## Help ## Help

View file

@ -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);
}

View file

@ -1,13 +0,0 @@
#![allow(unused)]
[package]
name = "ptex-filter"
version = "0.1.0"
edition = "2021"
[build-dependencies]
cc = "1.0"
pkg-config = "0.3"
[dependencies]
ptex = "0.3.0"

View file

@ -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");
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -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;
}

View file

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

View file

@ -1,18 +1,11 @@
[package] [package]
name = "kernels" name = "kernels"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
[lib]
crate-type = ["dylib"]
[dependencies] [dependencies]
spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu", branch = "main" } cuda_std = { git = "https://github.com/rust-gpu/rust-cuda", rev = "7fa76f3d717038a92c90bf4a482b0b8dd3259344" }
shared = { path = "../shared" } shared = { path = "../shared", features = ["cuda"] }
[package.metadata.rust-gpu.install]
spirv-builder-source = "https://github.com/Rust-GPU/rust-gpu"
[package.metadata.rust-gpu.build]
target = "spirv-unknown-vulkan1.2"
[lib]
crate-type = ["cdylib", "rlib"]

View file

@ -1,511 +0,0 @@
#[cfg(target_os = "cuda")]
pub mod device {
use shared::core::aggregates::{BVHAggregate, LinearBVHNode};
use shared::core::geometry::{Bounds3f, Normal3f, Point2f, Point3f, Ray, Vector3f};
use shared::core::interaction::LightSampleContext;
use shared::core::material::Material;
use shared::core::medium::MediumInterface;
use shared::core::primitive::{Primitive, PrimitiveTrait};
use shared::spectra::{SampledSpectrum, SampledWavelengths};
use shared::utils::atomic::GpuAtomicU32;
use shared::utils::soa::SoABuffer;
use shared::wavefront::work_items::*;
use shared::{Float, Ptr};
use cuda_std::*;
#[repr(C)]
pub struct IntersectClosestParams {
pub bvh: Ptr<BVHAggregate>,
// Input queue
pub ray_q: Ptr<RayQueue>,
// Output queues
pub escaped_ray_q: Ptr<EscapedRayQueue>,
pub hit_area_light_q: Ptr<HitAreaLightQueue>,
pub basic_eval_mtl_q: Ptr<MaterialEvalQueue>,
pub universal_eval_mtl_q: Ptr<MaterialEvalQueue>,
pub next_ray_q: Ptr<RayQueue>,
// Persistent state
pub pixel_sample_state: Ptr<PixelSampleState>,
pub n_rays: u32,
}
/// One thread per ray: traverse BVH, push results to output queues.
#[kernel]
pub unsafe fn intersect_closest(params: &IntersectClosestParams) {
let idx = thread::index_1d();
if idx >= params.n_rays {
return;
}
let i = idx as usize;
let ray_q = &*params.ray_q.as_raw();
let work = ray_q.storage.get(i);
let ray = Ray::new(
work.ray_o,
work.ray_d,
Some(work.ray_time),
work.ray_medium,
);
let pi = work.pixel_index as usize;
let pss = &*params.pixel_sample_state.as_raw();
// Read persistent path state
let beta = pss.beta.get(pi);
let r_u = pss.r_u.get(pi);
let r_l = pss.r_l.get(pi);
let lambda = pss.lambda.get(pi);
let depth = pss.depth.get(pi);
let specular_bounce = pss.specular_bounce.get(pi) != 0;
let prev_intr_ctx = pss.prev_intr_ctx.get(pi);
let eta_scale = pss.eta_scale.get(pi);
let any_non_specular = pss.any_non_specular_bounces.get(pi) != 0;
// BVH traversal — mirrors BVHAggregate::intersect exactly
let bvh = &*params.bvh.as_raw();
if bvh.nodes.is_empty() {
// No geometry — ray escapes
push_escaped(params, &work, &lambda, &beta, &r_u, &r_l, depth, specular_bounce, &prev_intr_ctx);
return;
}
let nodes_ptr = bvh.nodes.as_ptr();
let prims_ptr = bvh.primitives.as_ptr();
let mut best_si = None;
let mut hit_t: Float = Float::INFINITY;
let inv_dir = Vector3f::new(
1.0 / ray.d.x(),
1.0 / ray.d.y(),
1.0 / ray.d.z(),
);
let dir_is_neg = [
if inv_dir.x() < 0.0 { 1u8 } else { 0 },
if inv_dir.y() < 0.0 { 1u8 } else { 0 },
if inv_dir.z() < 0.0 { 1u8 } else { 0 },
];
let mut to_visit_offset: u32 = 0;
let mut current_node_index: usize = 0;
// GPU stack — 64 entries matches CPU, fits in registers/local memory
let mut nodes_to_visit = [0usize; 64];
loop {
let node = &*nodes_ptr.add(current_node_index);
if node.bounds.intersect_p(ray.o, hit_t, inv_dir, &dir_is_neg).is_some() {
if node.n_primitives > 0 {
// Leaf node — test primitives
let mut j = 0u16;
while j < node.n_primitives {
let prim_idx = node.primitives_offset + j as usize;
let prim = &*prims_ptr.add(prim_idx);
if let Some(si) = prim.intersect(&ray, Some(hit_t)) {
hit_t = si.t_hit();
best_si = Some(si);
}
j += 1;
}
if to_visit_offset == 0 {
break;
}
to_visit_offset -= 1;
current_node_index = nodes_to_visit[to_visit_offset as usize];
} else {
// Interior node — push far child, visit near child
if dir_is_neg[node.axis as usize] == 1 {
nodes_to_visit[to_visit_offset as usize] = current_node_index + 1;
to_visit_offset += 1;
current_node_index = node.primitives_offset;
} else {
nodes_to_visit[to_visit_offset as usize] = node.primitives_offset;
to_visit_offset += 1;
current_node_index += 1;
}
}
} else {
if to_visit_offset == 0 {
break;
}
to_visit_offset -= 1;
current_node_index = nodes_to_visit[to_visit_offset as usize];
}
}
// Sort result into output queues
let Some(si) = best_si else {
push_escaped(params, &work, &lambda, &beta, &r_u, &r_l, depth, specular_bounce, &prev_intr_ctx);
return;
};
let intr = &si.intr;
// Null material — medium interface, re-queue ray
if intr.material.is_null() {
let next_q = &*params.next_ray_q.as_raw();
next_q.push(RayWorkItem {
ray_o: intr.p(),
ray_d: work.ray_d,
ray_time: work.ray_time,
ray_medium: work.ray_medium,
has_differentials: work.has_differentials,
differential: work.differential,
pixel_index: work.pixel_index,
});
return;
}
// Area light hit
if !intr.area_light.is_null() {
let q = &*params.hit_area_light_q.as_raw();
q.push(HitAreaLightWorkItem {
area_light: intr.area_light,
p: intr.p(),
n: intr.n(),
uv: intr.common.uv,
wo: -work.ray_d,
lambda,
pixel_index: work.pixel_index,
beta,
r_u,
r_l,
depth,
specular_bounce,
prev_intr_ctx,
});
}
// Material evaluation: push to appropriate queue
// For now, push everything to universal eval queue.
// Basic vs universal split requires checking can_evaluate_textures
// on the material, which we can refine later.
let q = &*params.universal_eval_mtl_q.as_raw();
q.push(MaterialEvalWorkItem {
p: intr.pi(),
n: intr.n(),
ns: intr.shading.n,
dpdu: intr.shading.dpdu,
dpdv: intr.shading.dpdv,
uv: intr.common.uv,
wo: -work.ray_d,
time: work.ray_time,
face_index: intr.face_index,
material: intr.material,
area_light: intr.area_light,
medium_interface: intr.common.medium_interface,
pixel_index: work.pixel_index,
lambda,
beta,
r_u,
any_non_specular_bounces: any_non_specular,
depth,
eta_scale,
});
}
/// Shadow ray kernel — one thread per shadow ray, binary occlusion test.
#[kernel]
pub unsafe fn intersect_shadow(params: &IntersectShadowParams) {
let idx = thread::index_1d();
if idx >= params.n_rays {
return;
}
let i = idx as usize;
let shadow_q = &*params.shadow_ray_q.as_raw();
let work = shadow_q.storage.get(i);
let ray = Ray::new(
work.ray_o,
work.ray_d,
Some(work.ray_time),
Ptr::null(),
);
// BVH any-hit traversal
let bvh = &*params.bvh.as_raw();
let occluded = bvh_intersect_p(bvh, &ray, work.t_max);
// If NOT occluded, add direct lighting contribution
if !occluded {
let pss = &*params.pixel_sample_state.as_raw();
let pi = work.pixel_index as usize;
// Atomic add to each spectral channel
let mut l = pss.l.get(pi);
l += work.l_d;
pss.l.set(pi, l);
// NOTE: This set is not atomic per-channel. For correctness
// when multiple shadow rays hit the same pixel, we'd need
// per-channel AtomicFloat. For now this works because each
// pixel has at most one shadow ray in flight per depth.
}
}
/// Launch parameters for shadow ray kernel.
#[repr(C)]
pub struct IntersectShadowParams {
pub bvh: Ptr<BVHAggregate>,
pub shadow_ray_q: Ptr<ShadowRayQueue>,
pub pixel_sample_state: Ptr<PixelSampleState>,
pub n_rays: u32,
}
unsafe fn push_escaped(
params: &IntersectClosestParams,
work: &RayWorkItem,
lambda: &SampledWavelengths,
beta: &SampledSpectrum,
r_u: &SampledSpectrum,
r_l: &SampledSpectrum,
depth: u32,
specular_bounce: bool,
prev_intr_ctx: &LightSampleContext,
) {
let q = &*params.escaped_ray_q.as_raw();
q.push(EscapedRayWorkItem {
ray_o: work.ray_o,
ray_d: work.ray_d,
lambda: *lambda,
pixel_index: work.pixel_index,
beta: *beta,
r_u: *r_u,
r_l: *r_l,
depth,
specular_bounce,
prev_intr_ctx: *prev_intr_ctx,
});
}
/// BVH any-hit traversal for shadow rays — returns true if occluded.
unsafe fn bvh_intersect_p(bvh: &BVHAggregate, ray: &Ray, t_max: Float) -> bool {
if bvh.nodes.is_empty() {
return false;
}
let nodes_ptr = bvh.nodes.as_ptr();
let prims_ptr = bvh.primitives.as_ptr();
let inv_dir = Vector3f::new(
1.0 / ray.d.x(),
1.0 / ray.d.y(),
1.0 / ray.d.z(),
);
let dir_is_neg = [
if inv_dir.x() < 0.0 { 1u8 } else { 0 },
if inv_dir.y() < 0.0 { 1u8 } else { 0 },
if inv_dir.z() < 0.0 { 1u8 } else { 0 },
];
let mut to_visit_offset: u32 = 0;
let mut current_node_index: usize = 0;
let mut nodes_to_visit = [0usize; 64];
loop {
let node = &*nodes_ptr.add(current_node_index);
if node.bounds.intersect_p(ray.o, t_max, inv_dir, &dir_is_neg).is_some() {
if node.n_primitives > 0 {
let mut j = 0u16;
while j < node.n_primitives {
let prim_idx = node.primitives_offset + j as usize;
let prim = &*prims_ptr.add(prim_idx);
if prim.intersect_p(ray, Some(t_max)) {
return true;
}
j += 1;
}
if to_visit_offset == 0 {
break;
}
to_visit_offset -= 1;
current_node_index = nodes_to_visit[to_visit_offset as usize];
} else {
if dir_is_neg[node.axis as usize] == 1 {
nodes_to_visit[to_visit_offset as usize] = current_node_index + 1;
to_visit_offset += 1;
current_node_index = node.primitives_offset;
} else {
nodes_to_visit[to_visit_offset as usize] = node.primitives_offset;
to_visit_offset += 1;
current_node_index += 1;
}
}
} else {
if to_visit_offset == 0 {
break;
}
to_visit_offset -= 1;
current_node_index = nodes_to_visit[to_visit_offset as usize];
}
}
false
}
}
#[cfg(feature = "cuda")]
pub mod host {
use crate::core::aggregates::BVHAggregate;
use crate::core::geometry::Bounds3f;
use crate::core::primitive::PrimitiveTrait;
use crate::wavefront::aggregate::WavefrontAggregate;
use crate::wavefront::work_items::*;
use crate::{Ptr, Float};
use cust::prelude::*;
use cust::launch;
/// CUDA aggregate — holds the BVH and the compiled kernel module.
pub struct CudaAggregate {
pub bvh: Ptr<BVHAggregate>,
pub module: Module,
pub stream: Stream,
}
impl CudaAggregate {
pub fn new(bvh: Ptr<BVHAggregate>, ptx_data: &str) -> cust::error::CudaResult<Self> {
// Initialize CUDA context (assumes cust::init() already called)
let module = Module::from_ptx(ptx_data, &[])?;
let stream = Stream::new(StreamFlags::NON_BLOCKING, None)?;
Ok(Self { bvh, module, stream })
}
fn launch_intersect_closest(
&self,
n_rays: u32,
ray_q: &RayQueue,
escaped_ray_q: &EscapedRayQueue,
hit_area_light_q: &HitAreaLightQueue,
basic_eval_mtl_q: &MaterialEvalQueue,
universal_eval_mtl_q: &MaterialEvalQueue,
next_ray_q: &RayQueue,
pixel_sample_state: &PixelSampleState,
) -> cust::error::CudaResult<()> {
if n_rays == 0 {
return Ok(());
}
let func = self.module.get_function("intersect_closest")?;
// Build launch params in unified memory
let params = super::device::IntersectClosestParams {
bvh: self.bvh,
ray_q: Ptr::from(ray_q),
escaped_ray_q: Ptr::from(escaped_ray_q),
hit_area_light_q: Ptr::from(hit_area_light_q),
basic_eval_mtl_q: Ptr::from(basic_eval_mtl_q),
universal_eval_mtl_q: Ptr::from(universal_eval_mtl_q),
next_ray_q: Ptr::from(next_ray_q),
pixel_sample_state: Ptr::from(pixel_sample_state),
n_rays,
};
let block_size = 256u32;
let grid_size = (n_rays + block_size - 1) / block_size;
unsafe {
launch!(
func<<<grid_size, block_size, 0, self.stream>>>(
&params
)
)?;
}
self.stream.synchronize()?;
Ok(())
}
fn launch_intersect_shadow(
&self,
n_rays: u32,
shadow_ray_q: &ShadowRayQueue,
pixel_sample_state: &PixelSampleState,
) -> cust::error::CudaResult<()> {
if n_rays == 0 {
return Ok(());
}
let func = self.module.get_function("intersect_shadow")?;
let params = super::device::IntersectShadowParams {
bvh: self.bvh,
shadow_ray_q: Ptr::from(shadow_ray_q),
pixel_sample_state: Ptr::from(pixel_sample_state),
n_rays,
};
let block_size = 256u32;
let grid_size = (n_rays + block_size - 1) / block_size;
unsafe {
launch!(
func<<<grid_size, block_size, 0, self.stream>>>(
&params
)
)?;
}
self.stream.synchronize()?;
Ok(())
}
}
impl WavefrontAggregate for CudaAggregate {
fn bounds(&self) -> Bounds3f {
self.bvh.get().map(|b| b.bounds()).unwrap_or_default()
}
fn intersect_closest(
&self,
max_rays: usize,
ray_q: &RayQueue,
escaped_ray_q: &EscapedRayQueue,
hit_area_light_q: &HitAreaLightQueue,
basic_eval_mtl_q: &MaterialEvalQueue,
universal_eval_mtl_q: &MaterialEvalQueue,
next_ray_q: &RayQueue,
pixel_sample_state: &PixelSampleState,
) {
let n_rays = ray_q.size().min(max_rays as u32);
self.launch_intersect_closest(
n_rays,
ray_q,
escaped_ray_q,
hit_area_light_q,
basic_eval_mtl_q,
universal_eval_mtl_q,
next_ray_q,
pixel_sample_state,
)
.expect("CUDA intersect_closest kernel launch failed");
}
fn intersect_shadow(
&self,
max_rays: usize,
shadow_ray_q: &ShadowRayQueue,
pixel_sample_state: &PixelSampleState,
) {
let n_rays = shadow_ray_q.size().min(max_rays as u32);
self.launch_intersect_shadow(n_rays, shadow_ray_q, pixel_sample_state)
.expect("CUDA intersect_shadow kernel launch failed");
}
fn intersect_shadow_tr(
&self,
max_rays: usize,
shadow_ray_q: &ShadowRayQueue,
pixel_sample_state: &PixelSampleState,
) {
// Without participating media, shadow_tr is the same as shadow
self.intersect_shadow(max_rays, shadow_ray_q, pixel_sample_state);
}
}
}

View file

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

View file

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

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

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

View file

@ -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];
}

View file

@ -5,17 +5,19 @@ edition = "2024"
[dependencies] [dependencies]
bitflags = "2.10.0" bitflags = "2.10.0"
half = { version = "2.7.1", default-features = false } bumpalo = "3.19.1"
bytemuck = { version = "1.24.0", features = ["derive"] } bytemuck = { version = "1.24.0", features = ["derive"] }
enum_dispatch = "0.3.13" enum_dispatch = "0.3.13"
ash = { version = "0.38", optional = true } log = "0.4.29"
parking_lot = { version = "0.12.5", optional = true } num = "0.4.3"
gpu-allocator = { version = "0.28", features = ["vulkan"], optional = true } num-integer = "0.1.46"
num-traits = { version = "0.2.19", default-features = false, features = ["libm"] } 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 } 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"
[features] [features]
use_f64 = [] use_f64 = []
cuda = ["cuda_std"] cuda = ["cuda_std"]
cpu_debug = []
vulkan = ["dep:ash", "dep:gpu-allocator", "dep:parking_lot"]

View file

@ -1,14 +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\"))");
// `gpu` is set for every device backend, so host-only code can be gated once
// as #[cfg(not(gpu))] instead of naming each target. Adding a backend means
// editing this line, not 30-odd cfg attributes.
println!("cargo::rustc-check-cfg=cfg(gpu)");
let target = std::env::var("TARGET").unwrap_or_default();
if target.contains("spirv") || target.contains("cuda") {
println!("cargo::rustc-cfg=gpu");
}
}

View file

@ -1,5 +1,7 @@
use crate::core::bsdf::{BSDF, BSDFSample}; use crate::core::bsdf::BSDF;
use crate::core::bxdf::{BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode}; use crate::core::bxdf::{
BSDFSample, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode,
};
use crate::core::color::RGB; use crate::core::color::RGB;
use crate::core::geometry::{ use crate::core::geometry::{
Normal3f, Point2f, Vector3f, abs_cos_theta, cos_theta, same_hemisphere, Normal3f, Point2f, Vector3f, abs_cos_theta, cos_theta, same_hemisphere,
@ -8,8 +10,7 @@ use crate::core::scattering::{
TrowbridgeReitzDistribution, fr_complex_from_spectrum, fr_dielectric, fresnel_moment1, reflect, TrowbridgeReitzDistribution, fr_complex_from_spectrum, fr_dielectric, fresnel_moment1, reflect,
refract, refract,
}; };
use crate::spectra::{N_SPECTRUM_SAMPLES, RGBColorSpace, RGBUnboundedSpectrum, SampledSpectrum}; use crate::spectra::{RGBUnboundedSpectrum, SampledSpectrum, StandardColorSpaces};
use crate::utils::gpu_array_from_fn;
use crate::utils::math::{ use crate::utils::math::{
clamp, fast_exp, i0, lerp, log_i0, radians, safe_acos, safe_asin, safe_sqrt, sample_discrete, clamp, fast_exp, i0, lerp, log_i0, radians, safe_acos, safe_asin, safe_sqrt, sample_discrete,
square, trimmed_logistic, square, trimmed_logistic,
@ -17,9 +18,8 @@ use crate::utils::math::{
use crate::utils::sampling::{ use crate::utils::sampling::{
cosine_hemisphere_pdf, sample_cosine_hemisphere, sample_trimmed_logistic, cosine_hemisphere_pdf, sample_cosine_hemisphere, sample_trimmed_logistic,
}; };
use crate::{Float, INV_2_PI, INV_PI, PI, Ptr}; use crate::{Float, INV_2_PI, INV_PI, PI};
use core::any::Any; use core::any::Any;
use num_traits::Float as NumFloat;
static P_MAX: usize = 3; static P_MAX: usize = 3;
#[repr(C)] #[repr(C)]
@ -34,6 +34,7 @@ pub struct HairBxDF {
pub s: Float, pub s: Float,
pub sin_2k_alpha: [Float; P_MAX], pub sin_2k_alpha: [Float; P_MAX],
pub cos_2k_alpha: [Float; P_MAX], pub cos_2k_alpha: [Float; P_MAX],
pub colorspaces: StandardColorSpaces,
} }
impl HairBxDF { impl HairBxDF {
@ -44,6 +45,7 @@ impl HairBxDF {
beta_m: Float, beta_m: Float,
beta_n: Float, beta_n: Float,
alpha: Float, alpha: Float,
colorspaces: StandardColorSpaces,
) -> Self { ) -> Self {
let mut sin_2k_alpha = [0.; P_MAX]; let mut sin_2k_alpha = [0.; P_MAX];
let mut cos_2k_alpha = [0.; P_MAX]; let mut cos_2k_alpha = [0.; P_MAX];
@ -65,6 +67,7 @@ impl HairBxDF {
s: 0., s: 0.,
sin_2k_alpha, sin_2k_alpha,
cos_2k_alpha, cos_2k_alpha,
colorspaces,
} }
} }
@ -80,7 +83,7 @@ impl HairBxDF {
let ap0 = SampledSpectrum::new(f); let ap0 = SampledSpectrum::new(f);
let ap1 = t * (1.0 - f).powi(2); let ap1 = t * (1.0 - f).powi(2);
let tf = t * f; let tf = t * f;
gpu_array_from_fn(|p| match p { std::array::from_fn(|p| match p {
0 => ap0, 0 => ap0,
1 => ap1, 1 => ap1,
_ if p < P_MAX => ap1 * tf.pow_int(p - 1), _ if p < P_MAX => ap1 * tf.pow_int(p - 1),
@ -132,31 +135,14 @@ impl HairBxDF {
let t = t_value.exp(); let t = t_value.exp();
let ap = Self::ap(cos_theta_o, self.eta, self.h, t); let ap = Self::ap(cos_theta_o, self.eta, self.h, t);
let sum_y: Float = ap.iter().map(|s| s.average()).sum(); let sum_y: Float = ap.iter().map(|s| s.average()).sum();
gpu_array_from_fn(|i| ap[i].average() / sum_y) std::array::from_fn(|i| ap[i].average() / sum_y)
} }
pub fn sigma_a_from_concentration( pub fn sigma_a_from_concentration(&self, ce: Float, cp: Float) -> RGBUnboundedSpectrum {
ce: Float,
cp: Float,
srgb: Ptr<RGBColorSpace>,
) -> RGBUnboundedSpectrum {
let eumelanin_sigma_a = RGB::new(0.419, 0.697, 1.37); 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 pheomelanin_sigma_a = RGB::new(0.187, 0.4, 1.05);
let sigma_a = ce * eumelanin_sigma_a + cp * pheomelanin_sigma_a; let sigma_a = ce * eumelanin_sigma_a + cp * pheomelanin_sigma_a;
RGBUnboundedSpectrum::new(&srgb, sigma_a) RGBUnboundedSpectrum::new(&self.colorspaces.srgb, sigma_a)
}
pub fn sigma_a_from_reflectance(c: SampledSpectrum, beta_n: Float) -> SampledSpectrum {
let mut sigma_a = SampledSpectrum::zero();
for i in 0..N_SPECTRUM_SAMPLES {
sigma_a[i] = square(
c[i].ln()
/ (5.969 - 0.215 * beta_n + 2.532 * square(beta_n) - 10.73 * beta_n.powf(3.)
+ 5.574 * beta_n.powf(4.)
+ 0.245 * beta_n.powf(5.)),
);
}
sigma_a
} }
} }
@ -440,7 +426,9 @@ impl BxDFTrait for NormalizedFresnelBxDF {
BxDFFlags::REFLECTION | BxDFFlags::DIFFUSE BxDFFlags::REFLECTION | BxDFFlags::DIFFUSE
} }
fn regularize(&mut self) {} fn regularize(&mut self) {
return;
}
fn as_any(&self) -> &dyn Any { fn as_any(&self) -> &dyn Any {
self self
@ -472,7 +460,9 @@ impl BxDFTrait for EmptyBxDF {
BxDFFlags::UNSET BxDFFlags::UNSET
} }
fn regularize(&mut self) {} fn regularize(&mut self) {
return;
}
fn as_any(&self) -> &dyn Any { fn as_any(&self) -> &dyn Any {
self self

View file

@ -1,14 +1,14 @@
use crate::core::bsdf::BSDFSample; use crate::core::bxdf::{
use crate::core::bxdf::{BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode}; BSDFSample, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode,
use crate::core::geometry::{
abs_cos_theta, same_hemisphere, Normal3f, Point2f, Vector3f, VectorLike,
}; };
use crate::core::scattering::{fr_complex_from_spectrum, reflect, TrowbridgeReitzDistribution}; 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::spectra::SampledSpectrum;
use crate::utils::sampling::{cosine_hemisphere_pdf, sample_cosine_hemisphere}; use crate::utils::sampling::{cosine_hemisphere_pdf, sample_cosine_hemisphere};
use crate::{Float, INV_PI}; use crate::{Float, INV_PI};
use core::any::Any; use core::any::Any;
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -18,6 +18,8 @@ pub struct ConductorBxDF {
pub k: SampledSpectrum, pub k: SampledSpectrum,
} }
unsafe impl Send for ConductorBxDF {}
unsafe impl Sync for ConductorBxDF {}
impl ConductorBxDF { impl ConductorBxDF {
pub fn new( pub fn new(
@ -82,7 +84,7 @@ impl BxDFTrait for ConductorBxDF {
return None; return None;
} }
let f_spectrum = fr_complex_from_spectrum(wo.dot(wm).abs(), self.eta, self.k); 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) let f = self.mf_distrib.d(wm) * f_spectrum * self.mf_distrib.g(wo, wi)
/ (4. * cos_theta_i * cos_theta_o); / (4. * cos_theta_i * cos_theta_o);
@ -116,7 +118,8 @@ impl BxDFTrait for ConductorBxDF {
return SampledSpectrum::new(0.); return SampledSpectrum::new(0.);
} }
let wm_norm = wm.normalize(); let wm_norm = wm.normalize();
let f_spectrum = fr_complex_from_spectrum(wo.dot(wm_norm).abs(), self.eta, self.k);
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) self.mf_distrib.d(wm_norm) * f_spectrum * self.mf_distrib.g(wo, wi)
/ (4. * cos_theta_i * cos_theta_o) / (4. * cos_theta_i * cos_theta_o)
} }
@ -137,9 +140,8 @@ impl BxDFTrait for ConductorBxDF {
if wm.norm_squared() == 0. { if wm.norm_squared() == 0. {
return 0.; return 0.;
} }
let wm_norm = Normal3f::from(wm.normalize()); let wm_corr = Normal3f::new(0., 0., 1.).face_forward(wm);
let wm_corr = wm_norm.face_forward(Vector3f::new(0., 0., 1.)); self.mf_distrib.pdf(wo, wm_corr.into()) / (4. * wo.dot(wm).abs())
self.mf_distrib.pdf(wo, wm_corr.into()) / (4. * wo.dot(Vector3f::from(wm_norm)).abs())
} }
fn regularize(&mut self) { fn regularize(&mut self) {

View file

@ -1,5 +1,6 @@
use crate::core::bsdf::BSDFSample; use crate::core::bxdf::{
use crate::core::bxdf::{BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode}; BSDFSample, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode,
};
use crate::core::geometry::{ use crate::core::geometry::{
Normal3f, Point2f, Vector3f, VectorLike, abs_cos_theta, cos_theta, same_hemisphere, Normal3f, Point2f, Vector3f, VectorLike, abs_cos_theta, cos_theta, same_hemisphere,
}; };
@ -11,7 +12,6 @@ use crate::utils::math::square;
use crate::utils::sampling::{cosine_hemisphere_pdf, sample_cosine_hemisphere}; use crate::utils::sampling::{cosine_hemisphere_pdf, sample_cosine_hemisphere};
use crate::{Float, INV_PI}; use crate::{Float, INV_PI};
use core::any::Any; use core::any::Any;
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
@ -141,11 +141,14 @@ impl BxDFTrait for DielectricBxDF {
} }
if reflect { if reflect {
self.mf_distrib.pdf(wo, wm.into()) / (4. * wo.dot(wm.into()).abs()) * pr / (pr + pt) self.mf_distrib.pdf(
wo,
Vector3f::from(wm) / (4. * wo.dot(wm.into()).abs()) * pr / (pt + pr),
)
} else { } else {
let denom = square(wi.dot(wm.into()) + wo.dot(wm.into()) / etap); let denom = square(wi.dot(wm.into()) + wo.dot(wm.into()) / etap);
let dwm_dwi = wi.dot(wm.into()).abs() / denom; let dwm_dwi = wi.dot(wm.into()).abs() / denom;
self.mf_distrib.pdf(wo, wm.into()) * dwm_dwi * pt / (pr + pt) self.mf_distrib.pdf(wo, wm.into()) * dwm_dwi * pr / (pr + pt)
} }
} }
@ -362,6 +365,7 @@ impl BxDFTrait for ThinDielectricBxDF {
fn as_any(&self) -> &dyn Any { fn as_any(&self) -> &dyn Any {
self self
} }
fn regularize(&mut self) {
fn regularize(&mut self) {} todo!()
}
} }

View file

@ -1,5 +1,6 @@
use crate::core::bsdf::BSDFSample; use crate::core::bxdf::{
use crate::core::bxdf::{BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode}; BSDFSample, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode,
};
use crate::core::geometry::{Point2f, Vector3f, abs_cos_theta, same_hemisphere}; use crate::core::geometry::{Point2f, Vector3f, abs_cos_theta, same_hemisphere};
use crate::spectra::SampledSpectrum; use crate::spectra::SampledSpectrum;
use crate::utils::sampling::{cosine_hemisphere_pdf, sample_cosine_hemisphere}; use crate::utils::sampling::{cosine_hemisphere_pdf, sample_cosine_hemisphere};
@ -7,7 +8,7 @@ use crate::{Float, INV_PI};
use core::any::Any; use core::any::Any;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone, Default)] #[derive(Debug, Copy, Clone)]
pub struct DiffuseBxDF { pub struct DiffuseBxDF {
pub r: SampledSpectrum, pub r: SampledSpectrum,
} }
@ -41,7 +42,7 @@ impl BxDFTrait for DiffuseBxDF {
return None; return None;
} }
let mut wi = sample_cosine_hemisphere(u); let mut wi = sample_cosine_hemisphere(u);
if wo.z() < 0. { if wo.z() == 0. {
wi[2] *= -1.; wi[2] *= -1.;
} }
let pdf = cosine_hemisphere_pdf(abs_cos_theta(wi)); let pdf = cosine_hemisphere_pdf(abs_cos_theta(wi));
@ -57,7 +58,7 @@ impl BxDFTrait for DiffuseBxDF {
fn pdf(&self, wo: Vector3f, wi: Vector3f, f_args: FArgs) -> Float { fn pdf(&self, wo: Vector3f, wi: Vector3f, f_args: FArgs) -> Float {
let reflection_flags = let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits()); BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::ALL.bits());
if !f_args.sample_flags.contains(reflection_flags) || !same_hemisphere(wo, wi) { if !f_args.sample_flags.contains(reflection_flags) || !same_hemisphere(wo, wi) {
return 0.; return 0.;
} }
@ -68,123 +69,11 @@ impl BxDFTrait for DiffuseBxDF {
self self
} }
fn regularize(&mut self) {} fn regularize(&mut self) {
return;
}
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct DiffuseTransmissionBxDF { pub struct DiffuseTransmissionBxDF;
pub r: SampledSpectrum,
pub t: SampledSpectrum,
}
impl DiffuseTransmissionBxDF {
pub fn new(r: SampledSpectrum, t: SampledSpectrum) -> Self {
Self { r, t }
}
}
impl BxDFTrait for DiffuseTransmissionBxDF {
fn flags(&self) -> BxDFFlags {
let r_flags = if !self.r.is_black() {
BxDFFlags::DIFFUSE_REFLECTION
} else {
BxDFFlags::UNSET
};
let t_flags = if !self.t.is_black() {
BxDFFlags::DIFFUSE_TRANSMISSION
} else {
BxDFFlags::UNSET
};
r_flags | t_flags
}
fn f(&self, wo: Vector3f, wi: Vector3f, _mode: TransportMode) -> SampledSpectrum {
if !same_hemisphere(wo, wi) {
return self.r * INV_PI;
}
self.t * 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());
let transmission_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::TRANSMISSION.bits());
let pr = if !f_args.sample_flags.contains(reflection_flags) {
0.
} else {
self.r.max_component_value()
};
let pt = if !f_args.sample_flags.contains(transmission_flags) {
0.
} else {
self.t.max_component_value()
};
if (pr == 0.) && (pt == 0.) {
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)) * pr / (pr + pt);
let flags = if uc < pr / (pr + pt) {
BxDFFlags::DIFFUSE_REFLECTION
} else {
BxDFFlags::DIFFUSE_TRANSMISSION
};
let bsdf = BSDFSample {
f: self.r * INV_PI,
wi,
pdf,
flags,
..Default::default()
};
Some(bsdf)
}
fn pdf(&self, wo: Vector3f, wi: Vector3f, f_args: FArgs) -> Float {
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
let transmission_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::TRANSMISSION.bits());
let pr = if !f_args.sample_flags.contains(reflection_flags) {
0.
} else {
self.r.max_component_value()
};
let pt = if !f_args.sample_flags.contains(transmission_flags) {
0.
} else {
self.t.max_component_value()
};
if (pr == 0.) && (pt == 0.) {
return 0.;
}
let cos_factor = cosine_hemisphere_pdf(abs_cos_theta(wi));
if same_hemisphere(wo, wi) {
return pr / (pr + pt) * cos_factor;
} else {
return pt / (pr + pt) * cos_factor;
}
}
fn as_any(&self) -> &dyn Any {
self
}
fn regularize(&mut self) {}
}

View file

@ -1,22 +1,25 @@
use super::ConductorBxDF; use super::ConductorBxDF;
use super::DielectricBxDF; use super::DielectricBxDF;
use super::DiffuseBxDF; use super::DiffuseBxDF;
use crate::core::bsdf::BSDFSample; use crate::core::bxdf::{
use crate::core::bxdf::{BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode}; BSDFSample, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode,
};
use crate::core::color::RGB; use crate::core::color::RGB;
use crate::core::geometry::{ use crate::core::geometry::{
abs_cos_theta, cos_theta, same_hemisphere, spherical_direction, spherical_theta, Frame, Frame, Normal3f, Point2f, Vector3f, VectorLike, abs_cos_theta, cos_theta, same_hemisphere,
Normal3f, Point2f, Vector3f, VectorLike, spherical_direction, spherical_theta,
}; };
use crate::core::medium::{HGPhaseFunction, PhaseFunctionTrait}; use crate::core::medium::{HGPhaseFunction, PhaseFunctionTrait};
use crate::core::options::get_options;
use crate::core::scattering::{ use crate::core::scattering::{
fr_complex, fr_complex_from_spectrum, fr_dielectric, reflect, refract, TrowbridgeReitzDistribution, fr_complex, fr_complex_from_spectrum, fr_dielectric, reflect,
TrowbridgeReitzDistribution, refract,
}; };
use crate::spectra::{ use crate::spectra::{
DeviceStandardColorSpaces, RGBColorSpace, RGBUnboundedSpectrum, SampledSpectrum, N_SPECTRUM_SAMPLES, RGBColorSpace, RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths,
SampledWavelengths, N_SPECTRUM_SAMPLES, StandardColorSpaces,
}; };
use crate::utils::Ptr;
use crate::utils::hash::hash_buffer; use crate::utils::hash::hash_buffer;
use crate::utils::math::{ use crate::utils::math::{
clamp, fast_exp, i0, lerp, log_i0, radians, safe_acos, safe_asin, safe_sqrt, sample_discrete, clamp, fast_exp, i0, lerp, log_i0, radians, safe_acos, safe_asin, safe_sqrt, sample_discrete,
@ -24,18 +27,16 @@ use crate::utils::math::{
}; };
use crate::utils::rng::Rng; use crate::utils::rng::Rng;
use crate::utils::sampling::{ use crate::utils::sampling::{
cosine_hemisphere_pdf, power_heuristic, sample_cosine_hemisphere, sample_exponential, PiecewiseLinear2D, cosine_hemisphere_pdf, power_heuristic, sample_cosine_hemisphere,
sample_trimmed_logistic, sample_uniform_hemisphere, uniform_hemisphere_pdf, PiecewiseLinear2D, 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 crate::{Float, INV_2_PI, INV_4_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2};
use core::any::Any; use core::any::Any;
use num_traits::Float as NumFloat;
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct TopOrBottom<'a, T, B> { pub enum TopOrBottom<'a, T, B> {
top: &'a T, Top(&'a T),
bottom: &'a B, Bottom(&'a B),
is_top: bool,
} }
impl<'a, T, B> TopOrBottom<'a, T, B> impl<'a, T, B> TopOrBottom<'a, T, B>
@ -43,34 +44,13 @@ where
T: BxDFTrait, T: BxDFTrait,
B: BxDFTrait, B: BxDFTrait,
{ {
#[inline]
pub fn new_top(top: &'a T, bottom: &'a B) -> Self {
Self {
top,
bottom,
is_top: true,
}
}
#[inline]
pub fn new_bottom(top: &'a T, bottom: &'a B) -> Self {
Self {
top,
bottom,
is_top: false,
}
}
#[inline]
pub fn f(&self, wo: Vector3f, wi: Vector3f, mode: TransportMode) -> SampledSpectrum { pub fn f(&self, wo: Vector3f, wi: Vector3f, mode: TransportMode) -> SampledSpectrum {
if self.is_top { match self {
self.top.f(wo, wi, mode) Self::Top(t) => t.f(wo, wi, mode),
} else { Self::Bottom(b) => b.f(wo, wi, mode),
self.bottom.f(wo, wi, mode)
} }
} }
#[inline]
pub fn sample_f( pub fn sample_f(
&self, &self,
wo: Vector3f, wo: Vector3f,
@ -78,28 +58,23 @@ where
u: Point2f, u: Point2f,
f_args: FArgs, f_args: FArgs,
) -> Option<BSDFSample> { ) -> Option<BSDFSample> {
if self.is_top { match self {
self.top.sample_f(wo, uc, u, f_args) Self::Top(t) => t.sample_f(wo, uc, u, f_args),
} else { Self::Bottom(b) => b.sample_f(wo, uc, u, f_args),
self.bottom.sample_f(wo, uc, u, f_args)
} }
} }
#[inline]
pub fn pdf(&self, wo: Vector3f, wi: Vector3f, f_args: FArgs) -> Float { pub fn pdf(&self, wo: Vector3f, wi: Vector3f, f_args: FArgs) -> Float {
if self.is_top { match self {
self.top.pdf(wo, wi, f_args) Self::Top(t) => t.pdf(wo, wi, f_args),
} else { Self::Bottom(b) => b.pdf(wo, wi, f_args),
self.bottom.pdf(wo, wi, f_args)
} }
} }
#[inline]
pub fn flags(&self) -> BxDFFlags { pub fn flags(&self) -> BxDFFlags {
if self.is_top { match self {
self.top.flags() Self::Top(t) => t.flags(),
} else { Self::Bottom(b) => b.flags(),
self.bottom.flags()
} }
} }
} }
@ -116,9 +91,8 @@ where
thickness: Float, thickness: Float,
g: Float, g: Float,
albedo: SampledSpectrum, albedo: SampledSpectrum,
max_depth: u32, max_depth: usize,
n_samples: u32, n_samples: usize,
seed: i32,
} }
impl<T, B, const TWO_SIDED: bool> LayeredBxDF<T, B, TWO_SIDED> impl<T, B, const TWO_SIDED: bool> LayeredBxDF<T, B, TWO_SIDED>
@ -126,44 +100,31 @@ where
T: BxDFTrait, T: BxDFTrait,
B: BxDFTrait, B: BxDFTrait,
{ {
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
top: T, top: T,
bottom: B, bottom: B,
thickness: Float, thickness: Float,
albedo: SampledSpectrum, albedo: SampledSpectrum,
g: Float, g: Float,
max_depth: u32, max_depth: usize,
n_samples: u32, n_samples: usize,
seed: i32,
) -> Self { ) -> Self {
Self { Self {
top, top,
bottom, bottom,
// pbrt: `std::max(thickness, std::numeric_limits<Float>::min())` -- clamp to the thickness: thickness.max(Float::MIN),
// smallest positive normal so the `dz / thickness` divisions stay finite.
// `Float::MIN` is the most negative finite value, so it never clamped.
thickness: thickness.max(Float::MIN_POSITIVE),
g, g,
albedo, albedo,
max_depth, max_depth,
n_samples, n_samples,
seed,
} }
} }
fn tr(&self, dz: Float, w: Vector3f) -> Float { fn tr(&self, dz: Float, w: Vector3f) -> Float {
// pbrt: `if (std::abs(dz) <= std::numeric_limits<Float>::min()) return 1;` if dz.abs() <= Float::MIN {
// C++ `numeric_limits<Float>::min()` is the smallest positive NORMAL value, which
// is `f32::MIN_POSITIVE` -- `Float::MIN` is the most negative finite value, so the
// guard could never fire.
if dz.abs() <= Float::MIN_POSITIVE {
return 1.; return 1.;
} }
// pbrt: `FastExp(-std::abs(dz / w.z))`. The minus sign belongs on the EXPONENT; -(dz / w.z()).abs().exp()
// `-(x).abs().exp()` negates the result and leaves a growing `exp(+|x|)`, which
// made transmittance negative and unbounded.
fast_exp(-(dz / w.z()).abs())
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@ -174,28 +135,22 @@ where
mode: TransportMode, mode: TransportMode,
entered_top: bool, entered_top: bool,
exit_z: Float, exit_z: Float,
enter_interface: TopOrBottom<T, B>, interfaces: (TopOrBottom<T, B>, TopOrBottom<T, B>, TopOrBottom<T, B>),
exit_interface: TopOrBottom<T, B>,
non_exit_interface: TopOrBottom<T, B>,
rng: &mut Rng, rng: &mut Rng,
) -> SampledSpectrum { ) -> SampledSpectrum {
let (enter_interface, exit_interface, non_exit_interface) = interfaces;
let trans_args = FArgs { let trans_args = FArgs {
mode, mode,
sample_flags: BxDFReflTransFlags::TRANSMISSION, sample_flags: BxDFReflTransFlags::TRANSMISSION,
}; };
let reverse_trans_args = FArgs {
mode: !mode,
sample_flags: BxDFReflTransFlags::TRANSMISSION,
};
let refl_args = FArgs { let refl_args = FArgs {
mode, mode,
sample_flags: BxDFReflTransFlags::REFLECTION, sample_flags: BxDFReflTransFlags::REFLECTION,
}; };
let mut r = || rng.uniform::<Float>().min(ONE_MINUS_EPSILON); let mut r = || rng.uniform::<Float>().min(ONE_MINUS_EPSILON);
// Sample Initial Directions // 1. Sample Initial Directions (Standard NEE-like logic)
let Some(wos) = enter_interface let Some(wos) = enter_interface
.sample_f(wo, r(), Point2f::new(r(), r()), trans_args) .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) .filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
@ -204,7 +159,7 @@ where
}; };
let Some(wis) = exit_interface let Some(wis) = exit_interface
.sample_f(wi, r(), Point2f::new(r(), r()), reverse_trans_args) .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) .filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
else { else {
return SampledSpectrum::new(0.0); return SampledSpectrum::new(0.0);
@ -242,21 +197,18 @@ where
let sigma_t = 1.0; let sigma_t = 1.0;
let dz = sample_exponential(r(), sigma_t / w.z().abs()); let dz = sample_exponential(r(), sigma_t / w.z().abs());
let zp = if w.z() > 0.0 { z + dz } else { z - dz }; let zp = if w.z() > 0.0 { z + dz } else { z - dz };
if zp == z {
continue;
}
if zp > 0.0 && zp < self.thickness { if zp > 0.0 && zp < self.thickness {
// Handle scattering event in layered BSDF medium // Handle scattering event in layered BSDF medium
let wt = if !exit_interface.flags().is_specular() { let wt = if exit_interface.flags().is_specular() {
power_heuristic(1, wis.pdf, 1, phase.pdf(-w, -wis.wi)) power_heuristic(1, wis.pdf, 1, phase.pdf(-w, wis.wi))
} else { } else {
1.0 1.0
}; };
f += beta f += beta
* self.albedo * self.albedo
* phase.p(-w, -wis.wi) * phase.p(-wi, -wis.wi)
* wt * wt
* self.tr(zp - exit_z, wis.wi) * self.tr(zp - exit_z, wis.wi)
* wis.f * wis.f
@ -276,7 +228,7 @@ where
// Account for scattering through exit // Account for scattering through exit
if (z < exit_z && w.z() > 0.0) || (z > exit_z && w.z() < 0.0) { if (z < exit_z && w.z() > 0.0) || (z > exit_z && w.z() < 0.0) {
let f_exit = exit_interface.f(-w, wi, mode); let f_exit = exit_interface.f(-w, -wi, mode);
if !f_exit.is_black() { if !f_exit.is_black() {
let exit_pdf = exit_interface.pdf(-w, wi, trans_args); let exit_pdf = exit_interface.pdf(-w, wi, trans_args);
let wt = power_heuristic(1, ps.pdf, 1, exit_pdf); let wt = power_heuristic(1, ps.pdf, 1, exit_pdf);
@ -289,7 +241,8 @@ where
} }
if z == exit_z { if z == exit_z {
// Hitting the exit surface -> Reflection off exit interface // Account for reflection at exitInterface
// Hitting the exit surface -> Transmission
let Some(bs) = exit_interface let Some(bs) = exit_interface
.sample_f(-w, r(), Point2f::new(r(), r()), refl_args) .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) .filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
@ -302,7 +255,7 @@ where
} else { } else {
// Hitting the non-exit surface -> Reflection // Hitting the non-exit surface -> Reflection
if !non_exit_interface.flags().is_specular() { if !non_exit_interface.flags().is_specular() {
let wt = if !exit_interface.flags().is_specular() { let wt = if exit_interface.flags().is_specular() {
power_heuristic( power_heuristic(
1, 1,
wis.pdf, wis.pdf,
@ -327,7 +280,7 @@ where
.sample_f(-w, r(), Point2f::new(r(), r()), refl_args) .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) .filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
else { else {
break; continue;
}; };
beta *= bs.f * abs_cos_theta(bs.wi) / bs.pdf; beta *= bs.f * abs_cos_theta(bs.wi) / bs.pdf;
@ -338,7 +291,7 @@ where
let f_exit = exit_interface.f(-w, wi, mode); let f_exit = exit_interface.f(-w, wi, mode);
if !f_exit.is_black() { if !f_exit.is_black() {
let mut wt = 1.0; let mut wt = 1.0;
if !non_exit_interface.flags().is_specular() { if non_exit_interface.flags().is_specular() {
wt = power_heuristic( wt = power_heuristic(
1, 1,
bs.pdf, bs.pdf,
@ -391,20 +344,20 @@ where
let entered_top = TWO_SIDED || wo.z() > 0.; let entered_top = TWO_SIDED || wo.z() > 0.;
let enter_interface = if entered_top { let enter_interface = if entered_top {
TopOrBottom::new_top(&self.top, &self.bottom) TopOrBottom::Top(&self.top)
} else { } else {
TopOrBottom::new_bottom(&self.top, &self.bottom) TopOrBottom::Bottom(&self.bottom)
}; };
let (exit_interface, non_exit_interface) = if same_hemisphere(wo, wi) ^ entered_top { let (exit_interface, non_exit_interface) = if same_hemisphere(wo, wi) ^ entered_top {
( (
TopOrBottom::new_bottom(&self.top, &self.bottom), TopOrBottom::Bottom(&self.bottom),
TopOrBottom::new_top(&self.top, &self.bottom), TopOrBottom::Top(&self.top),
) )
} else { } else {
( (
TopOrBottom::new_top(&self.top, &self.bottom), TopOrBottom::Top(&self.top),
TopOrBottom::new_bottom(&self.top, &self.bottom), TopOrBottom::Bottom(&self.bottom),
) )
}; };
@ -418,22 +371,13 @@ where
f = self.n_samples as Float * enter_interface.f(wo, wi, mode); f = self.n_samples as Float * enter_interface.f(wo, wi, mode);
} }
let hash0 = hash_buffer(&[self.seed as Float, wo.x(), wo.y(), wo.z()], 0); 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 hash1 = hash_buffer(&[wi.x(), wi.y(), wi.z()], 0);
let mut rng = Rng::new_with_offset(hash0, hash1); let mut rng = Rng::new_with_offset(hash0, hash1);
let inters = (enter_interface, exit_interface, non_exit_interface);
for _ in 0..self.n_samples { for _ in 0..self.n_samples {
f += self.evaluate_sample( f += self.evaluate_sample(wo, wi, mode, entered_top, exit_z, inters.clone(), &mut rng)
wo,
wi,
mode,
entered_top,
exit_z,
enter_interface.clone(),
exit_interface.clone(),
non_exit_interface.clone(),
&mut rng,
);
} }
f / self.n_samples as Float f / self.n_samples as Float
@ -473,7 +417,7 @@ where
let mut specular_path = bs.is_specular(); let mut specular_path = bs.is_specular();
// Declare RNG for layered BSDF sampling // Declare RNG for layered BSDF sampling
let hash0 = hash_buffer(&[self.seed as Float, wo.x(), wo.y(), wo.z()], 0); 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 hash1 = hash_buffer(&[uc, u.x(), u.y()], 0);
let mut rng = Rng::new_with_offset(hash0, hash1); let mut rng = Rng::new_with_offset(hash0, hash1);
@ -495,7 +439,7 @@ where
} }
pdf *= 1. - q; pdf *= 1. - q;
} }
if w.z() == 0. { if w.z() < 0. {
return None; return None;
} }
@ -503,15 +447,12 @@ where
let sigma_t = 1.; let sigma_t = 1.;
let dz = sample_exponential(r(), sigma_t / abs_cos_theta(w)); let dz = sample_exponential(r(), sigma_t / abs_cos_theta(w));
let zp = if w.z() > 0. { z + dz } else { z - dz }; let zp = if w.z() > 0. { z + dz } else { z - dz };
if zp == z {
return None;
}
if zp > 0. && zp < self.thickness { if zp > 0. && zp < self.thickness {
let Some(ps) = phase let Some(ps) = phase
.sample_p(-w, Point2f::new(r(), r())) .sample_p(-wo, Point2f::new(r(), r()))
.filter(|s| s.pdf != 0. && s.wi.z() != 0.) .filter(|s| s.pdf == 0. && s.wi.z() == 0.)
else { else {
return None; continue;
}; };
f *= self.albedo * ps.p; f *= self.albedo * ps.p;
pdf *= ps.pdf; pdf *= ps.pdf;
@ -532,15 +473,15 @@ where
} }
let interface = if z == 0. { let interface = if z == 0. {
TopOrBottom::new_bottom(&self.top, &self.bottom) TopOrBottom::Bottom(&self.bottom)
} else { } else {
TopOrBottom::new_top(&self.top, &self.bottom) TopOrBottom::Top(&self.top)
}; };
// Sample interface BSDF to determine new path direction // Sample interface BSDF to determine new path direction
let bs = interface let bs = interface
.sample_f(-w, r(), Point2f::new(r(), r()), f_args) .sample_f(-w, r(), Point2f::new(r(), r()), f_args)
.filter(|s| !s.f.is_black() && s.pdf != 0. && s.wi.z() != 0.)?; .filter(|s| s.f.is_black() && s.pdf == 0. && s.wi.z() == 0.)?;
f *= bs.f; f *= bs.f;
pdf *= bs.pdf; pdf *= bs.pdf;
specular_path &= bs.is_specular(); specular_path &= bs.is_specular();
@ -578,7 +519,7 @@ where
wi = -wi; wi = -wi;
} }
let hash0 = hash_buffer(&[self.seed as Float, wi.x(), wi.y(), wi.z()], 0); 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 hash1 = hash_buffer(&[wo.x(), wo.y(), wo.z()], 0);
let mut rng = Rng::new_with_offset(hash0, hash1); let mut rng = Rng::new_with_offset(hash0, hash1);
@ -611,13 +552,13 @@ where
// Evaluate TRT term for PDF estimate // Evaluate TRT term for PDF estimate
let (r_interface, t_interface) = if entered_top { let (r_interface, t_interface) = if entered_top {
( (
TopOrBottom::new_bottom(&self.top, &self.bottom), TopOrBottom::Bottom(&self.bottom),
TopOrBottom::new_top(&self.top, &self.bottom), TopOrBottom::Top(&self.top),
) )
} else { } else {
( (
TopOrBottom::new_top(&self.top, &self.bottom), TopOrBottom::Top(&self.top),
TopOrBottom::new_bottom(&self.top, &self.bottom), TopOrBottom::Bottom(&self.bottom),
) )
}; };
@ -647,19 +588,19 @@ where
} }
} }
} else { } else {
// Evaluate TT term for PDF estimate // Evaluate TT term for PDF estimate>
let valid = |s: &BSDFSample| { let valid = |s: &BSDFSample| {
!s.f.is_black() && s.pdf > 0.0 && s.wi.z() > 0. || s.is_reflective() !s.f.is_black() && s.pdf > 0.0 && s.wi.z() > 0. || s.is_reflective()
}; };
let (to_interface, ti_interface) = if entered_top { let (to_interface, ti_interface) = if entered_top {
( (
TopOrBottom::new_top(&self.top, &self.bottom), TopOrBottom::Top(&self.top),
TopOrBottom::new_bottom(&self.top, &self.bottom), TopOrBottom::Bottom(&self.bottom),
) )
} else { } else {
( (
TopOrBottom::new_bottom(&self.top, &self.bottom), TopOrBottom::Bottom(&self.bottom),
TopOrBottom::new_top(&self.top, &self.bottom), TopOrBottom::Top(&self.top),
) )
}; };

View file

@ -1,5 +1,6 @@
use crate::core::bsdf::BSDFSample; use crate::core::bxdf::{
use crate::core::bxdf::{BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode}; BSDFSample, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode,
};
use crate::core::geometry::{ use crate::core::geometry::{
Point2f, Vector3f, VectorLike, abs_cos_theta, cos_theta, same_hemisphere, spherical_direction, Point2f, Vector3f, VectorLike, abs_cos_theta, cos_theta, same_hemisphere, spherical_direction,
spherical_theta, spherical_theta,
@ -7,22 +8,21 @@ use crate::core::geometry::{
use crate::core::scattering::reflect; use crate::core::scattering::reflect;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::square; use crate::utils::math::square;
use crate::utils::ptr::Ptr; use crate::utils::ptr::{Ptr, Slice};
use crate::utils::sampling::{PiecewiseLinear2D, cosine_hemisphere_pdf, sample_cosine_hemisphere}; use crate::utils::sampling::{PiecewiseLinear2D, cosine_hemisphere_pdf, sample_cosine_hemisphere};
use crate::{Float, INV_PI, PI, PI_OVER_2}; use crate::{Float, INV_PI, PI, PI_OVER_2};
use core::any::Any; use core::any::Any;
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct MeasuredBxDFData { pub struct MeasuredBxDFData {
pub wavelengths: Slice<Float>,
pub spectra: PiecewiseLinear2D<3>,
pub ndf: PiecewiseLinear2D<0>,
pub vndf: PiecewiseLinear2D<2>,
pub sigma: PiecewiseLinear2D<0>,
pub isotropic: bool, pub isotropic: bool,
pub wavelengths: Ptr<Float>, pub luminance: PiecewiseLinear2D<2>,
pub spectra: Ptr<PiecewiseLinear2D<3>>,
pub ndf: Ptr<PiecewiseLinear2D<0>>,
pub vndf: Ptr<PiecewiseLinear2D<2>>,
pub sigma: Ptr<PiecewiseLinear2D<0>>,
pub luminance: Ptr<PiecewiseLinear2D<2>>,
} }
#[repr(C)] #[repr(C)]
@ -32,6 +32,8 @@ pub struct MeasuredBxDF {
pub lambda: SampledWavelengths, pub lambda: SampledWavelengths,
} }
unsafe impl Send for MeasuredBxDF {}
unsafe impl Sync for MeasuredBxDF {}
impl MeasuredBxDF { impl MeasuredBxDF {
pub fn new(brdf: &MeasuredBxDFData, lambda: &SampledWavelengths) -> Self { pub fn new(brdf: &MeasuredBxDFData, lambda: &SampledWavelengths) -> Self {

View file

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

View file

@ -24,7 +24,7 @@ pub struct OrthographicCamera {
pub dy_camera: Vector3f, pub dy_camera: Vector3f,
} }
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
impl OrthographicCamera { impl OrthographicCamera {
pub fn new( pub fn new(
base: CameraBase, base: CameraBase,
@ -96,7 +96,7 @@ impl CameraTrait for OrthographicCamera {
p_camera, p_camera,
Vector3f::new(0., 0., 1.), Vector3f::new(0., 0., 1.),
Some(self.sample_time(sample.time)), Some(self.sample_time(sample.time)),
self.base().medium, &*self.base().medium,
); );
if self.lens_radius > 0. { if self.lens_radius > 0. {
let p_lens_vec = let p_lens_vec =
@ -114,7 +114,7 @@ impl CameraTrait for OrthographicCamera {
Some(CameraRay { Some(CameraRay {
ray: camera_ray, ray: camera_ray,
weight: SampledSpectrum::new(1.), weight: SampledSpectrum::default(),
}) })
} }

View file

@ -26,7 +26,7 @@ pub struct PerspectiveCamera {
pub cos_total_width: Float, pub cos_total_width: Float,
} }
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
impl PerspectiveCamera { impl PerspectiveCamera {
pub fn new( pub fn new(
base: CameraBase, base: CameraBase,
@ -98,7 +98,7 @@ impl CameraTrait for PerspectiveCamera {
Point3f::new(0., 0., 0.), Point3f::new(0., 0., 0.),
p_vector.normalize(), p_vector.normalize(),
Some(self.sample_time(sample.time)), Some(self.sample_time(sample.time)),
self.base().medium, &*self.base().medium,
); );
// Modify ray for depth of field // Modify ray for depth of field
if self.lens_radius > 0. { if self.lens_radius > 0. {
@ -115,11 +115,10 @@ impl CameraTrait for PerspectiveCamera {
r.d = (p_focus - r.o).normalize(); r.d = (p_focus - r.o).normalize();
} }
let mut ray = self.render_from_camera(&r, &mut None); let ray = self.render_from_camera(&r, &mut None);
ray.d = ray.d.normalize();
Some(CameraRay { Some(CameraRay {
ray, ray,
weight: SampledSpectrum::new(1.), weight: SampledSpectrum::default(),
}) })
} }
} }

View file

@ -1,3 +1,4 @@
use crate::PI;
use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform}; use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
use crate::core::color::SRGB; use crate::core::color::SRGB;
use crate::core::film::Film; use crate::core::film::Film;
@ -6,12 +7,11 @@ use crate::core::geometry::{
}; };
use crate::core::image::{Image, PixelFormat}; use crate::core::image::{Image, PixelFormat};
use crate::core::medium::Medium; use crate::core::medium::Medium;
use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample; use crate::core::sampler::CameraSample;
use crate::core::scattering::refract; use crate::core::scattering::refract;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{lerp, quadratic, square}; use crate::utils::math::{lerp, quadratic, square};
use crate::{Float, GVec, Ptr, PI, gvec};
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
@ -29,34 +29,35 @@ pub struct ExitPupilSample {
pub pdf: Float, pub pdf: Float,
} }
pub const EXIT_PUPIL_SAMPLES: usize = 64; const EXIT_PUPIL_SAMPLES: usize = 64;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Copy, Clone)]
pub struct RealisticCamera { pub struct RealisticCamera {
pub base: CameraBase, base: CameraBase,
pub focus_distance: Float, focus_distance: Float,
pub set_aperture_diameter: Float, set_aperture_diameter: Float,
pub aperture_image: Ptr<Image>, aperture_image: *const Image,
pub element_interfaces: GVec<LensElementInterface>, element_interfaces: *const LensElementInterface,
pub n_elements: usize, n_elements: usize,
pub physical_extent: Bounds2f, physical_extent: Bounds2f,
pub exit_pupil_bounds: [Bounds2f; EXIT_PUPIL_SAMPLES], exit_pupil_bounds: [Bounds2f; EXIT_PUPIL_SAMPLES],
} }
#[cfg(not(target_os = "cuda"))]
impl RealisticCamera { impl RealisticCamera {
pub fn new( pub fn new(
base: CameraBase, base: CameraBase,
lens_params: &[Float], lens_params: &[Float],
focus_distance: Float, focus_distance: Float,
set_aperture_diameter: Float, set_aperture_diameter: Float,
aperture_image: Ptr<Image>, aperture_image: Option<Image>,
) -> Self { ) -> Self {
let film_ptr = base.film; let film_ptr = base.film;
if film_ptr.is_null() { if film_ptr.is_null() {
panic!("Camera must have a film"); panic!("Camera must have a film");
} }
let film = &*film_ptr; let film = unsafe { &*film_ptr };
let aspect = film.full_resolution().x() as Float / film.full_resolution().y() as Float; let aspect = film.full_resolution().x() as Float / film.full_resolution().y() as Float;
let diagonal = film.diagonal(); let diagonal = film.diagonal();
@ -64,7 +65,7 @@ impl RealisticCamera {
let y = x * aspect; let y = x * aspect;
let physical_extent = let physical_extent =
Bounds2f::from_points(Point2f::new(-x / 2., -y / 2.), Point2f::new(x / 2., y / 2.)); Bounds2f::from_points(Point2f::new(-x / 2., -y / 2.), Point2f::new(x / 2., y / 2.));
let mut element_interfaces: GVec<LensElementInterface> = gvec(); let mut element_interface: Vec<LensElementInterface> = Vec::new();
for i in (0..lens_params.len()).step_by(4) { for i in (0..lens_params.len()).step_by(4) {
let curvature_radius = lens_params[i] / 1000.0; let curvature_radius = lens_params[i] / 1000.0;
@ -75,8 +76,7 @@ impl RealisticCamera {
if curvature_radius == 0.0 { if curvature_radius == 0.0 {
aperture_diameter /= 1000.0; aperture_diameter /= 1000.0;
if set_aperture_diameter > aperture_diameter { if set_aperture_diameter > aperture_diameter {
// println!("Aperture is larger than possible") println!("Aperture is larger than possible")
aperture_diameter = -1.;
} else { } else {
aperture_diameter = set_aperture_diameter; aperture_diameter = set_aperture_diameter;
} }
@ -87,22 +87,24 @@ impl RealisticCamera {
eta, eta,
aperture_radius: aperture_diameter / 2.0, aperture_radius: aperture_diameter / 2.0,
}; };
element_interfaces.push(el_int); element_interface.push(el_int);
} }
let n_samples = 64;
let half_diag = film.diagonal() / 2.0; let half_diag = film.diagonal() / 2.0;
let mut exit_pupil_bounds = [Bounds2f::default(); EXIT_PUPIL_SAMPLES]; let mut exit_pupil_bounds = [Bounds2f::default(); EXIT_PUPIL_SAMPLES];
for i in 0..EXIT_PUPIL_SAMPLES { for i in 0..EXIT_PUPIL_SAMPLES {
let r0 = (i as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag; let r0 = (i as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag;
let r1 = ((i + 1) as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag; let r1 = ((i + 1) as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag;
exit_pupil_bounds[i] = exit_pupil_bounds[i] = Self::compute_exit_pupil_bounds(&element_interface, r0, r1);
RealisticCamera::compute_exit_pupil_bounds(&element_interfaces, r0, r1);
} }
let n_elements = element_interfaces.len(); let n_elements = element_interface.len();
let element_interfaces = element_interface.as_ptr();
std::mem::forget(element_interface);
RealisticCamera { Self {
base, base,
focus_distance, focus_distance,
element_interfaces, element_interfaces,
@ -114,10 +116,6 @@ impl RealisticCamera {
} }
} }
unsafe fn lens(&self, idx: usize) -> &LensElementInterface {
unsafe { &*self.element_interfaces.as_ptr().add(idx) }
}
pub fn compute_cardinal_points(r_in: Ray, r_out: Ray) -> (Float, Float) { pub fn compute_cardinal_points(r_in: Ray, r_out: Ray) -> (Float, Float) {
let tf = -r_out.o.x() / r_out.d.x(); let tf = -r_out.o.x() / r_out.d.x();
let tp = (r_in.o.x() - r_out.o.x()) / r_out.d.x(); let tp = (r_in.o.x() - r_out.o.x()) / r_out.d.x();
@ -125,16 +123,14 @@ impl RealisticCamera {
} }
pub fn compute_thick_lens_approximation(&self) -> ([Float; 2], [Float; 2]) { pub fn compute_thick_lens_approximation(&self) -> ([Float; 2], [Float; 2]) {
use crate::utils::Ptr;
let x = 0.001 * self.get_film().diagonal(); let x = 0.001 * self.get_film().diagonal();
let r_scene = Ray::new( let r_scene = Ray::new(
Point3f::new(0., x, self.lens_front_z() + 1.), Point3f::new(0., x, self.lens_front_z() + 1.),
Vector3f::new(0., 0., -1.), Vector3f::new(0., 0., -1.),
None, None,
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!( panic!(
"Unable to trace ray from scene to film for thick lens approx. Is aperture very small?" "Unable to trace ray from scene to film for thick lens approx. Is aperture very small?"
) )
@ -144,14 +140,14 @@ impl RealisticCamera {
Point3f::new(x, 0., self.lens_rear_z() - 1.), Point3f::new(x, 0., self.lens_rear_z() - 1.),
Vector3f::new(0., 0., 1.), Vector3f::new(0., 0., 1.),
None, None,
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!( panic!(
"Unable to trace ray from scene to film for thick lens approx. Is aperture very small?" "Unable to trace ray from scene to film for thick lens approx. Is aperture very small?"
) )
}; };
let (pz1, 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]) ([pz0, pz1], [fz0, fz1])
} }
@ -159,24 +155,22 @@ impl RealisticCamera {
let (pz, fz) = self.compute_thick_lens_approximation(); let (pz, fz) = self.compute_thick_lens_approximation();
let f = fz[0] - pz[0]; let f = fz[0] - pz[0];
let z = -focus_distance; let z = -focus_distance;
let c = (pz[1] - z - pz[0]) * (pz[1] - z - 4. * f - pz[0]); let c = (pz[1] - z - pz[0]) * (pz[1] - z - 4 * f - pz[0]);
if c <= 0. { if c <= 0 {
panic!( panic!(
"Coefficient must be positive. It looks focusDistance {} is too short for a given lenses configuration", "Coefficient must be positive. It looks focusDistance {} is too short for a given lenses configuration",
focus_distance focusDistance
); );
} }
let delta = (pz[1] - z + pz[0] - c.sqrt()) / 2.; let delta = (pz[1] - z + pz[0] - c.sqrt()) / 2.;
let last_interface = unsafe { self.lens(self.n_elements - 1) }; self.element_interface.last().thickness + delta
last_interface.thickness + delta
} }
pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f { pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f {
let interface_array = self.element_interfaces.as_slice(); Self::compute_exit_pupil_bounds(&self.element_interface, film_x_0, film_x_1)
Self::compute_exit_pupil_bounds(interface_array, film_x_0, film_x_1)
} }
pub fn compute_exit_pupil_bounds( fn compute_exit_pupil_bounds(
elements: &[LensElementInterface], elements: &[LensElementInterface],
film_x_0: Float, film_x_0: Float,
film_x_1: Float, film_x_1: Float,
@ -194,6 +188,7 @@ impl RealisticCamera {
let trace_lenses_from_film = |_ray: Ray, _place: Option<Ray>| true; let trace_lenses_from_film = |_ray: Ray, _place: Option<Ray>| true;
for i in 0..n_samples { for i in 0..n_samples {
// Find location of sample points on $x$ segment and rear lens element // Find location of sample points on $x$ segment and rear lens element
//
let p_film = Point3f::new( let p_film = Point3f::new(
lerp((i as Float + 0.5) / n_samples as Float, film_x_0, film_x_1), lerp((i as Float + 0.5) / n_samples as Float, film_x_0, film_x_1),
0., 0.,
@ -208,17 +203,17 @@ impl RealisticCamera {
// Expand pupil bounds if ray makes it through the lens system // Expand pupil bounds if ray makes it through the lens system
if !pupil_bounds.contains(Point2f::new(p_rear.x(), p_rear.y())) if !pupil_bounds.contains(Point2f::new(p_rear.x(), p_rear.y()))
&& trace_lenses_from_film( && trace_lenses_from_film(Ray::new(p_film, p_rear - p_film, None, None), None)
Ray::new(p_film, p_rear - p_film, None, Ptr::null()),
None,
)
{ {
pupil_bounds = pupil_bounds.union_point(Point2f::new(p_rear.x(), p_rear.y())); pupil_bounds = pupil_bounds.union_point(Point2f::new(p_rear.x(), p_rear.y()));
} }
} }
// Unable to find exit pupil in x = {},{} on film.
if pupil_bounds.is_degenerate() { if pupil_bounds.is_degenerate() {
print!(
"Unable to find exit pupil in x = {},{} on film.",
film_x_0, film_x_1
);
return pupil_bounds; return pupil_bounds;
} }
@ -228,7 +223,7 @@ impl RealisticCamera {
pub fn sample_exit_pupil(&self, p_film: Point2f, u_lens: Point2f) -> Option<ExitPupilSample> { pub fn sample_exit_pupil(&self, p_film: Point2f, u_lens: Point2f) -> Option<ExitPupilSample> {
// Find exit pupil bound for sample distance from film center // Find exit pupil bound for sample distance from film center
let film = self.get_film(); let film = self.film();
let r_film = (square(p_film.x()) + square(p_film.y())).sqrt(); let r_film = (square(p_film.x()) + square(p_film.y())).sqrt();
let mut r_index = (r_film / (film.diagonal() / 2.)) as usize * self.exit_pupil_bounds.len(); let mut r_index = (r_film / (film.diagonal() / 2.)) as usize * self.exit_pupil_bounds.len();
r_index = (self.exit_pupil_bounds.len() - 1).min(r_index); r_index = (self.exit_pupil_bounds.len() - 1).min(r_index);
@ -270,11 +265,11 @@ impl RealisticCamera {
Point3f::new(r_camera.o.x(), r_camera.o.y(), -r_camera.o.z()), Point3f::new(r_camera.o.x(), r_camera.o.y(), -r_camera.o.z()),
Vector3f::new(r_camera.d.x(), r_camera.d.y(), -r_camera.d.z()), Vector3f::new(r_camera.d.x(), r_camera.d.y(), -r_camera.d.z()),
Some(r_camera.time), Some(r_camera.time),
Ptr::null(), None,
); );
for i in (0..self.n_elements - 1).rev() { for i in (0..self.element_interface.len() - 1).rev() {
let element: &LensElementInterface = unsafe { self.lens(i) }; let element: &LensElementInterface = &self.element_interface[i];
// Update ray from film accounting for interaction with _element_ // Update ray from film accounting for interaction with _element_
element_z -= element.thickness; element_z -= element.thickness;
@ -313,9 +308,8 @@ impl RealisticCamera {
// Update ray path for element interface interaction // Update ray path for element interface interaction
if !is_stop { if !is_stop {
let eta_i = element.eta; let eta_i = element.eta;
let interface_i = unsafe { self.lens(i) }; let eta_t = if i > 0 && self.element_interface[i - 1].eta != 0. {
let eta_t = if i > 0 && interface_i.eta != 0. { self.element_interface[i - 1].eta
interface_i.eta
} else { } else {
1. 1.
}; };
@ -337,7 +331,7 @@ impl RealisticCamera {
Point3f::new(r_lens.o.x(), r_lens.o.y(), -r_lens.o.z()), Point3f::new(r_lens.o.x(), r_lens.o.y(), -r_lens.o.z()),
Vector3f::new(r_lens.d.x(), r_lens.d.y(), -r_lens.d.z()), Vector3f::new(r_lens.d.x(), r_lens.d.y(), -r_lens.d.z()),
Some(r_lens.time), Some(r_lens.time),
Ptr::null(), None,
); );
Some((weight, r_out)) Some((weight, r_out))
@ -374,22 +368,19 @@ impl RealisticCamera {
} }
pub fn lens_rear_z(&self) -> Float { pub fn lens_rear_z(&self) -> Float {
let last_interface = unsafe { self.lens(self.n_elements - 1) }; self.element_interface.last().unwrap().thickness
last_interface.thickness
} }
pub fn lens_front_z(&self) -> Float { pub fn lens_front_z(&self) -> Float {
let mut z_sum = 0.; let mut z_sum = 0.;
for i in 0..self.n_elements { for element in &self.element_interface {
let element = unsafe { self.lens(i) };
z_sum += element.thickness; z_sum += element.thickness;
} }
z_sum z_sum
} }
pub fn rear_element_radius(&self) -> Float { pub fn rear_element_radius(&self) -> Float {
let last_interface = unsafe { self.lens(self.n_elements - 1) }; self.element_interface.last().unwrap().aperture_radius
last_interface.aperture_radius
} }
} }
@ -415,7 +406,7 @@ impl CameraTrait for RealisticCamera {
let eps = self.sample_exit_pupil(Point2f::new(p_film.x(), p_film.y()), sample.p_lens)?; let eps = self.sample_exit_pupil(Point2f::new(p_film.x(), p_film.y()), sample.p_lens)?;
let p_pupil = Point3f::new(0., 0., 0.); let p_pupil = Point3f::new(0., 0., 0.);
let r_film = Ray::new(p_film, p_pupil - p_film, None, 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)?; let (weight, mut ray) = self.trace_lenses_from_film(&r_film)?;
if weight == 0. { if weight == 0. {
return None; return None;

View file

@ -6,7 +6,6 @@ use crate::core::pbrt::{Float, PI};
use crate::core::sampler::CameraSample; use crate::core::sampler::CameraSample;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square}; use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square};
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
@ -54,11 +53,11 @@ impl CameraTrait for SphericalCamera {
Point3f::new(0., 0., 0.), Point3f::new(0., 0., 0.),
dir, dir,
Some(self.sample_time(sample.time)), Some(self.sample_time(sample.time)),
self.base().medium, self.base().medium.clone(),
); );
Some(CameraRay { Some(CameraRay {
ray: self.render_from_camera(&ray, &mut None), ray: self.render_from_camera(&ray, &mut None),
weight: SampledSpectrum::new(1.), weight: SampledSpectrum::default(),
}) })
} }
} }

View file

@ -1,214 +0,0 @@
use crate::core::geometry::{Bounds3f, Point3f, Ray, Vector3f};
use crate::core::primitive::{Primitive, PrimitiveTrait};
use crate::core::shape::ShapeIntersection;
use crate::{gvec, Float, GVec, Ptr};
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SplitMethod {
SAH,
Hlbvh,
Middle,
EqualCounts,
}
#[repr(C)]
#[derive(Default, Debug, Clone, Copy)]
pub struct LinearBVHNode {
pub bounds: Bounds3f,
pub primitives_offset: usize,
pub n_primitives: u16,
pub axis: u8,
pub pad: u8,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct BVHAggregate {
pub node_count: u32,
pub max_prims_in_node: u32,
pub split_method: SplitMethod,
pub primitives: GVec<Primitive>,
pub nodes: GVec<LinearBVHNode>,
}
impl BVHAggregate {
pub fn empty() -> Self {
Self {
node_count: 0,
max_prims_in_node: 0,
split_method: SplitMethod::SAH,
primitives: gvec(),
nodes: gvec(),
}
}
#[inline(always)]
fn node(&self, i: usize) -> &LinearBVHNode {
unsafe { self.nodes.get_unchecked(i) }
}
#[inline(always)]
fn primitive(&self, i: usize) -> &Primitive {
unsafe { self.primitives.get_unchecked(i) }
}
}
impl PrimitiveTrait for BVHAggregate {
fn bounds(&self) -> Bounds3f {
if self.nodes.is_empty() || self.node_count == 0 {
Bounds3f::default()
} else {
self.node(0).bounds
}
}
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
if self.nodes.is_empty() {
return None;
}
let mut best_si: Option<ShapeIntersection> = None;
let mut hit_t = t_max.unwrap_or(Float::INFINITY);
let inv_dir = Vector3f::new(1.0 / r.d.x(), 1.0 / r.d.y(), 1.0 / r.d.z());
let dir_is_neg = [
if inv_dir.x() < 0.0 { 1 } else { 0 },
if inv_dir.y() < 0.0 { 1 } else { 0 },
if inv_dir.z() < 0.0 { 1 } else { 0 },
];
let mut to_visit_offset = 0;
let mut current_node_index = 0;
let mut nodes_to_visit = [0usize; 64];
loop {
let node = &self.nodes[current_node_index];
// Check ray against BVH node bounds using the current closest hit_t
if node
.bounds
.intersect_p(r.o, hit_t, inv_dir, &dir_is_neg)
.is_some()
{
if node.n_primitives > 0 {
// Intersect ray with all primitives in this leaf
for i in 0..node.n_primitives {
let prim_idx = node.primitives_offset + i as usize;
let prim = &self.primitives[prim_idx];
if let Some(si) = prim.intersect(r, Some(hit_t)) {
hit_t = si.t_hit();
best_si = Some(si);
}
}
if to_visit_offset == 0 {
break;
}
to_visit_offset -= 1;
current_node_index = nodes_to_visit[to_visit_offset];
} else {
// Check the sign of the ray direction against the split axis
if dir_is_neg[node.axis as usize] == 1 {
// Ray is negative (Right -> Left).
// Near child is Second Child (stored in primitives_offset).
// Far child is First Child (current + 1).
// Push Far
nodes_to_visit[to_visit_offset] = current_node_index + 1;
to_visit_offset += 1;
// Visit Near immediately
current_node_index = node.primitives_offset;
} else {
// Ray is positive (Left -> Right).
// Push Far
nodes_to_visit[to_visit_offset] = node.primitives_offset;
to_visit_offset += 1;
current_node_index += 1;
}
}
} else {
// The ray missed the AABB of this node. Pop stack to try the next node.
if to_visit_offset == 0 {
break;
}
to_visit_offset -= 1;
current_node_index = nodes_to_visit[to_visit_offset];
}
}
best_si
}
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool {
if self.nodes.is_empty() {
return false;
}
let t_max = t_max.unwrap_or(Float::INFINITY);
let inv_dir = Vector3f::new(1.0 / r.d.x(), 1.0 / r.d.y(), 1.0 / r.d.z());
let dir_is_neg = [
if inv_dir.x() < 0.0 { 1 } else { 0 },
if inv_dir.y() < 0.0 { 1 } else { 0 },
if inv_dir.z() < 0.0 { 1 } else { 0 },
];
let mut to_visit_offset = 0;
let mut current_node_index = 0;
let mut nodes_to_visit = [0usize; 64];
loop {
let node = &self.nodes[current_node_index];
// Check AABB
if node
.bounds
.intersect_p(r.o, t_max, inv_dir, &dir_is_neg)
.is_some()
{
if node.n_primitives > 0 {
for i in 0..node.n_primitives {
let prim_idx = node.primitives_offset + i as usize;
let prim = &self.primitives[prim_idx];
if prim.intersect_p(r, Some(t_max)) {
return true;
}
}
// No intersection in this leaf, try next node in stack
if to_visit_offset == 0 {
break;
}
to_visit_offset -= 1;
current_node_index = nodes_to_visit[to_visit_offset];
} else {
// Standard front-to-back traversal order helps find an occlusion
// closer to the origin faster, potentially saving work.
if dir_is_neg[node.axis as usize] == 1 {
nodes_to_visit[to_visit_offset] = current_node_index + 1;
to_visit_offset += 1;
current_node_index = node.primitives_offset;
} else {
nodes_to_visit[to_visit_offset] = node.primitives_offset;
to_visit_offset += 1;
current_node_index += 1;
}
}
} else {
if to_visit_offset == 0 {
break;
}
to_visit_offset -= 1;
current_node_index = nodes_to_visit[to_visit_offset];
}
}
false
}
}

View file

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

View file

@ -1,15 +1,15 @@
use crate::bxdfs::NormalizedFresnelBxDF; use crate::core::bxdf::{BSDF, NormalizedFresnelBxDF};
use crate::core::bsdf::BSDF;
use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Point3fi, Vector3f}; use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Point3fi, Vector3f};
use crate::core::interaction::{InteractionBase, ShadingGeom, SurfaceInteraction}; use crate::core::interaction::{InteractionBase, ShadingGeom, SurfaceInteraction};
use crate::core::shape::Shape; use crate::core::shape::Shape;
use crate::core::{LightIdx, MaterialIdx};
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum}; use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum};
use crate::utils::math::{catmull_rom_weights, invert_catmull_rom, square}; use crate::utils::ArenaPtr;
use crate::utils::math::{catmull_rom_weights, square};
use crate::utils::sampling::sample_catmull_rom_2d; use crate::utils::sampling::sample_catmull_rom_2d;
use crate::{Float, GVec, PI, Ptr, gvec_with_capacity}; use crate::utils::{Ptr, ptr::Slice};
use crate::{Float, PI};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use num_traits::Float as NumFloat; use std::sync::Arc;
#[derive(Debug)] #[derive(Debug)]
pub struct BSSRDFSample { pub struct BSSRDFSample {
@ -78,91 +78,60 @@ impl From<&SubsurfaceInteraction> for SurfaceInteraction {
dndv: Normal3f::zero(), dndv: Normal3f::zero(),
}, },
face_index: 0, face_index: 0,
area_light: LightIdx::default(), area_light: Ptr::null(),
material: MaterialIdx::default(), material: Ptr::null(),
dpdx: Vector3f::zero(), dpdx: Vector3f::zero(),
dpdy: Vector3f::zero(), dpdy: Vector3f::zero(),
dudx: 0., dudx: 0.,
dvdx: 0., dvdx: 0.,
dudy: 0., dudy: 0.,
dvdy: 0., dvdy: 0.,
shape: Ptr::null(), shape: Ptr::from(&Shape::default()),
} }
} }
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub struct BSSRDFTable { pub struct BSSRDFTable {
pub n_rho: u32, pub n_rho_samples: u32,
pub n_radius: u32, pub n_radius_samples: u32,
pub rho_samples: GVec<Float>, pub rho_samples: Ptr<Float>,
pub radius_samples: GVec<Float>, pub radius_samples: Ptr<Float>,
pub profile: GVec<Float>, pub profile: Ptr<Float>,
pub rho_eff: GVec<Float>, pub rho_eff: Ptr<Float>,
pub profile_cdf: GVec<Float>, pub profile_cdf: Ptr<Float>,
} }
impl BSSRDFTable { impl BSSRDFTable {
pub fn new(n_rho: usize, n_radius: usize) -> Self {
let filled = |n: usize| {
let mut v: GVec<Float> = gvec_with_capacity(n);
v.resize(n, 0.);
v
};
Self {
n_rho: n_rho as u32,
n_radius: n_radius as u32,
rho_samples: filled(n_rho),
radius_samples: filled(n_radius),
profile: filled(n_rho * n_radius),
rho_eff: filled(n_rho),
profile_cdf: filled(n_rho * n_radius),
}
}
pub fn get_rho(&self) -> &[Float] { pub fn get_rho(&self) -> &[Float] {
&self.rho_samples unsafe { core::slice::from_raw_parts(self.rho_samples.0, self.n_rho_samples as usize) }
} }
pub fn get_radius(&self) -> &[Float] { pub fn get_radius(&self) -> &[Float] {
&self.radius_samples unsafe {
core::slice::from_raw_parts(self.radius_samples.0, self.n_radius_samples as usize)
}
} }
pub fn get_profile(&self) -> &[Float] { pub fn get_profile(&self) -> &[Float] {
// let n_profile = (self.n_rho * self.n_radius) as usize; let n_profile = (self.n_rho_samples * self.n_radius_samples) as usize;
&self.profile unsafe { core::slice::from_raw_parts(self.profile.0, n_profile) }
} }
pub fn get_cdf(&self) -> &[Float] { pub fn get_cdf(&self) -> &[Float] {
// let n_profile = (self.n_rho * self.n_radius) as usize; let n_profile = (self.n_rho_samples * self.n_radius_samples) as usize;
&self.profile_cdf unsafe { core::slice::from_raw_parts(self.profile_cdf.0, n_profile) }
} }
pub fn eval_profile(&self, rho_index: u32, radius_index: u32) -> Float { pub fn eval_profile(&self, rho_index: u32, radius_index: u32) -> Float {
debug_assert!(rho_index < self.n_rho); debug_assert!(rho_index < self.n_rho_samples);
debug_assert!(radius_index < self.n_radius); debug_assert!(radius_index < self.n_radius_samples);
let idx = (rho_index * self.n_radius + radius_index) as usize; let idx = (rho_index * self.n_radius_samples + radius_index) as usize;
unsafe { *self.profile.as_ptr().add(idx) } unsafe { *self.profile.0.add(idx) }
} }
} }
pub fn subsurface_from_diffuse(
t: &BSSRDFTable,
rho_eff: &SampledSpectrum,
mfp: &SampledSpectrum,
) -> (SampledSpectrum, SampledSpectrum) {
// (sigma_a, sigma_s)
let mut sigma_a = SampledSpectrum::zero();
let mut sigma_s = SampledSpectrum::zero();
for c in 0..N_SPECTRUM_SAMPLES {
let rho = invert_catmull_rom(&t.rho_samples, &t.rho_eff, rho_eff[c]);
sigma_s[c] = rho / mfp[c];
sigma_a[c] = (1. - rho) / mfp[c];
}
(sigma_a, sigma_s)
}
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, Default, Debug)] #[derive(Copy, Clone, Default, Debug)]
pub struct BSSRDFProbeSegment { pub struct BSSRDFProbeSegment {
@ -251,7 +220,7 @@ impl TabulatedBSSRDF {
sr += weight sr += weight
* self * self
.table .table
.eval_profile((rho_offset + j as i32) as u32, (radius_offset + k as i32) as u32); .eval_profile(rho_offset + j as u32, radius_offset + k as u32);
} }
} }
} }
@ -302,14 +271,14 @@ impl TabulatedBSSRDF {
for (j, rho_weight) in rho_weights.iter().enumerate() { for (j, rho_weight) in rho_weights.iter().enumerate() {
if *rho_weight != 0. { if *rho_weight != 0. {
// Update _rhoEff_ and _sr_ for wavelength // Update _rhoEff_ and _sr_ for wavelength
rho_eff += rhoeff_samples[(rho_offset + j as i32) as usize] * rho_weight; rho_eff += rhoeff_samples[rho_offset as usize + j] * rho_weight;
// Fix: Use .iter().enumerate() for 'k' // Fix: Use .iter().enumerate() for 'k'
for (k, radius_weight) in radius_weights.iter().enumerate() { for (k, radius_weight) in radius_weights.iter().enumerate() {
if *radius_weight != 0. { if *radius_weight != 0. {
sr += self sr += self
.table .table
.eval_profile((rho_offset + j as i32) as u32, (radius_offset + k as i32) as u32) .eval_profile(rho_offset + j as u32, radius_offset + k as u32)
* rho_weight * rho_weight
* radius_weight; * radius_weight;
} }

View file

@ -1,5 +1,4 @@
use crate::bxdfs::*; use crate::bxdfs::*;
use crate::core::bsdf::BSDFSample;
use crate::core::geometry::{Point2f, Vector3f, abs_cos_theta}; use crate::core::geometry::{Point2f, Vector3f, abs_cos_theta};
use crate::spectra::SampledSpectrum; use crate::spectra::SampledSpectrum;
use crate::utils::sampling::{sample_uniform_hemisphere, uniform_hemisphere_pdf}; use crate::utils::sampling::{sample_uniform_hemisphere, uniform_hemisphere_pdf};
@ -83,6 +82,70 @@ impl Not for TransportMode {
} }
} }
#[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()
}
}
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct FArgs { pub struct FArgs {
@ -146,7 +209,6 @@ pub trait BxDFTrait: Any {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum BxDF { pub enum BxDF {
Diffuse(DiffuseBxDF), Diffuse(DiffuseBxDF),
DiffuseTransmission(DiffuseTransmissionBxDF),
Dielectric(DielectricBxDF), Dielectric(DielectricBxDF),
ThinDielectric(ThinDielectricBxDF), ThinDielectric(ThinDielectricBxDF),
Conductor(ConductorBxDF), Conductor(ConductorBxDF),
@ -156,9 +218,3 @@ pub enum BxDF {
CoatedConductor(CoatedConductorBxDF), CoatedConductor(CoatedConductorBxDF),
NormalizedFresnel(NormalizedFresnelBxDF), NormalizedFresnel(NormalizedFresnelBxDF),
} }
impl Default for BxDF {
fn default() -> Self {
BxDF::Diffuse(DiffuseBxDF::default())
}
}

View file

@ -5,15 +5,15 @@ use crate::core::geometry::{
}; };
use crate::core::interaction::Interaction; use crate::core::interaction::Interaction;
use crate::core::medium::Medium; use crate::core::medium::Medium;
use crate::core::options::RenderingCoordinateSystem;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample; use crate::core::sampler::CameraSample;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::lerp; use crate::utils::math::lerp;
use crate::utils::options::RenderingCoordinateSystem;
use crate::utils::ptr::Ptr; use crate::utils::ptr::Ptr;
use crate::utils::transform::{AnimatedTransform, Transform}; use crate::utils::transform::{AnimatedTransform, Transform};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -41,10 +41,6 @@ pub struct CameraTransform {
} }
impl CameraTransform { impl CameraTransform {
pub fn render_from_world(&self) -> Transform {
self.world_from_render.inverse()
}
pub fn from_world( pub fn from_world(
world_from_camera: AnimatedTransform, world_from_camera: AnimatedTransform,
rendering_space: RenderingCoordinateSystem, rendering_space: RenderingCoordinateSystem,
@ -121,9 +117,9 @@ pub struct CameraBase {
pub medium: Ptr<Medium>, pub medium: Ptr<Medium>,
} }
#[repr(C)]
#[derive(Debug, Clone)]
#[enum_dispatch(CameraTrait)] #[enum_dispatch(CameraTrait)]
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub enum Camera { pub enum Camera {
Perspective(PerspectiveCamera), Perspective(PerspectiveCamera),
Orthographic(OrthographicCamera), Orthographic(OrthographicCamera),
@ -137,7 +133,7 @@ pub trait CameraTrait {
fn generate_ray(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option<CameraRay>; fn generate_ray(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option<CameraRay>;
fn get_film(&self) -> &Film { fn get_film(&self) -> &Film {
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
{ {
if self.base().film.is_null() { if self.base().film.is_null() {
panic!( panic!(
@ -145,7 +141,7 @@ pub trait CameraTrait {
); );
} }
} }
&self.base().film &*self.base().film
} }
fn sample_time(&self, u: Float) -> Float { fn sample_time(&self, u: Float) -> Float {
@ -167,6 +163,9 @@ pub trait CameraTrait {
sample: CameraSample, sample: CameraSample,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> Option<CameraRay> { ) -> Option<CameraRay> {
match self {
Camera::Orthographic(c) => c.generate_ray_differential(sample, lambda),
_ => {
let mut central_cam_ray = self.generate_ray(sample, lambda)?; let mut central_cam_ray = self.generate_ray(sample, lambda)?;
let mut rd = RayDifferential::default(); let mut rd = RayDifferential::default();
let mut rx_found = false; let mut rx_found = false;
@ -177,10 +176,10 @@ pub trait CameraTrait {
s_shift.p_film[0] += eps; s_shift.p_film[0] += eps;
if let Some(rx_cam_ray) = self.generate_ray(s_shift, lambda) { if let Some(rx_cam_ray) = self.generate_ray(s_shift, lambda) {
rd.rx_origin = rd.rx_origin = central_cam_ray.ray.o
central_cam_ray.ray.o + (rx_cam_ray.ray.o - central_cam_ray.ray.o) / eps; + (rx_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
rd.rx_direction = rd.rx_direction = central_cam_ray.ray.d
central_cam_ray.ray.d + (rx_cam_ray.ray.d - central_cam_ray.ray.d) / eps; + (rx_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
rx_found = true; rx_found = true;
break; break;
} }
@ -191,10 +190,10 @@ pub trait CameraTrait {
s_shift.p_film[1] += eps; s_shift.p_film[1] += eps;
if let Some(ry_cam_ray) = self.generate_ray(s_shift, lambda) { if let Some(ry_cam_ray) = self.generate_ray(s_shift, lambda) {
rd.ry_origin = rd.ry_origin = central_cam_ray.ray.o
central_cam_ray.ray.o + (ry_cam_ray.ray.o - central_cam_ray.ray.o) / eps; + (ry_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
rd.ry_direction = rd.ry_direction = central_cam_ray.ray.d
central_cam_ray.ray.d + (ry_cam_ray.ray.d - central_cam_ray.ray.d) / eps; + (ry_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
ry_found = true; ry_found = true;
break; break;
} }
@ -206,6 +205,8 @@ pub trait CameraTrait {
Some(central_cam_ray) Some(central_cam_ray)
} }
}
}
fn approximate_dp_dxy( fn approximate_dp_dxy(
&self, &self,
@ -231,13 +232,13 @@ pub trait CameraTrait {
Point3f::new(0., 0., 0.) + self.base().min_pos_differential_x, Point3f::new(0., 0., 0.) + self.base().min_pos_differential_x,
Vector3f::new(0., 0., 1.) + self.base().min_dir_differential_x, Vector3f::new(0., 0., 1.) + self.base().min_dir_differential_x,
None, None,
Ptr::default(), &Ptr::default(),
); );
let y_ray = Ray::new( let y_ray = Ray::new(
Point3f::new(0., 0., 0.) + self.base().min_pos_differential_y, Point3f::new(0., 0., 0.) + self.base().min_pos_differential_y,
Vector3f::new(0., 0., 1.) + self.base().min_dir_differential_y, Vector3f::new(0., 0., 1.) + self.base().min_dir_differential_y,
None, None,
Ptr::default(), &Ptr::default(),
); );
let n_down = Vector3f::from(n_down_z); let n_down = Vector3f::from(n_down_z);
let tx = -(n_down.dot(y_ray.o.into())) / n_down.dot(x_ray.d); let tx = -(n_down.dot(y_ray.o.into())) / n_down.dot(x_ray.d);

View file

@ -1,19 +1,17 @@
use crate::core::geometry::Point2f; use std::any::TypeId;
use crate::core::spectrum::Spectrum; use std::fmt;
use crate::utils::find_interval; use std::ops::{
use crate::utils::math::{clamp, evaluate_polynomial, lerp, SquareMatrix, SquareMatrix3f};
use crate::{Float, GVec, Ptr};
use core::any::TypeId;
use core::fmt;
use core::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
}; };
use crate::utils::error::{Error, Result};
use enum_dispatch::enum_dispatch;
use num_traits::Float as NumFloat;
#[repr(C)] use crate::core::geometry::Point2f;
#[derive(Debug, Default, Clone, Copy)] use crate::core::pbrt::{Float, find_interval};
use crate::core::spectrum::Spectrum;
use crate::utils::math::{SquareMatrix, SquareMatrix3f, clamp, evaluate_polynomial, lerp};
use enum_dispatch::enum_dispatch;
#[derive(Debug, Clone)]
pub struct XYZ { pub struct XYZ {
pub x: Float, pub x: Float,
pub y: Float, pub y: Float,
@ -26,15 +24,9 @@ impl From<(Float, Float, Float)> for XYZ {
} }
} }
impl From<[Float; 3]> for XYZ {
fn from(triplet: [Float; 3]) -> Self {
XYZ::new(triplet[0], triplet[1], triplet[2])
}
}
impl<'a> IntoIterator for &'a XYZ { impl<'a> IntoIterator for &'a XYZ {
type Item = &'a Float; type Item = &'a Float;
type IntoIter = core::array::IntoIter<&'a Float, 3>; type IntoIter = std::array::IntoIter<&'a Float, 3>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
[&self.x, &self.y, &self.z].into_iter() [&self.x, &self.y, &self.z].into_iter()
@ -89,9 +81,9 @@ impl XYZ {
} }
} }
impl Index<u32> for XYZ { impl Index<usize> for XYZ {
type Output = Float; type Output = Float;
fn index(&self, index: u32) -> &Self::Output { fn index(&self, index: usize) -> &Self::Output {
debug_assert!(index < 3); debug_assert!(index < 3);
match index { match index {
0 => &self.x, 0 => &self.x,
@ -101,8 +93,8 @@ impl Index<u32> for XYZ {
} }
} }
impl IndexMut<u32> for XYZ { impl IndexMut<usize> for XYZ {
fn index_mut(&mut self, index: u32) -> &mut Self::Output { fn index_mut(&mut self, index: usize) -> &mut Self::Output {
debug_assert!(index < 3); debug_assert!(index < 3);
match index { match index {
0 => &mut self.x, 0 => &mut self.x,
@ -255,26 +247,13 @@ impl fmt::Display for XYZ {
} }
} }
#[repr(C)] #[derive(Debug, Default, Copy, Clone)]
#[derive(Debug, Default, Clone, Copy)]
pub struct RGB { pub struct RGB {
pub r: Float, pub r: Float,
pub g: Float, pub g: Float,
pub b: Float, pub b: Float,
} }
impl From<[Float; 3]> for RGB {
fn from(slice: [Float; 3]) -> Self {
RGB::new(slice[0], slice[1], slice[2])
}
}
impl From<&[Float; 3]> for RGB {
fn from(slice: &[Float; 3]) -> Self {
RGB::new(slice[0], slice[1], slice[2])
}
}
impl From<(Float, Float, Float)> for RGB { impl From<(Float, Float, Float)> for RGB {
fn from(triplet: (Float, Float, Float)) -> Self { fn from(triplet: (Float, Float, Float)) -> Self {
RGB::new(triplet.0, triplet.1, triplet.2) RGB::new(triplet.0, triplet.1, triplet.2)
@ -283,7 +262,7 @@ impl From<(Float, Float, Float)> for RGB {
impl<'a> IntoIterator for &'a RGB { impl<'a> IntoIterator for &'a RGB {
type Item = &'a Float; type Item = &'a Float;
type IntoIter = core::array::IntoIter<&'a Float, 3>; type IntoIter = std::array::IntoIter<&'a Float, 3>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
[&self.r, &self.g, &self.b].into_iter() [&self.r, &self.g, &self.b].into_iter()
@ -307,35 +286,19 @@ impl RGB {
self.r.min(self.g).min(self.b) self.r.min(self.g).min(self.b)
} }
pub fn min_component_index(&self) -> u32 { pub fn min_component_index(&self) -> usize {
if self.r < self.g { if self.r < self.g {
if self.r < self.b { if self.r < self.b { 0 } else { 2 }
0
} else { } else {
2 if self.g < self.b { 1 } 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.g {
if self.r > self.b { if self.r > self.b { 0 } else { 2 }
0
} else { } else {
2 if self.g > self.b { 1 } else { 2 }
}
} else {
if self.g > self.b {
1
} else {
2
}
} }
} }
@ -352,30 +315,6 @@ impl RGB {
} }
} }
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,
}
}
}
impl Index<usize> for RGB { impl Index<usize> for RGB {
type Output = Float; type Output = Float;
fn index(&self, index: usize) -> &Self::Output { fn index(&self, index: usize) -> &Self::Output {
@ -388,28 +327,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 { impl IndexMut<usize> for RGB {
fn index_mut(&mut self, index: usize) -> &mut Self::Output { fn index_mut(&mut self, index: usize) -> &mut Self::Output {
debug_assert!(index < 3); debug_assert!(index < 3);
@ -620,13 +537,13 @@ pub struct RGBSigmoidPolynomial {
} }
impl RGBSigmoidPolynomial { impl RGBSigmoidPolynomial {
#[cfg(not(target_os = "cuda"))]
pub fn new(c0: Float, c1: Float, c2: Float) -> Self { pub fn new(c0: Float, c1: Float, c2: Float) -> Self {
Self { c0, c1, c2 } Self { c0, c1, c2 }
} }
pub fn evaluate(&self, lambda: Float) -> Float { pub fn evaluate(&self, lambda: Float) -> Float {
// pbrt: `s(EvaluatePolynomial(lambda, c2, c1, c0))` -- c2 is the constant term. let eval = evaluate_polynomial(lambda, &[self.c0, self.c1, self.c2]);
let eval = evaluate_polynomial(lambda, &[self.c2, self.c1, self.c0]);
Self::s(eval) Self::s(eval)
} }
@ -682,16 +599,6 @@ pub enum ColorEncoding {
SRGB(SRGBEncoding), SRGB(SRGBEncoding),
} }
impl ColorEncoding {
pub fn from_name(name: &str) -> Result<Self> {
match name {
"sRGB" | "srgb" => Ok(ColorEncoding::SRGB(SRGBEncoding)),
"linear" => Ok(ColorEncoding::Linear(LinearEncoding)),
_ => Err(Error::UnknownColorEncoding),
}
}
}
impl fmt::Display for ColorEncoding { impl fmt::Display for ColorEncoding {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Encoding") write!(f, "Encoding")
@ -1054,26 +961,16 @@ const SRGB_TO_LINEAR_LUT: [Float; 256] = [
1.0000000000, 1.0000000000,
]; ];
pub const RES: u32 = 64; pub const RES: usize = 64;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)] #[derive(Clone, Copy, Debug, Default)]
pub struct Coeffs { pub struct Coeffs {
pub c0: Float, pub c0: Float,
pub c1: Float, pub c1: Float,
pub c2: Float, pub c2: Float,
} }
impl From<&[Float; 3]> for Coeffs {
fn from(slice: &[Float; 3]) -> Coeffs {
Coeffs {
c0: slice[0],
c1: slice[1],
c2: slice[2],
}
}
}
impl Add for Coeffs { impl Add for Coeffs {
type Output = Self; type Output = Self;
#[inline(always)] #[inline(always)]
@ -1086,18 +983,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 { impl Mul<Float> for Coeffs {
type Output = Self; type Output = Self;
#[inline(always)] #[inline(always)]
@ -1111,37 +996,34 @@ impl Mul<Float> for Coeffs {
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Copy, Debug)]
pub struct RGBToSpectrumTable { pub struct RGBToSpectrumTable {
pub z_nodes: GVec<Float>, pub z_nodes: *const Float,
pub coeffs: GVec<Coeffs>, pub coeffs: *const Coeffs,
pub n_nodes: u32,
} }
unsafe impl Send for RGBToSpectrumTable {}
unsafe impl Sync for RGBToSpectrumTable {}
impl RGBToSpectrumTable { impl RGBToSpectrumTable {
#[inline(always)] #[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; let offset = bucket * (RES * RES * RES) + z * (RES * RES) + y * (RES) + x;
unsafe { *self.coeffs.as_ptr().add(offset as usize) } unsafe { *self.coeffs.add(offset) }
} }
pub fn evaluate(&self, rgb: RGB) -> RGBSigmoidPolynomial { pub fn evaluate(&self, rgb: RGB) -> RGBSigmoidPolynomial {
let m = rgb.max_component_value(); let m = rgb.max_component_value();
let min_val = rgb.min_component_value(); let min_val = rgb.min_component_value();
if m - min_val < 1e-4 { if m - min_val < 1e-4 {
let x: 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(); let c2 = (0.5 - x) / (x * (1.0 - x)).sqrt();
return RGBSigmoidPolynomial::new(0.0, 0.0, c2); return RGBSigmoidPolynomial::new(0.0, 0.0, c2);
} }
// Identify the primary bucket (c) based on the dominant axis // Identify the primary bucket (c) based on the dominant axis
let c_idx = if rgb[0] > rgb[1] { let c_idx = if rgb[0] > rgb[1] {
if rgb[0] > rgb[2] { if rgb[0] > rgb[2] { 0 } else { 2 }
0
} else {
2
}
} else if rgb[1] > rgb[2] { } else if rgb[1] > rgb[2] {
1 1
} else { } else {
@ -1163,25 +1045,25 @@ impl RGBToSpectrumTable {
let x = coord_a / z; let x = coord_a / z;
let y = coord_b / z; let y = coord_b / z;
let z_nodes = &self.z_nodes; let z_nodes_slice = unsafe { core::slice::from_raw_parts(self.z_nodes, RES) };
let zi = find_interval(RES, |i| z_nodes[i as usize] < z) as usize; let zi = find_interval(RES, |i| z_nodes_slice[i] < z);
let dz = (z - z_nodes[zi]) / (z_nodes[zi + 1] - z_nodes[zi]); let dz = (z - z_nodes_slice[zi]) / (z_nodes_slice[zi + 1] - z_nodes_slice[zi]);
let x_float = x * (RES - 1) as Float; let x_float = x * (RES - 1) as Float;
let xi = (x_float as u32).min(RES - 2); let xi = (x_float as usize).min(RES - 2);
let dx = x_float - xi as Float; let dx = x_float - xi as Float;
let y_float = y * (RES - 1) as Float; let y_float = y * (RES - 1) as Float;
let yi = (y_float as u32).min(RES - 2); let yi = (y_float as usize).min(RES - 2);
let dy = y_float - yi as Float; let dy = y_float - yi as Float;
let c000 = self.get_coeffs(c_idx, zi as u32, yi, xi); let c000 = self.get_coeffs(c_idx, zi, yi, xi);
let c001 = self.get_coeffs(c_idx, zi as u32, yi, xi + 1); let c001 = self.get_coeffs(c_idx, zi, yi, xi + 1);
let c010 = self.get_coeffs(c_idx, zi as u32, yi + 1, xi); let c010 = self.get_coeffs(c_idx, zi, yi + 1, xi);
let c011 = self.get_coeffs(c_idx, zi as u32, yi + 1, xi + 1); let c011 = self.get_coeffs(c_idx, zi, yi + 1, xi + 1);
let c100 = self.get_coeffs(c_idx, zi as u32 + 1, yi, xi); let c100 = self.get_coeffs(c_idx, zi + 1, yi, xi);
let c101 = self.get_coeffs(c_idx, zi as u32 + 1, yi, xi + 1); let c101 = self.get_coeffs(c_idx, zi + 1, yi, xi + 1);
let c110 = self.get_coeffs(c_idx, zi as u32 + 1, yi + 1, xi); let c110 = self.get_coeffs(c_idx, zi + 1, yi + 1, xi);
let c111 = self.get_coeffs(c_idx, zi as u32 + 1, yi + 1, xi + 1); let c111 = self.get_coeffs(c_idx, zi + 1, yi + 1, xi + 1);
let c00 = lerp(dx, c000, c001); let c00 = lerp(dx, c000, c001);
let c01 = lerp(dx, c010, c011); let c01 = lerp(dx, c010, c011);
let c10 = lerp(dx, c100, c101); let c10 = lerp(dx, c100, c101);

View file

@ -1,5 +1,5 @@
use crate::core::camera::CameraTransform; use crate::core::camera::CameraTransform;
use crate::core::color::{white_balance, MatrixMulColor, RGB, SRGB, XYZ}; use crate::core::color::{MatrixMulColor, RGB, SRGB, XYZ, white_balance};
use crate::core::filter::{Filter, FilterTrait}; use crate::core::filter::{Filter, FilterTrait};
use crate::core::geometry::{ use crate::core::geometry::{
Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Tuple, Vector2f, Vector2fi, Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Tuple, Vector2f, Vector2fi,
@ -7,21 +7,22 @@ use crate::core::geometry::{
}; };
use crate::core::image::{Image, PixelFormat}; use crate::core::image::{Image, PixelFormat};
use crate::core::interaction::SurfaceInteraction; use crate::core::interaction::SurfaceInteraction;
use crate::core::pbrt::Float;
use crate::core::spectrum::{Spectrum, SpectrumTrait, StandardSpectra}; use crate::core::spectrum::{Spectrum, SpectrumTrait, StandardSpectra};
use crate::spectra::{ use crate::spectra::{
colorspace, ConstantSpectrum, DenselySampledSpectrum, PiecewiseLinearSpectrum, RGBColorSpace, ConstantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES,
SampledSpectrum, SampledWavelengths, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, PiecewiseLinearSpectrum, RGBColorSpace, SampledSpectrum, SampledWavelengths, colorspace,
}; };
use crate::utils::AtomicFloat;
use crate::utils::containers::Array2D;
use crate::utils::math::linear_least_squares; use crate::utils::math::linear_least_squares;
use crate::utils::math::{wrap_equal_area_square, SquareMatrix}; use crate::utils::math::{SquareMatrix, wrap_equal_area_square};
use crate::utils::ptr::Ptr;
use crate::utils::sampling::VarianceEstimator; use crate::utils::sampling::VarianceEstimator;
use crate::utils::transform::AnimatedTransform; use crate::utils::transform::AnimatedTransform;
use crate::utils::{gpu_array_from_fn, AtomicFloat};
use crate::{gvec_from_slice, gvec_with_capacity, Array2D, Float, GVec, Ptr};
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Clone, Copy, Debug)]
pub struct RGBFilm { pub struct RGBFilm {
pub base: FilmBase, pub base: FilmBase,
pub max_component_value: Float, pub max_component_value: Float,
@ -32,61 +33,53 @@ pub struct RGBFilm {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Clone, Debug)]
pub struct RGBPixel { pub struct RGBPixel {
rgb_sum: [AtomicFloat; 3], rgb_sum: [AtomicFloat; 3],
weight_sum: AtomicFloat, weight_sum: AtomicFloat,
rgb_splat: [AtomicFloat; 3], rgb_splat: [AtomicFloat; 3],
} }
impl Default for RGBPixel { // #[cfg(not(target_os = "cuda"))]
fn default() -> Self { // impl RGBFilm {
Self { // pub fn new(
rgb_sum: gpu_array_from_fn(|_| AtomicFloat::default()), // base: FilmBase,
weight_sum: AtomicFloat::default(), // colorspace: &RGBColorSpace,
rgb_splat: gpu_array_from_fn(|_| AtomicFloat::default()), // 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::(base.pixel_bounds);
//
// Self {
// base,
// max_component_value,
// write_fp16,
// filter_integral,
// output_rgbf_from_sensor_rgb,
// pixels: std::sync::Arc::new(pixels_array),
// }
// }
// }
//
impl RGBFilm { impl RGBFilm {
pub fn new(
base: FilmBase,
colorspace: &RGBColorSpace,
max_component_value: Float,
write_fp16: bool,
) -> Self {
let sensor_ptr = base.sensor;
// TODO: This wont work on gpu, need to add check on host side
if sensor_ptr.is_null() {
panic!("Film must have a sensor");
}
let sensor = &*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 = gvec_with_capacity(count);
for _ in 0..count {
pixel_vec.push(RGBPixel::default());
}
let pixels: Array2D<RGBPixel> = Array2D::new(base.pixel_bounds);
RGBFilm {
base,
max_component_value,
write_fp16,
filter_integral,
output_rgbf_from_sensor_rgb,
pixels,
}
}
pub fn base(&self) -> &FilmBase { pub fn base(&self) -> &FilmBase {
&self.base &self.base
} }
@ -96,7 +89,7 @@ impl RGBFilm {
} }
pub fn get_sensor(&self) -> &PixelSensor { pub fn get_sensor(&self) -> &PixelSensor {
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
{ {
if self.base.sensor.is_null() { if self.base.sensor.is_null() {
panic!( panic!(
@ -104,7 +97,7 @@ impl RGBFilm {
); );
} }
} }
&self.base.sensor unsafe { &*self.base.sensor }
} }
pub fn add_sample( pub fn add_sample(
@ -115,10 +108,7 @@ impl RGBFilm {
_vi: Option<&VisibleSurface>, _vi: Option<&VisibleSurface>,
weight: Float, weight: Float,
) { ) {
if !self.base.pixel_bounds.contains_exclusive(p_film) { let sensor = unsafe { self.get_sensor() };
return;
}
let sensor = self.get_sensor();
let mut rgb = sensor.to_sensor_rgb(l, lambda); let mut rgb = sensor.to_sensor_rgb(l, lambda);
let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max); let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max);
if m > self.max_component_value { if m > self.max_component_value {
@ -127,13 +117,13 @@ impl RGBFilm {
let pixel = &self.pixels[p_film]; let pixel = &self.pixels[p_film];
for c in 0..3 { for c in 0..3 {
pixel.rgb_sum[c].add(weight * rgb[c as 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) { 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 mut rgb = sensor.to_sensor_rgb(l, lambda);
let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max); let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max);
if m > self.max_component_value { if m > self.max_component_value {
@ -158,32 +148,32 @@ impl RGBFilm {
if wt != 0. { if wt != 0. {
let pixel = &self.pixels[*pi]; let pixel = &self.pixels[*pi];
for i in 0..3 { for i in 0..3 {
pixel.rgb_splat[i].add((wt * rgb[i as 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 { 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) };
let mut rgb = RGB::new( let mut rgb = RGB::new(
pixel.rgb_sum[0].get() as Float, pixel.rgb_sum[0].load() as Float,
pixel.rgb_sum[1].get() as Float, pixel.rgb_sum[1].load() as Float,
pixel.rgb_sum[2].get() 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. { if weight_sum != 0. {
rgb /= weight_sum as Float rgb /= weight_sum as Float
} }
if let Some(splat) = splat_scale { if let Some(splat) = splat_scale {
for c in 0..3 { for c in 0..3 {
let splat_val = pixel.rgb_splat[c].get(); let splat_val = pixel.rgb_splat[c].load();
rgb[c] += splat * splat_val as Float / self.filter_integral; rgb[c] += splat * splat_val as Float / self.filter_integral;
} }
} else { } else {
for c in 0..3 { for c in 0..3 {
let splat_val = pixel.rgb_splat[c].get(); let splat_val = pixel.rgb_splat[c].load();
rgb[c] += splat_val as Float / self.filter_integral; rgb[c] += splat_val as Float / self.filter_integral;
} }
} }
@ -202,16 +192,16 @@ impl RGBFilm {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Default)]
#[cfg_attr(gpu, derive(Copy))] #[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
pub struct GBufferPixel { struct GBufferPixel {
pub rgb_sum: [AtomicFloat; 3], pub rgb_sum: [AtomicFloat; 3],
pub weight_sum: AtomicFloat, pub weight_sum: AtomicFloat,
pub g_buffer_weight_sum: AtomicFloat, pub g_bugger_weight_sum: AtomicFloat,
pub rgb_splat: [AtomicFloat; 3], pub rgb_splat: [AtomicFloat; 3],
pub p_sum: Point3f, pub p_sum: Point3f,
pub dz_dx_sum: AtomicFloat, pub dz_dx_sum: AtomicFloat,
pub dz_dy_sum: AtomicFloat, pub dz_dy_sum: Float,
pub n_sum: Normal3f, pub n_sum: Normal3f,
pub ns_sum: Normal3f, pub ns_sum: Normal3f,
pub uv_sum: Point2f, pub uv_sum: Point2f,
@ -219,40 +209,22 @@ pub struct GBufferPixel {
pub rgb_variance: VarianceEstimator, pub rgb_variance: VarianceEstimator,
} }
impl Default for GBufferPixel {
fn default() -> Self {
Self {
rgb_sum: gpu_array_from_fn(|_| AtomicFloat::default()),
weight_sum: AtomicFloat::default(),
rgb_splat: gpu_array_from_fn(|_| AtomicFloat::default()),
g_buffer_weight_sum: AtomicFloat::default(),
p_sum: Point3f::default(),
dz_dx_sum: AtomicFloat::default(),
dz_dy_sum: AtomicFloat::default(),
n_sum: Normal3f::default(),
ns_sum: Normal3f::default(),
uv_sum: Point2f::default(),
rgb_albedo_sum: gpu_array_from_fn(|_| AtomicFloat::default()),
rgb_variance: VarianceEstimator::default(),
}
}
}
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Copy)]
#[cfg_attr(gpu, derive(Copy))] #[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
pub struct GBufferFilm { pub struct GBufferFilm {
pub base: FilmBase, pub base: FilmBase,
pub output_from_render: AnimatedTransform, output_from_render: AnimatedTransform,
pub apply_inverse: bool, apply_inverse: bool,
pub pixels: Array2D<GBufferPixel>, pixels: Array2D<GBufferPixel>,
pub colorspace: RGBColorSpace, colorspace: RGBColorSpace,
pub max_component_value: Float, max_component_value: Float,
pub write_fp16: bool, write_fp16: bool,
pub filter_integral: Float, filter_integral: Float,
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>, output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
} }
#[cfg(not(target_os = "cuda"))]
impl GBufferFilm { impl GBufferFilm {
pub fn new( pub fn new(
base: &FilmBase, base: &FilmBase,
@ -267,14 +239,14 @@ impl GBufferFilm {
if sensor_ptr.is_null() { if sensor_ptr.is_null() {
panic!("Film must have a sensor"); panic!("Film must have a sensor");
} }
let sensor = &*sensor_ptr; let sensor = unsafe { &*sensor_ptr };
let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor.xyz_from_sensor_rgb; let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor.xyz_from_sensor_rgb;
let filter_integral = base.filter.integral(); let filter_integral = base.filter.integral();
let pixels = Array2D::new(base.pixel_bounds); let pixels = Array2D::new(base.pixel_bounds);
GBufferFilm { Self {
base: base.clone(), base: base.clone(),
output_from_render: *output_from_render, output_from_render: output_from_render.clone(),
apply_inverse, apply_inverse,
pixels, pixels,
colorspace: colorspace.clone(), colorspace: colorspace.clone(),
@ -284,7 +256,9 @@ impl GBufferFilm {
output_rgbf_from_sensor_rgb, output_rgbf_from_sensor_rgb,
} }
} }
}
impl GBufferFilm {
pub fn base(&self) -> &FilmBase { pub fn base(&self) -> &FilmBase {
&self.base &self.base
} }
@ -294,7 +268,7 @@ impl GBufferFilm {
} }
pub fn get_sensor(&self) -> &PixelSensor { pub fn get_sensor(&self) -> &PixelSensor {
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
{ {
if self.base.sensor.is_null() { if self.base.sensor.is_null() {
panic!( panic!(
@ -302,22 +276,11 @@ impl GBufferFilm {
); );
} }
} }
&self.base.sensor unsafe { &*self.base.sensor }
}
pub fn add_sample(
&self,
_p_film: Point2i,
_l: SampledSpectrum,
_lambda: &SampledWavelengths,
_visible_surface: Option<&VisibleSurface>,
_weight: Float,
) {
todo!()
} }
pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) { pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) {
let sensor = self.get_sensor(); let sensor = unsafe { self.get_sensor() };
let mut rgb = sensor.to_sensor_rgb(l, lambda); let mut rgb = sensor.to_sensor_rgb(l, lambda);
let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max); let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max);
if m > self.max_component_value { if m > self.max_component_value {
@ -342,7 +305,7 @@ impl GBufferFilm {
if wt != 0. { if wt != 0. {
let pixel = &self.pixels[*pi]; let pixel = &self.pixels[*pi];
for i in 0..3 { for i in 0..3 {
pixel.rgb_splat[i].add((wt * rgb[i]) as f32); pixel.rgb_splat[i].add((wt * rgb[i]) as f64);
} }
} }
} }
@ -355,25 +318,25 @@ impl GBufferFilm {
} }
pub fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option<Float>) -> RGB { pub fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option<Float>) -> RGB {
let pixel = &self.pixels.get(p); let pixel = unsafe { &self.pixels.get(p) };
let mut rgb = RGB::new( let mut rgb = RGB::new(
pixel.rgb_sum[0].get() as Float, pixel.rgb_sum[0].load() as Float,
pixel.rgb_sum[1].get() as Float, pixel.rgb_sum[1].load() as Float,
pixel.rgb_sum[2].get() 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. { if weight_sum != 0. {
rgb /= weight_sum as Float rgb /= weight_sum as Float
} }
if let Some(splat) = splat_scale { if let Some(splat) = splat_scale {
for c in 0..3 { for c in 0..3 {
let splat_val = pixel.rgb_splat[c].get(); let splat_val = pixel.rgb_splat[c].load();
rgb[c] += splat * splat_val as Float / self.filter_integral; rgb[c] += splat * splat_val as Float / self.filter_integral;
} }
} else { } else {
for c in 0..3 { for c in 0..3 {
let splat_val = pixel.rgb_splat[c].get(); let splat_val = pixel.rgb_splat[c].load();
rgb[c] += splat_val as Float / self.filter_integral; rgb[c] += splat_val as Float / self.filter_integral;
} }
} }
@ -386,102 +349,35 @@ impl GBufferFilm {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug)] #[derive(Debug, Default)]
#[cfg_attr(gpu, derive(Copy))] #[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
pub struct SpectralPixel { pub struct SpectralPixel {
pub rgb_sum: [AtomicFloat; 3], pub rgb_sum: [AtomicFloat; 3],
pub rgb_weight_sum: AtomicFloat, pub rgb_weigh_sum: AtomicFloat,
pub rgb_splat: [AtomicFloat; 3], pub rgb_splat: [AtomicFloat; 3],
pub bucket_offset: usize, pub bucket_offset: usize,
} }
impl Clone for SpectralPixel {
fn clone(&self) -> Self {
Self {
rgb_sum: gpu_array_from_fn(|i| AtomicFloat::new(self.rgb_sum[i].get())),
rgb_weight_sum: AtomicFloat::new(self.rgb_weight_sum.get()),
rgb_splat: gpu_array_from_fn(|i| AtomicFloat::new(self.rgb_splat[i].get())),
bucket_offset: self.bucket_offset,
}
}
}
impl Default for SpectralPixel {
fn default() -> Self {
Self {
rgb_sum: gpu_array_from_fn(|_| AtomicFloat::new(0.0)),
rgb_weight_sum: AtomicFloat::new(0.0),
rgb_splat: gpu_array_from_fn(|_| AtomicFloat::new(0.0)),
bucket_offset: 0,
}
}
}
#[repr(C)] #[repr(C)]
#[derive(Debug)] #[derive(Debug, Default)]
#[cfg_attr(gpu, derive(Copy, Clone))] #[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
pub struct SpectralFilm { pub struct SpectralFilm {
pub base: FilmBase, pub base: FilmBase,
pub colorspace: RGBColorSpace,
pub lambda_min: Float, pub lambda_min: Float,
pub lambda_max: Float, pub lambda_max: Float,
pub n_buckets: usize, pub n_buckets: usize,
pub max_component_value: Float, pub max_component_value: Float,
pub write_fp16: bool, pub write_fp16: bool,
pub filter_integral: Float, pub filter_integral: Float,
pub colorspace: RGBColorSpace,
pub pixels: Array2D<SpectralPixel>, pub pixels: Array2D<SpectralPixel>,
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>, pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
pub bucket_sums: GVec<f64>, pub bucket_sums: *mut f64,
pub weight_sums: GVec<f64>, pub weight_sums: *mut f64,
pub bucket_splats: GVec<AtomicFloat>, pub bucket_splats: *mut AtomicFloat,
} }
impl SpectralFilm { 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 {
let n_pixels = base.pixel_bounds.area() as usize;
let total_buckets = n_pixels * n_buckets;
let bucket_sums = gvec_with_capacity(total_buckets);
let weight_sums = gvec_with_capacity(total_buckets);
let mut bucket_splats = gvec_with_capacity(total_buckets);
for _ in 0..total_buckets {
bucket_splats.push(AtomicFloat::new(0.0));
}
let mut pixels = Array2D::<SpectralPixel>::new(base.pixel_bounds);
for i in 0..n_pixels {
let pixel = pixels.get_linear_mut(i);
pixel.bucket_offset = i * n_buckets;
}
SpectralFilm {
base: *base,
colorspace: colorspace.clone(),
lambda_min,
lambda_max,
n_buckets,
max_component_value,
write_fp16,
filter_integral: base.filter.integral(),
output_rgbf_from_sensor_rgb: SquareMatrix::identity(),
pixels: Array2D::from_slice(base.pixel_bounds, pixels.as_slice()),
bucket_sums,
weight_sums,
bucket_splats,
}
}
pub fn base(&self) -> &FilmBase { pub fn base(&self) -> &FilmBase {
&self.base &self.base
} }
@ -493,38 +389,115 @@ impl SpectralFilm {
fn uses_visible_surface(&self) -> bool { fn uses_visible_surface(&self) -> bool {
true true
} }
pub fn add_sample(
&self,
_p_film: Point2i,
_l: SampledSpectrum,
_lambda: &SampledWavelengths,
_visible_surface: Option<&VisibleSurface>,
_weight: Float,
) {
todo!()
}
pub fn add_splat(&mut self, _p: Point2f, _v: SampledSpectrum, _lambda: &SampledWavelengths) {
todo!()
}
pub fn get_pixel_rgb(&self, _p: Point2i, _splat_scale: Option<Float>) -> RGB {
todo!()
}
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct PixelSensor { pub struct PixelSensor {
pub xyz_from_sensor_rgb: SquareMatrix<Float, 3>, pub xyz_from_sensor_rgb: SquareMatrix<Float, 3>,
pub r_bar: Ptr<DenselySampledSpectrum>, pub r_bar: DenselySampledSpectrum,
pub g_bar: Ptr<DenselySampledSpectrum>, pub g_bar: DenselySampledSpectrum,
pub b_bar: Ptr<DenselySampledSpectrum>, pub b_bar: DenselySampledSpectrum,
pub imaging_ratio: Float, pub imaging_ratio: Float,
} }
impl PixelSensor { 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: &Spectrum,
imaging_ratio: Float,
spectra: *const StandardSpectra,
swatches: &[Spectrum; 24],
) -> Self {
// 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(spectra.y);
for i in 0..Self::N_SWATCH_REFLECTANCES {
let s = swatches[i].clone();
let xyz = Self::project_reflectance::<XYZ>(
&s,
&output_colorspace.illuminant,
spectra.x,
spectra.y,
spectra.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: Ptr<Spectrum>,
imaging_ratio: Float,
spectra: *const StandardSpectra,
) -> Self {
let r_bar = DenselySampledSpectrum::from_spectrum(spectra.x);
let g_bar = DenselySampledSpectrum::from_spectrum(spectra.y);
let b_bar = DenselySampledSpectrum::from_spectrum(spectra.z);
let xyz_from_sensor_rgb: SquareMatrix<Float, 3>;
if let Some(illum) = sensor_illum {
let source_white = illum.to_xyz(spectra).xy();
let target_white = output_colorspace.w;
xyz_from_sensor_rgb = white_balance(source_white, target_white);
} else {
xyz_from_sensor_rgb = SquareMatrix::<Float, 3>::default();
}
Self {
xyz_from_sensor_rgb,
r_bar,
g_bar,
b_bar,
imaging_ratio,
}
}
pub fn project_reflectance<T>( pub fn project_reflectance<T>(
refl: &Spectrum, refl: &Spectrum,
illum: &Spectrum, illum: &Spectrum,
@ -556,9 +529,11 @@ impl PixelSensor {
result[2] *= inv_g; result[2] *= inv_g;
} }
T::from([result[0], result[1], result[2]]) T::from((result[0], result[1], result[2]))
}
} }
impl PixelSensor {
pub fn to_sensor_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB { pub fn to_sensor_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB {
let l_norm = SampledSpectrum::safe_div(&l, &lambda.pdf()); let l_norm = SampledSpectrum::safe_div(&l, &lambda.pdf());
self.imaging_ratio self.imaging_ratio
@ -604,19 +579,18 @@ pub struct FilmBase {
pub pixel_bounds: Bounds2i, pub pixel_bounds: Bounds2i,
pub filter: Filter, pub filter: Filter,
pub diagonal: Float, pub diagonal: Float,
pub sensor: Ptr<PixelSensor>, pub sensor: *const PixelSensor,
} }
#[repr(C)] #[repr(C)]
#[derive(Debug)] #[derive(Debug)]
#[cfg_attr(gpu, derive(Copy, Clone))] #[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
pub enum Film { pub enum Film {
RGB(RGBFilm), RGB(RGBFilm),
GBuffer(GBufferFilm), GBuffer(GBufferFilm),
Spectral(SpectralFilm), Spectral(SpectralFilm),
} }
impl Film { impl Film {
pub fn base(&self) -> &FilmBase { pub fn base(&self) -> &FilmBase {
match self { match self {

View file

@ -1,8 +1,9 @@
use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f}; use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f};
use crate::core::pbrt::Float;
use crate::filters::*; use crate::filters::*;
use crate::utils::containers::Array2D;
use crate::utils::math::{gaussian, gaussian_integral, lerp, sample_tent, windowed_sinc}; use crate::utils::math::{gaussian, gaussian_integral, lerp, sample_tent, windowed_sinc};
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
use crate::{Array2D, Float, Ptr};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
pub struct FilterSample { pub struct FilterSample {
@ -11,7 +12,7 @@ pub struct FilterSample {
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug)] #[derive(Clone, Debug, Copy)]
pub struct FilterSampler { pub struct FilterSampler {
pub domain: Bounds2f, pub domain: Bounds2f,
pub distrib: PiecewiseConstant2D, pub distrib: PiecewiseConstant2D,
@ -19,6 +20,7 @@ pub struct FilterSampler {
} }
impl FilterSampler { impl FilterSampler {
#[cfg(not(target_os = "cuda"))]
pub fn new<F>(radius: Vector2f, func: F) -> Self pub fn new<F>(radius: Vector2f, func: F) -> Self
where where
F: Fn(Point2f) -> Float, F: Fn(Point2f) -> Float,
@ -27,9 +29,11 @@ impl FilterSampler {
Point2f::new(-radius.x(), -radius.y()), Point2f::new(-radius.x(), -radius.y()),
Point2f::new(radius.x(), radius.y()), Point2f::new(radius.x(), radius.y()),
); );
let nx = (32.0 * radius.x()) as i32;
let ny = (32.0 * radius.y()) as i32; let nx = (32.0 * radius.x()) as usize;
let mut f = Array2D::new_dims(nx, ny); 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 y in 0..f.y_size() {
for x in 0..f.x_size() { for x in 0..f.x_size() {
let p = domain.lerp(Point2f::new( let p = domain.lerp(Point2f::new(
@ -40,16 +44,17 @@ impl FilterSampler {
} }
} }
let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain); let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain);
Self { domain, distrib, f } Self { domain, f, distrib }
} }
pub fn sample(&self, u: Point2f) -> FilterSample { pub fn sample(&self, u: Point2f) -> FilterSample {
let (p, pdf, pi) = self.distrib.sample(u); let (p, pdf, pi) = self.distrib.sample(u);
if pdf == 0.0 { if pdf == 0.0 {
return FilterSample { p, weight: 0.0 }; return FilterSample { p, weight: 0.0 };
} }
let idx = pi.x() as usize + pi.y() as usize * self.f.x_size();
let weight = self.f.as_slice()[idx] / pdf; let weight = *self.f.get_linear(pi.x() as u32 + self.f.x_size()) / pdf;
FilterSample { p, weight } FilterSample { p, weight }
} }
} }
@ -66,26 +71,9 @@ pub trait FilterTrait {
#[enum_dispatch(FilterTrait)] #[enum_dispatch(FilterTrait)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Filter { pub enum Filter {
Box(Ptr<BoxFilter>), Box(BoxFilter),
Gaussian(Ptr<GaussianFilter>), Gaussian(GaussianFilter),
Mitchell(Ptr<MitchellFilter>), Mitchell(MitchellFilter),
LanczosSinc(Ptr<LanczosSincFilter>), LanczosSinc(LanczosSincFilter),
Triangle(Ptr<TriangleFilter>), Triangle(TriangleFilter),
}
impl<T: FilterTrait> FilterTrait for Ptr<T> {
fn radius(&self) -> Vector2f {
self.get().unwrap().radius()
}
fn integral(&self) -> Float {
self.get().unwrap().integral()
}
fn evaluate(&self, p: Point2f) -> Float {
self.get().unwrap().evaluate(p)
}
fn sample(&self, p: Point2f) -> FilterSample {
self.get().unwrap().sample(p)
}
} }

View file

@ -1,14 +1,12 @@
use super::{Float, NumFloat}; use super::{Float, NumFloat};
use super::{Point, Point2i, Point2f, Point3, Point3f, Vector, Vector2, Vector2f, Vector3, Vector3f}; use super::{Point, Point2f, Point3, Point3f, Vector, Vector2, Vector2f, Vector3, Vector3f};
use crate::core::geometry::traits::{SqrtExt, VectorLike}; use crate::core::geometry::traits::{Sqrt, VectorLike};
use crate::core::geometry::{max, min}; use crate::core::geometry::{max, min};
use crate::utils::gpu_array_from_fn;
use crate::utils::interval::Interval; use crate::utils::interval::Interval;
use crate::utils::math::lerp; use crate::utils::math::lerp;
use crate::{gamma, gamma_t};
use core::mem;
use core::ops::{Add, Div, DivAssign, Mul, Sub};
use num_traits::{Bounded, Num}; use num_traits::{Bounded, Num};
use std::mem;
use std::ops::{Add, Div, DivAssign, Mul, Sub};
// AABB BOUNDING BOXES // AABB BOUNDING BOXES
@ -20,7 +18,7 @@ pub struct Bounds<T, const N: usize> {
impl<'a, T, const N: usize> IntoIterator for &'a Bounds<T, N> { impl<'a, T, const N: usize> IntoIterator for &'a Bounds<T, N> {
type Item = &'a Point<T, N>; type Item = &'a Point<T, N>;
type IntoIter = core::array::IntoIter<&'a Point<T, N>, 2>; type IntoIter = std::array::IntoIter<&'a Point<T, N>, 2>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
[&self.p_min, &self.p_max].into_iter() [&self.p_min, &self.p_max].into_iter()
@ -139,7 +137,7 @@ where
} }
pub fn corner(&self, corner_index: usize) -> Point<T, N> { pub fn corner(&self, corner_index: usize) -> Point<T, N> {
Point(gpu_array_from_fn(|i| { Point(std::array::from_fn(|i| {
if (corner_index >> i) & 1 == 1 { if (corner_index >> i) & 1 == 1 {
self.p_max[i] self.p_max[i]
} else { } else {
@ -208,7 +206,7 @@ where
impl<T> Bounds3<T> impl<T> Bounds3<T>
where where
T: NumFloat + PartialOrd + Copy + Default + SqrtExt, T: NumFloat + PartialOrd + Copy + Default + Sqrt,
{ {
pub fn bounding_sphere(&self) -> (Point3<T>, T) { pub fn bounding_sphere(&self) -> (Point3<T>, T) {
let two = T::one() + T::one(); let two = T::one() + T::one();
@ -221,7 +219,7 @@ where
(center, radius) (center, radius)
} }
pub fn intersect(&self, o: Point3<T>, d: Vector3<T>, t_max: T) -> Option<(T, T)> { pub fn insersect(&self, o: Point3<T>, d: Vector3<T>, t_max: T) -> Option<(T, T)> {
let mut t0 = T::zero(); let mut t0 = T::zero();
let mut t1 = t_max; let mut t1 = t_max;
@ -232,8 +230,6 @@ where
if t_near > t_far { if t_near > t_far {
mem::swap(&mut t_near, &mut t_far); mem::swap(&mut t_near, &mut t_far);
} }
t_far = t_far * (T::one() + (T::one() + T::one()) * gamma_t::<T>(3));
t0 = if t_near > t0 { t_near } else { t0 }; t0 = if t_near > t0 { t_near } else { t0 };
t1 = if t_far < t1 { t_far } else { t1 }; t1 = if t_far < t1 { t_far } else { t1 };
if t0 > t1 { if t0 > t1 {
@ -254,22 +250,7 @@ where
} }
} }
impl Bounds2f {
pub fn unit() -> Self {
Self::from_points(Point2f::new(0.0, 0.0), Point2f::new(1.0, 1.0))
}
}
impl Bounds3f { impl Bounds3f {
/// SAH bucket index for `p` along `dim`, in `[0, n_buckets)`. `self` is the
/// centroid bounds, so `offset` is in [0,1] and only `offset == 1` needs the
/// clamp -- same as pbrt's `if (b == nBuckets) b = nBuckets - 1`.
#[inline]
pub fn sah_bucket(&self, p: &Point3f, dim: usize, n_buckets: usize) -> usize {
let offset = self.offset(p)[dim];
((n_buckets as Float * offset) as usize).min(n_buckets - 1)
}
#[inline(always)] #[inline(always)]
pub fn intersect_p( pub fn intersect_p(
&self, &self,
@ -286,10 +267,7 @@ impl Bounds3f {
// Check Y // Check Y
let ty_min = (bounds[dir_is_neg[1]].y() - o.y()) * inv_dir.y(); let ty_min = (bounds[dir_is_neg[1]].y() - o.y()) * inv_dir.y();
let mut ty_max = (bounds[1 - dir_is_neg[1]].y() - o.y()) * inv_dir.y(); let ty_max = (bounds[1 - dir_is_neg[1]].y() - o.y()) * inv_dir.y();
t_max = t_max * (1. + 2. * gamma(3));
ty_max = ty_max * (1. + 2. * gamma(3));
if t_min > ty_max || ty_min > t_max { if t_min > ty_max || ty_min > t_max {
return None; return None;
@ -303,8 +281,7 @@ impl Bounds3f {
// Check Z // Check Z
let tz_min = (bounds[dir_is_neg[2]].z() - o.z()) * inv_dir.z(); let tz_min = (bounds[dir_is_neg[2]].z() - o.z()) * inv_dir.z();
let mut tz_max = (bounds[1 - dir_is_neg[2]].z() - o.z()) * inv_dir.z(); let tz_max = (bounds[1 - dir_is_neg[2]].z() - o.z()) * inv_dir.z();
tz_max = tz_max * (1. + 2. * gamma(3));
if t_min > tz_max || tz_min > t_max { if t_min > tz_max || tz_min > t_max {
return None; return None;
@ -337,11 +314,7 @@ impl Bounds3f {
let mut t_min = (bounds[dir_is_neg[0]].x() - o.x()) * inv_dir.x(); let mut t_min = (bounds[dir_is_neg[0]].x() - o.x()) * inv_dir.x();
let mut t_max = (bounds[1 - dir_is_neg[0]].x() - o.x()) * inv_dir.x(); let mut t_max = (bounds[1 - dir_is_neg[0]].x() - o.x()) * inv_dir.x();
let ty_min = (bounds[dir_is_neg[1]].y() - o.y()) * inv_dir.y(); let ty_min = (bounds[dir_is_neg[1]].y() - o.y()) * inv_dir.y();
let mut ty_max = (bounds[1 - dir_is_neg[1]].y() - o.y()) * inv_dir.y(); let ty_max = (bounds[1 - dir_is_neg[1]].y() - o.y()) * inv_dir.y();
t_max = t_max * (1. + 2. * gamma(3));
ty_max = ty_max * (1. + 2. * gamma(3));
if t_min > ty_max || ty_min > t_max { if t_min > ty_max || ty_min > t_max {
return false; return false;
@ -354,8 +327,7 @@ impl Bounds3f {
} }
let tz_min = (bounds[dir_is_neg[2]].z() - o.z()) * inv_dir.z(); let tz_min = (bounds[dir_is_neg[2]].z() - o.z()) * inv_dir.z();
let mut tz_max = (bounds[1 - dir_is_neg[2]].z() - o.z()) * inv_dir.z(); let tz_max = (bounds[1 - dir_is_neg[2]].z() - o.z()) * inv_dir.z();
tz_max = tz_max * (1. + 2. * gamma(3));
if t_min > tz_max || tz_min > t_max { if t_min > tz_max || tz_min > t_max {
return false; return false;
@ -370,35 +342,3 @@ impl Bounds3f {
(t_min < ray_t_max) && (t_max > 0.0) (t_min < ray_t_max) && (t_max > 0.0)
} }
} }
pub struct BoundsPixelIterator {
bounds: Bounds2i,
current: Point2i,
}
impl Iterator for BoundsPixelIterator {
type Item = Point2i;
fn next(&mut self) -> Option<Point2i> {
if self.current.y() >= self.bounds.p_max.y() {
return None;
}
let result = self.current;
let mut x = self.current.x() + 1;
let mut y = self.current.y();
if x >= self.bounds.p_max.x() {
x = self.bounds.p_min.x();
y += 1;
}
self.current = Point2i::new(x, y);
Some(result)
}
}
impl Bounds2i {
pub fn pixels(&self) -> BoundsPixelIterator {
BoundsPixelIterator {
bounds: *self,
current: self.p_min,
}
}
}

View file

@ -1,7 +1,6 @@
use super::{Bounds3f, Float, PI, Point3f, Vector3f, VectorLike}; use super::{Bounds3f, Float, PI, Point3f, Vector3f, VectorLike};
use crate::utils::math::{degrees, safe_acos, safe_asin, safe_sqrt, square}; use crate::utils::math::{degrees, safe_acos, safe_asin, safe_sqrt, square};
use crate::utils::transform::TransformGeneric; use crate::utils::transform::TransformGeneric;
use num_traits::Float as NumFloat;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct DirectionCone { pub struct DirectionCone {
@ -52,13 +51,13 @@ impl DirectionCone {
* Vector3f::new( * Vector3f::new(
w.x() w.x()
* (wp.y() * w.y() + wp.z() * w.z() * (wp.y() * w.y() + wp.z() * w.z()
- wp.x() * (square(w.y()) + square(w.z()))), - wp.x() * (square(w.y() + square(w.z())))),
w.y() w.y()
* (wp.x() * w.x() + wp.z() * w.z() * (wp.x() * w.x() + wp.z() * w.z()
- wp.y() * (square(w.x()) + square(w.z()))), - wp.y() * (square(w.x() + square(w.z())))),
w.z() w.z()
* (wp.x() * w.x() + wp.y() * w.y() * (wp.x() * w.x() + wp.y() * w.y()
- wp.z() * (square(w.x()) + square(w.y()))), - wp.z() * (square(w.x() + square(w.y())))),
) )
} }
@ -91,10 +90,10 @@ impl DirectionCone {
let theta_b = safe_acos(b.cos_theta); let theta_b = safe_acos(b.cos_theta);
let theta_d = a.w.angle_between(b.w); let theta_d = a.w.angle_between(b.w);
if (theta_d + theta_b).min(PI) <= theta_a { if (theta_d + theta_b).min(PI) <= theta_b {
return a.clone(); return a.clone();
} }
if (theta_d + theta_a).min(PI) <= theta_b { if (theta_d + theta_a).min(PI) <= theta_a {
return b.clone(); return b.clone();
} }
@ -107,7 +106,7 @@ impl DirectionCone {
// Find the merged cone's axis and return cone union // Find the merged cone's axis and return cone union
let theta_r = theta_o - theta_a; let theta_r = theta_o - theta_a;
let wr = a.w.cross(b.w); let wr = a.w.cross(b.w);
if wr.norm_squared() == 0. { if wr.norm_squared() >= 0. {
return DirectionCone::entire_sphere(); return DirectionCone::entire_sphere();
} }

View file

@ -12,7 +12,7 @@ pub use self::primitives::{
Vector3i, Vector3i,
}; };
pub use self::ray::{Ray, RayDifferential}; pub use self::ray::{Ray, RayDifferential};
pub use self::traits::{Lerp, SqrtExt, Tuple, VectorLike}; pub use self::traits::{Lerp, Sqrt, Tuple, VectorLike};
use crate::core::pbrt::{Float, PI}; use crate::core::pbrt::{Float, PI};
use crate::utils::math::{clamp, square}; use crate::utils::math::{clamp, square};

View file

@ -1,14 +1,13 @@
use super::traits::{SqrtExt, Tuple, VectorLike}; use super::traits::{Sqrt, Tuple, VectorLike};
use super::{Float, NumFloat, PI}; use super::{Float, NumFloat, PI};
use crate::utils::interval::Interval; use crate::utils::interval::Interval;
use crate::utils::math::{clamp, difference_of_products, quadratic, safe_asin}; use crate::utils::math::{clamp, difference_of_products, quadratic, safe_asin};
use core::fmt; use num_traits::{AsPrimitive, FloatConst, Num, Signed, Zero};
use core::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use core::iter::Sum; use std::iter::Sum;
use core::ops::{ use std::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
}; };
use num_traits::{AsPrimitive, FloatConst, Num, Signed, Zero};
pub trait MulAdd<M = Self, A = Self> { pub trait MulAdd<M = Self, A = Self> {
type Output; type Output;
@ -19,15 +18,7 @@ impl MulAdd<Float, Float> for Float {
type Output = Float; type Output = Float;
#[inline(always)] #[inline(always)]
fn mul_add(self, multiplier: Float, addend: Float) -> Self::Output { fn mul_add(self, multiplier: Float, addend: Float) -> Self::Output {
num_traits::Float::mul_add(self, multiplier, addend) self.mul_add(multiplier, addend)
}
}
impl MulAdd<f64, f64> for f64 {
type Output = f64;
#[inline(always)]
fn mul_add(self, multiplier: f64, addend: f64) -> Self::Output {
num_traits::Float::mul_add(self, multiplier, addend)
} }
} }
@ -44,45 +35,6 @@ pub struct Point<T, const N: usize>(pub [T; N]);
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Normal<T, const N: usize>(pub [T; N]); pub struct Normal<T, const N: usize>(pub [T; N]);
impl<T: fmt::Display, const N: usize> fmt::Display for Vector<T, N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Vector(")?;
for (i, item) in (&self.0).into_iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", item)?;
}
write!(f, ")")
}
}
impl<T: fmt::Display, const N: usize> fmt::Display for Point<T, N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Point(")?;
for (i, item) in (&self.0).into_iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", item)?;
}
write!(f, ")")
}
}
impl<T: fmt::Display, const N: usize> fmt::Display for Normal<T, N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Normal(")?;
for (i, item) in (&self.0).into_iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", item)?;
}
write!(f, ")")
}
}
#[macro_export] #[macro_export]
macro_rules! impl_tuple_core { macro_rules! impl_tuple_core {
($Struct:ident) => { ($Struct:ident) => {
@ -225,27 +177,6 @@ macro_rules! impl_tuple_core {
}; };
} }
#[macro_export]
macro_rules! impl_num_zero {
($Struct:ident) => {
impl<T, const N: usize> num_traits::Zero for $Struct<T, N>
where
T: num_traits::Zero + Copy + PartialEq,
{
#[inline]
fn zero() -> Self {
Self([T::zero(); N])
}
#[inline]
fn is_zero(&self) -> bool {
self.0.iter().all(|c| c.is_zero())
}
}
};
}
impl_num_zero!(Vector);
impl_num_zero!(Normal);
#[macro_export] #[macro_export]
macro_rules! impl_scalar_ops { macro_rules! impl_scalar_ops {
($Struct:ident) => { ($Struct:ident) => {
@ -374,7 +305,7 @@ macro_rules! impl_float_vector_ops {
+ Mul<Output = T> + Mul<Output = T>
+ Sub<Output = T> + Sub<Output = T>
+ Div<Output = T> + Div<Output = T>
+ SqrtExt, + Sqrt,
{ {
type Scalar = T; type Scalar = T;
fn dot(self, rhs: Self) -> T { fn dot(self, rhs: Self) -> T {
@ -518,7 +449,7 @@ impl<T: Copy, const N: usize> From<Point<T, N>> for Vector<T, N> {
impl<T, const N: usize> Point<T, N> impl<T, const N: usize> Point<T, N>
where where
T: NumFloat + SqrtExt, T: NumFloat + Sqrt,
{ {
pub fn distance(self, other: Self) -> T { pub fn distance(self, other: Self) -> T {
(self - other).norm() (self - other).norm()
@ -636,33 +567,33 @@ impl<T: Copy> Vector4<T> {
// Vector operations // Vector operations
impl<T> Vector3<T> impl<T> Vector3<T>
where where
T: Num + Copy + Neg<Output = T> + Zero + MulAdd<T, T, Output = T>, T: Num + Copy + Neg<Output = T>,
{ {
pub fn cross(self, rhs: Self) -> Self { pub fn cross(self, rhs: Self) -> Self {
Self([ Self([
difference_of_products(self[1], rhs[2], self[2], rhs[1]), self[1] * rhs[2] - self[2] * rhs[1],
difference_of_products(self[2], rhs[0], self[0], rhs[2]), self[2] * rhs[0] - self[0] * rhs[2],
difference_of_products(self[0], rhs[1], self[1], rhs[0]), self[0] * rhs[1] - self[1] * rhs[0],
]) ])
} }
} }
impl<T> Normal3<T> impl<T> Normal3<T>
where where
T: Num + Copy + Neg<Output = T> + Zero + MulAdd<T, T, Output = T>, T: Num + Copy + Neg<Output = T>,
{ {
pub fn cross(self, rhs: Self) -> Self { pub fn cross(self, rhs: Self) -> Self {
Self([ Self([
difference_of_products(self[1], rhs[2], self[2], rhs[1]), self[1] * rhs[2] - self[2] * rhs[1],
difference_of_products(self[2], rhs[0], self[0], rhs[2]), self[2] * rhs[0] - self[0] * rhs[2],
difference_of_products(self[0], rhs[1], self[1], rhs[0]), self[0] * rhs[1] - self[1] * rhs[0],
]) ])
} }
} }
impl<T> Vector3<T> impl<T> Vector3<T>
where where
T: Num + NumFloat + Copy + Neg<Output = T> + Zero + MulAdd<T, T, Output = T>, T: Num + NumFloat + Copy + Neg<Output = T>,
{ {
pub fn coordinate_system(&self) -> (Self, Self) pub fn coordinate_system(&self) -> (Self, Self)
where where
@ -692,7 +623,7 @@ where
impl<T> Normal3<T> impl<T> Normal3<T>
where where
T: Num + NumFloat + Copy + Neg<Output = T> + Zero + MulAdd<T, T, Output = T>, T: Num + NumFloat + Copy + Neg<Output = T>,
{ {
pub fn coordinate_system(&self) -> (Self, Self) pub fn coordinate_system(&self) -> (Self, Self)
where where
@ -883,7 +814,7 @@ impl<const N: usize> From<Point<i32, N>> for Point<Float, N> {
impl<T> Normal3<T> impl<T> Normal3<T>
where where
T: Num + PartialOrd + Copy + Neg<Output = T> + SqrtExt, T: Num + PartialOrd + Copy + Neg<Output = T> + Sqrt,
{ {
pub fn face_forward(self, v: impl Into<Vector3<T>>) -> Self { pub fn face_forward(self, v: impl Into<Vector3<T>>) -> Self {
let v: Vector3<T> = v.into(); let v: Vector3<T> = v.into();

View file

@ -1,7 +1,8 @@
use super::{Normal3f, Point3f, Point3fi, Vector3f, VectorLike}; use super::{Normal3f, Point3f, Point3fi, Vector3f, VectorLike};
use crate::core::medium::Medium; use crate::core::medium::Medium;
use crate::core::pbrt::Float;
use crate::utils::math::{next_float_down, next_float_up}; use crate::utils::math::{next_float_down, next_float_up};
use crate::{gvec_with_capacity, Float, GVec, Ptr}; use crate::utils::ptr::Ptr;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -29,12 +30,12 @@ impl Default for Ray {
} }
impl Ray { impl Ray {
pub fn new(o: Point3f, d: Vector3f, time: Option<Float>, medium: Ptr<Medium>) -> Self { pub fn new(o: Point3f, d: Vector3f, time: Option<Float>, medium: &Medium) -> Self {
Self { Self {
o, o,
d, d,
time: time.unwrap_or_else(|| Self::default().time), time: time.unwrap_or_else(|| Self::default().time),
medium, medium: Ptr::from(medium),
..Self::default() ..Self::default()
} }
} }
@ -43,21 +44,25 @@ impl Ray {
self.o + self.d * t self.o + self.d * t
} }
pub fn offset_origin(pi: &Point3fi, n: &Normal3f, w: &Vector3f) -> Point3f { pub fn offset_origin(p: &Point3fi, n: &Normal3f, w: &Vector3f) -> Point3f {
let d: Float = Vector3f::from(n.abs()).dot(pi.error()); let d: Float = Vector3f::from(n.abs()).dot(p.error());
let mut disp: Vector3f = Vector3f::from(*n) * d; let normal: Vector3f = Vector3f::from(*n);
if w.dot(Vector3f::from(*n)) < 0.0 {
disp = -disp; let mut offset = p.midpoint();
if w.dot(normal) < 0.0 {
offset -= normal * d;
} else {
offset += normal * d;
} }
let mut po = pi.midpoint() + disp;
for i in 0..3 { for i in 0..3 {
if disp[i] > 0.0 { if n[i] > 0.0 {
po[i] = next_float_up(po[i]); offset[i] = next_float_up(offset[i]);
} else if disp[i] < 0.0 { } else if n[i] < 0.0 {
po[i] = next_float_down(po[i]); offset[i] = next_float_down(offset[i]);
} }
} }
po offset
} }
pub fn spawn(pi: &Point3fi, n: &Normal3f, time: Float, d: Vector3f) -> Ray { pub fn spawn(pi: &Point3fi, n: &Normal3f, time: Float, d: Vector3f) -> Ray {

View file

@ -1,9 +1,8 @@
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::utils::gpu_array_from_fn;
use crate::utils::interval::Interval; use crate::utils::interval::Interval;
use crate::utils::math::{next_float_down, next_float_up}; use crate::utils::math::{next_float_down, next_float_up};
use core::ops::{Add, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub};
use num_traits::{Float as NumFloat, FloatConst, Num, One, Signed, Zero}; use num_traits::{Float as NumFloat, FloatConst, Num, One, Signed, Zero};
use std::ops::{Add, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub};
pub trait Tuple<T, const N: usize>: pub trait Tuple<T, const N: usize>:
Sized + Copy + Index<usize, Output = T> + IndexMut<usize> Sized + Copy + Index<usize, Output = T> + IndexMut<usize>
@ -19,7 +18,7 @@ pub trait Tuple<T, const N: usize>:
where where
T: Copy, T: Copy,
{ {
let new_data = gpu_array_from_fn(|i| self[p[i]]); let new_data = p.map(|index| self[index]);
Self::from_array(new_data) Self::from_array(new_data)
} }
@ -78,7 +77,7 @@ pub trait VectorLike:
+ Div<Self::Scalar, Output = Self> + Div<Self::Scalar, Output = Self>
+ Mul<Self::Scalar, Output = Self> + Mul<Self::Scalar, Output = Self>
{ {
type Scalar: Copy + Zero + Add<Output = Self::Scalar> + Mul<Output = Self::Scalar> + SqrtExt; type Scalar: Copy + Zero + Add<Output = Self::Scalar> + Mul<Output = Self::Scalar> + Sqrt;
fn dot(self, rhs: Self) -> Self::Scalar; fn dot(self, rhs: Self) -> Self::Scalar;
fn norm_squared(self) -> Self::Scalar { fn norm_squared(self) -> Self::Scalar {
@ -97,7 +96,7 @@ pub trait VectorLike:
} }
fn norm(&self) -> Self::Scalar { fn norm(&self) -> Self::Scalar {
self.norm_squared().sqrt_ext() self.norm_squared().sqrt()
} }
fn normalize(self) -> Self fn normalize(self) -> Self
@ -120,36 +119,36 @@ pub trait VectorLike:
} }
} }
pub trait SqrtExt { pub trait Sqrt {
fn sqrt_ext(self) -> Self; fn sqrt(self) -> Self;
} }
impl SqrtExt for Float { impl Sqrt for Float {
fn sqrt_ext(self) -> Self { fn sqrt(self) -> Self {
<Self as num_traits::Float>::sqrt(self) self.sqrt()
} }
} }
impl SqrtExt for f64 { impl Sqrt for f64 {
fn sqrt_ext(self) -> Self { fn sqrt(self) -> Self {
<Self as num_traits::Float>::sqrt(self) self.sqrt()
} }
} }
impl SqrtExt for i32 { impl Sqrt for i32 {
fn sqrt_ext(self) -> Self { fn sqrt(self) -> Self {
self.isqrt() self.isqrt()
} }
} }
impl SqrtExt for u32 { impl Sqrt for u32 {
fn sqrt_ext(self) -> Self { fn sqrt(self) -> Self {
self.isqrt() self.isqrt()
} }
} }
impl SqrtExt for Interval { impl Sqrt for Interval {
fn sqrt_ext(self) -> Self { fn sqrt(self) -> Self {
let low = if self.low < 0.0 { let low = if self.low < 0.0 {
0.0 0.0
} else { } else {

View file

@ -1,60 +0,0 @@
use crate::core::light::Light;
use crate::core::material::Material;
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LightIdx(pub u32);
impl LightIdx {
pub const NONE: Self = LightIdx(u32::MAX);
pub fn is_none(self) -> bool { self.0 == u32::MAX }
}
impl Default for LightIdx {
fn default() -> Self { Self::NONE }
}
impl LightIdx {
#[inline]
pub fn get(self, lights: &[Light]) -> &Light {
debug_assert!(!self.is_none(), "LightIdx::get on NONE handle");
&lights[self.0 as usize]
}
#[inline]
pub fn try_get(self, lights: &[Light]) -> Option<&Light> {
if self.is_none() {
None
} else {
lights.get(self.0 as usize)
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MaterialIdx(pub u32);
impl MaterialIdx {
pub const NONE: Self = MaterialIdx(u32::MAX);
pub fn is_none(self) -> bool { self.0 == u32::MAX }
#[inline]
pub fn get(self, materials: &[Material]) -> &Material {
debug_assert!(!self.is_none(), "MaterialIdx::get on NONE handle");
&materials[self.0 as usize]
}
#[inline]
pub fn try_get(self, materials: &[Material]) -> Option<&Material> {
if self.is_none() {
None
} else {
materials.get(self.0 as usize)
}
}
}
impl Default for MaterialIdx {
fn default() -> Self { Self::NONE }
}

View file

@ -1,13 +1,13 @@
use crate::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR}; use crate::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR};
use crate::core::geometry::{Bounds2f, Point2f, Point2fi, Point2i}; use crate::core::geometry::{Bounds2f, Point2f, Point2fi, Point2i};
use crate::utils::math::{f16_to_f32_software, lerp, square}; use crate::core::pbrt::Float;
use crate::{gvec_with_capacity, Float, GVec, Ptr}; use crate::utils::containers::Array2D;
use crate::utils::error::{Error, Result}; use crate::utils::math::{f16_to_f32, lerp, square};
use core::hash; use core::hash;
use core::ops::{Deref, DerefMut}; use half::f16;
use num_traits::Float as NumFloat; use smallvec::{SmallVec, smallvec};
use std::ops::{Deref, DerefMut};
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WrapMode { pub enum WrapMode {
Black, Black,
@ -16,18 +16,6 @@ pub enum WrapMode {
OctahedralSphere, OctahedralSphere,
} }
impl WrapMode {
pub fn parse(name: &str) -> Result<WrapMode> {
match name {
"clamp" => Ok(WrapMode::Clamp),
"black" => Ok(WrapMode::Black),
"repeat" => Ok(WrapMode::Repeat),
"octahedralsphere" => Ok(WrapMode::OctahedralSphere),
_ => Err(Error::UnknownWrapMode),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct WrapMode2D { pub struct WrapMode2D {
pub uv: [WrapMode; 2], pub uv: [WrapMode; 2],
@ -47,16 +35,6 @@ pub enum PixelFormat {
F32, F32,
} }
impl core::fmt::Display for PixelFormat {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
PixelFormat::U8 => write!(f, "U256"),
PixelFormat::F16 => write!(f, "Half"),
PixelFormat::F32 => write!(f, "Float"),
}
}
}
impl PixelFormat { impl PixelFormat {
pub fn is_8bit(&self) -> bool { pub fn is_8bit(&self) -> bool {
matches!(self, PixelFormat::U8) matches!(self, PixelFormat::U8)
@ -80,253 +58,69 @@ impl PixelFormat {
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub struct Pixels { pub enum Pixels {
data: GVec<u8>, U8(*const u8),
format: PixelFormat, F16(*const u16),
F32(*const f32),
} }
impl Pixels { #[repr(C)]
pub fn new(data: GVec<u8>, format: PixelFormat) -> Self { #[derive(Debug, Clone, Copy)]
Self { data, format } pub struct Image {
pub format: PixelFormat,
pub pixels: Pixels,
pub encoding: ColorEncoding,
pub resolution: Point2i,
pub n_channels: i32,
}
impl Image {
pub fn resolution(&self) -> Point2i {
self.resolution
}
pub fn is_valid(&self) -> bool {
self.resolution.x() > 0. && self.resolution.y() > 0.
} }
pub fn format(&self) -> PixelFormat { pub fn format(&self) -> PixelFormat {
self.format self.format
} }
pub fn as_ptr(&self) -> *const u8 {
self.data.as_ptr()
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn texel_count(&self) -> usize {
self.data.len() / self.format.texel_bytes()
}
pub unsafe fn read_u8(&self, texel_offset: usize) -> u8 {
unsafe { *self.data.as_ptr().add(texel_offset) }
}
pub unsafe fn read_f16(&self, texel_offset: usize) -> u16 {
let byte_offset = texel_offset * 2;
unsafe { *(self.data.as_ptr().add(byte_offset) as *const u16) }
}
pub unsafe fn read_f32(&self, texel_offset: usize) -> f32 {
let byte_offset = texel_offset * 4;
unsafe { *(self.data.as_ptr().add(byte_offset) as *const f32) }
}
pub unsafe fn read(&self, texel_offset: usize, encoding: &ColorEncoding) -> Float {
// SAFETY: `texel_offset` is in range by this fn's own contract.
unsafe {
match self.format {
PixelFormat::U8 => encoding.to_linear_scalar(self.read_u8(texel_offset)),
PixelFormat::F16 => f16_to_f32_software(self.read_f16(texel_offset)),
PixelFormat::F32 => self.read_f32(texel_offset),
}
}
}
pub unsafe fn write_u8(&mut self, texel_offset: usize, val: u8) {
unsafe { *self.data.as_mut_ptr().add(texel_offset) = val };
}
pub unsafe fn write_f16(&mut self, texel_offset: usize, val: u16) {
let byte_offset = texel_offset * 2;
unsafe { *(self.data.as_mut_ptr().add(byte_offset) as *mut u16) = val };
}
pub unsafe fn write_f32(&mut self, texel_offset: usize, val: f32) {
let byte_offset = texel_offset * 4;
unsafe { *(self.data.as_mut_ptr().add(byte_offset) as *mut f32) = val };
}
pub fn empty(texel_count: usize, format: PixelFormat) -> Self {
let byte_count = texel_count * format.texel_bytes();
let mut data = gvec_with_capacity(byte_count);
data.resize(byte_count, 0u8);
Self { data, format }
}
pub fn from_u8_slice(slice: &[u8]) -> Self {
let mut data = gvec_with_capacity(slice.len());
data.extend_from_slice(slice);
Self {
data,
format: PixelFormat::U8,
}
}
pub fn from_f32_slice(slice: &[f32]) -> Self {
let byte_len = slice.len() * 4;
let mut data = gvec_with_capacity(byte_len);
let bytes = unsafe { core::slice::from_raw_parts(slice.as_ptr() as *const u8, byte_len) };
data.extend_from_slice(bytes);
Self {
data,
format: PixelFormat::F32,
}
}
pub fn from_f16_slice(slice: &[u16]) -> Self {
let byte_len = slice.len() * 2;
let mut data = gvec_with_capacity(byte_len);
let bytes = unsafe { core::slice::from_raw_parts(slice.as_ptr() as *const u8, byte_len) };
data.extend_from_slice(bytes);
Self {
data,
format: PixelFormat::F16,
}
}
pub fn as_u8(&self) -> &[u8] {
&self.data
}
pub fn as_f16(&mut self) -> &[u16] {
assert_eq!(self.format, PixelFormat::F16);
unsafe {
core::slice::from_raw_parts(self.data.as_ptr() as *const u16, self.data.len() / 2)
}
}
pub fn as_u8_mut(&mut self) -> &mut [u8] {
&mut self.data
}
pub fn as_f16_mut(&mut self) -> &mut [u16] {
assert_eq!(self.format, PixelFormat::F16);
unsafe {
core::slice::from_raw_parts_mut(self.data.as_mut_ptr() as *mut u16, self.data.len() / 2)
}
}
pub fn as_f32_slice(&self) -> &[f32] {
assert_eq!(self.format, PixelFormat::F32);
unsafe {
core::slice::from_raw_parts(self.data.as_ptr() as *const f32, self.data.len() / 4)
}
}
pub fn as_f32_slice_mut(&mut self) -> &mut [f32] {
assert_eq!(self.format, PixelFormat::F32);
unsafe {
core::slice::from_raw_parts_mut(self.data.as_mut_ptr() as *mut f32, self.data.len() / 4)
}
}
}
#[derive(Clone, Debug)]
pub struct ImageBase {
pub format: PixelFormat,
pub encoding: ColorEncoding,
pub resolution: Point2i,
pub n_channels: i32,
}
#[repr(C)]
#[derive(Clone, Debug)]
pub struct Image {
pub format: PixelFormat,
pub encoding: ColorEncoding,
pub resolution: Point2i,
pub n_channels: i32,
pub pixels: Pixels,
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct ImageView {
pub pixels: *const u8,
pub byte_len: usize,
pub resolution: Point2i,
pub n_channels: i32,
pub format: PixelFormat,
pub encoding: ColorEncoding,
}
impl Image {
pub fn new(
format: PixelFormat,
resolution: Point2i,
n_channels: i32,
encoding: ColorEncoding,
) -> Self {
let texel_count = (resolution.x() * resolution.y()) as usize * n_channels as usize;
Self {
format,
encoding,
resolution,
n_channels,
pixels: Pixels::empty(texel_count, format),
}
}
pub fn from_u8(
data: &[u8],
resolution: Point2i,
n_channels: i32,
encoding: ColorEncoding,
) -> Self {
let expected = (resolution.x() * resolution.y()) as usize * n_channels as usize;
assert_eq!(data.len(), expected, "Pixel data size mismatch");
Self {
format: PixelFormat::U8,
encoding,
resolution,
n_channels,
pixels: Pixels::from_u8_slice(data),
}
}
pub fn from_f32(data: &[f32], resolution: Point2i, n_channels: i32) -> Self {
let expected = (resolution.x() * resolution.y()) as usize * n_channels as usize;
assert_eq!(data.len(), expected, "Pixel data size mismatch");
Self {
format: PixelFormat::F32,
encoding: LINEAR,
resolution,
n_channels,
pixels: Pixels::from_f32_slice(data),
}
}
pub fn from_f16(data: &[u16], resolution: Point2i, n_channels: i32) -> Self {
let expected = (resolution.x() * resolution.y()) as usize * n_channels as usize;
assert_eq!(data.len(), expected, "Pixel data size mismatch");
Self {
format: PixelFormat::F16,
encoding: LINEAR,
resolution,
n_channels,
pixels: Pixels::from_f16_slice(data),
}
}
pub fn resolution(&self) -> Point2i {
self.resolution
}
pub fn n_channels(&self) -> i32 { pub fn n_channels(&self) -> i32 {
self.n_channels self.n_channels
} }
pub fn format(&self) -> PixelFormat { pub fn pixel_offset(&self, p: Point2i) -> u32 {
self.format 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 is_valid(&self) -> bool { pub fn get_channel_with_wrap(&self, p: Point2i, c: i32, wrap_mode: WrapMode2D) -> Float {
self.resolution.x() > 0 && self.resolution.y() > 0 if !self.remap_pixel_coords(&mut p, wrap_mode) {
return 0.;
} }
pub fn pixel_offset(&self, p: Point2i) -> usize { let offset = self.pixel_offset(p) + c;
let width = self.resolution.x() as usize; unsafe {
(p.y() as usize * width + p.x() as usize) * self.n_channels as usize match self.pixels {
Pixels::U8(ptr) => {
let raw_u8 = *ptr.add(offset);
self.encoding.to_linear_scalar(raw_u8)
}
Pixels::F16(ptr) => {
let half_bits = *ptr.add(offset);
f16_to_f32(f16::from_bits(half_bits))
}
Pixels::F32(ptr) => *ptr.add(offset),
}
}
}
pub fn get_channel(&self, p: Point2i, c: i32) -> Float {
self.get_channel_with_wrap(p, c, WrapMode::Clamp.into())
} }
pub fn remap_pixel_coords(&self, p: &mut Point2i, wrap_mode: WrapMode2D) -> bool { pub fn remap_pixel_coords(&self, p: &mut Point2i, wrap_mode: WrapMode2D) -> bool {
@ -346,102 +140,6 @@ impl Image {
true true
} }
pub fn base(&self) -> ImageBase {
ImageBase {
format: self.format,
encoding: self.encoding,
resolution: self.resolution,
n_channels: self.n_channels,
}
}
pub fn get_channel(&self, p: Point2i, c: i32) -> Float {
self.get_channel_with_wrap(p, c, WrapMode::Clamp.into())
}
pub fn get_channel_with_wrap(&self, mut p: Point2i, c: i32, wrap_mode: WrapMode2D) -> Float {
if !self.remap_pixel_coords(&mut p, wrap_mode) {
return 0.0;
}
let offset = self.pixel_offset(p) + c as usize;
unsafe { self.pixels.read(offset, &self.encoding) }
}
pub fn lookup_nearest_channel(&self, p: Point2f, c: i32) -> Float {
self.lookup_nearest_channel_with_wrap(p, c, WrapMode::Clamp.into())
}
pub 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)
}
pub fn get_channels<const N: usize>(&self, p: Point2i) -> [Float; N] {
self.get_channels_with_wrap(p, WrapMode::Clamp.into())
}
pub fn get_channels_with_wrap<const N: usize>(
&self,
mut p: Point2i,
wrap_mode: WrapMode2D,
) -> [Float; N] {
debug_assert!(N <= self.n_channels as usize);
let mut result = [0.0; N];
if !self.remap_pixel_coords(&mut p, wrap_mode) {
return result;
}
let offset = self.pixel_offset(p);
for i in 0..N {
result[i] = unsafe { self.pixels.read(offset + i, &self.encoding) };
}
result
}
pub fn get_channels_average(&self, p: Point2i) -> Float {
let offset = self.pixel_offset(p);
let nc = self.n_channels as usize;
let mut sum = 0.0;
for i in 0..nc {
sum += unsafe { self.pixels.read(offset + i, &self.encoding) };
}
sum / nc as Float
}
pub fn set_channel(&mut self, p: Point2i, c: i32, mut value: Float) {
if value.is_nan() {
value = 0.0;
}
let res = self.resolution;
if p.x() < 0 || p.x() >= res.x() || p.y() < 0 || p.y() >= res.y() {
return;
}
let offset = self.pixel_offset(p) + c as usize;
unsafe {
match self.format {
PixelFormat::U8 => {
self.pixels
.write_u8(offset, self.encoding.from_linear_scalar(value));
}
PixelFormat::F16 => {
self.pixels
.write_f16(offset, half::f16::from_f32(value).to_bits());
}
PixelFormat::F32 => {
self.pixels.write_f32(offset, value);
}
}
}
}
pub fn bilerp_channel(&self, p: Point2f, c: i32) -> Float { pub fn bilerp_channel(&self, p: Point2f, c: i32) -> Float {
self.bilerp_channel_with_wrap(p, c, WrapMode::Clamp.into()) self.bilerp_channel_with_wrap(p, c, WrapMode::Clamp.into())
} }
@ -460,51 +158,21 @@ impl Image {
lerp(dy, lerp(dx, v00, v10), lerp(dx, v01, v11)) lerp(dy, lerp(dx, v00, v10), lerp(dx, v01, v11))
} }
pub fn has_any_infinite_pixels(&self) -> bool { pub fn lookup_nearest_channel_with_wrap(
for y in 0..self.resolution.y() { &self,
for x in 0..self.resolution.x() { p: Point2f,
for c in 0..self.n_channels { c: i32,
if self.get_channel(Point2i::new(x, y), c).is_infinite() { wrap_mode: WrapMode2D,
return true; ) -> Float {
} let pi = Point2i::new(
} p.x() as i32 * self.resolution.x(),
} p.y() as i32 * self.resolution.y(),
} );
false
self.get_channel_with_wrap(pi, c, wrap_mode)
} }
pub fn has_any_nan_pixels(&self) -> bool { pub fn lookup_nearest_channel(&self, p: Point2f, c: i32) -> Float {
for y in 0..self.resolution.y() { self.lookup_nearest_channel_with_wrap(p, c, WrapMode::Clamp.into())
for x in 0..self.resolution.x() {
for c in 0..self.n_channels {
if self.get_channel(Point2i::new(x, y), c).is_nan() {
return true;
} }
} }
}
}
false
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FilterFunction {
Point,
Bilinear,
Trilinear,
Ewa,
}
impl FilterFunction {
pub fn parse(name: &str) -> Result<FilterFunction> {
match name {
"ewa" | "EWA" => Ok(FilterFunction::Ewa),
"trilinear" => Ok(FilterFunction::Trilinear),
"bilinear" => Ok(FilterFunction::Bilinear),
"point" => Ok(FilterFunction::Point),
_ => Err(Error::UnknownFilterFunction),
}
}
}

View file

@ -1,7 +1,6 @@
use crate::bxdfs::DiffuseBxDF; use crate::Float;
use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF; use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::{BxDF, BxDFFlags}; use crate::core::bxdf::{BSDF, BxDF, BxDFFlags, DiffuseBxDF};
use crate::core::camera::{Camera, CameraTrait}; use crate::core::camera::{Camera, CameraTrait};
use crate::core::geometry::{ use crate::core::geometry::{
Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike, Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike,
@ -9,17 +8,19 @@ use crate::core::geometry::{
use crate::core::image::Image; use crate::core::image::Image;
use crate::core::light::{Light, LightTrait}; use crate::core::light::{Light, LightTrait};
use crate::core::material::{ use crate::core::material::{
bump_map, normal_map, Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map,
}; };
use crate::core::medium::{Medium, MediumInterface, PhaseFunction}; use crate::core::medium::{Medium, MediumInterface, PhaseFunction};
use crate::core::options::get_options;
use crate::core::sampler::{Sampler, SamplerTrait}; use crate::core::sampler::{Sampler, SamplerTrait};
use crate::core::shape::Shape; use crate::core::shape::Shape;
use crate::core::texture::{FloatTexture, UniversalTextureEvaluator}; use crate::core::texture::{GPUFloatTexture, UniversalTextureEvaluator};
use crate::core::{LightIdx, MaterialIdx};
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::math::{clamp, difference_of_products, square}; use crate::utils::math::{clamp, difference_of_products, square};
use crate::{Ptr, Float};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::any::Any;
use std::default;
#[repr(C)] #[repr(C)]
#[derive(Default, Copy, Clone, Debug)] #[derive(Default, Copy, Clone, Debug)]
@ -217,42 +218,40 @@ pub struct ShadingGeom {
#[repr(C)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
pub struct SurfaceInteraction { pub struct SurfaceInteraction {
pub area_light: LightIdx,
pub material: MaterialIdx,
pub shape: Ptr<Shape>,
pub common: InteractionBase, pub common: InteractionBase,
pub shading: ShadingGeom,
pub dpdu: Vector3f, pub dpdu: Vector3f,
pub dpdv: Vector3f, pub dpdv: Vector3f,
pub dndu: Normal3f, pub dndu: Normal3f,
pub dndv: Normal3f, pub dndv: Normal3f,
pub shading: ShadingGeom,
pub face_index: u32,
pub area_light: Ptr<Light>,
pub material: Ptr<Material>,
pub shape: Ptr<Shape>,
pub dpdx: Vector3f, pub dpdx: Vector3f,
pub dpdy: Vector3f, pub dpdy: Vector3f,
pub face_index: i32,
pub dudx: Float, pub dudx: Float,
pub dvdx: Float, pub dvdx: Float,
pub dudy: Float, pub dudy: Float,
pub dvdy: Float, pub dvdy: Float,
} }
unsafe impl Send for SurfaceInteraction {}
unsafe impl Sync for SurfaceInteraction {}
impl SurfaceInteraction { impl SurfaceInteraction {
pub fn le( pub fn le(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
&self, if !self.area_light.is_null() {
w: Vector3f, self.area_light
lambda: &SampledWavelengths, .l(self.p(), self.n(), self.common.uv, w, lambda)
lights: &[Light], } else {
) -> SampledSpectrum { SampledSpectrum::new(0.)
if self.area_light.is_none() {
return SampledSpectrum::new(0.);
} }
let light = self.area_light.get(lights);
light.l(self.p(), self.n(), self.common.uv, w, lambda)
} }
pub fn compute_differentials(&mut self, r: &Ray, camera: &Camera, samples_per_pixel: i32) { pub fn compute_differentials(&mut self, r: &Ray, camera: &Camera, samples_per_pixel: i32) {
let computed = if r.has_differentials { let computed = if !r.differential.is_null() {
let diff = r.differential; let diff = unsafe { &*r.differential };
let dot_rx = self.common.n.dot(diff.rx_direction.into()); let dot_rx = self.common.n.dot(diff.rx_direction.into());
let dot_ry = self.common.n.dot(diff.ry_direction.into()); let dot_ry = self.common.n.dot(diff.ry_direction.into());
@ -339,24 +338,88 @@ impl SurfaceInteraction {
let new_ray = Ray::spawn(&self.pi(), &self.n(), ray.time, ray.d); let new_ray = Ray::spawn(&self.pi(), &self.n(), ray.time, ray.d);
ray.o = new_ray.o; ray.o = new_ray.o;
// Skipping other variables, since they should not change when passing through surface // Skipping other variables, since they should not change when passing through surface
if ray.has_differentials { if !ray.differential.is_null() {
let mut diff = ray.differential; let diff = unsafe { &mut *ray.differential };
diff.rx_origin += diff.rx_direction * t; diff.rx_origin += diff.rx_direction * t;
diff.ry_origin += diff.ry_direction * t; diff.ry_origin += diff.ry_direction * t;
} }
} }
pub fn compute_bump_geom( #[cfg(not(target_os = "cuda"))]
pub fn get_bsdf(
&mut self,
r: &Ray,
lambda: &SampledWavelengths,
camera: &Camera,
sampler: &mut Sampler,
) -> Option<BSDF> {
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 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.clone()
};
let ctx = MaterialEvalContext::from(&*self);
let tex_eval = UniversalTextureEvaluator;
let displacement = material.get_displacement();
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);
}
let mut bsdf = material.get_bsdf(&tex_eval, &ctx, lambda);
if get_options().force_diffuse {
let r = bsdf.rho_wo(self.common.wo, &[sampler.get1d()], &[sampler.get2d()]);
let diff_bxdf = BxDF::Diffuse(DiffuseBxDF::new(r));
bsdf = BSDF::new(self.shading.n, self.shading.dpdu, Some(diff_bxdf));
}
Some(bsdf)
}
#[cfg(not(target_os = "cuda"))]
pub fn get_bssrdf(
&self,
_ray: &Ray,
lambda: &SampledWavelengths,
_camera: &Camera,
) -> Option<BSSRDF> {
let material = {
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.clone()
};
let ctx = MaterialEvalContext::from(self);
let tex_eval = UniversalTextureEvaluator;
material.get_bssrdf(&tex_eval, &ctx, lambda)
}
fn compute_bump_geom(
&mut self, &mut self,
tex_eval: &UniversalTextureEvaluator, tex_eval: &UniversalTextureEvaluator,
displacement: Ptr<FloatTexture>, displacement: Ptr<GPUFloatTexture>,
normal_image: Ptr<Image>, normal_image: Ptr<Image>,
) { ) {
let ctx = NormalBumpEvalContext::from(&*self); let ctx = NormalBumpEvalContext::from(&*self);
let (dpdu, dpdv) = if !displacement.is_null() { let (dpdu, dpdv) = if !displacement.is_null() {
bump_map(tex_eval, &displacement, &ctx) bump_map(tex_eval, &displacement, &ctx)
} else if !normal_image.is_null() { } else if let Some(map) = normal_image {
normal_map(&normal_image, &ctx) normal_map(map.as_ref(), &ctx)
} else { } else {
(self.shading.dpdu, self.shading.dpdv) (self.shading.dpdu, self.shading.dpdv)
}; };
@ -378,8 +441,7 @@ impl SurfaceInteraction {
) -> Ray { ) -> Ray {
let mut rd = self.spawn_ray(wi); let mut rd = self.spawn_ray(wi);
if ray_i.has_differentials { if let Some(diff_i) = &ray_i.differential {
let diff_i = ray_i.differential;
let mut n = self.shading.n; let mut n = self.shading.n;
let mut dndx = self.shading.dndu * self.dudx + self.shading.dndv * self.dvdx; let mut dndx = self.shading.dndu * self.dudx + self.shading.dndv * self.dvdx;
@ -521,9 +583,9 @@ impl SurfaceInteraction {
dndu, dndu,
dndv, dndv,
}, },
material: MaterialIdx::default(), material: Ptr::null(),
face_index: 0, face_index: 0,
area_light: LightIdx::default(), area_light: Ptr::null(),
dpdx: Vector3f::zero(), dpdx: Vector3f::zero(),
dpdy: Vector3f::zero(), dpdy: Vector3f::zero(),
dudx: 0.0, dudx: 0.0,
@ -544,7 +606,7 @@ impl SurfaceInteraction {
dndv: Normal3f, dndv: Normal3f,
time: Float, time: Float,
flip: bool, flip: bool,
face_index: i32, face_index: u32,
) -> Self { ) -> Self {
let mut si = Self::new(pi, uv, wo, dpdu, dpdv, dndu, dndv, time, flip); let mut si = Self::new(pi, uv, wo, dpdu, dpdv, dndu, dndv, time, flip);
si.face_index = face_index; si.face_index = face_index;
@ -563,8 +625,6 @@ impl SurfaceInteraction {
self.shading.n = ns; self.shading.n = ns;
if orientation { if orientation {
self.common.n = self.n().face_forward(self.shading.n); self.common.n = self.n().face_forward(self.shading.n);
} else {
self.shading.n = self.shading.n.face_forward(self.common.n);
} }
self.shading.dpdu = dpdus; self.shading.dpdu = dpdus;
self.shading.dpdv = dpdvs; self.shading.dpdv = dpdvs;
@ -590,16 +650,16 @@ impl SurfaceInteraction {
} }
} }
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
pub fn set_intersection_properties( pub fn set_intersection_properties(
&mut self, &mut self,
mtl: MaterialIdx, mtl: &Material,
area: LightIdx, area: &Light,
ray_medium: Ptr<Medium>, ray_medium: &Medium,
prim_medium_interface: MediumInterface, prim_medium_interface: MediumInterface,
) { ) {
self.material = mtl; self.material = Ptr::from(mtl);
self.area_light = area; self.area_light = Ptr::from(area);
if prim_medium_interface.is_medium_transition() { if prim_medium_interface.is_medium_transition() {
self.common.medium_interface = prim_medium_interface; self.common.medium_interface = prim_medium_interface;

View file

@ -3,6 +3,7 @@ use crate::core::geometry::{
Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray, Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray,
Vector3f, VectorLike, cos_theta, Vector3f, VectorLike, cos_theta,
}; };
use crate::core::image::Image;
use crate::core::interaction::{ use crate::core::interaction::{
Interaction, InteractionBase, InteractionTrait, MediumInteraction, SimpleInteraction, Interaction, InteractionBase, InteractionTrait, MediumInteraction, SimpleInteraction,
SurfaceInteraction, SurfaceInteraction,
@ -16,6 +17,8 @@ use crate::spectra::{
}; };
use crate::utils::Transform; use crate::utils::Transform;
use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square}; use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square};
use crate::utils::ptr::Ptr;
use crate::utils::sampling::PiecewiseConstant2D;
use crate::{Float, PI}; use crate::{Float, PI};
use bitflags::bitflags; use bitflags::bitflags;
@ -61,7 +64,7 @@ pub struct LightLiSample {
pub p_light: Interaction, pub p_light: Interaction,
} }
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
impl LightLiSample { 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: Interaction) -> Self {
Self { Self {
@ -176,9 +179,7 @@ impl LightBase {
} }
#[repr(C)] #[repr(C)]
// Default gives phi == 0, which `union` treats as empty -- that is what the SAH #[derive(Debug, Copy, Clone)]
// bucket accumulation starts from.
#[derive(Debug, Copy, Clone, Default)]
pub struct LightBounds { pub struct LightBounds {
pub bounds: Bounds3f, pub bounds: Bounds3f,
pub phi: Float, pub phi: Float,
@ -188,7 +189,7 @@ pub struct LightBounds {
pub two_sided: bool, pub two_sided: bool,
} }
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
impl LightBounds { impl LightBounds {
pub fn new( pub fn new(
bounds: &Bounds3f, bounds: &Bounds3f,
@ -211,8 +212,7 @@ impl LightBounds {
impl LightBounds { impl LightBounds {
pub fn centroid(&self) -> Point3f { pub fn centroid(&self) -> Point3f {
// (pMin + pMax) / 2 -- Point has no scalar Div, so go via Vector. self.bounds.p_min + Vector3f::from(self.bounds.p_max) / 2.
Point3f::from((Vector3f::from(self.bounds.p_min) + Vector3f::from(self.bounds.p_max)) / 2.)
} }
pub fn importance(&self, p: Point3f, n: Normal3f) -> Float { pub fn importance(&self, p: Point3f, n: Normal3f) -> Float {
@ -270,12 +270,11 @@ impl LightBounds {
} }
pub fn union(a: &Self, b: &Self) -> Self { pub fn union(a: &Self, b: &Self) -> Self {
// If one LightBounds has zero power, return the *other* (lights.h:137).
if a.phi == 0. { if a.phi == 0. {
return *b; return a.clone();
} }
if b.phi == 0. { if b.phi == 0. {
return *a; return b.clone();
} }
let a_cone = DirectionCone::new(a.w, a.cos_theta_o); let a_cone = DirectionCone::new(a.w, a.cos_theta_o);
@ -315,25 +314,21 @@ pub trait LightTrait {
uv: Point2f, uv: Point2f,
w: Vector3f, w: Vector3f,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum;
self.base().l(p, n, uv, w, lambda)
}
fn le(&self, ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum { fn le(&self, ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum;
self.base().le(ray, lambda)
}
fn light_type(&self) -> LightType { fn light_type(&self) -> LightType {
self.base().light_type self.base().light_type
} }
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
fn bounds(&self) -> Option<LightBounds>; fn bounds(&self) -> Option<LightBounds>;
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
fn preprocess(&mut self, scene_bounds: &Bounds3f); fn preprocess(&mut self, scene_bounds: &Bounds3f);
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum; fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum;
} }
@ -345,9 +340,9 @@ pub enum Light {
DiffuseArea(DiffuseAreaLight), DiffuseArea(DiffuseAreaLight),
Distant(DistantLight), Distant(DistantLight),
Goniometric(GoniometricLight), Goniometric(GoniometricLight),
InfiniteUniform(UniformInfiniteLight), InfiniteUniform(InfiniteUniformLight),
InfiniteImage(ImageInfiniteLight), InfiniteImage(InfiniteImageLight),
InfinitePortal(PortalInfiniteLight), InfinitePortal(InfinitePortalLight),
Point(PointLight), Point(PointLight),
Projection(ProjectionLight), Projection(ProjectionLight),
Spot(SpotLight), Spot(SpotLight),

View file

@ -1,6 +1,6 @@
use crate::materials::*; use crate::materials::*;
use core::ops::Deref;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::ops::Deref;
use crate::Float; use crate::Float;
use crate::bxdfs::{ use crate::bxdfs::{
@ -14,7 +14,9 @@ use crate::core::image::{Image, WrapMode, WrapMode2D};
use crate::core::interaction::{Interaction, InteractionTrait, ShadingGeom, SurfaceInteraction}; use crate::core::interaction::{Interaction, InteractionTrait, ShadingGeom, SurfaceInteraction};
use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{FloatTexture, SpectrumTexture, TextureEvalContext, TextureEvaluator}; use crate::core::texture::{
GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext, TextureEvaluator,
};
use crate::materials::*; use crate::materials::*;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr; use crate::utils::Ptr;
@ -63,7 +65,7 @@ pub struct NormalBumpEvalContext {
pub dudy: Float, pub dudy: Float,
pub dvdx: Float, pub dvdx: Float,
pub dvdy: Float, pub dvdy: Float,
pub face_index: i32, pub face_index: u32,
} }
impl From<&SurfaceInteraction> for NormalBumpEvalContext { impl From<&SurfaceInteraction> for NormalBumpEvalContext {
@ -72,7 +74,7 @@ impl From<&SurfaceInteraction> for NormalBumpEvalContext {
p: si.p(), p: si.p(),
uv: si.common.uv, uv: si.common.uv,
n: si.n(), n: si.n(),
shading: si.shading, shading: si.shading.clone(),
dudx: si.dudx, dudx: si.dudx,
dudy: si.dudy, dudy: si.dudy,
dvdx: si.dvdx, dvdx: si.dvdx,
@ -120,7 +122,7 @@ pub fn normal_map(normal_map: &Image, ctx: &NormalBumpEvalContext) -> (Vector3f,
pub fn bump_map<T: TextureEvaluator>( pub fn bump_map<T: TextureEvaluator>(
tex_eval: &T, tex_eval: &T,
displacement: &FloatTexture, displacement: &GPUFloatTexture,
ctx: &NormalBumpEvalContext, ctx: &NormalBumpEvalContext,
) -> (Vector3f, Vector3f) { ) -> (Vector3f, Vector3f) {
debug_assert!(tex_eval.can_evaluate(&[Ptr::from(displacement)], &[])); debug_assert!(tex_eval.can_evaluate(&[Ptr::from(displacement)], &[]));
@ -160,7 +162,7 @@ pub trait MaterialTrait {
&self, &self,
tex_eval: &T, tex_eval: &T,
ctx: &MaterialEvalContext, ctx: &MaterialEvalContext,
lambda: &mut SampledWavelengths, lambda: &SampledWavelengths,
) -> BSDF; ) -> BSDF;
fn get_bssrdf<T: TextureEvaluator>( fn get_bssrdf<T: TextureEvaluator>(
@ -172,11 +174,10 @@ pub trait MaterialTrait {
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool; fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool;
fn get_normal_map(&self) -> Option<&Image>; fn get_normal_map(&self) -> Option<&Image>;
fn get_displacement(&self) -> Ptr<FloatTexture>; fn get_displacement(&self) -> Ptr<GPUFloatTexture>;
fn has_subsurface_scattering(&self) -> bool; fn has_subsurface_scattering(&self) -> bool;
} }
#[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
#[enum_dispatch(MaterialTrait)] #[enum_dispatch(MaterialTrait)]
pub enum Material { pub enum Material {
@ -192,17 +193,3 @@ pub enum Material {
ThinDielectric(ThinDielectricMaterial), ThinDielectric(ThinDielectricMaterial),
Mix(MixMaterial), Mix(MixMaterial),
} }
impl Material {
#[inline(never)]
pub fn is_conductor(&self) -> bool {
matches!(self, Material::Conductor(_))
}
}
// TODO: THIS IS A HACK JUST FOR TESTING
impl PartialEq for Material {
fn eq(&self, other: &Self) -> bool {
core::mem::discriminant(self) == core::mem::discriminant(other)
}
}

View file

@ -1,19 +1,20 @@
use enum_dispatch::enum_dispatch;
use std::sync::Arc;
use crate::core::geometry::{ use crate::core::geometry::{
spherical_direction, Bounds3f, Frame, Point2f, Point3f, Point3i, Ray, Vector3f, VectorLike, Bounds3f, Frame, Point2f, Point3f, Point3i, Ray, Vector3f, VectorLike, spherical_direction,
}; };
use crate::core::pbrt::{Float, INV_4_PI, PI}; use crate::core::pbrt::{Float, INV_4_PI, PI};
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::spectra::{ use crate::spectra::{
BlackbodySpectrum, DenselySampledSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum,
SampledSpectrum, SampledWavelengths, LAMBDA_MAX, LAMBDA_MIN, RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths,
}; };
use crate::utils::containers::SampledGrid; use crate::utils::containers::SampledGrid;
use crate::utils::math::{clamp, square}; use crate::utils::math::{clamp, square};
use crate::utils::ptr::Ptr;
use crate::utils::rng::Rng; use crate::utils::rng::Rng;
use crate::utils::transform::Transform; use crate::utils::transform::Transform;
use crate::{gvec_with_capacity, leak, GVec, Ptr};
use enum_dispatch::enum_dispatch;
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -89,31 +90,29 @@ impl PhaseFunctionTrait for HGPhaseFunction {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub struct MajorantGrid { pub struct MajorantGrid {
pub bounds: Bounds3f, pub bounds: Bounds3f,
pub res: Point3i, pub res: Point3i,
pub voxels: GVec<Float>, pub voxels: *const Float,
pub n_voxels: u32,
} }
unsafe impl Send for MajorantGrid {}
unsafe impl Sync for MajorantGrid {}
impl MajorantGrid { impl MajorantGrid {
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
pub fn new(bounds: Bounds3f, res: Point3i) -> Self { pub fn new(bounds: Bounds3f, res: Point3i) -> Self {
let n_voxels = (res.x() * res.y() * res.z()) as usize;
let voxels = gvec_with_capacity(n_voxels);
Self { Self {
bounds, bounds,
res, res,
voxels, voxels: Vec::with_capacity((res.x() * res.y() * res.z()) as usize),
n_voxels: n_voxels as u32,
} }
} }
#[inline(always)] #[inline(always)]
fn is_valid(&self) -> bool { fn is_valid(&self) -> bool {
!self.voxels.is_empty() !self.voxels.is_null()
} }
#[inline(always)] #[inline(always)]
@ -124,15 +123,15 @@ impl MajorantGrid {
let idx = z * self.res.x() * self.res.y() + y * self.res.x() + x; let idx = z * self.res.x() * self.res.y() + y * self.res.x() + x;
if idx >= 0 && (idx as u32) < self.n_voxels { if idx >= 0 && (idx as usize) < self.voxels.len() {
unsafe { *self.voxels.as_ptr().add(idx as usize) } unsafe { *self.voxels.add(idx as usize) }
} else { } else {
0.0 0.0
} }
} }
#[inline(always)] #[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() { if !self.is_valid() {
return; return;
} }
@ -140,7 +139,7 @@ impl MajorantGrid {
let idx = x + self.res.x() * (y + self.res.y() * z); let idx = x + self.res.x() * (y + self.res.y() * z);
unsafe { unsafe {
*self.voxels.as_mut_ptr().add(idx as usize) = v; *self.voxels.add(idx as usize) = v;
} }
} }
@ -168,7 +167,7 @@ pub struct RayMajorantSegment {
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub enum RayMajorantIterator { pub enum RayMajorantIterator {
Homogeneous(HomogeneousMajorantIterator), Homogeneous(HomogeneousMajorantIterator),
DDA(DDAMajorantIterator), DDA(DDAMajorantIterator),
@ -189,7 +188,7 @@ impl Iterator for RayMajorantIterator {
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub struct HomogeneousMajorantIterator { pub struct HomogeneousMajorantIterator {
called: bool, called: bool,
seg: RayMajorantSegment, seg: RayMajorantSegment,
@ -222,12 +221,12 @@ impl Iterator for HomogeneousMajorantIterator {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub struct DDAMajorantIterator { pub struct DDAMajorantIterator {
sigma_t: SampledSpectrum, sigma_t: SampledSpectrum,
t_min: Float, t_min: Float,
t_max: Float, t_max: Float,
grid: Ptr<MajorantGrid>, grid: MajorantGrid,
next_crossing_t: [Float; 3], next_crossing_t: [Float; 3],
delta_t: [Float; 3], delta_t: [Float; 3],
step: [i32; 3], step: [i32; 3],
@ -247,7 +246,7 @@ impl DDAMajorantIterator {
t_min, t_min,
t_max, t_max,
sigma_t: *sigma_t, sigma_t: *sigma_t,
grid: Ptr::from(&*grid), grid: *grid,
next_crossing_t: [0.0; 3], next_crossing_t: [0.0; 3],
delta_t: [0.0; 3], delta_t: [0.0; 3],
step: [0; 3], step: [0; 3],
@ -265,7 +264,7 @@ impl DDAMajorantIterator {
let p_grid_start = grid.bounds.offset(&ray.at(t_min)); let p_grid_start = grid.bounds.offset(&ray.at(t_min));
let grid_intersect = Vector3f::from(p_grid_start); let grid_intersect = Vector3f::from(p_grid_start);
let res = [grid.res.x(), grid.res.y(), grid.res.z()]; let res = [grid.res.x, grid.res.y, grid.res.z];
for axis in 0..3 { for axis in 0..3 {
iter.voxel[axis] = clamp( iter.voxel[axis] = clamp(
@ -368,7 +367,7 @@ impl MediumProperties {
} }
#[enum_dispatch] #[enum_dispatch]
pub trait MediumTrait: Send + Sync + core::fmt::Debug { pub trait MediumTrait: Send + Sync + std::fmt::Debug {
fn is_emissive(&self) -> bool; fn is_emissive(&self) -> bool;
fn sample_point(&self, p: Point3f, lambda: &SampledWavelengths) -> MediumProperties; fn sample_point(&self, p: Point3f, lambda: &SampledWavelengths) -> MediumProperties;
fn sample_ray( fn sample_ray(
@ -450,10 +449,36 @@ pub enum Medium {
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct HomogeneousMedium { pub struct HomogeneousMedium {
pub sigma_a_spec: Ptr<DenselySampledSpectrum>, sigma_a_spec: DenselySampledSpectrum,
pub sigma_s_spec: Ptr<DenselySampledSpectrum>, sigma_s_spec: DenselySampledSpectrum,
pub le_spec: Ptr<DenselySampledSpectrum>, le_spec: DenselySampledSpectrum,
pub phase: HGPhaseFunction, phase: HGPhaseFunction,
}
impl HomogeneousMedium {
pub fn new(
sigma_a: Spectrum,
sigma_s: Spectrum,
sigma_scale: Float,
le: Spectrum,
le_scale: Float,
g: Float,
) -> Self {
let mut sigma_a_spec = DenselySampledSpectrum::from_spectrum(&sigma_a);
let mut sigma_s_spec = DenselySampledSpectrum::from_spectrum(&sigma_s);
let mut le_spec = DenselySampledSpectrum::from_spectrum(&le);
sigma_a_spec.scale(sigma_scale);
sigma_s_spec.scale(sigma_scale);
le_spec.scale(le_scale);
Self {
sigma_a_spec,
sigma_s_spec,
le_spec,
phase: HGPhaseFunction::new(g),
}
}
} }
impl MediumTrait for HomogeneousMedium { impl MediumTrait for HomogeneousMedium {
@ -492,17 +517,70 @@ impl MediumTrait for HomogeneousMedium {
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct GridMedium { pub struct GridMedium {
pub bounds: Bounds3f, bounds: Bounds3f,
pub render_from_medium: Transform, render_from_medium: Transform,
pub sigma_a_spec: Ptr<DenselySampledSpectrum>, sigma_a_spec: DenselySampledSpectrum,
pub sigma_s_spec: Ptr<DenselySampledSpectrum>, sigma_s_spec: DenselySampledSpectrum,
pub density_grid: Ptr<SampledGrid<Float>>, density_grid: SampledGrid<Float>,
pub phase: HGPhaseFunction, phase: HGPhaseFunction,
pub temperature_grid: Ptr<SampledGrid<Float>>, temperature_grid: SampledGrid<Float>,
pub le_spec: Ptr<DenselySampledSpectrum>, le_spec: DenselySampledSpectrum,
pub le_scale: Ptr<SampledGrid<Float>>, le_scale: SampledGrid<Float>,
pub is_emissive: bool, is_emissive: bool,
pub majorant_grid: Ptr<MajorantGrid>, majorant_grid: MajorantGrid,
}
impl GridMedium {
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "cuda"))]
pub fn new(
bounds: &Bounds3f,
render_from_medium: &Transform,
sigma_a: &Spectrum,
sigma_s: &Spectrum,
sigma_scale: Float,
g: Float,
density_grid: SampledGrid<Float>,
temperature_grid: SampledGrid<Float>,
le: &Spectrum,
le_scale: SampledGrid<Float>,
) -> Self {
let mut sigma_a_spec = DenselySampledSpectrum::from_spectrum(sigma_a);
let mut sigma_s_spec = DenselySampledSpectrum::from_spectrum(sigma_s);
let le_spec = DenselySampledSpectrum::from_spectrum(le);
sigma_a_spec.scale(sigma_scale);
sigma_s_spec.scale(sigma_scale);
let mut majorant_grid = MajorantGrid::new(*bounds, Point3i::new(16, 16, 16));
let is_emissive = if temperature_grid.is_some() {
true
} else {
le_spec.max_value() > 0.
};
for z in 0..majorant_grid.res.z() {
for y in 0..majorant_grid.res.y() {
for x in 0..majorant_grid.res.x() {
let bounds = majorant_grid.voxel_bounds(x, y, z);
majorant_grid.set(x, y, z, density_grid.max_value(bounds));
}
}
}
Self {
bounds: *bounds,
render_from_medium: *render_from_medium,
sigma_a_spec,
sigma_s_spec,
density_grid,
phase: HGPhaseFunction::new(g),
temperature_grid,
le_spec,
le_scale,
is_emissive,
majorant_grid,
}
}
} }
impl MediumTrait for GridMedium { impl MediumTrait for GridMedium {
@ -525,11 +603,12 @@ impl MediumTrait for GridMedium {
}; };
let le = if scale > 0.0 { let le = if scale > 0.0 {
let raw_emission = if !self.temperature_grid.is_null() { let raw_emission = match &self.temperature_grid {
let temp = self.temperature_grid.lookup(p); Some(grid) => {
let temp = grid.lookup(p);
BlackbodySpectrum::new(temp).sample(lambda) BlackbodySpectrum::new(temp).sample(lambda)
} else { }
self.le_spec.sample(lambda) None => self.le_spec.sample(lambda),
}; };
raw_emission * scale raw_emission * scale
@ -585,15 +664,59 @@ impl MediumTrait for GridMedium {
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct RGBGridMedium { pub struct RGBGridMedium {
pub bounds: Bounds3f, bounds: Bounds3f,
pub render_from_medium: Transform, render_from_medium: Transform,
pub phase: HGPhaseFunction, phase: HGPhaseFunction,
pub le_scale: Float, le_scale: Float,
pub sigma_scale: Float, sigma_scale: Float,
pub sigma_a_grid: Ptr<SampledGrid<RGBUnboundedSpectrum>>, sigma_a_grid: SampledGrid<RGBUnboundedSpectrum>,
pub sigma_s_grid: Ptr<SampledGrid<RGBUnboundedSpectrum>>, sigma_s_grid: SampledGrid<RGBUnboundedSpectrum>,
pub le_grid: Ptr<SampledGrid<RGBIlluminantSpectrum>>, le_grid: SampledGrid<RGBIlluminantSpectrum>,
pub majorant_grid: Ptr<MajorantGrid>, majorant_grid: MajorantGrid,
}
impl RGBGridMedium {
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "cuda"))]
pub fn new(
bounds: &Bounds3f,
render_from_medium: &Transform,
g: Float,
sigma_a_grid: SampledGrid<RGBUnboundedSpectrum>,
sigma_s_grid: SampledGrid<RGBUnboundedSpectrum>,
sigma_scale: Float,
le_grid: SampledGrid<RGBIlluminantSpectrum>,
le_scale: Float,
) -> Self {
let mut majorant_grid = MajorantGrid::new(*bounds, Point3i::new(16, 16, 16));
for z in 0..majorant_grid.res.x() {
for y in 0..majorant_grid.res.y() {
for x in 0..majorant_grid.res.x() {
let bounds = majorant_grid.voxel_bounds(x, y, z);
let convert = |s: &RGBUnboundedSpectrum| s.max_value();
let max_sigma_t = sigma_a_grid
.as_ref()
.map_or(1.0, |g| g.max_value_convert(bounds, convert))
+ sigma_s_grid
.as_ref()
.map_or(1.0, |g| g.max_value_convert(bounds, convert));
majorant_grid.set(x, y, z, sigma_scale * max_sigma_t);
}
}
}
Self {
bounds: *bounds,
render_from_medium: *render_from_medium,
le_grid,
le_scale,
phase: HGPhaseFunction::new(g),
sigma_a_grid,
sigma_s_grid,
sigma_scale,
majorant_grid,
}
}
} }
impl MediumTrait for RGBGridMedium { impl MediumTrait for RGBGridMedium {
@ -666,8 +789,7 @@ impl MediumTrait for RGBGridMedium {
} }
} }
#[repr(C)] #[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)]
pub struct CloudMedium; pub struct CloudMedium;
impl MediumTrait for CloudMedium { impl MediumTrait for CloudMedium {
fn is_emissive(&self) -> bool { fn is_emissive(&self) -> bool {
@ -685,9 +807,7 @@ impl MediumTrait for CloudMedium {
todo!() todo!()
} }
} }
#[derive(Debug, Clone)]
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct NanoVDBMedium; pub struct NanoVDBMedium;
impl MediumTrait for NanoVDBMedium { impl MediumTrait for NanoVDBMedium {
fn is_emissive(&self) -> bool { fn is_emissive(&self) -> bool {
@ -713,6 +833,8 @@ pub struct MediumInterface {
pub outside: Ptr<Medium>, pub outside: Ptr<Medium>,
} }
unsafe impl Send for MediumInterface {}
unsafe impl Sync for MediumInterface {}
impl Default for MediumInterface { impl Default for MediumInterface {
fn default() -> Self { fn default() -> Self {
@ -724,13 +846,18 @@ impl Default for MediumInterface {
} }
impl MediumInterface { impl MediumInterface {
pub fn new(inside: Ptr<Medium>, outside: Ptr<Medium>) -> Self { pub fn new(inside: &Medium, outside: &Medium) -> Self {
Self { inside, outside } Self {
inside: Ptr::from(inside),
outside: Ptr::from(outside),
} }
}
pub fn empty() -> Self { pub fn empty() -> Self {
Self::default() Self::default()
} }
pub fn is_medium_transition(&self) -> bool { pub fn is_medium_transition(&self) -> bool {
self.inside != self.outside self.inside.0 != self.outside.0
} }
} }

View file

@ -1,4 +1,3 @@
pub mod aggregates;
pub mod bsdf; pub mod bsdf;
pub mod bssrdf; pub mod bssrdf;
pub mod bxdf; pub mod bxdf;
@ -7,12 +6,12 @@ pub mod color;
pub mod film; pub mod film;
pub mod filter; pub mod filter;
pub mod geometry; pub mod geometry;
pub mod handle;
pub mod image; pub mod image;
pub mod interaction; pub mod interaction;
pub mod light; pub mod light;
pub mod material; pub mod material;
pub mod medium; pub mod medium;
pub mod options;
pub mod pbrt; pub mod pbrt;
pub mod primitive; pub mod primitive;
pub mod sampler; pub mod sampler;
@ -20,5 +19,3 @@ pub mod scattering;
pub mod shape; pub mod shape;
pub mod spectrum; pub mod spectrum;
pub mod texture; pub mod texture;
pub use handle::{LightIdx, MaterialIdx};

View file

@ -1,7 +1,7 @@
use alloc::string::String;
use crate::Float;
use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i}; use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i};
use core::ops::Deref; use crate::Float;
use std::ops::Deref;
use std::sync::OnceLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RenderingCoordinateSystem { pub enum RenderingCoordinateSystem {
@ -10,7 +10,7 @@ pub enum RenderingCoordinateSystem {
World, World,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone)]
pub struct BasicPBRTOptions { pub struct BasicPBRTOptions {
pub seed: i32, pub seed: i32,
pub quiet: bool, pub quiet: bool,
@ -18,7 +18,6 @@ pub struct BasicPBRTOptions {
pub disable_wavelength_jitter: bool, pub disable_wavelength_jitter: bool,
pub disable_texture_filtering: bool, pub disable_texture_filtering: bool,
pub force_diffuse: bool, pub force_diffuse: bool,
pub record_pixel_statistics: bool,
pub use_gpu: bool, pub use_gpu: bool,
pub wavefront: bool, pub wavefront: bool,
pub interactive: bool, pub interactive: bool,
@ -35,7 +34,6 @@ impl Default for BasicPBRTOptions {
disable_wavelength_jitter: false, disable_wavelength_jitter: false,
disable_texture_filtering: false, disable_texture_filtering: false,
force_diffuse: false, force_diffuse: false,
record_pixel_statistics: false,
use_gpu: false, use_gpu: false,
wavefront: false, wavefront: false,
interactive: false, interactive: false,
@ -50,9 +48,9 @@ pub struct PBRTOptions {
pub basic: BasicPBRTOptions, pub basic: BasicPBRTOptions,
pub n_threads: usize, pub n_threads: usize,
pub log_level: &'static str, pub log_level: String,
pub write_partial_images: bool, pub write_partial_images: bool,
pub image_file: &'static str, pub image_file: String,
pub pixel_samples: Option<i32>, pub pixel_samples: Option<i32>,
pub gpu_device: Option<u32>, pub gpu_device: Option<u32>,
pub mse_reference_image: Option<String>, pub mse_reference_image: Option<String>,
@ -60,7 +58,7 @@ pub struct PBRTOptions {
pub debug_start: Option<(Point2i, i32)>, pub debug_start: Option<(Point2i, i32)>,
pub quick_render: bool, pub quick_render: bool,
pub upgrade: bool, pub upgrade: bool,
pub display_server: &'static str, pub display_server: String,
pub crop_window: Option<Bounds2f>, pub crop_window: Option<Bounds2f>,
pub pixel_bounds: Option<Bounds2i>, pub pixel_bounds: Option<Bounds2i>,
pub pixel_material: Option<Point2i>, pub pixel_material: Option<Point2i>,
@ -72,17 +70,17 @@ impl Default for PBRTOptions {
Self { Self {
basic: BasicPBRTOptions::default(), basic: BasicPBRTOptions::default(),
n_threads: 0, n_threads: 0,
log_level: "info", log_level: "info".to_string(),
write_partial_images: false, write_partial_images: false,
pixel_samples: None, pixel_samples: None,
gpu_device: None, gpu_device: None,
quick_render: false, quick_render: false,
upgrade: false, upgrade: false,
image_file: "output.exr", image_file: "output.exr".to_string(),
mse_reference_image: None, mse_reference_image: None,
mse_reference_output: None, mse_reference_output: None,
debug_start: None, debug_start: Some((Point2i::default(), 0)),
display_server: "", display_server: "".to_string(),
crop_window: None, crop_window: None,
pixel_bounds: None, pixel_bounds: None,
pixel_material: None, pixel_material: None,
@ -98,3 +96,22 @@ impl Deref for PBRTOptions {
&self.basic &self.basic
} }
} }
static OPTIONS: OnceLock<PBRTOptions> = OnceLock::new();
pub fn init_pbrt(options: PBRTOptions) {
OPTIONS
.set(options)
.expect("PBRT has already been initialized!");
}
pub fn cleanup_pbrt() {
todo!()
}
pub fn get_options() -> &'static PBRTOptions {
OPTIONS.get().unwrap_or_else(|| {
static DEFAULT: OnceLock<PBRTOptions> = OnceLock::new();
DEFAULT.get_or_init(PBRTOptions::default)
})
}

View file

@ -1,13 +1,9 @@
use crate::core::geometry::Lerp; use crate::core::geometry::Lerp;
use core::ops::{Add, Mul}; use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
use num_traits::{Float as NumFloat, Num, NumCast, PrimInt}; use num_traits::{Num, PrimInt};
use std::hash::Hash;
use crate::core::light::LightTrait; use std::ops::{Add, Mul};
use crate::core::shape::Shape; use std::sync::{Arc, Mutex};
use crate::core::texture::FloatTexture;
use crate::lights::*;
use crate::spectra::{DenselySampledSpectrum, RGBColorSpace};
use crate::utils::Ptr;
pub type Float = f32; pub type Float = f32;
@ -96,30 +92,44 @@ impl FloatBitOps for f64 {
pub const MACHINE_EPSILON: Float = Float::EPSILON * 0.5; pub const MACHINE_EPSILON: Float = Float::EPSILON * 0.5;
pub const SHADOW_EPSILON: Float = 0.0001; pub const SHADOW_EPSILON: Float = 0.0001;
pub const ONE_MINUS_EPSILON: Float = 0.99999994; pub const ONE_MINUS_EPSILON: Float = 0.99999994;
pub const PI: Float = core::f32::consts::PI; pub const PI: Float = std::f32::consts::PI;
pub const INV_PI: Float = core::f32::consts::FRAC_1_PI; pub const INV_PI: Float = 0.318_309_886_183_790_671_54;
pub const INV_2_PI: Float = 0.159_154_943_091_895_335_77; pub const INV_2_PI: Float = 0.159_154_943_091_895_335_77;
pub const INV_4_PI: Float = 0.079_577_471_545_947_667_88; pub const INV_4_PI: Float = 0.079_577_471_545_947_667_88;
pub const PI_OVER_2: Float = core::f32::consts::FRAC_PI_2; pub const PI_OVER_2: Float = 1.570_796_326_794_896_619_23;
pub const PI_OVER_4: Float = core::f32::consts::FRAC_PI_4; pub const PI_OVER_4: Float = 0.785_398_163_397_448_309_61;
pub const SQRT_2: Float = core::f32::consts::SQRT_2; pub const SQRT_2: Float = 1.414_213_562_373_095_048_80;
#[inline] #[inline]
pub fn gamma_t<T: NumFloat + NumCast>(n: i32) -> T { pub fn find_interval<F>(sz: u32, pred: F) -> u32
let n = T::from(n).unwrap(); where
let eps = T::epsilon() / (T::one() + T::one()); F: Fn(u32) -> bool,
n * eps / (T::one() - n * eps) {
let mut first = 0;
let mut len = sz;
while len > 0 {
let half = len >> 1;
let middle = first + half;
if pred(middle) {
first = middle + 1;
len -= half + 1;
} else {
len = half;
}
}
let ret = (first as i32 - 1).max(0) as u32;
ret.min(sz.saturating_sub(2))
} }
#[inline] #[inline]
pub fn gamma(n: i32) -> Float { pub fn gamma(n: i32) -> Float {
gamma_t::<Float>(n) n as Float * MACHINE_EPSILON / (1. - n as Float * MACHINE_EPSILON)
} }
#[cfg(feature = "cpu_debug")] // Define the static counters. These are thread-safe.
pub mod debug {
use core::sync::atomic::AtomicU64;
use core::sync::atomic::Ordering as SyncOrdering;
pub static RARE_EVENT_TOTAL_CALLS: AtomicU64 = AtomicU64::new(0); pub static RARE_EVENT_TOTAL_CALLS: AtomicU64 = AtomicU64::new(0);
pub static RARE_EVENT_CONDITION_MET: AtomicU64 = AtomicU64::new(0); pub static RARE_EVENT_CONDITION_MET: AtomicU64 = AtomicU64::new(0);
@ -149,4 +159,3 @@ pub mod debug {
} }
}; };
} }
}

View file

@ -1,22 +1,21 @@
use crate::core::aggregates::BVHAggregate; use crate::core::aggregates::LinearBVHNode;
use crate::core::geometry::{Bounds3f, Ray}; use crate::core::geometry::{Bounds3f, Ray};
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::light::Light; use crate::core::light::Light;
use crate::core::material::Material; use crate::core::material::Material;
use crate::core::medium::{Medium, MediumInterface}; use crate::core::medium::{Medium, MediumInterface};
use crate::core::pbrt::Float;
use crate::core::shape::{Shape, ShapeIntersection, ShapeTrait}; use crate::core::shape::{Shape, ShapeIntersection, ShapeTrait};
use crate::core::texture::{FloatTexture, TextureEvalContext}; use crate::core::texture::{GPUFloatTexture, TextureEvalContext};
use crate::core::{LightIdx, MaterialIdx}; use crate::utils::ArenaPtr;
use crate::utils::hash::hash_float; use crate::utils::hash::hash_float;
use crate::utils::transform::{AnimatedTransform, Transform}; use crate::utils::transform::{AnimatedTransform, Transform};
use crate::{Float, Ptr};
use alloc::boxed::Box;
use alloc::sync::Arc;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::sync::Arc;
#[enum_dispatch] #[enum_dispatch]
pub trait PrimitiveTrait: Send + Sync { pub trait PrimitiveTrait {
fn bounds(&self) -> Bounds3f; fn bounds(&self) -> Bounds3f;
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection>; fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection>;
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool; fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool;
@ -25,13 +24,15 @@ pub trait PrimitiveTrait: Send + Sync {
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct GeometricPrimitive { pub struct GeometricPrimitive {
pub shape: Ptr<Shape>, shape: *const Shape,
pub material: MaterialIdx, material: *const Material,
pub area_light: LightIdx, area_light: *const Light,
pub medium_interface: MediumInterface, medium_interface: MediumInterface,
pub alpha: Ptr<FloatTexture>, alpha: *const GPUFloatTexture,
} }
unsafe impl Send for GeometricPrimitive {}
unsafe impl Sync for GeometricPrimitive {}
impl PrimitiveTrait for GeometricPrimitive { impl PrimitiveTrait for GeometricPrimitive {
fn bounds(&self) -> Bounds3f { fn bounds(&self) -> Bounds3f {
@ -40,8 +41,7 @@ impl PrimitiveTrait for GeometricPrimitive {
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> { fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
let mut si = self.shape.intersect(r, t_max)?; let mut si = self.shape.intersect(r, t_max)?;
if !self.alpha.is_null() { if let Some(ref alpha) = self.alpha {
let alpha = &self.alpha.get().unwrap();
let ctx = TextureEvalContext::from(&si.intr); let ctx = TextureEvalContext::from(&si.intr);
let a = alpha.evaluate(&ctx); let a = alpha.evaluate(&ctx);
if a < 1.0 { if a < 1.0 {
@ -67,17 +67,17 @@ impl PrimitiveTrait for GeometricPrimitive {
} }
si.set_intersection_properties( si.set_intersection_properties(
self.material, self.material.clone(),
self.area_light, self.area_light.clone(),
self.medium_interface, Some(self.medium_interface.clone()),
r.medium, Some(r.medium.clone().expect("Medium not set")),
); );
Some(si) Some(si)
} }
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool { fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool {
if !self.alpha.is_null() { if self.alpha.is_some() {
self.intersect(r, t_max).is_some() self.intersect(r, t_max).is_some()
} else { } else {
self.shape.intersect_p(r, t_max) self.shape.intersect_p(r, t_max)
@ -88,35 +88,14 @@ impl PrimitiveTrait for GeometricPrimitive {
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct SimplePrimitive { pub struct SimplePrimitive {
pub shape: Ptr<Shape>, shape: ArenaPtr<Shape>,
pub material: MaterialIdx, material: ArenaPtr<Material>,
} }
impl PrimitiveTrait for SimplePrimitive { #[derive(Debug, Clone)]
fn bounds(&self) -> Bounds3f {
self.shape.bounds()
}
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
let mut si = self.shape.intersect(r, t_max)?;
si.set_intersection_properties(
self.material,
LightIdx::default(),
MediumInterface::default(),
r.medium,
);
Some(si)
}
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool {
self.shape.intersect_p(r, t_max)
}
}
#[derive(Debug, Clone, Copy)]
pub struct TransformedPrimitive { pub struct TransformedPrimitive {
pub primitive: Ptr<Primitive>, pub primitive: ArenaPtr<Primitive>,
pub render_from_primitive: Ptr<Transform>, pub render_from_primitive: Transform,
} }
impl PrimitiveTrait for TransformedPrimitive { impl PrimitiveTrait for TransformedPrimitive {
@ -142,17 +121,16 @@ impl PrimitiveTrait for TransformedPrimitive {
Some(si) Some(si)
} }
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool { fn intersect_p(&self, _r: &Ray, _t_max: Option<Float>) -> bool {
let (ray, t_max) = self.render_from_primitive.apply_inverse_ray(r, t_max); todo!()
self.primitive.intersect_p(&ray, Some(t_max))
} }
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct AnimatedPrimitive { pub struct AnimatedPrimitive {
pub primitive: Ptr<Primitive>, primitive: ArenaPtr<Primitive>,
pub render_from_primitive: Ptr<AnimatedTransform>, render_from_primitive: AnimatedTransform,
} }
impl PrimitiveTrait for AnimatedPrimitive { impl PrimitiveTrait for AnimatedPrimitive {
@ -183,12 +161,38 @@ impl PrimitiveTrait for AnimatedPrimitive {
} }
#[repr(C)] #[repr(C)]
#[derive(Default, Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct LinearBVHNode { pub struct BVHAggregatePrimitive {
bounds: Bounds3f, max_prims_in_node: u32,
primitives: *const ArenaPtr<Primitive>,
nodes: *const LinearBVHNode,
} }
#[derive(Debug, Clone, Copy)] impl PrimitiveTrait for BVHAggregatePrimitive {
fn bounds(&self) -> Bounds3f {
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_empty() {
return None;
}
self.intersect(r, t_max)
}
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool {
if self.nodes.is_empty() {
return false;
}
self.intersect_p(r, t_max)
}
}
#[derive(Debug, Clone)]
pub struct KdTreeAggregate; pub struct KdTreeAggregate;
impl PrimitiveTrait for KdTreeAggregate { impl PrimitiveTrait for KdTreeAggregate {
@ -205,28 +209,12 @@ impl PrimitiveTrait for KdTreeAggregate {
} }
} }
#[repr(C)] #[derive(Clone, Debug)]
#[derive(Clone, Debug, Copy)]
#[enum_dispatch(PrimitiveTrait)] #[enum_dispatch(PrimitiveTrait)]
pub enum Primitive { pub enum Primitive {
Simple(SimplePrimitive),
Geometric(GeometricPrimitive), Geometric(GeometricPrimitive),
Transformed(TransformedPrimitive), Transformed(TransformedPrimitive),
Animated(AnimatedPrimitive), Animated(AnimatedPrimitive),
BVH(Ptr<BVHAggregate>), BVH(BVHAggregatePrimitive),
KdTree(KdTreeAggregate), KdTree(KdTreeAggregate),
} }
impl<T: PrimitiveTrait> PrimitiveTrait for Ptr<T> {
fn bounds(&self) -> Bounds3f {
self.get().unwrap().bounds()
}
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
self.get().unwrap().intersect(r, t_max)
}
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool {
self.get().unwrap().intersect_p(r, t_max)
}
}

View file

@ -1,17 +1,21 @@
use crate::core::filter::FilterTrait; use crate::core::filter::FilterTrait;
use crate::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f}; use crate::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f};
use crate::core::pbrt::{Float, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4}; 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::Ptr;
use crate::utils::containers::Array2D;
use crate::utils::math::{ use crate::utils::math::{
BinaryPermuteScrambler, DigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler, BinaryPermuteScrambler, DigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler,
PRIME_TABLE_SIZE, Scrambler, clamp, encode_morton_2, inverse_radical_inverse, lerp, log2_int, PRIME_TABLE_SIZE, Scrambler, clamp, compute_radical_inverse_permutations, encode_morton_2,
owen_scrambled_radical_inverse, permutation_element, radical_inverse, round_up_pow2, inverse_radical_inverse, lerp, log2_int, owen_scrambled_radical_inverse, permutation_element,
scrambled_radical_inverse, sobol_interval_to_index, sobol_sample, radical_inverse, round_up_pow2, scrambled_radical_inverse, sobol_interval_to_index,
sobol_sample,
}; };
use crate::utils::rng::Rng; use crate::utils::rng::Rng;
use crate::utils::sobol::N_SOBOL_DIMENSIONS; use crate::utils::sobol::N_SOBOL_DIMENSIONS;
use crate::utils::{hash::*, sobol}; use crate::utils::{hash::*, sobol};
use crate::{GVec, Ptr, gvec};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use rand::seq::index::sample;
#[repr(C)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
@ -39,13 +43,13 @@ where
#[repr(C)] #[repr(C)]
#[derive(Default, Debug, Clone, Copy)] #[derive(Default, Debug, Clone, Copy)]
pub struct IndependentSampler { pub struct IndependentSampler {
pub samples_per_pixel: i32, pub samples_per_pixel: u32,
pub seed: u64, pub seed: u64,
pub rng: Rng, pub rng: Rng,
} }
impl IndependentSampler { impl IndependentSampler {
pub fn new(samples_per_pixel: i32, seed: u64) -> Self { pub fn new(samples_per_pixel: u32, seed: u64) -> Self {
Self { Self {
samples_per_pixel, samples_per_pixel,
seed, seed,
@ -55,10 +59,10 @@ impl IndependentSampler {
} }
impl SamplerTrait for IndependentSampler { impl SamplerTrait for IndependentSampler {
fn samples_per_pixel(&self) -> i32 { fn samples_per_pixel(&self) -> u32 {
self.samples_per_pixel 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: u32, dim: Option<u32>) {
let hash_input = [p.x() as u64, p.y() as u64, self.seed]; let hash_input = [p.x() as u64, p.y() as u64, self.seed];
let sequence_index = hash_buffer(&hash_input, 0); let sequence_index = hash_buffer(&hash_input, 0);
self.rng.set_sequence(sequence_index); self.rng.set_sequence(sequence_index);
@ -77,7 +81,7 @@ impl SamplerTrait for IndependentSampler {
} }
} }
pub const MAX_HALTON_RESOLUTION: i32 = 128; const MAX_HALTON_RESOLUTION: i32 = 128;
#[repr(C)] #[repr(C)]
#[derive(Debug, Default, Clone, PartialEq, Eq, Copy)] #[derive(Debug, Default, Clone, PartialEq, Eq, Copy)]
@ -90,41 +94,75 @@ pub enum RandomizeStrategy {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Default, Debug, Clone, Copy)]
pub struct HaltonSampler { pub struct HaltonSampler {
pub samples_per_pixel: i32, samples_per_pixel: u32,
pub randomize: RandomizeStrategy, randomize: RandomizeStrategy,
pub base_scales: [u64; 2], base_scales: [u64; 2],
pub base_exponents: [u64; 2], base_exponents: [u64; 2],
pub mult_inverse: [u64; 2], mult_inverse: [u64; 2],
pub halton_index: u64, halton_index: u64,
pub dim: u32, dim: u32,
pub digit_permutations: Ptr<DigitPermutation>, digit_permutations: Ptr<DigitPermutation>,
}
#[allow(clippy::derivable_impls)]
impl Default for HaltonSampler {
fn default() -> Self {
Self {
samples_per_pixel: 0,
randomize: RandomizeStrategy::default(),
base_scales: [0; 2],
base_exponents: [0; 2],
mult_inverse: [0; 2],
halton_index: 0,
dim: 0,
digit_permutations: Ptr::default(),
}
}
} }
impl HaltonSampler { impl HaltonSampler {
pub fn sample_dimension(&self, dimension: u32) -> Float { pub fn new(
samples_per_pixel: u32,
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: u32) -> Float {
if self.randomize == RandomizeStrategy::None { if self.randomize == RandomizeStrategy::None {
radical_inverse(dimension, self.halton_index) radical_inverse(dimension, self.halton_index)
} else if self.randomize == RandomizeStrategy::PermuteDigits { } else if self.randomize == RandomizeStrategy::PermuteDigits {
let digit_perm = unsafe { &*self.digit_permutations.add(dimension as usize) }; scrambled_radical_inverse(
scrambled_radical_inverse(dimension, self.halton_index, digit_perm) dimension,
self.halton_index,
&self.digit_permutations[dimension as usize],
)
} else { } else {
owen_scrambled_radical_inverse( owen_scrambled_radical_inverse(
dimension, dimension,
@ -134,12 +172,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); let (x, _) = Self::extended_gcd(a as u64, n as u64);
x.rem_euclid(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 { if b == 0 {
return (1, 0); return (1, 0);
} }
@ -153,11 +191,11 @@ impl HaltonSampler {
} }
impl SamplerTrait for HaltonSampler { impl SamplerTrait for HaltonSampler {
fn samples_per_pixel(&self) -> i32 { fn samples_per_pixel(&self) -> u32 {
self.samples_per_pixel 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: u32, dim: Option<u32>) {
self.halton_index = 0; self.halton_index = 0;
let sample_stride = self.base_scales[0] * self.base_scales[1]; let sample_stride = self.base_scales[0] * self.base_scales[1];
@ -192,18 +230,14 @@ impl SamplerTrait for HaltonSampler {
} }
fn get1d(&mut self) -> Float { fn get1d(&mut self) -> Float {
// pbrt: `SampleDimension(dimension++)` -- POST-increment. Pre-incrementing makes if self.dim > PRIME_TABLE_SIZE as u32 {
// the next Get2D() reuse the dimension this call just consumed.
if self.dim >= PRIME_TABLE_SIZE as u32 {
self.dim = 2; self.dim = 2;
} }
let dim = self.dim; self.sample_dimension(self.dim)
self.dim += 1;
self.sample_dimension(dim)
} }
fn get2d(&mut self) -> Point2f { fn get2d(&mut self) -> Point2f {
if self.dim + 1 >= PRIME_TABLE_SIZE as u32 { if self.dim > PRIME_TABLE_SIZE as u32 {
self.dim = 2; self.dim = 2;
} }
let dim = self.dim; let dim = self.dim;
@ -214,7 +248,7 @@ impl SamplerTrait for HaltonSampler {
fn get_pixel2d(&mut self) -> Point2f { fn get_pixel2d(&mut self) -> Point2f {
Point2f::new( Point2f::new(
radical_inverse(0, self.halton_index >> self.base_exponents[0]), radical_inverse(0, self.halton_index >> self.base_exponents[0]),
radical_inverse(1, self.halton_index / self.base_scales[1]), radical_inverse(1, self.halton_index >> self.base_exponents[1]),
) )
} }
} }
@ -222,20 +256,20 @@ impl SamplerTrait for HaltonSampler {
#[repr(C)] #[repr(C)]
#[derive(Default, Debug, Clone, Copy)] #[derive(Default, Debug, Clone, Copy)]
pub struct StratifiedSampler { pub struct StratifiedSampler {
x_pixel_samples: i32, x_pixel_samples: u32,
y_pixel_samples: i32, y_pixel_samples: u32,
jitter: bool, jitter: bool,
seed: u64, seed: u64,
rng: Rng, rng: Rng,
pixel: Point2i, pixel: Point2i,
sample_index: i32, sample_index: u32,
dim: u32, dim: u32,
} }
impl StratifiedSampler { impl StratifiedSampler {
pub fn new( pub fn new(
x_pixel_samples: i32, x_pixel_samples: u32,
y_pixel_samples: i32, y_pixel_samples: u32,
seed: Option<u64>, seed: Option<u64>,
jitter: bool, jitter: bool,
) -> Self { ) -> Self {
@ -253,11 +287,11 @@ impl StratifiedSampler {
} }
impl SamplerTrait for StratifiedSampler { impl SamplerTrait for StratifiedSampler {
fn samples_per_pixel(&self) -> i32 { fn samples_per_pixel(&self) -> u32 {
self.x_pixel_samples * self.y_pixel_samples self.x_pixel_samples * self.y_pixel_samples
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) { fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option<u32>) {
self.pixel = p; self.pixel = p;
self.sample_index = sample_index; self.sample_index = sample_index;
let hash_input = [p.x() as u64, p.y() as u64, self.seed]; let hash_input = [p.x() as u64, p.y() as u64, self.seed];
@ -304,9 +338,8 @@ impl SamplerTrait for StratifiedSampler {
hash as u32, hash as u32,
); );
self.dim += 2; self.dim += 2;
// pbrt: both the modulus and the divisor are xPixelSamples.
let x = stratum % self.x_pixel_samples as u32; let x = stratum % self.x_pixel_samples as u32;
let y = stratum / self.x_pixel_samples as u32; let y = stratum / self.y_pixel_samples as u32;
let dx = if self.jitter { let dx = if self.jitter {
self.rng.uniform::<Float>() self.rng.uniform::<Float>()
} else { } else {
@ -331,16 +364,16 @@ impl SamplerTrait for StratifiedSampler {
#[repr(C)] #[repr(C)]
#[derive(Default, Debug, Clone, Copy)] #[derive(Default, Debug, Clone, Copy)]
pub struct PaddedSobolSampler { pub struct PaddedSobolSampler {
samples_per_pixel: i32, samples_per_pixel: u32,
seed: u64, seed: u64,
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
pixel: Point2i, pixel: Point2i,
sample_index: i32, sample_index: u32,
dim: u32, dim: u32,
} }
impl PaddedSobolSampler { impl PaddedSobolSampler {
pub fn new(samples_per_pixel: i32, randomize: RandomizeStrategy, seed: Option<u64>) -> Self { pub fn new(samples_per_pixel: u32, randomize: RandomizeStrategy, seed: Option<u64>) -> Self {
Self { Self {
samples_per_pixel, samples_per_pixel,
seed: seed.unwrap_or(0), seed: seed.unwrap_or(0),
@ -369,10 +402,10 @@ impl PaddedSobolSampler {
} }
impl SamplerTrait for PaddedSobolSampler { impl SamplerTrait for PaddedSobolSampler {
fn samples_per_pixel(&self) -> i32 { fn samples_per_pixel(&self) -> u32 {
self.samples_per_pixel 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: u32, dim: Option<u32>) {
self.pixel = p; self.pixel = p;
self.sample_index = sample_index; self.sample_index = sample_index;
self.dim = dim.unwrap_or(0); self.dim = dim.unwrap_or(0);
@ -385,14 +418,13 @@ impl SamplerTrait for PaddedSobolSampler {
self.dim as u64, self.dim as u64,
self.seed, self.seed,
]; ];
let hash = hash_buffer(&hash_input, 0); let hash = hash_buffer(&hash_input, 0) as u32;
let index = permutation_element( let index = permutation_element(
self.sample_index as u32, self.sample_index as u32,
self.samples_per_pixel as u32, self.samples_per_pixel as u32,
hash as u32, hash,
); );
self.dim += 1; self.sample_dimension(0, index, hash >> 32)
self.sample_dimension(0, index, (hash >> 32) as u32)
} }
fn get2d(&mut self) -> Point2f { fn get2d(&mut self) -> Point2f {
let hash_input = [ let hash_input = [
@ -401,16 +433,16 @@ impl SamplerTrait for PaddedSobolSampler {
self.dim as u64, self.dim as u64,
self.seed, self.seed,
]; ];
let hash = hash_buffer(&hash_input, 0); let hash = hash_buffer(&hash_input, 0) as u32;
let index = permutation_element( let index = permutation_element(
self.sample_index as u32, self.sample_index as u32,
self.samples_per_pixel as u32, self.samples_per_pixel as u32,
hash as u32, hash,
); );
self.dim += 2; self.dim += 2;
Point2f::new( Point2f::new(
self.sample_dimension(0, index, hash as u32), self.sample_dimension(0, index, hash),
self.sample_dimension(1, index, (hash >> 32) as u32), self.sample_dimension(1, index, hash >> 32),
) )
} }
@ -421,7 +453,7 @@ impl SamplerTrait for PaddedSobolSampler {
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct SobolSampler { pub struct SobolSampler {
samples_per_pixel: i32, samples_per_pixel: u32,
scale: i32, scale: i32,
seed: u64, seed: u64,
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
@ -432,7 +464,7 @@ pub struct SobolSampler {
impl SobolSampler { impl SobolSampler {
pub fn new( pub fn new(
samples_per_pixel: i32, samples_per_pixel: u32,
full_resolution: Point2i, full_resolution: Point2i,
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
seed: Option<u64>, seed: Option<u64>,
@ -473,10 +505,10 @@ impl SobolSampler {
} }
impl SamplerTrait for SobolSampler { impl SamplerTrait for SobolSampler {
fn samples_per_pixel(&self) -> i32 { fn samples_per_pixel(&self) -> u32 {
self.samples_per_pixel 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: u32, dim: Option<u32>) {
self.pixel = p; self.pixel = p;
self.dim = 2.max(dim.unwrap_or(0)); self.dim = 2.max(dim.unwrap_or(0));
self.sobol_index = self.sobol_index =
@ -517,7 +549,7 @@ impl SamplerTrait for SobolSampler {
) as Float; ) as Float;
u[1] = clamp( u[1] = clamp(
u[1] * self.scale as Float - self.pixel[1] as Float, u[1] * self.scale as Float - self.pixel[1] as Float,
0., 1.,
ONE_MINUS_EPSILON, ONE_MINUS_EPSILON,
) as Float; ) as Float;
u u
@ -529,41 +561,27 @@ impl SamplerTrait for SobolSampler {
pub struct ZSobolSampler { pub struct ZSobolSampler {
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
seed: u64, seed: u64,
log2_samples_per_pixel: i32, log2_samples_per_pixel: u32,
n_base4_digits: u32, n_base4_digits: u32,
morton_index: u64, morton_index: u64,
dim: u32, dim: u32,
} }
/// pbrt writes `0x55555555u * dimension` -- a 32-bit unsigned product that wraps.
#[inline]
fn scramble_seed(dim: u32) -> u64 {
0x5555_5555u32.wrapping_mul(dim) as u64
}
/// pbrt's `Hash(dimension, seed)`: both are `int`, so exactly 8 packed bytes.
#[inline]
fn dim_seed_hash(dim: u32, seed: u64) -> u64 {
hash_buffer(&[dim, seed as u32], 0)
}
impl ZSobolSampler { impl ZSobolSampler {
pub fn new( pub fn new(
samples_per_pixel: i32, samples_per_pixel: u32,
full_resolution: Point2i, full_resolution: Point2i,
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
seed: Option<u64>, seed: Option<u64>,
) -> Self { ) -> Self {
// pbrt calls the integer Log2Int overload; the float one disagrees for let log2_samples_per_pixel = log2_int(samples_per_pixel as Float) as u32;
// non-power-of-two sample counts.
let log2_samples_per_pixel = (samples_per_pixel.max(1) as u32).ilog2();
let res = round_up_pow2(full_resolution.x().max(full_resolution.y())); let res = round_up_pow2(full_resolution.x().max(full_resolution.y()));
let log4_samples_per_pixel = log2_samples_per_pixel.div_ceil(2); let log4_samples_per_pixel = log2_samples_per_pixel.div_ceil(2);
let n_base4_digits = (res.max(1) as u32).ilog2() + log4_samples_per_pixel; let n_base4_digits = log2_int(res as Float) as u32 + log4_samples_per_pixel;
Self { Self {
randomize, randomize,
seed: seed.unwrap_or(0), seed: seed.unwrap_or(0),
log2_samples_per_pixel: log2_samples_per_pixel as i32, log2_samples_per_pixel,
n_base4_digits, n_base4_digits,
morton_index: 0, morton_index: 0,
dim: 0, dim: 0,
@ -609,18 +627,17 @@ impl ZSobolSampler {
let higher_digits = self.morton_index >> (digit_shift + 2); let higher_digits = self.morton_index >> (digit_shift + 2);
let mix_input = higher_digits ^ scramble_seed(self.dim); let mix_input = higher_digits ^ (0x55555555 * self.dim as u64);
let p = (mix_bits(mix_input) >> 24) % 24; let p = (mix_bits(mix_input) >> 24) % 24;
digit = PERMUTATIONS[p as usize][digit as usize] as u64; digit = PERMUTATIONS[p as u32][digit as u32] as u64;
sample_index |= digit << digit_shift; sample_index |= digit << digit_shift;
} }
if pow2_samples { if pow2_samples {
let digit = self.morton_index & 1; let lsb = self.morton_index & 1;
sample_index |= sample_index |= lsb;
digit ^ (mix_bits((self.morton_index >> 1) ^ scramble_seed(self.dim)) & 1);
} }
sample_index sample_index
@ -628,11 +645,10 @@ impl ZSobolSampler {
} }
impl SamplerTrait for ZSobolSampler { impl SamplerTrait for ZSobolSampler {
fn samples_per_pixel(&self) -> i32 { fn samples_per_pixel(&self) -> u32 {
1 << self.log2_samples_per_pixel todo!()
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option<u32>) {
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
self.dim = dim.unwrap_or(0); self.dim = dim.unwrap_or(0);
self.morton_index = (encode_morton_2(p.x() as u32, p.y() as u32) self.morton_index = (encode_morton_2(p.x() as u32, p.y() as u32)
<< self.log2_samples_per_pixel) << self.log2_samples_per_pixel)
@ -641,25 +657,31 @@ impl SamplerTrait for ZSobolSampler {
fn get1d(&mut self) -> Float { fn get1d(&mut self) -> Float {
let sample_index = self.get_sample_index(); let sample_index = self.get_sample_index();
let hash_input = [self.dim as u64, self.seed];
let hash = hash_buffer(&hash_input, 0) as u32;
self.dim += 1; self.dim += 1;
let hash = dim_seed_hash(self.dim, self.seed) as u32; if self.randomize == RandomizeStrategy::None {
// Always Sobol dimension 0 -- decorrelation comes from the hash. return sobol_sample(sample_index, self.dim, NoRandomizer);
}
match self.randomize { match self.randomize {
RandomizeStrategy::None => sobol_sample(sample_index, 0, NoRandomizer),
RandomizeStrategy::PermuteDigits => { RandomizeStrategy::PermuteDigits => {
sobol_sample(sample_index, 0, BinaryPermuteScrambler::new(hash)) sobol_sample(sample_index, self.dim, BinaryPermuteScrambler::new(hash))
} }
RandomizeStrategy::FastOwen => { RandomizeStrategy::FastOwen => {
sobol_sample(sample_index, 0, FastOwenScrambler::new(hash)) sobol_sample(sample_index, self.dim, FastOwenScrambler::new(hash))
} }
RandomizeStrategy::Owen => sobol_sample(sample_index, 0, OwenScrambler::new(hash)), RandomizeStrategy::Owen => {
sobol_sample(sample_index, self.dim, OwenScrambler::new(hash))
}
RandomizeStrategy::None => unreachable!(),
} }
} }
fn get2d(&mut self) -> Point2f { fn get2d(&mut self) -> Point2f {
let sample_index = self.get_sample_index(); let sample_index = self.get_sample_index();
self.dim += 2; self.dim += 2;
let hash = dim_seed_hash(self.dim, self.seed); let hash_input = [self.dim as u64, self.seed];
let hash = hash_buffer(&hash_input, 0);
let sample_hash = [hash as u32, (hash >> 32) as u32]; let sample_hash = [hash as u32, (hash >> 32) as u32];
if self.randomize == RandomizeStrategy::None { if self.randomize == RandomizeStrategy::None {
return Point2f::new( return Point2f::new(
@ -690,92 +712,16 @@ impl SamplerTrait for ZSobolSampler {
} }
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
struct PrimarySample { pub struct MLTSampler;
value: Float,
last_mod_iteration: i64,
value_backup: Float,
mod_backup: i64,
}
impl PrimarySample {
fn backup(&mut self) {
self.value_backup = self.value;
self.mod_backup = self.last_mod_iteration;
}
fn restore(&mut self) {
self.value = self.value_backup;
self.last_mod_iteration = self.mod_backup;
}
}
#[derive(Debug, Clone)]
pub struct MLTSampler {
mutations_per_pixel: i32,
rng: Rng,
sigma: Float,
large_step_prob: Float,
stream_count: i32,
x: GVec<PrimarySample>,
current_iter: i64,
large_step: bool,
last_large_step_iter: i64,
stream_ind: i32,
sample_ind: i32,
seed: u64,
}
impl MLTSampler {
pub fn new(
mutations_per_pixel: i32,
rng_seq_ind: i32,
sigma: Float,
large_step_prob: Float,
stream_count: i32,
seed: u64,
) -> Self {
Self {
mutations_per_pixel,
rng: Rng::new(mix_bits(rng_seq_ind.try_into().unwrap()) ^ mix_bits(seed)),
seed,
sigma,
large_step_prob,
stream_count,
x: gvec(),
current_iter: 0,
large_step: true,
last_large_step_iter: 0,
stream_ind: 0,
sample_ind: 0,
}
}
pub fn get_next_index(&mut self) -> i32 {
self.sample_ind += 1;
self.stream_ind + self.stream_count * self.sample_ind
}
}
impl SamplerTrait for MLTSampler { impl SamplerTrait for MLTSampler {
fn samples_per_pixel(&self) -> i32 { fn samples_per_pixel(&self) -> u32 {
self.mutations_per_pixel todo!()
} }
fn start_pixel_sample(&mut self, _p: Point2i, _sample_index: u32, _dim: Option<u32>) {
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) { todo!()
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);
self.rng
.advance((sample_index as u64) * 65536 + (dim.unwrap_or(0) as u64));
} }
fn get1d(&mut self) -> Float { fn get1d(&mut self) -> Float {
#[cfg(not(any(feature = "cuda", feature = "vulkan")))] todo!()
{
return 0.;
}
let ind = self.get_next_index();
} }
fn get2d(&mut self) -> Point2f { fn get2d(&mut self) -> Point2f {
todo!() todo!()
@ -787,8 +733,8 @@ impl SamplerTrait for MLTSampler {
#[enum_dispatch] #[enum_dispatch]
pub trait SamplerTrait { pub trait SamplerTrait {
fn samples_per_pixel(&self) -> i32; fn samples_per_pixel(&self) -> u32;
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>); fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option<u32>);
fn get1d(&mut self) -> Float; fn get1d(&mut self) -> Float;
fn get2d(&mut self) -> Point2f; fn get2d(&mut self) -> Point2f;
fn get_pixel2d(&mut self) -> Point2f; fn get_pixel2d(&mut self) -> Point2f;

View file

@ -2,13 +2,12 @@ use crate::core::geometry::{
Normal3f, Point2f, Vector2f, Vector3f, VectorLike, abs_cos_theta, cos_phi, cos2_theta, sin_phi, Normal3f, Point2f, Vector2f, Vector3f, VectorLike, abs_cos_theta, cos_phi, cos2_theta, sin_phi,
tan2_theta, tan2_theta,
}; };
use crate::core::pbrt::{Float, INV_4_PI, PI}; use crate::core::pbrt::{Float, PI};
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum}; use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum};
use crate::utils::math::{clamp, lerp, safe_sqrt, square}; use crate::utils::math::{clamp, lerp, safe_sqrt, square};
use crate::utils::sampling::sample_uniform_disk_polar; use crate::utils::sampling::sample_uniform_disk_polar;
use num_traits::Float as NumFloat;
use crate::utils::complex::Complex; use num::complex::Complex;
#[repr(C)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
@ -18,11 +17,7 @@ pub struct TrowbridgeReitzDistribution {
} }
impl TrowbridgeReitzDistribution { impl TrowbridgeReitzDistribution {
pub fn new(mut alpha_x: Float, mut alpha_y: Float) -> Self { pub fn new(alpha_x: Float, alpha_y: Float) -> Self {
if alpha_x.max(alpha_y) >= 1e-3 {
alpha_x = alpha_x.max(1e-4);
alpha_y = alpha_y.max(1e-4);
}
Self { alpha_x, alpha_y } Self { alpha_x, alpha_y }
} }
@ -32,13 +27,8 @@ impl TrowbridgeReitzDistribution {
return 0.; return 0.;
} }
let cos4_theta = square(cos2_theta(wm)); let cos4_theta = square(cos2_theta(wm));
if cos4_theta < 1e-16 {
return 0.;
}
let e = let e =
tan2_theta * (square(cos_phi(wm) / self.alpha_x) + square(sin_phi(wm) / self.alpha_y)); tan2_theta * (square(cos_phi(wm) / self.alpha_x) + square(sin_phi(wm) / self.alpha_y));
1.0 / (PI * self.alpha_x * self.alpha_y * cos4_theta * square(1. + e)) 1.0 / (PI * self.alpha_x * self.alpha_y * cos4_theta * square(1. + e))
} }
@ -60,7 +50,7 @@ impl TrowbridgeReitzDistribution {
} }
pub fn g1(&self, w: Vector3f) -> Float { pub fn g1(&self, w: Vector3f) -> Float {
1. / (1. + self.lambda(w)) 1. / (1. / self.lambda(w))
} }
pub fn d_from_w(&self, w: Vector3f, wm: Vector3f) -> Float { pub fn d_from_w(&self, w: Vector3f, wm: Vector3f) -> Float {
@ -85,7 +75,7 @@ impl TrowbridgeReitzDistribution {
let mut p = sample_uniform_disk_polar(u); let mut p = sample_uniform_disk_polar(u);
let h = (1. - square(p.x())).sqrt(); let h = (1. - square(p.x())).sqrt();
p[1] = lerp((1. + wh.z()) / 2., h, p.y()); p[1] = lerp((1. + wh.z()) / 2., h, p.y());
let pz = (1. - Vector2f::from(p).norm_squared()).max(0.).sqrt(); let pz = 0_f32.max(1. - Vector2f::from(p).norm_squared());
let nh = p.x() * t1 + p.y() * t2 + pz * wh; let nh = p.x() * t1 + p.y() * t2 + pz * wh;
Vector3f::new( Vector3f::new(
self.alpha_x * nh.x(), self.alpha_x * nh.x(),
@ -158,16 +148,16 @@ pub fn fr_dielectric(cos_theta_i: Float, eta: Float) -> Float {
(square(r_parl) + square(r_perp)) / 2. (square(r_parl) + square(r_perp)) / 2.
} }
pub fn fr_complex(cos_theta_i: Float, eta: Complex) -> Float { pub fn fr_complex(cos_theta_i: Float, eta: Complex<Float>) -> Float {
let cos_corr = clamp(cos_theta_i, 0., 1.); let cos_corr = clamp(cos_theta_i, 0., 1.);
let sin2_theta_i = 1. - square(cos_corr); let sin2_theta_i = 1. - square(cos_corr);
let sin2_theta_t: Complex = sin2_theta_i / square(eta); let sin2_theta_t: Complex<Float> = sin2_theta_i / square(eta);
let cos2_theta_t: Complex = (1. - sin2_theta_t).sqrt(); let cos2_theta_t: Complex<Float> = (1. - sin2_theta_t).sqrt();
let r_parl = (eta * cos_corr - cos2_theta_t) / (eta * cos_corr + cos2_theta_t); let r_parl = (eta * cos_corr - cos2_theta_t) / (eta * cos_corr + cos2_theta_t);
let r_perp = (cos_corr - eta * cos2_theta_t) / (cos_corr + eta * cos2_theta_t); let r_perp = (cos_corr - eta * cos2_theta_t) / (cos_corr + eta * cos2_theta_t);
(square(r_parl.norm()) + square(r_perp.norm())) / 2. (r_parl.norm() + r_perp.norm()) / 2.
} }
pub fn fr_complex_from_spectrum( pub fn fr_complex_from_spectrum(
@ -188,9 +178,11 @@ pub fn fresnel_moment1(eta: Float) -> Float {
let eta4 = eta3 * eta; let eta4 = eta3 * eta;
let eta5 = eta4 * eta; let eta5 = eta4 * eta;
if eta < 1. { if eta < 1. {
0.45966 - 1.73965 * eta + 3.37668 * eta2 - 3.904945 * eta3 + 2.49277 * eta4 - 0.68441 * eta5 return 0.45966 - 1.73965 * eta + 3.37668 * eta2 - 3.904945 * eta3 + 2.49277 * eta4
- 0.68441 * eta5;
} else { } else {
-4.61686 + 11.1136 * eta - 10.4646 * eta2 + 5.11455 * eta3 - 1.27198 * eta4 + 0.12746 * eta5 return -4.61686 + 11.1136 * eta - 10.4646 * eta2 + 5.11455 * eta3 - 1.27198 * eta4
+ 0.12746 * eta5;
} }
} }
@ -201,28 +193,18 @@ pub fn fresnel_moment2(eta: Float) -> Float {
let eta5 = eta4 * eta; let eta5 = eta4 * eta;
if eta < 1. { if eta < 1. {
0.27614 - 0.87350 * eta + 1.12077 * eta2 - 0.65095 * eta3 + 0.07883 * eta4 + 0.04860 * eta5 return 0.27614 - 0.87350 * eta + 1.12077 * eta2 - 0.65095 * eta3
+ 0.07883 * eta4
+ 0.04860 * eta5;
} else { } else {
let r_eta = 1. / eta; let r_eta = 1. / eta;
let r_eta2 = r_eta * r_eta; let r_eta2 = r_eta * r_eta;
let r_eta3 = r_eta2 * r_eta; let r_eta3 = r_eta2 * r_eta;
-547.033 + 45.3087 * r_eta3 - 218.725 * r_eta2 + 458.843 * r_eta + 404.557 * eta return -547.033 + 45.3087 * r_eta3 - 218.725 * r_eta2 + 458.843 * r_eta + 404.557 * eta
- 189.519 * eta2 - 189.519 * eta2
+ 54.9327 * eta3 + 54.9327 * eta3
- 9.00603 * eta4 - 9.00603 * eta4
+ 0.63942 * eta5 + 0.63942 * eta5;
} }
} }
#[inline]
pub fn henyey_greenstein(cos_theta: Float, mut g: Float) -> Float {
// The Henyey-Greenstein phase function isn't suitable for |g| \approx
// 1 so we clamp it before it becomes numerically instable. (It's an
// analogous situation to BSDFs: if the BSDF is perfectly specular, one
// should use one based on a Dirac delta distribution rather than a
// very smooth microfacet distribution...)
g = g.clamp(-0.99, 0.99);
let denom = 1. + square(g) + 2. * g * cos_theta;
INV_4_PI * (1. - square(g)) / (denom * safe_sqrt(denom))
}

View file

@ -1,11 +1,10 @@
use crate::core::geometry::{ use crate::core::geometry::{
Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f, Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
Vector3fi, VectorLike, ray, Vector3fi, VectorLike,
}; };
use crate::core::interaction::{ use crate::core::interaction::{
Interaction, InteractionTrait, MediumInteraction, SurfaceInteraction, Interaction, InteractionTrait, MediumInteraction, SurfaceInteraction,
}; };
use crate::core::{MaterialIdx, LightIdx};
use crate::core::light::Light; use crate::core::light::Light;
use crate::core::material::Material; use crate::core::material::Material;
use crate::core::medium::{Medium, MediumInterface}; use crate::core::medium::{Medium, MediumInterface};
@ -38,10 +37,10 @@ impl ShapeIntersection {
pub fn set_intersection_properties( pub fn set_intersection_properties(
&mut self, &mut self,
mtl: MaterialIdx, mtl: &Material,
area: LightIdx, area: &Light,
prim_medium_interface: MediumInterface, prim_medium_interface: MediumInterface,
ray_medium: Ptr<Medium>, ray_medium: &Medium,
) { ) {
self.intr self.intr
.set_intersection_properties(mtl, area, ray_medium, prim_medium_interface); .set_intersection_properties(mtl, area, ray_medium, prim_medium_interface);
@ -119,7 +118,7 @@ impl ShapeSampleContext {
} }
pub fn spawn_ray(&self, w: Vector3f) -> Ray { pub fn spawn_ray(&self, w: Vector3f) -> Ray {
Ray::new(self.offset_ray_origin(w), w, Some(self.time), Ptr::null()) Ray::new(self.offset_ray_origin(w), w, Some(self.time), &Ptr::null())
} }
} }

View file

@ -1,11 +1,10 @@
use crate::{Float, Ptr}; use crate::Float;
use crate::core::color::{RGB, XYZ}; use crate::core::color::{RGB, XYZ};
use crate::spectra::*;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
pub use crate::spectra::*;
#[enum_dispatch] #[enum_dispatch]
pub trait SpectrumTrait { pub trait SpectrumTrait: Copy {
fn evaluate(&self, lambda: Float) -> Float; fn evaluate(&self, lambda: Float) -> Float;
fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum { fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
SampledSpectrum::from_fn(|i| self.evaluate(lambda[i])) SampledSpectrum::from_fn(|i| self.evaluate(lambda[i]))
@ -16,47 +15,31 @@ pub trait SpectrumTrait {
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct StandardSpectra { pub struct StandardSpectra {
pub x: Ptr<DenselySampledSpectrum>, pub x: DenselySampledSpectrum,
pub y: Ptr<DenselySampledSpectrum>, pub y: DenselySampledSpectrum,
pub z: Ptr<DenselySampledSpectrum>, pub z: DenselySampledSpectrum,
pub d65: Ptr<DenselySampledSpectrum>, pub d65: DenselySampledSpectrum,
} }
unsafe impl Send for StandardSpectra {}
unsafe impl Sync for StandardSpectra {}
#[repr(C)] #[repr(C)]
#[enum_dispatch(SpectrumTrait)] #[enum_dispatch(SpectrumTrait)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum Spectrum { pub enum Spectrum {
Constant(ConstantSpectrum), Constant(ConstantSpectrum),
Dense(Ptr<DenselySampledSpectrum>), Dense(DenselySampledSpectrum),
Piecewise(Ptr<PiecewiseLinearSpectrum>), Piecewise(PiecewiseLinearSpectrum),
Blackbody(BlackbodySpectrum), Blackbody(BlackbodySpectrum),
RGBAlbedo(RGBAlbedoSpectrum), RGBAlbedo(RGBAlbedoSpectrum),
RGBIlluminant(RGBIlluminantSpectrum), RGBIlluminant(RGBIlluminantSpectrum),
RGBUnbounded(RGBUnboundedSpectrum), RGBUnbounded(RGBUnboundedSpectrum),
} }
/// `enum_dispatch` already generates `From<Variant> for Spectrum`, so wrapping a
/// `ConstantSpectrum` etc. is `.into()`. Only the plain-`Float` hop is missing,
/// and it is the one written most often at default-value sites.
impl From<Float> for Spectrum {
fn from(c: Float) -> Self {
Spectrum::Constant(ConstantSpectrum::new(c))
}
}
impl<T: SpectrumTrait> SpectrumTrait for Ptr<T> {
fn evaluate(&self, lambda: Float) -> Float {
self.get().unwrap().evaluate(lambda)
}
fn max_value(&self) -> Float {
self.get().unwrap().max_value()
}
}
impl Spectrum { impl Spectrum {
pub fn std_illuminant_d65() -> Self { pub fn std_illuminant_d65() -> Self {
unimplemented!("Use crate::spectra::default_illuminant() on host") todo!()
} }
pub fn to_xyz(&self, std: &StandardSpectra) -> XYZ { pub fn to_xyz(&self, std: &StandardSpectra) -> XYZ {
@ -67,7 +50,7 @@ impl Spectrum {
XYZ::new(x, y, z) / CIE_Y_INTEGRAL 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); let xyz = self.to_xyz(std);
cs.to_rgb(xyz) cs.to_rgb(xyz)
} }

View file

@ -1,6 +1,6 @@
use crate::core::color::ColorEncoding; use crate::core::color::ColorEncoding;
use crate::core::geometry::{ use crate::core::geometry::{
spherical_phi, spherical_theta, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike, spherical_phi, spherical_theta,
}; };
use crate::core::image::WrapMode; use crate::core::image::WrapMode;
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
@ -8,15 +8,12 @@ use crate::spectra::{
RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
SampledWavelengths, SampledWavelengths,
}; };
use crate::textures::*;
use crate::utils::math::square;
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::utils::Transform; use crate::utils::Transform;
use crate::utils::math::square;
use crate::{Float, INV_2_PI, INV_PI, PI}; use crate::{Float, INV_2_PI, INV_PI, PI};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use num_traits::Float as NumFloat;
pub use crate::textures::*;
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, Copy)] #[derive(Clone, Debug, Copy)]
@ -78,7 +75,7 @@ impl UVMapping {
let dsdy = self.su * ctx.dudy; let dsdy = self.su * ctx.dudy;
let dtdx = self.sv * ctx.dvdx; let dtdx = self.sv * ctx.dvdx;
let dtdy = self.sv * ctx.dvdy; let dtdy = self.sv * ctx.dvdy;
let st = Point2f::new(self.su * ctx.uv[0] + self.du, self.sv * ctx.uv[1] + self.dv); let st = Point2f::new(self.su * ctx.uv[0] + self.du, self.sv * ctx.uv[1] * self.dv);
TexCoord2D { TexCoord2D {
st, st,
dsdx, dsdx,
@ -107,7 +104,7 @@ impl SphericalMapping {
let x2y2 = square(pt.x()) + square(pt.y()); let x2y2 = square(pt.x()) + square(pt.y());
let sqrtx2y2 = x2y2.sqrt(); let sqrtx2y2 = x2y2.sqrt();
let dsdp = Vector3f::new(-pt.y(), pt.x(), 0.) / (2. * PI * x2y2); let dsdp = Vector3f::new(-pt.y(), pt.x(), 0.) / (2. * PI * x2y2);
let dtdp = 1. / (PI * (x2y2 + square(pt.z()))) let dtdp = 1. / (PI * (x2y2 * square(pt.z())))
* Vector3f::new( * Vector3f::new(
pt.x() * pt.z() / sqrtx2y2, pt.x() * pt.z() / sqrtx2y2,
pt.y() * pt.z() / sqrtx2y2, pt.y() * pt.z() / sqrtx2y2,
@ -148,7 +145,7 @@ impl CylindricalMapping {
let pt = self.texture_from_render.apply_to_point(ctx.p); let pt = self.texture_from_render.apply_to_point(ctx.p);
let x2y2 = square(pt.x()) + square(pt.y()); let x2y2 = square(pt.x()) + square(pt.y());
let dsdp = Vector3f::new(-pt.y(), pt.x(), 0.) / (2. * PI * x2y2); let dsdp = Vector3f::new(-pt.y(), pt.x(), 0.) / (2. * PI * x2y2);
let dtdp = Vector3f::new(0., 0., 1.); let dtdp = Vector3f::new(1., 0., 0.);
let dpdx = self.texture_from_render.apply_to_vector(ctx.dpdx); let dpdx = self.texture_from_render.apply_to_vector(ctx.dpdx);
let dpdy = self.texture_from_render.apply_to_vector(ctx.dpdy); let dpdy = self.texture_from_render.apply_to_vector(ctx.dpdy);
let dsdx = dsdp.dot(dpdx); let dsdx = dsdp.dot(dpdx);
@ -245,7 +242,7 @@ pub struct PointTransformMapping {
} }
impl PointTransformMapping { impl PointTransformMapping {
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
pub fn new(texture_from_render: Transform) -> Self { pub fn new(texture_from_render: Transform) -> Self {
Self { Self {
texture_from_render, texture_from_render,
@ -273,7 +270,7 @@ pub struct TextureEvalContext {
pub dudy: Float, pub dudy: Float,
pub dvdx: Float, pub dvdx: Float,
pub dvdy: Float, pub dvdy: Float,
pub face_index: i32, pub face_index: u32,
} }
impl TextureEvalContext { impl TextureEvalContext {
@ -288,7 +285,7 @@ impl TextureEvalContext {
dudy: Float, dudy: Float,
dvdx: Float, dvdx: Float,
dvdy: Float, dvdy: Float,
face_index: i32, face_index: u32,
) -> Self { ) -> Self {
Self { Self {
p, p,
@ -341,34 +338,36 @@ impl From<&Interaction> for TextureEvalContext {
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum FloatTexture { pub enum GPUFloatTexture {
Constant(FloatConstantTexture), Constant(FloatConstantTexture),
DirectionMix(FloatDirectionMixTexture), DirectionMix(GPUFloatDirectionMixTexture),
Scaled(FloatScaledTexture), Scaled(GPUFloatScaledTexture),
Bilerp(FloatBilerpTexture), Bilerp(FloatBilerpTexture),
Checkerboard(FloatCheckerboardTexture), Checkerboard(FloatCheckerboardTexture),
Dots(FloatDotsTexture), Dots(FloatDotsTexture),
FBm(FBmTexture), FBm(FBmTexture),
Windy(WindyTexture), Windy(WindyTexture),
Wrinkled(WrinkledTexture), Wrinkled(WrinkledTexture),
Image(FloatImageTexture), Ptex(GPUFloatPtexTexture),
Mix(FloatMixTexture), Image(GPUFloatImageTexture),
Mix(GPUFloatMixTexture),
} }
impl FloatTexture { impl GPUFloatTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
match self { match self {
FloatTexture::Constant(t) => t.evaluate(ctx), GPUFloatTexture::Constant(t) => t.evaluate(ctx),
FloatTexture::DirectionMix(t) => t.evaluate(ctx), GPUFloatTexture::DirectionMix(t) => t.evaluate(ctx),
FloatTexture::Scaled(t) => t.evaluate(ctx), GPUFloatTexture::Scaled(t) => t.evaluate(ctx),
FloatTexture::Bilerp(t) => t.evaluate(ctx), GPUFloatTexture::Bilerp(t) => t.evaluate(ctx),
FloatTexture::Checkerboard(t) => t.evaluate(ctx), GPUFloatTexture::Checkerboard(t) => t.evaluate(ctx),
FloatTexture::Dots(t) => t.evaluate(ctx), GPUFloatTexture::Dots(t) => t.evaluate(ctx),
FloatTexture::FBm(t) => t.evaluate(ctx), GPUFloatTexture::FBm(t) => t.evaluate(ctx),
FloatTexture::Windy(t) => t.evaluate(ctx), GPUFloatTexture::Windy(t) => t.evaluate(ctx),
FloatTexture::Wrinkled(t) => t.evaluate(ctx), GPUFloatTexture::Wrinkled(t) => t.evaluate(ctx),
FloatTexture::Image(t) => t.evaluate(ctx), GPUFloatTexture::Ptex(t) => t.evaluate(ctx),
FloatTexture::Mix(t) => t.evaluate(ctx), GPUFloatTexture::Image(t) => t.evaluate(ctx),
GPUFloatTexture::Mix(t) => t.evaluate(ctx),
} }
} }
} }
@ -384,51 +383,53 @@ pub enum SpectrumType {
#[repr(C)] #[repr(C)]
#[enum_dispatch] #[enum_dispatch]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum SpectrumTexture { pub enum GPUSpectrumTexture {
Constant(SpectrumConstantTexture), Constant(SpectrumConstantTexture),
Bilerp(SpectrumBilerpTexture), Bilerp(SpectrumBilerpTexture),
Checkerboard(SpectrumCheckerboardTexture), Checkerboard(SpectrumCheckerboardTexture),
Marble(MarbleTexture), Marble(MarbleTexture),
DirectionMix(SpectrumDirectionMixTexture), DirectionMix(GPUSpectrumDirectionMixTexture),
Dots(SpectrumDotsTexture), Dots(SpectrumDotsTexture),
Scaled(SpectrumScaledTexture), Scaled(GPUSpectrumScaledTexture),
Image(SpectrumImageTexture), Image(GPUSpectrumImageTexture),
Mix(SpectrumMixTexture), Ptex(GPUSpectrumPtexTexture),
Mix(GPUSpectrumMixTexture),
} }
impl SpectrumTexture { impl GPUSpectrumTexture {
pub fn evaluate( pub fn evaluate(
&self, &self,
ctx: &TextureEvalContext, ctx: &TextureEvalContext,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum {
match self { match self {
SpectrumTexture::Constant(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Constant(t) => t.evaluate(ctx, lambda),
SpectrumTexture::Bilerp(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Bilerp(t) => t.evaluate(ctx, lambda),
SpectrumTexture::Checkerboard(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Checkerboard(t) => t.evaluate(ctx, lambda),
SpectrumTexture::Marble(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Marble(t) => t.evaluate(ctx, lambda),
SpectrumTexture::DirectionMix(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::DirectionMix(t) => t.evaluate(ctx, lambda),
SpectrumTexture::Dots(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Dots(t) => t.evaluate(ctx, lambda),
SpectrumTexture::Scaled(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Scaled(t) => t.evaluate(ctx, lambda),
SpectrumTexture::Image(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Ptex(t) => t.evaluate(ctx, lambda),
SpectrumTexture::Mix(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Image(t) => t.evaluate(ctx, lambda),
GPUSpectrumTexture::Mix(t) => t.evaluate(ctx, lambda),
} }
} }
} }
pub trait TextureEvaluator: Send + Sync { pub trait TextureEvaluator: Send + Sync {
fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float; fn evaluate_float(&self, tex: &GPUFloatTexture, ctx: &TextureEvalContext) -> Float;
fn evaluate_spectrum( fn evaluate_spectrum(
&self, &self,
tex: &SpectrumTexture, tex: &GPUSpectrumTexture,
ctx: &TextureEvalContext, ctx: &TextureEvalContext,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum; ) -> SampledSpectrum;
fn can_evaluate( fn can_evaluate(
&self, &self,
_ftex: &[Ptr<FloatTexture>], _ftex: &[Ptr<GPUFloatTexture>],
_stex: &[Ptr<SpectrumTexture>], _stex: &[Ptr<GPUSpectrumTexture>],
) -> bool; ) -> bool;
} }
@ -437,13 +438,13 @@ pub trait TextureEvaluator: Send + Sync {
pub struct UniversalTextureEvaluator; pub struct UniversalTextureEvaluator;
impl TextureEvaluator for UniversalTextureEvaluator { impl TextureEvaluator for UniversalTextureEvaluator {
fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float { fn evaluate_float(&self, tex: &GPUFloatTexture, ctx: &TextureEvalContext) -> Float {
tex.evaluate(ctx) tex.evaluate(ctx)
} }
fn evaluate_spectrum( fn evaluate_spectrum(
&self, &self,
tex: &SpectrumTexture, tex: &GPUSpectrumTexture,
ctx: &TextureEvalContext, ctx: &TextureEvalContext,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum {
@ -452,64 +453,9 @@ impl TextureEvaluator for UniversalTextureEvaluator {
fn can_evaluate( fn can_evaluate(
&self, &self,
_float_textures: &[Ptr<FloatTexture>], _float_textures: &[Ptr<GPUFloatTexture>],
_spectrum_textures: &[Ptr<SpectrumTexture>], _spectrum_textures: &[Ptr<GPUSpectrumTexture>],
) -> bool { ) -> bool {
true true
} }
} }
#[repr(C)]
#[derive(Copy, Clone, Default)]
pub struct BasicTextureEvaluator;
impl TextureEvaluator for BasicTextureEvaluator {
fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float {
match tex {
FloatTexture::Constant(t) => t.evaluate(ctx),
FloatTexture::Image(t) => t.evaluate(ctx),
_ => 0.0,
}
}
fn evaluate_spectrum(
&self,
tex: &SpectrumTexture,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum {
match tex {
SpectrumTexture::Constant(t) => t.evaluate(ctx, lambda),
SpectrumTexture::Image(t) => t.evaluate(ctx, lambda),
_ => SampledSpectrum::new(0.0),
}
}
fn can_evaluate(
&self,
ftex: &[Ptr<FloatTexture>],
stex: &[Ptr<SpectrumTexture>],
) -> bool {
for t in ftex {
if t.is_null() {
continue;
}
match t.get().unwrap() {
FloatTexture::Constant(_)
| FloatTexture::Image(_) => {}
_ => return false,
}
}
for t in stex {
if t.is_null() {
continue;
}
match t.get().unwrap() {
SpectrumTexture::Constant(_)
| SpectrumTexture::Image(_) => {}
_ => return false,
}
}
true
}
}

View file

@ -1,5 +1,6 @@
use crate::Float; use crate::Float;
use bytemuck::cast_slice; use bytemuck::cast_slice;
use once_cell::sync::Lazy;
#[repr(C, align(16))] #[repr(C, align(16))]
struct AlignedData<const N: usize>(pub [u8; N]); struct AlignedData<const N: usize>(pub [u8; N]);

View file

@ -1,10 +1,10 @@
use crate::Float;
use crate::core::filter::{FilterSample, FilterSampler, FilterTrait}; use crate::core::filter::{FilterSample, FilterSampler, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f}; use crate::core::geometry::{Point2f, Vector2f};
use crate::utils::math::{gaussian, gaussian_integral}; use crate::utils::math::{gaussian, gaussian_integral};
use crate::{Ptr, Float};
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug)] #[derive(Clone, Debug, Copy)]
pub struct GaussianFilter { pub struct GaussianFilter {
pub radius: Vector2f, pub radius: Vector2f,
pub sigma: Float, pub sigma: Float,
@ -15,18 +15,20 @@ pub struct GaussianFilter {
impl GaussianFilter { impl GaussianFilter {
pub fn new(radius: Vector2f, sigma: Float) -> Self { pub fn new(radius: Vector2f, sigma: Float) -> Self {
let exp_x = gaussian(radius.x(), 0.0, sigma); let exp_x = gaussian(radius.x(), 0., sigma);
let exp_y = gaussian(radius.y(), 0.0, sigma); let exp_y = gaussian(radius.y(), 0., sigma);
let sampler = FilterSampler::new(radius, move |p: Point2f| { let sampler = FilterSampler::new(radius, move |p: Point2f| {
let gx = (gaussian(p.x(), 0.0, sigma) - exp_x).max(0.0); let gx = (gaussian(p.x(), 0., sigma) - exp_x).max(0.0);
let gy = (gaussian(p.y(), 0.0, sigma) - exp_y).max(0.0); let gy = (gaussian(p.y(), 0., sigma) - exp_y).max(0.0);
gx * gy gx * gy
}); });
Self { Self {
radius, radius,
sigma, sigma,
exp_x, exp_x: gaussian(radius.x(), 0., sigma),
exp_y, exp_y: gaussian(radius.y(), 0., sigma),
sampler, sampler,
} }
} }

View file

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

View file

@ -1,10 +1,9 @@
use crate::Float;
use crate::core::filter::{FilterSample, FilterSampler, FilterTrait}; use crate::core::filter::{FilterSample, FilterSampler, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f}; use crate::core::geometry::{Point2f, Vector2f};
use crate::Float;
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub struct MitchellFilter { pub struct MitchellFilter {
pub radius: Vector2f, pub radius: Vector2f,
pub b: Float, pub b: Float,
@ -12,7 +11,23 @@ pub struct MitchellFilter {
pub sampler: FilterSampler, pub sampler: FilterSampler,
} }
pub fn mitchell_1d_eval(b: Float, c: Float, x: Float) -> Float { impl MitchellFilter {
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(); let x = x.abs();
if x <= 1.0 { if x <= 1.0 {
((12.0 - 9.0 * b - 6.0 * c) * x.powi(3) ((12.0 - 9.0 * b - 6.0 * c) * x.powi(3)
@ -30,21 +45,8 @@ pub fn mitchell_1d_eval(b: Float, c: Float, x: Float) -> Float {
} }
} }
impl MitchellFilter {
pub fn new(radius: Vector2f, b: Float, c: Float) -> Self {
let sampler = FilterSampler::new(radius, move |p: Point2f| {
mitchell_1d_eval(p.x() / radius.x(), b, c) * mitchell_1d_eval(p.y() / radius.y(), b, c)
});
Self {
radius,
b,
c,
sampler,
}
}
fn mitchell_1d(&self, x: Float) -> Float { fn mitchell_1d(&self, x: Float) -> Float {
mitchell_1d_eval(self.b, self.c, x) Self::mitchell_1d_eval(self.b, self.c, x)
} }
} }

View file

@ -2,7 +2,6 @@ use crate::Float;
use crate::core::filter::{FilterSample, FilterTrait}; use crate::core::filter::{FilterSample, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f}; use crate::core::geometry::{Point2f, Vector2f};
use crate::utils::math::sample_tent; use crate::utils::math::sample_tent;
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, Copy)] #[derive(Clone, Debug, Copy)]

View file

@ -1,13 +1,10 @@
#![allow(unused_imports, dead_code)] #![allow(unused_imports, dead_code)]
#![feature(allocator_api)] #![feature(float_erf)]
#![feature(associated_type_defaults)] #![feature(f16)]
#![no_std]
extern crate alloc;
pub mod bxdfs; pub mod bxdfs;
pub mod cameras; pub mod cameras;
pub mod core; pub mod core;
#[cfg(not(target_arch = "spirv"))]
pub mod data; pub mod data;
pub mod filters; pub mod filters;
pub mod lights; pub mod lights;
@ -16,10 +13,5 @@ pub mod shapes;
pub mod spectra; pub mod spectra;
pub mod textures; pub mod textures;
pub mod utils; pub mod utils;
pub mod wavefront;
pub use core::pbrt::*; pub use core::pbrt::*;
pub use utils::alloc::{gbox, gvec, gvec_from_slice, gvec_with_capacity, leak, GBox, GVec};
pub use utils::{Array2D, BasicPBRTOptions, PBRTOptions, Ptr, Transform};
pub use utils::soa::WorkQueue;
pub use wavefront::{WavefrontAggregate};

View file

@ -1,3 +1,4 @@
use crate::PI;
use crate::core::color::{RGB, XYZ}; use crate::core::color::{RGB, XYZ};
use crate::core::geometry::*; use crate::core::geometry::*;
use crate::core::image::Image; use crate::core::image::Image;
@ -8,24 +9,23 @@ use crate::core::light::{
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
}; };
use crate::core::medium::MediumInterface; use crate::core::medium::MediumInterface;
use crate::core::pbrt::Float;
use crate::core::shape::{Shape, ShapeSampleContext, ShapeTrait}; use crate::core::shape::{Shape, ShapeSampleContext, ShapeTrait};
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{ use crate::core::texture::{
FloatTexture, TextureEvalContext, TextureEvaluator, UniversalTextureEvaluator, GPUFloatTexture, TextureEvalContext, TextureEvaluator, UniversalTextureEvaluator,
}; };
use crate::spectra::*; use crate::spectra::*;
use crate::utils::hash::hash_float; use crate::utils::hash::hash_float;
use crate::utils::{Ptr, Transform}; use crate::utils::{Ptr, Transform};
use crate::{Float, PI};
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, Copy)] #[derive(Clone, Debug, Copy)]
pub struct DiffuseAreaLight { pub struct DiffuseAreaLight {
pub base: LightBase, pub base: LightBase,
pub shape: Ptr<Shape>, pub shape: Ptr<Shape>,
pub alpha: Ptr<FloatTexture>, pub alpha: Ptr<GPUFloatTexture>,
pub colorspace: Ptr<RGBColorSpace>, pub image_color_space: Ptr<RGBColorSpace>,
pub lemit: Ptr<DenselySampledSpectrum>, pub lemit: Ptr<DenselySampledSpectrum>,
pub image: Ptr<Image>, pub image: Ptr<Image>,
pub area: Float, pub area: Float,
@ -33,13 +33,23 @@ pub struct DiffuseAreaLight {
pub scale: Float, pub scale: Float,
} }
unsafe impl Send for DiffuseAreaLight {}
unsafe impl Sync for DiffuseAreaLight {}
impl DiffuseAreaLight { impl DiffuseAreaLight {
fn l_base(&self, n: Normal3f, wo: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
if !self.two_sided && n.dot(wo.into()) <= 0.0 {
return SampledSpectrum::new(0.0);
}
self.lemit.sample(lambda) * self.scale
}
fn alpha_masked(&self, intr: &Interaction) -> bool { fn alpha_masked(&self, intr: &Interaction) -> bool {
if self.alpha.is_null() { if self.alpha.is_null() {
return false; return false;
}; };
let ctx = TextureEvalContext::from(intr); let ctx = TextureEvalContext::from(intr);
let a = UniversalTextureEvaluator.evaluate_float(&self.alpha, &ctx); let a = UniversalTextureEvaluator.evaluate_float(&*self.alpha, &ctx);
if a >= 1.0 { if a >= 1.0 {
return false; return false;
} }
@ -63,12 +73,7 @@ impl LightTrait for DiffuseAreaLight {
_allow_incomplete_pdf: bool, _allow_incomplete_pdf: bool,
) -> Option<LightLiSample> { ) -> Option<LightLiSample> {
let shape_ctx = ShapeSampleContext::new(ctx.pi, ctx.n, ctx.ns, 0.0); let shape_ctx = ShapeSampleContext::new(ctx.pi, ctx.n, ctx.ns, 0.0);
let ss = self.shape.sample_from_context(&shape_ctx, u)?; let ss = self.shape.sample_from_context(&shape_ctx, u)?;
if ss.pdf == 0.0 || (ss.intr.p() - ctx.p()).norm_squared() == 0.0 {
return None;
}
let mut intr = ss.intr; let mut intr = ss.intr;
intr.set_medium_interface(self.base.medium_interface); intr.set_medium_interface(self.base.medium_interface);
let p = intr.p(); let p = intr.p();
@ -103,10 +108,9 @@ impl LightTrait for DiffuseAreaLight {
w: Vector3f, w: Vector3f,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum {
if !self.two_sided && n.dot(w.into()) < 0. { if self.two_sided && n.dot(w.into()) < 0. {
return SampledSpectrum::new(0.); return SampledSpectrum::new(0.);
} }
let intr = Interaction::Surface(SurfaceInteraction::new_minimal( let intr = Interaction::Surface(SurfaceInteraction::new_minimal(
Point3fi::new_from_point(p), Point3fi::new_from_point(p),
uv, uv,
@ -119,10 +123,13 @@ impl LightTrait for DiffuseAreaLight {
let mut rgb = RGB::default(); let mut rgb = RGB::default();
uv[1] = 1. - uv[1]; uv[1] = 1. - uv[1];
for c in 0..3 { for c in 0..3 {
rgb[c] = self.image.bilerp_channel(uv, c); rgb[c] = self.image.bilerp_channel(uv, c as i32);
} }
let spec = RGBIlluminantSpectrum::new(&self.colorspace, rgb.clamp_zero()); let spec = RGBIlluminantSpectrum::new(
self.image_color_space.as_ref().unwrap(),
rgb.clamp_zero(),
);
self.scale * spec.sample(lambda) self.scale * spec.sample(lambda)
} else { } else {
@ -130,7 +137,11 @@ impl LightTrait for DiffuseAreaLight {
} }
} }
#[cfg(not(gpu))] fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
}
#[cfg(not(target_os = "cuda"))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
let mut l = SampledSpectrum::new(0.); let mut l = SampledSpectrum::new(0.);
if !self.image.is_null() { if !self.image.is_null() {
@ -138,10 +149,12 @@ impl LightTrait for DiffuseAreaLight {
for x in 0..self.image.resolution().x() { for x in 0..self.image.resolution().x() {
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {
rgb[c] = self.image.get_channel(Point2i::new(x, y), c); rgb[c] = self.image.get_channel(Point2i::new(x, y), c as i32);
} }
l += RGBIlluminantSpectrum::new(
l += RGBIlluminantSpectrum::new(&self.colorspace, rgb.clamp_zero()) self.image_color_space.as_ref().unwrap(),
rgb.clamp_zero(),
)
.sample(&lambda); .sample(&lambda);
} }
} }
@ -153,15 +166,17 @@ impl LightTrait for DiffuseAreaLight {
PI * two_side * self.area * l PI * two_side * self.area * l
} }
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {} fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
unimplemented!()
}
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
fn bounds(&self) -> Option<LightBounds> { fn bounds(&self) -> Option<LightBounds> {
let mut phi = 0.; let mut phi = 0.;
if !self.image.is_null() { if !self.image.is_null() {
for y in 0..self.image.resolution().y() { for y in 0..self.image.resolution.y() {
for x in 0..self.image.resolution().x() { for x in 0..self.image.resolution.x() {
for c in 0..3 { for c in 0..3 {
phi += self.image.get_channel(Point2i::new(x, y), c); phi += self.image.get_channel(Point2i::new(x, y), c);
} }

View file

@ -5,9 +5,8 @@ use crate::core::interaction::{Interaction, InteractionBase, SimpleInteraction};
use crate::core::light::{LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait}; use crate::core::light::{LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait};
use crate::core::spectrum::SpectrumTrait; use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths}; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::utils::math::square; use crate::utils::{ArenaPtr, Ptr};
use crate::{Float, PI, Ptr}; use crate::{Float, PI};
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -42,7 +41,7 @@ impl LightTrait for DistantLight {
} }
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
self.scale * self.lemit.sample(&lambda) * PI * square(self.scene_radius) self.scale * self.lemit.sample(&lambda) * PI * self.scene_radius.sqrt()
} }
fn sample_li( fn sample_li(
@ -75,6 +74,21 @@ impl LightTrait for DistantLight {
0. 0.
} }
fn l(
&self,
_p: Point3f,
_n: Normal3f,
_uv: Point2f,
_w: Vector3f,
_lambda: &SampledWavelengths,
) -> SampledSpectrum {
todo!()
}
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
}
fn preprocess(&mut self, scene_bounds: &Bounds3f) { fn preprocess(&mut self, scene_bounds: &Bounds3f) {
let (center, radius) = scene_bounds.bounding_sphere(); let (center, radius) = scene_bounds.bounding_sphere();
self.scene_center = center; self.scene_center = center;

View file

@ -1,8 +1,5 @@
use crate::core::geometry::{ use crate::core::geometry::{Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f};
Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f, VectorLike,
};
use crate::core::image::Image; use crate::core::image::Image;
use crate::core::interaction::{Interaction, InteractionBase, SimpleInteraction};
use crate::core::light::{ use crate::core::light::{
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
}; };
@ -14,13 +11,13 @@ use crate::utils::sampling::PiecewiseConstant2D;
use crate::utils::{Ptr, Transform}; use crate::utils::{Ptr, Transform};
use crate::{Float, PI}; use crate::{Float, PI};
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone)]
pub struct GoniometricLight { pub struct GoniometricLight {
pub base: LightBase, pub base: LightBase,
pub iemit: Ptr<DenselySampledSpectrum>, iemit: DenselySampledSpectrum,
pub scale: Float, scale: Float,
pub image: Ptr<Image>, image: Ptr<Image>,
pub distrib: Ptr<PiecewiseConstant2D>, distrib: Ptr<PiecewiseConstant2D>,
} }
impl GoniometricLight { impl GoniometricLight {
@ -37,20 +34,12 @@ impl LightTrait for GoniometricLight {
fn sample_li( fn sample_li(
&self, &self,
ctx: &LightSampleContext, _ctx: &LightSampleContext,
_u: Point2f, _u: Point2f,
lambda: &SampledWavelengths, _lambda: &SampledWavelengths,
_allow_incomplete_pdf: bool, _allow_incomplete_pdf: bool,
) -> Option<LightLiSample> { ) -> Option<LightLiSample> {
let render_from_light = self.base().render_from_light; todo!()
let p = render_from_light.apply_to_point(Point3f::new(0., 0., 0.));
let wi = (p - ctx.p()).normalize();
let wl = render_from_light.apply_inverse_vector(-wi);
let li = self.i(wl, lambda) / p.distance_squared(ctx.p());
let base = InteractionBase::new_boundary(p, 0., self.base.medium_interface);
let intr = SimpleInteraction::new(base);
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
} }
fn pdf_li( fn pdf_li(
@ -62,41 +51,39 @@ impl LightTrait for GoniometricLight {
0. 0.
} }
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {} fn l(
&self,
_p: Point3f,
_n: Normal3f,
_uv: Point2f,
_w: Vector3f,
_lambda: &SampledWavelengths,
) -> SampledSpectrum {
todo!()
}
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
}
#[cfg(not(target_os = "cuda"))]
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
todo!()
}
#[cfg(not(target_os = "cuda"))]
fn bounds(&self) -> Option<LightBounds> { fn bounds(&self) -> Option<LightBounds> {
let mut sum_y = 0.; todo!()
for x in 0..self.image.resolution().x() {
for y in 0..self.image.resolution().y() {
sum_y += self.image.get_channel(Point2i::new(x, y), 0);
}
}
let phi = self.scale * self.iemit.max_value() * 4. * PI * sum_y
/ (self.image.resolution().x() * self.image.resolution().y()) as f32;
let p = self
.base()
.render_from_light
.apply_to_point(Point3f::new(0., 0., 0.));
Some(LightBounds::new(
&Bounds3f::from_points(p, p),
Vector3f::new(0., 0., 1.),
phi,
PI.cos(),
(PI / 2.).cos(),
false,
))
} }
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
let resolution = self.image.resolution();
let mut sum_y = 0.; let mut sum_y = 0.;
for y in 0..resolution.y() { for y in 0..self.image.resolution.y() {
for x in 0..resolution.x() { for x in 0..self.image.resolution.x() {
sum_y += self.image.get_channel(Point2i::new(x, y), 0); sum_y += self.image.get_channel(Point2i::new(x, y), 0);
} }
} }
self.scale * self.iemit.sample(&lambda) * 4. * PI * sum_y self.scale * self.iemit.sample(&lambda) * 4. * PI * sum_y
/ (resolution.x() * resolution.y()) as Float / (self.image.resolution.x() * self.image.resolution.y()) as Float
} }
} }

View file

@ -1,10 +1,23 @@
use crate::{
core::{
geometry::{Frame, VectorLike},
interaction::InteractionBase,
},
spectra::{RGBColorSpace, RGBIlluminantSpectrum},
utils::{
math::{clamp, equal_area_sphere_to_square, equal_area_square_to_sphere, square},
sampling::{
AliasTable, PiecewiseConstant2D, WindowedPiecewiseConstant2D, sample_uniform_sphere,
uniform_sphere_pdf,
},
},
};
use crate::core::color::RGB; use crate::core::color::RGB;
use crate::core::geometry::{ use crate::core::geometry::{
Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector3f, Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector3f,
}; };
use crate::core::geometry::{Frame, VectorLike};
use crate::core::image::{Image, PixelFormat, WrapMode}; use crate::core::image::{Image, PixelFormat, WrapMode};
use crate::core::interaction::InteractionBase;
use crate::core::interaction::{Interaction, SimpleInteraction}; use crate::core::interaction::{Interaction, SimpleInteraction};
use crate::core::light::{ use crate::core::light::{
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
@ -12,19 +25,14 @@ use crate::core::light::{
use crate::core::medium::{Medium, MediumInterface}; use crate::core::medium::{Medium, MediumInterface};
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths}; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::spectra::{RGBColorSpace, RGBIlluminantSpectrum}; use crate::utils::Transform;
use crate::utils::math::{clamp, equal_area_sphere_to_square, equal_area_square_to_sphere, square}; use crate::utils::ptr::Ptr;
use crate::utils::sampling::{
AliasTable, PiecewiseConstant2D, WindowedPiecewiseConstant2D, sample_uniform_sphere,
uniform_sphere_pdf,
};
use crate::utils::{Ptr, Transform};
use crate::{Float, PI}; use crate::{Float, PI};
use num_traits::Float as NumFloat; use std::sync::Arc;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct UniformInfiniteLight { pub struct InfiniteUniformLight {
pub base: LightBase, pub base: LightBase,
pub lemit: Ptr<DenselySampledSpectrum>, pub lemit: Ptr<DenselySampledSpectrum>,
pub scale: Float, pub scale: Float,
@ -32,28 +40,10 @@ pub struct UniformInfiniteLight {
pub scene_radius: Float, pub scene_radius: Float,
} }
impl UniformInfiniteLight { unsafe impl Send for InfiniteUniformLight {}
pub fn new( unsafe impl Sync for InfiniteUniformLight {}
render_from_light: Transform,
scale: Float,
lemit: Ptr<DenselySampledSpectrum>,
) -> Self {
let base = LightBase::new(
LightType::Infinite,
render_from_light,
MediumInterface::default(),
);
Self {
base,
lemit,
scale,
scene_center: Point3f::default(),
scene_radius: 0.0,
}
}
}
impl LightTrait for UniformInfiniteLight { impl LightTrait for InfiniteUniformLight {
fn base(&self) -> &LightBase { fn base(&self) -> &LightBase {
&self.base &self.base
} }
@ -97,19 +87,32 @@ impl LightTrait for UniformInfiniteLight {
uniform_sphere_pdf() uniform_sphere_pdf()
} }
fn l(
&self,
_p: Point3f,
_n: Normal3f,
_uv: Point2f,
_w: Vector3f,
_lambda: &SampledWavelengths,
) -> SampledSpectrum {
todo!()
}
fn le(&self, _ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum { fn le(&self, _ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum {
self.scale * self.lemit.sample(lambda) self.scale * self.lemit.sample(lambda)
} }
fn preprocess(&mut self, scene_bounds: &Bounds3f) { #[cfg(not(target_os = "cuda"))]
(self.scene_center, self.scene_radius) = scene_bounds.bounding_sphere(); fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
todo!()
} }
#[cfg(not(target_os = "cuda"))]
fn bounds(&self) -> Option<LightBounds> { fn bounds(&self) -> Option<LightBounds> {
None todo!()
} }
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
4. * PI * PI * square(self.scene_radius) * self.scale * self.lemit.sample(&lambda) 4. * PI * PI * square(self.scene_radius) * self.scale * self.lemit.sample(&lambda)
} }
@ -117,7 +120,7 @@ impl LightTrait for UniformInfiniteLight {
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct ImageInfiniteLight { pub struct InfiniteImageLight {
pub base: LightBase, pub base: LightBase,
pub image: Ptr<Image>, pub image: Ptr<Image>,
pub image_color_space: Ptr<RGBColorSpace>, pub image_color_space: Ptr<RGBColorSpace>,
@ -128,47 +131,25 @@ pub struct ImageInfiniteLight {
pub scene_center: Point3f, pub scene_center: Point3f,
} }
impl ImageInfiniteLight { unsafe impl Send for InfiniteImageLight {}
pub fn new( unsafe impl Sync for InfiniteImageLight {}
render_from_light: Transform,
scale: Float,
image: Ptr<Image>,
image_color_space: Ptr<RGBColorSpace>,
distrib: Ptr<PiecewiseConstant2D>,
compensated_distrib: Ptr<PiecewiseConstant2D>,
) -> Self {
let base = LightBase::new(
LightType::Infinite,
render_from_light,
MediumInterface::default(),
);
Self {
base,
image,
image_color_space,
scale,
distrib,
compensated_distrib,
scene_center: Point3f::default(),
scene_radius: 0.0,
}
}
impl InfiniteImageLight {
fn image_le(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum { fn image_le(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {
rgb[c] = self.image.lookup_nearest_channel_with_wrap( rgb[c] = self.image.lookup_nearest_channel_with_wrap(
uv, uv,
c, c as i32,
WrapMode::OctahedralSphere.into(), WrapMode::OctahedralSphere.into(),
); );
} }
let spec = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero()); let spec = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero());
self.scale * spec.sample(lambda) self.scale * spec.sample(lambda)
} }
} }
impl LightTrait for ImageInfiniteLight { impl LightTrait for InfiniteImageLight {
fn base(&self) -> &LightBase { fn base(&self) -> &LightBase {
&self.base &self.base
} }
@ -217,6 +198,17 @@ impl LightTrait for ImageInfiniteLight {
pdf / (4. * PI) pdf / (4. * PI)
} }
fn l(
&self,
_p: Point3f,
_n: Normal3f,
_uv: Point2f,
_w: Vector3f,
_lambda: &SampledWavelengths,
) -> SampledSpectrum {
todo!()
}
fn le(&self, ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum { fn le(&self, ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum {
let w_light = self let w_light = self
.base .base
@ -227,28 +219,36 @@ impl LightTrait for ImageInfiniteLight {
self.image_le(uv, lambda) self.image_le(uv, lambda)
} }
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
let mut sum_l = SampledSpectrum::new(0.); let mut sum_l = SampledSpectrum::new(0.);
let width = self.image.resolution().x(); let width = self.image.resolution.x();
let height = self.image.resolution().y(); let height = self.image.resolution.y();
for v in 0..height { for v in 0..height {
for u in 0..width { for u in 0..width {
let rgb = RGB::from(self.image.get_channels_with_wrap::<3>( let mut rgb = RGB::default();
for c in 0..3 {
rgb[c] = self.image.get_channel_with_wrap(
Point2i::new(u, v), Point2i::new(u, v),
c as i32,
WrapMode::OctahedralSphere.into(), WrapMode::OctahedralSphere.into(),
)); );
sum_l += RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero()) }
sum_l += RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero())
.sample(&lambda); .sample(&lambda);
} }
} }
4. * PI * PI * square(self.scene_radius) * self.scale * sum_l / (width * height) as Float 4. * PI * PI * square(self.scene_radius) * self.scale * sum_l / (width * height) as Float
} }
#[cfg(not(target_os = "cuda"))]
fn preprocess(&mut self, scene_bounds: &Bounds3f) { fn preprocess(&mut self, scene_bounds: &Bounds3f) {
(self.scene_center, self.scene_radius) = scene_bounds.bounding_sphere(); let (scene_center, scene_radius) = scene_bounds.bounding_sphere();
self.scene_center = scene_center;
self.scene_radius = scene_radius;
} }
#[cfg(not(target_os = "cuda"))]
fn bounds(&self) -> Option<LightBounds> { fn bounds(&self) -> Option<LightBounds> {
None None
} }
@ -256,52 +256,25 @@ impl LightTrait for ImageInfiniteLight {
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct PortalInfiniteLight { pub struct InfinitePortalLight {
pub base: LightBase, pub base: LightBase,
pub image: Ptr<Image>, pub image: Ptr<Image>,
pub image_color_space: Ptr<RGBColorSpace>, pub image_color_space: Ptr<RGBColorSpace>,
pub scale: Float, pub scale: Float,
pub portal: [Point3f; 4], pub portal: [Point3f; 4],
pub portal_frame: Frame, pub portal_frame: Frame,
pub distribution: Ptr<WindowedPiecewiseConstant2D>, pub distribution: WindowedPiecewiseConstant2D,
pub scene_center: Point3f, pub scene_center: Point3f,
pub scene_radius: Float, pub scene_radius: Float,
} }
impl PortalInfiniteLight { impl InfinitePortalLight {
pub fn new(
render_from_light: Transform,
scale: Float,
image: Ptr<Image>,
image_color_space: Ptr<RGBColorSpace>,
portal: [Point3f; 4],
portal_frame: Frame,
distribution: Ptr<WindowedPiecewiseConstant2D>,
) -> Self {
let base = LightBase::new(
LightType::Infinite,
render_from_light,
MediumInterface::default(),
);
Self {
base,
image,
image_color_space,
scale,
portal,
portal_frame,
distribution,
scene_center: Point3f::default(),
scene_radius: 0.0,
}
}
pub fn image_lookup(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum { pub fn image_lookup(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {
rgb[c] = self.image.lookup_nearest_channel(uv, c) rgb[c] = self.image.lookup_nearest_channel(uv, c as i32)
} }
let spec = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero()); let spec = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero());
self.scale * spec.sample(lambda) self.scale * spec.sample(lambda)
} }
@ -337,7 +310,7 @@ impl PortalInfiniteLight {
(self.portal[1] - self.portal[0]).norm() * (self.portal[3] - self.portal[0]).norm() (self.portal[1] - self.portal[0]).norm() * (self.portal[3] - self.portal[0]).norm()
} }
pub fn render_from_image_with(portal_frame: Frame, uv: Point2f) -> (Vector3f, Float) { pub fn render_from_image(portal_frame: Frame, uv: Point2f) -> (Vector3f, Float) {
let alpha = -PI / 2.0 + uv.x() * PI; let alpha = -PI / 2.0 + uv.x() * PI;
let beta = -PI / 2.0 + uv.y() * PI; let beta = -PI / 2.0 + uv.y() * PI;
@ -350,14 +323,9 @@ impl PortalInfiniteLight {
(portal_frame.from_local(w), duv_dw) (portal_frame.from_local(w), duv_dw)
} }
#[inline]
pub fn render_from_image(&self, uv: Point2f) -> (Vector3f, Float) {
Self::render_from_image_with(self.portal_frame, uv)
}
} }
impl LightTrait for PortalInfiniteLight { impl LightTrait for InfinitePortalLight {
fn base(&self) -> &LightBase { fn base(&self) -> &LightBase {
&self.base &self.base
} }
@ -371,7 +339,7 @@ impl LightTrait for PortalInfiniteLight {
) -> Option<LightLiSample> { ) -> Option<LightLiSample> {
let b = self.image_bounds(ctx.p())?; let b = self.image_bounds(ctx.p())?;
let (uv, map_pdf) = self.distribution.sample(u, b)?; let (uv, map_pdf) = self.distribution.sample(u, b)?;
let (wi, duv_dw) = self.render_from_image(uv); let (wi, duv_dw) = Self::render_from_image(self.portal_frame, uv);
if duv_dw == 0. { if duv_dw == 0. {
return None; return None;
} }
@ -395,6 +363,17 @@ impl LightTrait for PortalInfiniteLight {
pdf / duv_dw pdf / duv_dw
} }
fn l(
&self,
_p: Point3f,
_n: Normal3f,
_uv: Point2f,
_w: Vector3f,
_lambda: &SampledWavelengths,
) -> SampledSpectrum {
todo!()
}
fn le(&self, ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum { fn le(&self, ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum {
let uv = self.image_from_render(ray.d.normalize()); let uv = self.image_from_render(ray.d.normalize());
let b = self.image_bounds(ray.o); let b = self.image_bounds(ray.o);
@ -404,34 +383,17 @@ impl LightTrait for PortalInfiniteLight {
} }
} }
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, _lambda: SampledWavelengths) -> SampledSpectrum {
let mut sum_l = SampledSpectrum::new(0.); todo!()
let width = self.image.resolution().x();
let height = self.image.resolution().y();
for y in 0..height {
for x in 0..width {
let rgb = RGB::from(self.image.get_channels::<3>(Point2i::new(x, y)));
let st = Point2f::new(
(x as Float + 0.5) / width as Float,
(y as Float + 0.5) / height as Float,
);
let (_, duv_dw) = self.render_from_image(st);
sum_l += RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero())
.sample(&lambda)
/ duv_dw;
}
} }
self.scale * self.area() * sum_l / (width * height) as Float #[cfg(not(target_os = "cuda"))]
}
#[cfg(not(gpu))]
fn preprocess(&mut self, scene_bounds: &Bounds3f) { fn preprocess(&mut self, scene_bounds: &Bounds3f) {
(self.scene_center, self.scene_radius) = scene_bounds.bounding_sphere(); (self.scene_center, self.scene_radius) = scene_bounds.bounding_sphere();
} }
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
fn bounds(&self) -> Option<LightBounds> { fn bounds(&self) -> Option<LightBounds> {
None None
} }

View file

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

View file

@ -7,8 +7,8 @@ use crate::core::light::{
}; };
use crate::core::spectrum::SpectrumTrait; use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths}; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::{Float, INV_2_PI, PI, Ptr, Transform}; use crate::utils::ptr::Ptr;
use num_traits::Float as NumFloat; use crate::{Float, PI};
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -51,14 +51,32 @@ impl LightTrait for PointLight {
0. 0.
} }
#[cfg(not(gpu))] fn l(
&self,
_p: Point3f,
_n: Normal3f,
_uv: Point2f,
_w: Vector3f,
_lambda: &SampledWavelengths,
) -> SampledSpectrum {
todo!()
}
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
}
#[cfg(not(target_os = "cuda"))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
4. * PI * self.scale * self.i.sample(&lambda) 4. * PI * self.scale * self.i.sample(&lambda)
} }
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {} #[cfg(not(target_os = "cuda"))]
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
todo!()
}
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
fn bounds(&self) -> Option<LightBounds> { fn bounds(&self) -> Option<LightBounds> {
let p = self let p = self
.base .base
@ -70,7 +88,7 @@ impl LightTrait for PointLight {
Vector3f::new(0., 0., 1.), Vector3f::new(0., 0., 1.),
phi, phi,
PI.cos(), PI.cos(),
INV_2_PI.cos(), (PI / 2.).cos(),
false, false,
)) ))
} }

View file

@ -4,7 +4,6 @@ use crate::core::geometry::{
Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f, VectorLike, cos_theta, Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f, VectorLike, cos_theta,
}; };
use crate::core::image::Image; use crate::core::image::Image;
use crate::core::interaction::{Interaction, InteractionBase, SimpleInteraction};
use crate::core::light::{ use crate::core::light::{
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
}; };
@ -12,11 +11,11 @@ use crate::core::medium::MediumInterface;
use crate::core::spectrum::SpectrumTrait; use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{radians, square}; use crate::utils::math::{radians, square};
use crate::utils::ptr::Ptr;
use crate::{ use crate::{
spectra::{RGBColorSpace, RGBIlluminantSpectrum}, spectra::{RGBColorSpace, RGBIlluminantSpectrum},
utils::{Ptr, Transform, sampling::PiecewiseConstant2D}, utils::{Transform, sampling::PiecewiseConstant2D},
}; };
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -34,7 +33,7 @@ pub struct ProjectionLight {
} }
impl ProjectionLight { impl ProjectionLight {
pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { pub fn i(&self, w: Vector3f, lambda: SampledWavelengths) -> SampledSpectrum {
if w.z() < self.hither { if w.z() < self.hither {
return SampledSpectrum::new(0.); return SampledSpectrum::new(0.);
} }
@ -45,10 +44,10 @@ impl ProjectionLight {
let uv = Point2f::from(self.screen_bounds.offset(&Point2f::new(ps.x(), ps.y()))); let uv = Point2f::from(self.screen_bounds.offset(&Point2f::new(ps.x(), ps.y())));
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {
rgb[c] = self.image.lookup_nearest_channel(uv, c); rgb[c] = self.image.lookup_nearest_channel(uv, c as i32);
} }
let s = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero()); let s = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero());
self.scale * s.sample(lambda) self.scale * s.sample(&lambda)
} }
} }
@ -59,23 +58,12 @@ impl LightTrait for ProjectionLight {
fn sample_li( fn sample_li(
&self, &self,
ctx: &LightSampleContext, _ctx: &LightSampleContext,
_u: Point2f, _u: Point2f,
lambda: &SampledWavelengths, _lambda: &SampledWavelengths,
_allow_incomplete_pdf: bool, _allow_incomplete_pdf: bool,
) -> Option<LightLiSample> { ) -> Option<LightLiSample> {
let render_from_light = self.base().render_from_light; todo!()
let p = render_from_light.apply_to_point(Point3f::new(0., 0., 0.));
let wi = (p - ctx.p()).normalize();
let wl = render_from_light.apply_inverse_vector(-wi);
let li = self.i(wl, lambda) / p.distance_squared(ctx.p());
if li.is_black() {
return None;
}
let base = InteractionBase::new_boundary(p, 0., self.base.medium_interface);
let intr = SimpleInteraction::new(base);
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
} }
fn pdf_li( fn pdf_li(
@ -84,17 +72,31 @@ impl LightTrait for ProjectionLight {
_wi: Vector3f, _wi: Vector3f,
_allow_incomplete_pdf: bool, _allow_incomplete_pdf: bool,
) -> Float { ) -> Float {
0. todo!()
}
fn l(
&self,
_p: Point3f,
_n: Normal3f,
_uv: Point2f,
_w: Vector3f,
_lambda: &SampledWavelengths,
) -> SampledSpectrum {
todo!()
}
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
} }
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
let mut sum = SampledSpectrum::new(0.); let mut sum = SampledSpectrum::new(0.);
let res = self.image.resolution(); for y in 0..self.image.resolution.y() {
for y in 0..res.y() { for x in 0..self.image.resolution.x() {
for x in 0..res.x() {
let ps = self.screen_bounds.lerp(Point2f::new( let ps = self.screen_bounds.lerp(Point2f::new(
(x as Float + 0.5) / res.x() as Float, (x as Float + 0.5) / self.image.resolution.x() as Float,
(y as Float + 0.5) / res.y() 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( let w_raw = Vector3f::from(self.light_from_screen.apply_to_point(Point3f::new(
ps.x(), ps.x(),
@ -105,58 +107,21 @@ impl LightTrait for ProjectionLight {
let dwda = cos_theta(w).powi(3); let dwda = cos_theta(w).powi(3);
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {
rgb[c] = self.image.get_channel(Point2i::new(x, y), c); rgb[c] = self.image.get_channel(Point2i::new(x, y), c as i32);
} }
let s = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero()); let s = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero());
sum += s.sample(&lambda) * dwda; 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) {} fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
todo!()
}
fn bounds(&self) -> Option<LightBounds> { fn bounds(&self) -> Option<LightBounds> {
let mut sum = 0.; todo!()
for v in 0..self.image.resolution().y() {
for u in 0..self.image.resolution().x() {
let uv = Point2i::new(u, v);
sum += self.image.get_channel(uv, 0).max(
self.image
.get_channel(uv, 1)
.max(self.image.get_channel(uv, 2)),
);
}
}
let phi =
self.scale * sum / (self.image.resolution().x() * self.image.resolution().y()) as f32;
let p_corner = Point3f::new(
self.screen_bounds.p_max.x(),
self.screen_bounds.p_max.y(),
0.,
);
let w_corner = Vector3f::from(self.light_from_screen.apply_to_point(p_corner)).normalize();
let cos_total_width = cos_theta(w_corner);
let p = self
.base
.render_from_light
.apply_to_point(Point3f::new(0., 0., 0.));
let w = self
.base
.render_from_light
.apply_to_vector(Vector3f::new(0., 0., 1.));
Some(LightBounds::new(
&Bounds3f::from_points(p, p),
w,
phi,
1.,
cos_total_width,
false,
))
} }
} }

View file

@ -1,18 +1,18 @@
use crate::core::LightIdx;
use crate::core::geometry::primitives::OctahedralVector; use crate::core::geometry::primitives::OctahedralVector;
use crate::core::geometry::{Bounds3f, DirectionCone, Normal3f, Point3f, Vector3f, VectorLike}; use crate::core::geometry::{Bounds3f, Normal3f, Point3f, Vector3f, VectorLike};
use crate::core::light::{Light, LightBounds, LightSampleContext}; use crate::core::geometry::{DirectionCone, Normal};
use crate::core::light::Light;
use crate::core::light::{LightBounds, LightSampleContext};
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{clamp, lerp, sample_discrete}; use crate::utils::math::{clamp, lerp, sample_discrete};
use crate::utils::math::{safe_sqrt, square}; use crate::utils::math::{safe_sqrt, square};
use crate::utils::ptr::Ptr; use crate::utils::ptr::{Ptr, Slice};
use crate::utils::sampling::AliasTable; use crate::utils::sampling::AliasTable;
use crate::{Float, ONE_MINUS_EPSILON, PI}; use crate::{Float, ONE_MINUS_EPSILON, PI};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use num_traits::Float as NumFloat;
#[derive(Clone, Copy, Debug, Default)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct CompactLightBounds { pub struct CompactLightBounds {
pub w: OctahedralVector, pub w: OctahedralVector,
pub phi: Float, pub phi: Float,
@ -26,19 +26,7 @@ pub struct CompactLightBounds {
pub qb: [[u16; 3]; 2], pub qb: [[u16; 3]; 2],
} }
#[allow(clippy::derivable_impls)] const _: () = assert!(std::mem::size_of::<CompactLightBounds>() == 24);
impl Default for CompactLightBounds {
fn default() -> Self {
Self {
w: OctahedralVector::default(),
phi: Float::default(),
packed_info: u32::default(),
qb: [[u16::default(); 3]; 2],
}
}
}
const _: () = assert!(core::mem::size_of::<CompactLightBounds>() == 24);
impl CompactLightBounds { impl CompactLightBounds {
pub fn new(lb: &LightBounds, all_b: &Bounds3f) -> Self { pub fn new(lb: &LightBounds, all_b: &Bounds3f) -> Self {
@ -164,25 +152,27 @@ impl CompactLightBounds {
} }
} }
#[repr(C)] #[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)]
pub struct SampledLight { pub struct SampledLight {
pub light: LightIdx, pub light: Ptr<Light>,
pub p: Float, pub p: Float,
} }
impl SampledLight {
pub fn new(light: Light, p: Float) -> Self {
Self {
light: Ptr::from(&light),
p,
}
}
}
#[enum_dispatch] #[enum_dispatch]
pub trait LightSamplerTrait { pub trait LightSamplerTrait {
fn sample_with_context(&self, ctx: &LightSampleContext, u: Float) -> Option<SampledLight>;
fn pmf_with_context(&self, ctx: &LightSampleContext, light: &Light) -> Float;
fn sample(&self, u: Float) -> Option<SampledLight>; fn sample(&self, u: Float) -> Option<SampledLight>;
fn pmf(&self, idx: LightIdx) -> Float; fn pmf(&self, light: &Light) -> Float;
/// Samplers that ignore the shading context inherit these.
fn sample_with_context(&self, _ctx: &LightSampleContext, u: Float) -> Option<SampledLight> {
self.sample(u)
}
fn pmf_with_context(&self, _ctx: &LightSampleContext, idx: LightIdx) -> Float {
self.pmf(idx)
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -193,58 +183,106 @@ pub enum LightSampler {
BVH(BVHLightSampler), BVH(BVHLightSampler),
} }
#[repr(C)] #[derive(Clone, Debug)]
#[derive(Clone, Copy, Debug)]
pub struct UniformLightSampler { pub struct UniformLightSampler {
lights: *const Light,
lights_len: u32, lights_len: u32,
} }
impl UniformLightSampler { impl UniformLightSampler {
pub fn new(lights_len: u32) -> Self { pub fn new(lights: *const Light, lights_len: u32) -> Self {
Self { lights_len } Self { lights, lights_len }
}
#[inline(always)]
fn light(&self, idx: usize) -> Light {
unsafe { *self.lights.add(idx) }
} }
} }
impl LightSamplerTrait for UniformLightSampler { 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 {
self.pmf(light)
}
fn sample(&self, u: Float) -> Option<SampledLight> { fn sample(&self, u: Float) -> Option<SampledLight> {
if self.lights_len == 0 { if self.lights_len == 0 {
return None; return None;
} }
let light_index = ((u * self.lights_len as Float) as u32).min(self.lights_len - 1);
let light_index = (u as u32 * self.lights_len).min(self.lights_len - 1) as usize;
Some(SampledLight { Some(SampledLight {
light: LightIdx(light_index), light: Ptr::from(&self.light(light_index)),
p: 1.0 / self.lights_len as Float, p: 1. / self.lights_len as Float,
}) })
} }
fn pmf(&self, _light: &Light) -> Float {
fn pmf(&self, _idx: LightIdx) -> Float {
if self.lights_len == 0 { if self.lights_len == 0 {
return 0.0; return 0.;
} }
1.0 / 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)] #[repr(C)]
#[derive(Clone, Debug, Copy)] #[derive(Clone, Debug, Copy)]
pub struct PowerLightSampler { pub struct PowerLightSampler {
pub alias_table: Ptr<AliasTable>, pub lights: Slice<Light>,
pub lights_len: u32,
pub alias_table: AliasTable,
} }
unsafe impl Send for PowerLightSampler {}
unsafe impl Sync for PowerLightSampler {}
impl LightSamplerTrait for PowerLightSampler { 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 {
self.pmf(light)
}
fn sample(&self, u: Float) -> Option<SampledLight> { fn sample(&self, u: Float) -> Option<SampledLight> {
if self.alias_table.size() == 0 { if self.alias_table.size() == 0 {
return None; return None;
} }
let (light_index, pmf, _) = self.alias_table.sample(u); let (light_index, pmf, _) = self.alias_table.sample(u);
let light_ref = &self.lights[light_index as usize];
Some(SampledLight { Some(SampledLight {
light: LightIdx(light_index), light: Ptr::from(light_ref),
p: pmf, p: pmf,
}) })
} }
fn pmf(&self, idx: LightIdx) -> Float { fn pmf(&self, light: &Light) -> Float {
self.alias_table.pmf(idx.0) if self.lights_len == 0 {
return 0.0;
}
let light_ptr = light as *const Light;
let start = self.lights.as_ptr();
let end = unsafe { start.add(self.lights.len as usize) };
if light_ptr >= start && light_ptr < end {
let index = unsafe { light_ptr.offset_from(start) };
return self.alias_table.pmf(index as u32);
}
0.
} }
} }
@ -257,7 +295,7 @@ pub struct LightBVHNode {
packed_data: u32, packed_data: u32,
} }
const _: () = assert!(core::mem::size_of::<LightBVHNode>() == 32); const _: () = assert!(std::mem::size_of::<LightBVHNode>() == 32);
impl LightBVHNode { impl LightBVHNode {
/// Mask to isolate the Leaf Flag (Bit 31) /// Mask to isolate the Leaf Flag (Bit 31)
@ -312,67 +350,49 @@ impl LightBVHNode {
pub fn child_or_light_index(&self) -> u32 { pub fn child_or_light_index(&self) -> u32 {
self.packed_data & Self::INDEX_MASK self.packed_data & Self::INDEX_MASK
} }
pub fn sample(&self, _ctx: &LightSampleContext, _u: Float) -> Option<SampledLight> {
todo!("Implement LightBVHNode::Sample logic")
}
} }
/// Canary value stored in `bit_trails` for a light that is not a BVH leaf, i.e. an #[derive(Clone, Debug)]
/// infinite light or one with negative `phi`. Stands in for pbrt's
/// `lightToBitTrail.HasKey(light)`.
pub const NO_BIT_TRAIL: u64 = u64::MAX;
#[derive(Clone, Debug, Copy)]
pub struct BVHLightSampler { pub struct BVHLightSampler {
pub nodes: Ptr<LightBVHNode>, pub nodes: *const LightBVHNode,
/// Handles of the infinite lights, in scene order. pub lights: *const Light,
pub infinite_lights: Ptr<LightIdx>, pub infinite_lights: *const Light,
/// Indexed by *global* light index, matching the leaf indices stored in pub bit_trails: *const u64,
/// `nodes`; `NO_BIT_TRAIL` where the light has no leaf.
pub bit_trails: Ptr<u64>,
pub nodes_len: u32, pub nodes_len: u32,
pub lights_len: u32, pub lights_len: u32,
pub infinite_lights_len: u32, pub infinite_lights_len: u32,
pub all_light_bounds: Bounds3f, pub all_light_bounds: Bounds3f,
} }
unsafe impl Send for BVHLightSampler {}
unsafe impl Sync for BVHLightSampler {}
impl BVHLightSampler { impl BVHLightSampler {
// Each array is paired with the length stored alongside it, so the slice can
// only be formed one way and indexing past the end is a bounds check rather
// than a silent read. These three are the only `unsafe` in the sampler.
#[inline(always)]
fn nodes(&self) -> &[LightBVHNode] {
unsafe { self.nodes.as_slice(self.nodes_len as usize) }
}
#[inline(always)]
fn infinite_lights(&self) -> &[LightIdx] {
unsafe {
self.infinite_lights
.as_slice(self.infinite_lights_len as usize)
}
}
/// One bit trail per light, indexed by global light index.
#[inline(always)]
fn bit_trails(&self) -> &[u64] {
unsafe { self.bit_trails.as_slice(self.lights_len as usize) }
}
#[inline(always)] #[inline(always)]
fn node(&self, idx: usize) -> &LightBVHNode { fn node(&self, idx: usize) -> &LightBVHNode {
&self.nodes()[idx] unsafe { &*self.nodes.add(idx) }
} }
#[inline(always)] #[inline(always)]
fn infinite_light(&self, idx: usize) -> LightIdx { fn light(&self, idx: usize) -> Light {
self.infinite_lights()[idx] unsafe { *self.lights.add(idx) }
}
#[inline(always)]
fn infinite_light(&self, idx: usize) -> Light {
unsafe { *self.infinite_lights.add(idx) }
} }
#[inline(always)] #[inline(always)]
fn bit_trail(&self, idx: usize) -> u64 { fn bit_trail(&self, idx: usize) -> u64 {
self.bit_trails()[idx] unsafe { *self.bit_trails.add(idx) }
} }
pub fn evaluate_cost(b: &LightBounds, bounds: &Bounds3f, dim: usize) -> Float { fn evaluate_cost(&self, b: &LightBounds, bounds: &Bounds3f, dim: usize) -> Float {
let theta_o = b.cos_theta_o.acos(); let theta_o = b.cos_theta_o.acos();
let theta_e = b.cos_theta_e.acos(); let theta_e = b.cos_theta_e.acos();
let theta_w = (theta_o + theta_e).min(PI); let theta_w = (theta_o + theta_e).min(PI);
@ -390,19 +410,15 @@ impl LightSamplerTrait for BVHLightSampler {
fn sample_with_context(&self, ctx: &LightSampleContext, mut u: Float) -> Option<SampledLight> { fn sample_with_context(&self, ctx: &LightSampleContext, mut u: Float) -> Option<SampledLight> {
let empty_nodes = if self.nodes_len == 0 { 0. } else { 1. }; let empty_nodes = if self.nodes_len == 0 { 0. } else { 1. };
let inf_size = self.infinite_lights_len as Float; let inf_size = self.infinite_lights_len as Float;
let light_size = self.lights_len as Float;
let p_inf = inf_size / (inf_size + empty_nodes); let p_inf = inf_size / (inf_size + empty_nodes);
if u < p_inf { if u < p_inf {
u /= p_inf; u /= p_inf;
// Uniformly sample an infinite light and return its handle let ind = (u * light_size).min(light_size - 1.) as usize;
// (`lightsamplers.h:277`: `infiniteLights[index]`).
let ind = ((u * inf_size) as usize).min(self.infinite_lights_len as usize - 1);
let pmf = p_inf / inf_size; let pmf = p_inf / inf_size;
return Some(SampledLight { return Some(SampledLight::new(self.infinite_light(ind), pmf));
light: self.infinite_light(ind),
p: pmf,
});
} }
if self.nodes_len == 0 { if self.nodes_len == 0 {
@ -437,33 +453,36 @@ impl LightSamplerTrait for BVHLightSampler {
node_ind = if child == 0 { child0_idx } else { child1_idx }; node_ind = if child == 0 { child0_idx } else { child1_idx };
} else { } else {
if node_ind > 0 || node.light_bounds.importance(p, n, &self.all_light_bounds) > 0. { if node_ind > 0 || node.light_bounds.importance(p, n, &self.all_light_bounds) > 0. {
// child_or_light_index() is the global index into the scene lights array let light_idx = node.child_or_light_index() as usize;
return Some(SampledLight { return Some(SampledLight::new(self.light(light_idx), pmf));
light: LightIdx(node.child_or_light_index()),
p: pmf,
});
} }
return None; return None;
} }
} }
} }
fn pmf_with_context(&self, ctx: &LightSampleContext, idx: LightIdx) -> Float { 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 empty_nodes = if self.nodes_len == 0 { 0. } else { 1. };
let n_infinite = self.infinite_lights_len as Float; let n_infinite = self.infinite_lights_len as Float;
let light_index = idx.0 as usize; let inf_start = self.infinite_lights;
if light_index >= self.lights_len as usize { 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; return 0.0;
} }
// bit_trail[light_index] encodes the path from root to this light's leaf. let light_index = unsafe { light_ptr.offset_from(finite_start) as usize };
// Canary value to check if no leaf. No leaf, it his infinite, or its power
// was zero
let mut bit_trail = self.bit_trail(light_index); let mut bit_trail = self.bit_trail(light_index);
if bit_trail == NO_BIT_TRAIL {
return 1.0 / (n_infinite + empty_nodes);
}
let p_inf = n_infinite / (n_infinite + empty_nodes); let p_inf = n_infinite / (n_infinite + empty_nodes);
let mut pmf = 1.0 - p_inf; let mut pmf = 1.0 - p_inf;
let mut node_ind = 0; let mut node_ind = 0;
@ -488,12 +507,17 @@ impl LightSamplerTrait for BVHLightSampler {
} }
let which_child = (bit_trail & 1) as usize; let which_child = (bit_trail & 1) as usize;
// Update probability: prob of picking the correct child
pmf *= ci[which_child] / sum_importance; pmf *= ci[which_child] / sum_importance;
// Advance
node_ind = if which_child == 1 { node_ind = if which_child == 1 {
node.child_or_light_index() as usize node.child_or_light_index() as usize
} else { } else {
node_ind + 1 node_ind + 1
}; };
bit_trail >>= 1; bit_trail >>= 1;
} }
} }
@ -502,17 +526,20 @@ impl LightSamplerTrait for BVHLightSampler {
if self.lights_len == 0 { if self.lights_len == 0 {
return None; return None;
} }
let light_ind = (u * self.lights_len as Float).min(self.lights_len as Float - 1.) as u32;
Some(SampledLight { let light_ind = (u * self.lights_len as Float).min(self.lights_len as Float - 1.) as usize;
light: LightIdx(light_ind),
p: 1. / self.lights_len as Float, Some(SampledLight::new(
}) self.light(light_ind),
1. / self.lights_len as Float,
))
} }
fn pmf(&self, _idx: LightIdx) -> Float { fn pmf(&self, _light: &Light) -> Float {
if self.lights_len == 0 { if self.lights_len == 0 {
return 0.; return 0.;
} }
1. / self.lights_len as Float 1. / self.lights_len as Float
} }
} }

View file

@ -5,8 +5,8 @@ use crate::core::interaction::{Interaction, InteractionBase, InteractionTrait, S
use crate::core::light::{LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait}; use crate::core::light::{LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait};
use crate::core::spectrum::SpectrumTrait; use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths}; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::{Float, PI, Ptr, Transform}; use crate::utils::Ptr;
use num_traits::Float as NumFloat; use crate::{Float, PI};
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -65,7 +65,22 @@ impl LightTrait for SpotLight {
0. 0.
} }
#[cfg(not(gpu))] fn l(
&self,
_p: Point3f,
_n: Normal3f,
_uv: Point2f,
_w: Vector3f,
_lambda: &SampledWavelengths,
) -> SampledSpectrum {
todo!()
}
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
}
#[cfg(not(target_os = "cuda"))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
self.scale self.scale
* self.iemit.sample(&lambda) * self.iemit.sample(&lambda)
@ -74,8 +89,12 @@ impl LightTrait for SpotLight {
* ((1. - self.cos_falloff_start) + (self.cos_falloff_start - self.cos_falloff_end) / 2.) * ((1. - self.cos_falloff_start) + (self.cos_falloff_start - self.cos_falloff_end) / 2.)
} }
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {} #[cfg(not(target_os = "cuda"))]
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
todo!()
}
#[cfg(not(target_os = "cuda"))]
fn bounds(&self) -> Option<LightBounds> { fn bounds(&self) -> Option<LightBounds> {
let p = self let p = self
.base .base

View file

@ -8,7 +8,7 @@ use crate::core::image::Image;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait}; use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{FloatTexture, SpectrumTexture, TextureEvaluator}; use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator};
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::utils::math::clamp; use crate::utils::math::clamp;
@ -17,51 +17,49 @@ use crate::utils::math::clamp;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct CoatedDiffuseMaterial { pub struct CoatedDiffuseMaterial {
pub normal_map: Ptr<Image>, pub normal_map: Ptr<Image>,
pub displacement: Ptr<FloatTexture>, pub displacement: Ptr<GPUFloatTexture>,
pub reflectance: Ptr<SpectrumTexture>, pub reflectance: Ptr<GPUSpectrumTexture>,
pub albedo: Ptr<SpectrumTexture>, pub albedo: Ptr<GPUSpectrumTexture>,
pub u_roughness: Ptr<FloatTexture>, pub u_roughness: Ptr<GPUFloatTexture>,
pub v_roughness: Ptr<FloatTexture>, pub v_roughness: Ptr<GPUFloatTexture>,
pub thickness: Ptr<FloatTexture>, pub thickness: Ptr<GPUFloatTexture>,
pub g: Ptr<FloatTexture>, pub g: Ptr<GPUFloatTexture>,
pub eta: Ptr<Spectrum>, pub eta: Ptr<Spectrum>,
pub max_depth: u32,
pub n_samples: u32,
pub remap_roughness: bool, pub remap_roughness: bool,
pub seed: i32, pub max_depth: usize,
pub n_samples: usize,
} }
impl CoatedDiffuseMaterial { impl CoatedDiffuseMaterial {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "cuda"))]
pub fn new( pub fn new(
reflectance: Ptr<SpectrumTexture>, reflectance: &GPUSpectrumTexture,
u_roughness: Ptr<FloatTexture>, u_roughness: &GPUFloatTexture,
v_roughness: Ptr<FloatTexture>, v_roughness: &GPUFloatTexture,
thickness: Ptr<FloatTexture>, thickness: &GPUFloatTexture,
albedo: Ptr<SpectrumTexture>, albedo: &GPUSpectrumTexture,
g: Ptr<FloatTexture>, g: &GPUFloatTexture,
displacement: Ptr<FloatTexture>, eta: &Spectrum,
eta: Ptr<Spectrum>, displacement: &GPUFloatTexture,
normal_map: Ptr<Image>, normal_map: &Image,
remap_roughness: bool, remap_roughness: bool,
max_depth: u32, max_depth: usize,
n_samples: u32, n_samples: usize,
seed: i32,
) -> Self { ) -> Self {
Self { Self {
displacement, displacement: Ptr::from(displacement),
normal_map, normal_map: Ptr::from(normal_map),
reflectance, reflectance: Ptr::from(reflectance),
albedo, albedo: Ptr::from(albedo),
u_roughness, u_roughness: Ptr::from(u_roughness),
v_roughness, v_roughness: Ptr::from(v_roughness),
thickness, thickness: Ptr::from(thickness),
g, g: Ptr::from(g),
eta, eta: Ptr::from(eta),
remap_roughness, remap_roughness,
max_depth, max_depth,
n_samples, n_samples,
seed,
} }
} }
} }
@ -71,7 +69,7 @@ impl MaterialTrait for CoatedDiffuseMaterial {
&self, &self,
tex_eval: &T, tex_eval: &T,
ctx: &MaterialEvalContext, ctx: &MaterialEvalContext,
lambda: &mut SampledWavelengths, lambda: &SampledWavelengths,
) -> BSDF { ) -> BSDF {
let r = SampledSpectrum::clamp( let r = SampledSpectrum::clamp(
&tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda), &tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda),
@ -115,10 +113,9 @@ impl MaterialTrait for CoatedDiffuseMaterial {
gg, gg,
self.max_depth, self.max_depth,
self.n_samples, self.n_samples,
self.seed,
)); ));
BSDF::new(ctx.ns, ctx.dpdus, bxdf) BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf))
} }
fn get_bssrdf<T>( fn get_bssrdf<T>(
@ -141,7 +138,7 @@ impl MaterialTrait for CoatedDiffuseMaterial {
Some(&*self.normal_map) Some(&*self.normal_map)
} }
fn get_displacement(&self) -> Ptr<FloatTexture> { fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement self.displacement
} }
@ -153,64 +150,62 @@ impl MaterialTrait for CoatedDiffuseMaterial {
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct CoatedConductorMaterial { pub struct CoatedConductorMaterial {
displacement: Ptr<FloatTexture>,
interface_uroughness: Ptr<FloatTexture>,
interface_vroughness: Ptr<FloatTexture>,
thickness: Ptr<FloatTexture>,
interface_eta: Ptr<Spectrum>,
g: Ptr<FloatTexture>,
albedo: Ptr<SpectrumTexture>,
conductor_uroughness: Ptr<FloatTexture>,
conductor_vroughness: Ptr<FloatTexture>,
conductor_eta: Ptr<SpectrumTexture>,
k: Ptr<SpectrumTexture>,
reflectance: Ptr<SpectrumTexture>,
normal_map: Ptr<Image>, normal_map: Ptr<Image>,
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>,
remap_roughness: bool,
max_depth: u32, max_depth: u32,
n_samples: u32, n_samples: u32,
remap_roughness: bool,
seed: i32,
} }
impl CoatedConductorMaterial { impl CoatedConductorMaterial {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "cuda"))]
pub fn new( pub fn new(
displacement: Ptr<FloatTexture>, normal_map: &Image,
interface_uroughness: Ptr<FloatTexture>, displacement: &GPUFloatTexture,
interface_vroughness: Ptr<FloatTexture>, interface_uroughness: &GPUFloatTexture,
thickness: Ptr<FloatTexture>, interface_vroughness: &GPUFloatTexture,
g: Ptr<FloatTexture>, thickness: &GPUFloatTexture,
albedo: Ptr<SpectrumTexture>, interface_eta: &Spectrum,
conductor_uroughness: Ptr<FloatTexture>, g: &GPUFloatTexture,
conductor_vroughness: Ptr<FloatTexture>, albedo: &GPUSpectrumTexture,
conductor_eta: Ptr<SpectrumTexture>, conductor_uroughness: &GPUFloatTexture,
k: Ptr<SpectrumTexture>, conductor_vroughness: &GPUFloatTexture,
reflectance: Ptr<SpectrumTexture>, conductor_eta: &GPUSpectrumTexture,
normal_map: Ptr<Image>, k: &GPUSpectrumTexture,
interface_eta: Ptr<Spectrum>, reflectance: &GPUSpectrumTexture,
remap_roughness: bool,
max_depth: u32, max_depth: u32,
n_samples: u32, n_samples: u32,
remap_roughness: bool,
seed: i32,
) -> Self { ) -> Self {
Self { Self {
displacement, displacement: Ptr::from(displacement),
normal_map, normal_map: Ptr::from(normal_map),
interface_uroughness, interface_uroughness: Ptr::from(interface_uroughness),
interface_vroughness, interface_vroughness: Ptr::from(interface_vroughness),
thickness, thickness: Ptr::from(thickness),
interface_eta, interface_eta: Ptr::from(interface_eta),
g, g: Ptr::from(g),
albedo, albedo: Ptr::from(albedo),
conductor_uroughness, conductor_uroughness: Ptr::from(conductor_uroughness),
conductor_vroughness, conductor_vroughness: Ptr::from(conductor_vroughness),
conductor_eta, conductor_eta: Ptr::from(conductor_eta),
k, k: Ptr::from(k),
reflectance, reflectance: Ptr::from(reflectance),
remap_roughness, remap_roughness,
max_depth, max_depth,
n_samples, n_samples,
seed,
} }
} }
} }
@ -220,7 +215,7 @@ impl MaterialTrait for CoatedConductorMaterial {
&self, &self,
tex_eval: &T, tex_eval: &T,
ctx: &MaterialEvalContext, ctx: &MaterialEvalContext,
lambda: &mut SampledWavelengths, lambda: &SampledWavelengths,
) -> BSDF { ) -> BSDF {
let mut iurough = tex_eval.evaluate_float(&self.interface_uroughness, ctx); let mut iurough = tex_eval.evaluate_float(&self.interface_uroughness, ctx);
let mut ivrough = tex_eval.evaluate_float(&self.interface_vroughness, ctx); let mut ivrough = tex_eval.evaluate_float(&self.interface_vroughness, ctx);
@ -234,6 +229,7 @@ impl MaterialTrait for CoatedConductorMaterial {
let mut ieta = self.interface_eta.evaluate(lambda[0]); let mut ieta = self.interface_eta.evaluate(lambda[0]);
if self.interface_eta.is_constant() { if self.interface_eta.is_constant() {
let mut lambda = *lambda;
lambda.terminate_secondary_inplace(); lambda.terminate_secondary_inplace();
} }
@ -242,9 +238,12 @@ impl MaterialTrait for CoatedConductorMaterial {
} }
let (mut ce, mut ck) = if !self.conductor_eta.is_null() { let (mut ce, mut ck) = if !self.conductor_eta.is_null() {
let k_tex = self.k; let k_tex = self
.k
.as_ref()
.expect("CoatedConductor: 'k' must be provided if 'conductor_eta' is present");
let ce = tex_eval.evaluate_spectrum(&self.conductor_eta, ctx, lambda); let ce = tex_eval.evaluate_spectrum(&self.conductor_eta, ctx, lambda);
let ck = tex_eval.evaluate_spectrum(k_tex.get().unwrap(), ctx, lambda); let ck = tex_eval.evaluate_spectrum(k_tex, ctx, lambda);
(ce, ck) (ce, ck)
} else { } else {
let r = SampledSpectrum::clamp( let r = SampledSpectrum::clamp(
@ -283,11 +282,10 @@ impl MaterialTrait for CoatedConductorMaterial {
thick, thick,
a, a,
gg, gg,
self.max_depth, self.max_depth as usize,
self.n_samples, self.n_samples as usize,
self.seed,
)); ));
BSDF::new(ctx.ns, ctx.dpdus, bxdf) BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf))
} }
fn get_bssrdf<T>( fn get_bssrdf<T>(
@ -309,34 +307,30 @@ impl MaterialTrait for CoatedConductorMaterial {
self.conductor_vroughness, self.conductor_vroughness,
]; ];
let mut spectrum_textures = [Ptr::null(); 4]; let mut spectrum_textures = Vec::with_capacity(4);
let mut n = 0;
spectrum_textures[n] = self.albedo; spectrum_textures.push(self.albedo);
n += 1;
if !self.conductor_eta.is_null() { if !self.conductor_eta.is_null() {
spectrum_textures[n] = self.conductor_eta; spectrum_textures.push(self.conductor_eta);
n += 1;
} }
if !self.k.is_null() { if !self.k.is_null() {
spectrum_textures[n] = self.k; spectrum_textures.push(self.k);
n += 1;
} }
if !self.conductor_eta.is_null() { if !self.conductor_eta.is_null() {
spectrum_textures[n] = self.reflectance; spectrum_textures.push(self.reflectance);
} }
tex_eval.can_evaluate(&float_textures, &spectrum_textures) tex_eval.can_evaluate(&float_textures, &spectrum_textures)
} }
fn get_normal_map(&self) -> Option<&Image> { fn get_normal_map(&self) -> Option<&Image> {
self.normal_map.get() Some(&*self.normal_map)
} }
fn get_displacement(&self) -> Ptr<FloatTexture> { fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement self.displacement
} }

View file

@ -4,57 +4,52 @@ use crate::bxdfs::{
MeasuredBxDF, MeasuredBxDFData, MeasuredBxDF, MeasuredBxDFData,
}; };
use crate::core::bsdf::BSDF; use crate::core::bsdf::BSDF;
use crate::core::bssrdf::{BSSRDF, BSSRDFTable, TabulatedBSSRDF, subsurface_from_diffuse}; use crate::core::bssrdf::{BSSRDF, BSSRDFTable};
use crate::core::bxdf::BxDF; use crate::core::bxdf::BxDF;
use crate::core::image::Image; use crate::core::image::Image;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait}; use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{FloatTexture, SpectrumTexture, TextureEvaluator}; use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator};
use crate::spectra::{RGBColorSpace, SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::textures::SpectrumMixTexture; use crate::textures::GPUSpectrumMixTexture;
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::utils::math::clamp; use crate::utils::math::clamp;
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub enum HairAbsorption {
SigmaA(Ptr<SpectrumTexture>),
Color(Ptr<SpectrumTexture>),
Melanin {
eumelanin: Ptr<FloatTexture>,
pheomelanin: Ptr<FloatTexture>,
},
}
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct HairMaterial { pub struct HairMaterial {
pub hair_absorption: HairAbsorption, pub sigma_a: Ptr<GPUSpectrumTexture>,
pub eta: Ptr<FloatTexture>, pub color: Ptr<GPUSpectrumTexture>,
pub beta_m: Ptr<FloatTexture>, pub eumelanin: Ptr<GPUFloatTexture>,
pub beta_n: Ptr<FloatTexture>, pub pheomelanin: Ptr<GPUFloatTexture>,
pub alpha: Ptr<FloatTexture>, pub eta: Ptr<GPUFloatTexture>,
pub colorspace: Ptr<RGBColorSpace>, pub beta_m: Ptr<GPUFloatTexture>,
pub beta_n: Ptr<GPUFloatTexture>,
pub alpha: Ptr<GPUFloatTexture>,
} }
impl HairMaterial { impl HairMaterial {
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
pub fn new( pub fn new(
hair_absorption: HairAbsorption, sigma_a: Ptr<GPUSpectrumTexture>,
eta: Ptr<FloatTexture>, color: Ptr<GPUSpectrumTexture>,
beta_m: Ptr<FloatTexture>, eumelanin: Ptr<GPUFloatTexture>,
beta_n: Ptr<FloatTexture>, pheomelanin: Ptr<GPUFloatTexture>,
alpha: Ptr<FloatTexture>, eta: Ptr<GPUFloatTexture>,
colorspace: Ptr<RGBColorSpace>, beta_m: Ptr<GPUFloatTexture>,
beta_n: Ptr<GPUFloatTexture>,
alpha: Ptr<GPUFloatTexture>,
) -> Self { ) -> Self {
Self { Self {
hair_absorption, sigma_a,
color,
eumelanin,
pheomelanin,
eta, eta,
beta_m, beta_m,
beta_n, beta_n,
alpha, alpha,
colorspace,
} }
} }
} }
@ -62,89 +57,30 @@ impl HairMaterial {
impl MaterialTrait for HairMaterial { impl MaterialTrait for HairMaterial {
fn get_bsdf<T: TextureEvaluator>( fn get_bsdf<T: TextureEvaluator>(
&self, &self,
tex_eval: &T, _tex_eval: &T,
ctx: &MaterialEvalContext, _ctx: &MaterialEvalContext,
lambda: &mut SampledWavelengths, _lambda: &SampledWavelengths,
) -> BSDF { ) -> BSDF {
let bm = tex_eval.evaluate_float(&self.beta_m, ctx).clamp(1e-2, 1.0); todo!()
let bn = tex_eval.evaluate_float(&self.beta_n, ctx).clamp(1e-2, 1.0);
let a = tex_eval.evaluate_float(&self.alpha, ctx);
let e = tex_eval.evaluate_float(&self.eta, ctx);
let sig_a = match self.hair_absorption {
// Absorption coefficient, not reflectance, can be larger than 1
HairAbsorption::SigmaA(sigma_a) => {
SampledSpectrum::clamp_zero(&tex_eval.evaluate_spectrum(&sigma_a, ctx, lambda))
} }
HairAbsorption::Color(color) => {
let c = SampledSpectrum::clamp(
&tex_eval.evaluate_spectrum(&color, ctx, lambda),
0.,
1.,
);
HairBxDF::sigma_a_from_reflectance(c, bn)
}
HairAbsorption::Melanin {
eumelanin,
pheomelanin,
} => {
debug_assert!(!eumelanin.is_null() || !pheomelanin.is_null());
let eu = if !eumelanin.is_null() {
tex_eval.evaluate_float(&eumelanin, ctx)
} else {
0.
};
let pheo = if !pheomelanin.is_null() {
tex_eval.evaluate_float(&pheomelanin, ctx)
} else {
0.
};
HairBxDF::sigma_a_from_concentration(eu.max(0.0), pheo.max(0.0), self.colorspace)
.sample(lambda)
}
};
let h = -1. + 2. * ctx.uv[1];
let bxdf = BxDF::Hair(HairBxDF::new(h, e, sig_a, bm, bn, a));
BSDF::new(ctx.ns, ctx.dpdus, bxdf)
}
fn get_bssrdf<T>( fn get_bssrdf<T>(
&self, &self,
_tex_eval: &T, _tex_eval: &T,
_ctx: &MaterialEvalContext, _ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths, _lambda: &SampledWavelengths,
) -> Option<BSSRDF> { ) -> Option<BSSRDF> {
None todo!()
} }
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool { fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
match self.hair_absorption { todo!()
HairAbsorption::SigmaA(t) | HairAbsorption::Color(t) => {
tex_eval.can_evaluate(&[self.eta, self.beta_m, self.beta_n, self.alpha], &[t])
}
HairAbsorption::Melanin {
eumelanin,
pheomelanin,
} => tex_eval.can_evaluate(
&[
self.eta,
self.beta_m,
self.beta_n,
self.alpha,
eumelanin,
pheomelanin,
],
&[],
),
}
} }
fn get_normal_map(&self) -> Option<&Image> { fn get_normal_map(&self) -> Option<&Image> {
None todo!()
} }
fn get_displacement(&self) -> Ptr<FloatTexture> { fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
Ptr::null() Ptr::null()
} }
@ -156,7 +92,7 @@ impl MaterialTrait for HairMaterial {
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct MeasuredMaterial { pub struct MeasuredMaterial {
pub displacement: Ptr<FloatTexture>, pub displacement: Ptr<GPUFloatTexture>,
pub normal_map: Ptr<Image>, pub normal_map: Ptr<Image>,
pub brdf: Ptr<MeasuredBxDFData>, pub brdf: Ptr<MeasuredBxDFData>,
} }
@ -165,11 +101,11 @@ impl MaterialTrait for MeasuredMaterial {
fn get_bsdf<T: TextureEvaluator>( fn get_bsdf<T: TextureEvaluator>(
&self, &self,
_tex_eval: &T, _tex_eval: &T,
ctx: &MaterialEvalContext, _ctx: &MaterialEvalContext,
lambda: &mut SampledWavelengths, _lambda: &SampledWavelengths,
) -> BSDF { ) -> BSDF {
let bxdf = BxDF::Measured(MeasuredBxDF::new(&self.brdf, lambda)); // MeasuredBxDF::new(&self.brdf, lambda)
BSDF::new(ctx.ns, ctx.dpdus, bxdf) todo!()
} }
fn get_bssrdf<T>( fn get_bssrdf<T>(
@ -189,7 +125,7 @@ impl MaterialTrait for MeasuredMaterial {
Some(&*self.normal_map) Some(&*self.normal_map)
} }
fn get_displacement(&self) -> Ptr<FloatTexture> { fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement self.displacement
} }
@ -198,108 +134,51 @@ impl MaterialTrait for MeasuredMaterial {
} }
} }
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub enum SubsurfaceScattering {
Coefficients {
sigma_a: Ptr<SpectrumTexture>,
sigma_s: Ptr<SpectrumTexture>,
},
Reflectance {
reflectance: Ptr<SpectrumTexture>,
mfp: Ptr<SpectrumTexture>,
},
}
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct SubsurfaceMaterial { pub struct SubsurfaceMaterial {
pub normal_map: Ptr<Image>, pub normal_map: Ptr<Image>,
pub displacement: Ptr<FloatTexture>, pub displacement: Ptr<GPUFloatTexture>,
pub scattering: SubsurfaceScattering, pub sigma_a: Ptr<GPUSpectrumTexture>,
pub sigma_s: Ptr<GPUSpectrumMixTexture>,
pub reflectance: Ptr<GPUSpectrumMixTexture>,
pub mfp: Ptr<GPUSpectrumMixTexture>,
pub eta: Float, pub eta: Float,
pub scale: Float, pub scale: Float,
pub u_roughness: Ptr<FloatTexture>, pub u_roughness: Ptr<GPUFloatTexture>,
pub v_roughness: Ptr<FloatTexture>, pub v_roughness: Ptr<GPUFloatTexture>,
pub remap_roughness: bool, pub remap_roughness: bool,
pub table: Ptr<BSSRDFTable>, pub table: BSSRDFTable,
} }
impl MaterialTrait for SubsurfaceMaterial { impl MaterialTrait for SubsurfaceMaterial {
fn get_bsdf<T: TextureEvaluator>( fn get_bsdf<T: TextureEvaluator>(
&self, &self,
tex_eval: &T, _tex_eval: &T,
ctx: &MaterialEvalContext, _ctx: &MaterialEvalContext,
_lambda: &mut SampledWavelengths, _lambda: &SampledWavelengths,
) -> BSDF { ) -> BSDF {
let mut u_rough = tex_eval.evaluate_float(&self.u_roughness, ctx); todo!()
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);
} }
fn get_bssrdf<T>(
let distrib = TrowbridgeReitzDistribution::new(u_rough, v_rough);
let bxdf = BxDF::Dielectric(DielectricBxDF::new(self.eta, distrib));
BSDF::new(ctx.ns, ctx.dpdus, bxdf)
}
fn get_bssrdf<T: TextureEvaluator>(
&self, &self,
tex_eval: &T, _tex_eval: &T,
ctx: &MaterialEvalContext, _ctx: &MaterialEvalContext,
lambda: &SampledWavelengths, _lambda: &SampledWavelengths,
) -> Option<BSSRDF> { ) -> Option<BSSRDF> {
let (sig_a, sig_s) = match self.scattering { todo!()
SubsurfaceScattering::Coefficients { sigma_a, sigma_s } => {
let s_a = SampledSpectrum::clamp_zero(
&(self.scale * tex_eval.evaluate_spectrum(&sigma_a, ctx, lambda)),
);
let s_s = SampledSpectrum::clamp_zero(
&(self.scale * tex_eval.evaluate_spectrum(&sigma_s, ctx, lambda)),
);
(s_a, s_s)
}
SubsurfaceScattering::Reflectance { reflectance, mfp } => {
debug_assert!(!reflectance.is_null() && !mfp.is_null());
let mfree =
SampledSpectrum::clamp_zero(&tex_eval.evaluate_spectrum(&mfp, ctx, lambda));
let r = SampledSpectrum::clamp_zero(&tex_eval.evaluate_spectrum(
&reflectance,
ctx,
lambda,
));
subsurface_from_diffuse(&self.table, &r, &mfree)
}
};
Some(BSSRDF::Tabulated(TabulatedBSSRDF::new(
ctx.p,
ctx.wo,
ctx.ns,
self.eta,
&sig_a,
&sig_s,
&self.table,
)))
} }
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool { fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
// Slight divergence from PBRT, we check against reflectance and mfp as well in reflectance todo!()
// mode. Test thoroughly, keep as is for now (20260902)
let spectra = match self.scattering {
SubsurfaceScattering::Coefficients { sigma_a, sigma_s } => [sigma_a, sigma_s],
SubsurfaceScattering::Reflectance { reflectance, mfp } => [reflectance, mfp],
};
tex_eval.can_evaluate(&[self.u_roughness, self.v_roughness], &spectra)
} }
fn get_normal_map(&self) -> Option<&Image> { fn get_normal_map(&self) -> Option<&Image> {
Some(&*self.normal_map) todo!()
} }
fn get_displacement(&self) -> Ptr<FloatTexture> { fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement todo!()
} }
fn has_subsurface_scattering(&self) -> bool { fn has_subsurface_scattering(&self) -> bool {

View file

@ -8,7 +8,7 @@ use crate::core::image::Image;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait}; use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{FloatTexture, SpectrumTexture, TextureEvaluator}; use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator};
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::utils::math::clamp; use crate::utils::math::clamp;
@ -16,84 +16,32 @@ use crate::utils::math::clamp;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct ConductorMaterial { pub struct ConductorMaterial {
pub normal_map: Ptr<Image>, pub displacement: Ptr<GPUFloatTexture>,
pub reflectance: Ptr<SpectrumTexture>, pub eta: Ptr<GPUSpectrumTexture>,
pub eta: Ptr<SpectrumTexture>, pub k: Ptr<GPUSpectrumTexture>,
pub k: Ptr<SpectrumTexture>, pub reflectance: Ptr<GPUSpectrumTexture>,
pub u_roughness: Ptr<FloatTexture>, pub u_roughness: Ptr<GPUFloatTexture>,
pub v_roughness: Ptr<FloatTexture>, pub v_roughness: Ptr<GPUFloatTexture>,
pub displacement: Ptr<FloatTexture>,
pub remap_roughness: bool, pub remap_roughness: bool,
} pub normal_map: Ptr<Image>,
impl ConductorMaterial {
#[allow(clippy::too_many_arguments)]
pub fn new(
normal_map: Ptr<Image>,
reflectance: Ptr<SpectrumTexture>,
eta: Ptr<SpectrumTexture>,
k: Ptr<SpectrumTexture>,
u_roughness: Ptr<FloatTexture>,
v_roughness: Ptr<FloatTexture>,
displacement: Ptr<FloatTexture>,
remap_roughness: bool,
) -> Self {
Self {
normal_map,
reflectance,
eta,
k,
u_roughness,
v_roughness,
displacement,
remap_roughness,
}
}
} }
impl MaterialTrait for ConductorMaterial { impl MaterialTrait for ConductorMaterial {
fn get_bsdf<T: TextureEvaluator>( fn get_bsdf<T: TextureEvaluator>(
&self, &self,
tex_eval: &T, _tex_eval: &T,
ctx: &MaterialEvalContext, _ctx: &MaterialEvalContext,
lambda: &mut SampledWavelengths, _lambda: &SampledWavelengths,
) -> BSDF { ) -> BSDF {
let mut u_rough = tex_eval.evaluate_float(&self.u_roughness, ctx); todo!()
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 (etas, ks) = if !self.eta.is_null() {
(
tex_eval.evaluate_spectrum(&self.eta, ctx, lambda),
tex_eval.evaluate_spectrum(&self.k, ctx, lambda),
)
} else {
let r = SampledSpectrum::clamp(
&tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda),
0.,
0.9999,
);
let one_minus_r = SampledSpectrum::new(1.) - r;
(
SampledSpectrum::new(1.),
2. * r.sqrt() / SampledSpectrum::clamp_zero(&one_minus_r).sqrt(),
)
};
let distrib = TrowbridgeReitzDistribution::new(u_rough, v_rough);
let bxdf = BxDF::Conductor(ConductorBxDF::new(&distrib, etas, ks));
BSDF::new(ctx.ns, ctx.dpdus, bxdf)
}
fn get_bssrdf<T>( fn get_bssrdf<T>(
&self, &self,
_tex_eval: &T, _tex_eval: &T,
_ctx: &MaterialEvalContext, _ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths, _lambda: &SampledWavelengths,
) -> Option<BSSRDF> { ) -> Option<BSSRDF> {
None todo!()
} }
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool { fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate( tex_eval.can_evaluate(
@ -103,14 +51,14 @@ impl MaterialTrait for ConductorMaterial {
} }
fn get_normal_map(&self) -> Option<&Image> { fn get_normal_map(&self) -> Option<&Image> {
self.normal_map.get() todo!()
} }
fn get_displacement(&self) -> Ptr<FloatTexture> { fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement todo!()
} }
fn has_subsurface_scattering(&self) -> bool { fn has_subsurface_scattering(&self) -> bool {
false todo!()
} }
} }

View file

@ -1,7 +1,5 @@
use crate::Ptr;
use crate::bxdfs::{ use crate::bxdfs::{
CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF, HairBxDF, CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF, HairBxDF,
ThinDielectricBxDF,
}; };
use crate::core::bsdf::BSDF; use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF; use crate::core::bssrdf::BSSRDF;
@ -10,19 +8,20 @@ use crate::core::image::Image;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait}; use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{FloatTexture, SpectrumTexture, TextureEvaluator}; use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator};
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::math::clamp; use crate::utils::math::clamp;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct DielectricMaterial { pub struct DielectricMaterial {
pub normal_map: Ptr<Image>, normal_map: Ptr<Image>,
pub displacement: Ptr<FloatTexture>, displacement: Ptr<GPUFloatTexture>,
pub u_roughness: Ptr<FloatTexture>, u_roughness: Ptr<GPUFloatTexture>,
pub v_roughness: Ptr<FloatTexture>, v_roughness: Ptr<GPUFloatTexture>,
pub eta: Ptr<Spectrum>, eta: Ptr<Spectrum>,
pub remap_roughness: bool, remap_roughness: bool,
} }
impl MaterialTrait for DielectricMaterial { impl MaterialTrait for DielectricMaterial {
@ -30,11 +29,11 @@ impl MaterialTrait for DielectricMaterial {
&self, &self,
tex_eval: &T, tex_eval: &T,
ctx: &MaterialEvalContext, ctx: &MaterialEvalContext,
lambda: &mut SampledWavelengths, lambda: &SampledWavelengths,
) -> BSDF { ) -> BSDF {
let mut sampled_eta = self.eta.evaluate(lambda[0]); let mut sampled_eta = self.eta.evaluate(lambda[0]);
if !self.eta.is_constant() { if !self.eta.is_constant() {
lambda.terminate_secondary_inplace(); lambda.terminate_secondary();
} }
if sampled_eta == 0.0 { if sampled_eta == 0.0 {
@ -52,7 +51,7 @@ impl MaterialTrait for DielectricMaterial {
let distrib = TrowbridgeReitzDistribution::new(u_rough, v_rough); let distrib = TrowbridgeReitzDistribution::new(u_rough, v_rough);
let bxdf = BxDF::Dielectric(DielectricBxDF::new(sampled_eta, distrib)); let bxdf = BxDF::Dielectric(DielectricBxDF::new(sampled_eta, distrib));
BSDF::new(ctx.ns, ctx.dpdus, bxdf) BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf))
} }
fn get_bssrdf<T>( fn get_bssrdf<T>(
@ -72,7 +71,7 @@ impl MaterialTrait for DielectricMaterial {
Some(&*self.normal_map) Some(&*self.normal_map)
} }
fn get_displacement(&self) -> Ptr<FloatTexture> { fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement self.displacement
} }
@ -84,38 +83,26 @@ impl MaterialTrait for DielectricMaterial {
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct ThinDielectricMaterial { pub struct ThinDielectricMaterial {
pub displacement: Ptr<FloatTexture>, pub displacement: Ptr<GPUFloatTexture>,
pub normal_map: Ptr<Image>, pub normal_map: Ptr<Image>,
pub eta: Ptr<Spectrum>, pub eta: Ptr<Spectrum>,
} }
impl MaterialTrait for ThinDielectricMaterial { impl MaterialTrait for ThinDielectricMaterial {
fn get_bsdf<T: TextureEvaluator>( fn get_bsdf<T: TextureEvaluator>(
&self, &self,
_tex_eval: &T, _tex_eval: &T,
ctx: &MaterialEvalContext, _ctx: &MaterialEvalContext,
lambda: &mut SampledWavelengths, _lambda: &SampledWavelengths,
) -> BSDF { ) -> BSDF {
let mut sampled_eta = self.eta.evaluate(lambda[0]); todo!()
if !self.eta.is_constant() {
lambda.terminate_secondary_inplace();
} }
if sampled_eta == 0. {
sampled_eta = 1.;
}
let bxdf = BxDF::ThinDielectric(ThinDielectricBxDF::new(sampled_eta));
BSDF::new(ctx.ns, ctx.dpdus, bxdf)
}
fn get_bssrdf<T>( fn get_bssrdf<T>(
&self, &self,
_tex_eval: &T, _tex_eval: &T,
_ctx: &MaterialEvalContext, _ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths, _lambda: &SampledWavelengths,
) -> Option<BSSRDF> { ) -> Option<BSSRDF> {
None todo!()
} }
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool { fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
@ -126,7 +113,7 @@ impl MaterialTrait for ThinDielectricMaterial {
Some(&*self.normal_map) Some(&*self.normal_map)
} }
fn get_displacement(&self) -> Ptr<FloatTexture> { fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement self.displacement
} }

View file

@ -1,8 +1,6 @@
use crate::Float; use crate::Float;
use crate::Ptr;
use crate::bxdfs::{ use crate::bxdfs::{
CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF, CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF, HairBxDF,
DiffuseTransmissionBxDF, HairBxDF,
}; };
use crate::core::bsdf::BSDF; use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF; use crate::core::bssrdf::BSSRDF;
@ -11,16 +9,17 @@ use crate::core::image::Image;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait}; use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{FloatTexture, SpectrumTexture, TextureEvaluator}; use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator};
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::math::clamp; use crate::utils::math::clamp;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct DiffuseMaterial { pub struct DiffuseMaterial {
pub normal_map: Ptr<Image>, pub normal_map: Ptr<Image>,
pub displacement: Ptr<FloatTexture>, pub displacement: Ptr<GPUFloatTexture>,
pub reflectance: Ptr<SpectrumTexture>, pub reflectance: Ptr<GPUSpectrumTexture>,
} }
impl MaterialTrait for DiffuseMaterial { impl MaterialTrait for DiffuseMaterial {
@ -28,12 +27,11 @@ impl MaterialTrait for DiffuseMaterial {
&self, &self,
tex_eval: &T, tex_eval: &T,
ctx: &MaterialEvalContext, ctx: &MaterialEvalContext,
lambda: &mut SampledWavelengths, lambda: &SampledWavelengths,
) -> BSDF { ) -> BSDF {
let spec = tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda); let r = tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda);
let r = SampledSpectrum::clamp(&spec, 0., 1.);
let bxdf = BxDF::Diffuse(DiffuseBxDF::new(r)); let bxdf = BxDF::Diffuse(DiffuseBxDF::new(r));
BSDF::new(ctx.ns, ctx.dpdus, bxdf) BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf))
} }
fn get_bssrdf<T>( fn get_bssrdf<T>(
@ -42,7 +40,7 @@ impl MaterialTrait for DiffuseMaterial {
_ctx: &MaterialEvalContext, _ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths, _lambda: &SampledWavelengths,
) -> Option<BSSRDF> { ) -> Option<BSSRDF> {
None todo!()
} }
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool { fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
@ -50,10 +48,10 @@ impl MaterialTrait for DiffuseMaterial {
} }
fn get_normal_map(&self) -> Option<&Image> { fn get_normal_map(&self) -> Option<&Image> {
self.normal_map.get() Some(&*self.normal_map)
} }
fn get_displacement(&self) -> Ptr<FloatTexture> { fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement self.displacement
} }
@ -65,33 +63,21 @@ impl MaterialTrait for DiffuseMaterial {
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct DiffuseTransmissionMaterial { pub struct DiffuseTransmissionMaterial {
pub normal_map: Ptr<Image>, pub image: Ptr<Image>,
pub displacement: Ptr<FloatTexture>, pub displacement: Ptr<GPUFloatTexture>,
pub reflectance: Ptr<SpectrumTexture>, pub reflectance: Ptr<GPUFloatTexture>,
pub transmittance: Ptr<SpectrumTexture>, pub transmittance: Ptr<GPUFloatTexture>,
pub scale: Float, pub scale: Float,
} }
impl MaterialTrait for DiffuseTransmissionMaterial { impl MaterialTrait for DiffuseTransmissionMaterial {
fn get_bsdf<T: TextureEvaluator>( fn get_bsdf<T: TextureEvaluator>(
&self, &self,
tex_eval: &T, _tex_eval: &T,
ctx: &MaterialEvalContext, _ctx: &MaterialEvalContext,
lambda: &mut SampledWavelengths, _lambda: &SampledWavelengths,
) -> BSDF { ) -> BSDF {
let r = SampledSpectrum::clamp( todo!()
&(self.scale * tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda)),
0.,
1.,
);
let t = SampledSpectrum::clamp(
&(self.scale * tex_eval.evaluate_spectrum(&self.transmittance, ctx, lambda)),
0.,
1.,
);
let bxdf = BxDF::DiffuseTransmission(DiffuseTransmissionBxDF::new(r, t));
BSDF::new(ctx.ns, ctx.dpdus, bxdf)
} }
fn get_bssrdf<T>( fn get_bssrdf<T>(
&self, &self,
@ -99,18 +85,18 @@ impl MaterialTrait for DiffuseTransmissionMaterial {
_ctx: &MaterialEvalContext, _ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths, _lambda: &SampledWavelengths,
) -> Option<BSSRDF> { ) -> Option<BSSRDF> {
None todo!()
} }
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool { fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(&[], &[self.reflectance, self.transmittance]) tex_eval.can_evaluate(&[self.reflectance, self.transmittance], &[])
} }
fn get_normal_map(&self) -> Option<&Image> { fn get_normal_map(&self) -> Option<&Image> {
self.normal_map.get() Some(&*self.image)
} }
fn get_displacement(&self) -> Ptr<FloatTexture> { fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement self.displacement
} }

View file

@ -8,17 +8,17 @@ use crate::core::image::Image;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait}; use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{FloatTexture, SpectrumTexture, TextureEvaluator}; use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator};
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::hash::hash_float; use crate::utils::hash::hash_float;
use crate::utils::math::clamp; use crate::utils::math::clamp;
use crate::utils::{ArenaPtr, Ptr};
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct MixMaterial { pub struct MixMaterial {
pub amount: Ptr<FloatTexture>, pub amount: Ptr<GPUFloatTexture>,
pub materials: [Ptr<Material>; 2], pub materials: [ArenaPtr<Material>; 2],
} }
impl MixMaterial { impl MixMaterial {
@ -47,7 +47,7 @@ impl MaterialTrait for MixMaterial {
&self, &self,
tex_eval: &T, tex_eval: &T,
ctx: &MaterialEvalContext, ctx: &MaterialEvalContext,
lambda: &mut SampledWavelengths, lambda: &SampledWavelengths,
) -> BSDF { ) -> BSDF {
if let Some(mat) = self.choose_material(tex_eval, ctx) { if let Some(mat) = self.choose_material(tex_eval, ctx) {
mat.get_bsdf(tex_eval, ctx, lambda) mat.get_bsdf(tex_eval, ctx, lambda)
@ -73,7 +73,7 @@ impl MaterialTrait for MixMaterial {
None None
} }
fn get_displacement(&self) -> Ptr<FloatTexture> { fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
panic!( panic!(
"MixMaterial::get_displacement() shouldn't be called. \ "MixMaterial::get_displacement() shouldn't be called. \
Displacement is not supported on Mix materials directly." Displacement is not supported on Mix materials directly."

View file

@ -1,16 +1,16 @@
use crate::core::geometry::{ use crate::core::geometry::{
spherical_quad_area, Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Tuple, Vector3f,
Ray, Tuple, Vector3f, VectorLike, VectorLike, spherical_quad_area,
}; };
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::pbrt::{gamma, Float}; use crate::core::pbrt::{Float, gamma};
use crate::core::shape::{Shape, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait}; use crate::core::shape::{Shape, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait};
use crate::shapes::mesh::BilinearPatchMesh; use crate::utils::Transform;
use crate::utils::math::{clamp, difference_of_products, lerp, quadratic, SquareMatrix}; use crate::utils::math::{SquareMatrix, clamp, difference_of_products, lerp, quadratic};
use crate::utils::mesh::BilinearPatchMesh;
use crate::utils::sampling::{ use crate::utils::sampling::{
bilinear_pdf, invert_spherical_rectangle_sample, sample_bilinear, sample_spherical_rectangle, bilinear_pdf, invert_spherical_rectangle_sample, sample_bilinear, sample_spherical_rectangle,
}; };
use crate::{GVec, Ptr, Transform};
use core::ops::Add; use core::ops::Add;
#[repr(C)] #[repr(C)]
@ -46,22 +46,26 @@ impl BilinearIntersection {
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct BilinearPatchShape { pub struct BilinearPatchShape {
pub mesh: Ptr<BilinearPatchMesh>, pub mesh: BilinearPatchMesh,
pub blp_index: i32, pub blp_index: u32,
pub area: Float, pub area: Float,
pub rectangle: bool, pub rectangle: bool,
} }
impl BilinearPatchShape { impl BilinearPatchShape {
pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 1e-4; pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 1e-4;
fn mesh(&self) -> Ptr<BilinearPatchMesh> { fn mesh(&self) -> BilinearPatchMesh {
self.mesh self.mesh
} }
#[inline(always)] #[inline(always)]
fn get_vertex_indices(&self) -> [usize; 4] { fn get_vertex_indices(&self) -> [usize; 4] {
unsafe { unsafe {
let base_ptr = self.mesh.vertex_indices.as_ptr().add((self.blp_index as usize) * 4); let base_ptr = self
.mesh
.vertex_indices
.0
.add((self.blp_index as usize) * 4);
[ [
*base_ptr.add(0) as usize, *base_ptr.add(0) as usize,
*base_ptr.add(1) as usize, *base_ptr.add(1) as usize,
@ -73,33 +77,51 @@ impl BilinearPatchShape {
#[inline(always)] #[inline(always)]
fn get_points(&self) -> [Point3f; 4] { fn get_points(&self) -> [Point3f; 4] {
let mesh = self.mesh();
let [v0, v1, v2, v3] = self.get_vertex_indices(); let [v0, v1, v2, v3] = self.get_vertex_indices();
[mesh.p[v0], mesh.p[v1], mesh.p[v2], mesh.p[v3]] unsafe {
[
*self.mesh.p.0.add(v0),
*self.mesh.p.0.add(v1),
*self.mesh.p.0.add(v2),
*self.mesh.p.0.add(v3),
]
}
} }
#[inline(always)] #[inline(always)]
fn get_uvs(&self) -> Option<[Point2f; 4]> { fn get_uvs(&self) -> Option<[Point2f; 4]> {
let mesh = self.mesh(); if self.mesh.uv.is_null() {
if mesh.uv.is_empty() {
return None; return None;
} }
let [v0, v1, v2, v3] = self.get_vertex_indices(); let [v0, v1, v2, v3] = self.get_vertex_indices();
Some([mesh.uv[v0], mesh.uv[v1], mesh.uv[v2], mesh.uv[v3]]) unsafe {
Some([
*self.mesh.uv.0.add(v0),
*self.mesh.uv.0.add(v1),
*self.mesh.uv.0.add(v2),
*self.mesh.uv.0.add(v3),
])
}
} }
#[inline(always)] #[inline(always)]
fn get_shading_normals(&self) -> Option<[Normal3f; 4]> { fn get_shading_normals(&self) -> Option<[Normal3f; 4]> {
let mesh = self.mesh(); if self.mesh.n.is_null() {
if mesh.n.is_empty() {
return None; return None;
} }
let [v0, v1, v2, v3] = self.get_vertex_indices(); let [v0, v1, v2, v3] = self.get_vertex_indices();
Some([mesh.n[v0], mesh.n[v1], mesh.n[v2], mesh.n[v3]]) unsafe {
Some([
*self.mesh.n.0.add(v0),
*self.mesh.n.0.add(v1),
*self.mesh.n.0.add(v2),
*self.mesh.n.0.add(v3),
])
}
} }
#[cfg(not(gpu))] #[cfg(not(target_os = "cuda"))]
pub fn new(mesh: Ptr<BilinearPatchMesh>, blp_index: i32) -> Self { pub fn new(mesh: BilinearPatchMesh, blp_index: u32) -> Self {
let mut bp = BilinearPatchShape { let mut bp = BilinearPatchShape {
mesh, mesh,
blp_index, blp_index,
@ -393,7 +415,7 @@ impl BilinearPatchShape {
let Some(normals) = shading_normals else { let Some(normals) = shading_normals else {
return; return;
}; };
let n00 = normals[0]; let n00 = normals[1];
let n10 = normals[1]; let n10 = normals[1];
let n01 = normals[2]; let n01 = normals[2];
let n11 = normals[3]; let n11 = normals[3];
@ -437,11 +459,7 @@ impl BilinearPatchShape {
ss.pdf *= dist_sq / abs_dot; ss.pdf *= dist_sq / abs_dot;
if ss.pdf.is_infinite() { if ss.pdf.is_infinite() { None } else { Some(ss) }
None
} else {
Some(ss)
}
} }
fn sample_parametric_coords(&self, corners: &[Point3f; 4], u: Point2f) -> (Point2f, Float) { fn sample_parametric_coords(&self, corners: &[Point3f; 4], u: Point2f) -> (Point2f, Float) {
@ -708,11 +726,7 @@ impl ShapeTrait for BilinearPatchShape {
let (_, dpdu, dpdv) = self.calculate_base_derivatives(&corners, uv); let (_, dpdu, dpdv) = self.calculate_base_derivatives(&corners, uv);
let cross = dpdu.cross(dpdv).norm(); let cross = dpdu.cross(dpdv).norm();
if cross == 0. { if cross == 0. { 0. } else { param_pdf / cross }
0.
} else {
param_pdf / cross
}
} }
#[inline] #[inline]
@ -743,11 +757,7 @@ impl ShapeTrait for BilinearPatchShape {
return 0.; return 0.;
} }
let pdf = isect_pdf * distsq / absdot; let pdf = isect_pdf * distsq / absdot;
if pdf.is_infinite() { if pdf.is_infinite() { 0. } else { pdf }
0.
} else {
pdf
}
} else { } else {
let mut pdf = 1. / spherical_quad_area(v00, v10, v01, v11); let mut pdf = 1. / spherical_quad_area(v00, v10, v01, v11);
if ctx.ns != Normal3f::zero() { if ctx.ns != Normal3f::zero() {

View file

@ -5,13 +5,11 @@ use crate::core::geometry::{
}; };
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::shape::{ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait}; use crate::core::shape::{ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait};
use crate::utils::gpu_array_from_fn;
use crate::utils::math::{clamp, lerp, square}; use crate::utils::math::{clamp, lerp, square};
use crate::utils::splines::{ use crate::utils::splines::{
bound_cubic_bezier, cubic_bezier_control_points, evaluate_cubic_bezier, subdivide_cubic_bezier, bound_cubic_bezier, cubic_bezier_control_points, evaluate_cubic_bezier, subdivide_cubic_bezier,
}; };
use crate::utils::transform::{Transform, look_at}; use crate::utils::transform::{Transform, look_at};
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
@ -53,7 +51,7 @@ impl CurveCommon {
assert_eq!(c.len(), 4); assert_eq!(c.len(), 4);
let cp_obj: [Point3f; 4] = c[..4].try_into().unwrap(); let cp_obj: [Point3f; 4] = c[..4].try_into().unwrap();
let mut n: [Normal3f; 2] = gpu_array_from_fn(|_| Normal3f::default()); let mut n = [Normal3f::default(); 2];
let mut normal_angle: Float = 0.; let mut normal_angle: Float = 0.;
let mut inv_sin_normal_angle: Float = 0.; let mut inv_sin_normal_angle: Float = 0.;
if norm.len() == 2 { if norm.len() == 2 {
@ -116,7 +114,7 @@ impl CurveShape {
} }
let ray_from_object = look_at(ray.o, ray.o + ray.d, dx).expect("Inversion error"); let ray_from_object = look_at(ray.o, ray.o + ray.d, dx).expect("Inversion error");
let cp: [Point3f; 4] = gpu_array_from_fn(|i| ray_from_object.apply_to_point(cp_obj[i])); let cp = [0; 4].map(|i| ray_from_object.apply_to_point(cp_obj[i]));
let max_width = lerp(self.u_min, self.common.width[0], self.common.width[1]).max(lerp( let max_width = lerp(self.u_min, self.common.width[0], self.common.width[1]).max(lerp(
self.u_max, self.u_max,

View file

@ -9,14 +9,13 @@ use crate::core::shape::{
use crate::utils::splines::{ use crate::utils::splines::{
bound_cubic_bezier, cubic_bezier_control_points, evaluate_cubic_bezier, subdivide_cubic_bezier, bound_cubic_bezier, cubic_bezier_control_points, evaluate_cubic_bezier, subdivide_cubic_bezier,
}; };
use crate::utils::transform::{look_at, Transform}; use crate::utils::transform::{Transform, look_at};
use crate::{gamma, Float, PI}; use crate::{Float, PI, gamma};
use crate::core::geometry::{SqrtExt, Tuple}; use crate::core::geometry::{Sqrt, Tuple};
use crate::utils::interval::Interval; use crate::utils::interval::Interval;
use crate::utils::math::{clamp, difference_of_products, lerp, radians, square}; use crate::utils::math::{clamp, difference_of_products, lerp, square};
use core::mem; use std::mem;
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -43,11 +42,9 @@ impl CylinderShape {
) -> Self { ) -> Self {
Self { Self {
radius, radius,
// pbrt: `zMin(std::min(zMin, zMax)), zMax(std::max(zMin, zMax)), z_min,
// phiMax(Radians(Clamp(phiMax, 0, 360)))`. phiMax arrives in DEGREES. z_max,
z_min: z_min.min(z_max), phi_max,
z_max: z_min.max(z_max),
phi_max: radians(clamp(phi_max, 0., 360.)),
render_from_object, render_from_object,
object_from_render, object_from_render,
reverse_orientation, reverse_orientation,
@ -63,20 +60,21 @@ impl CylinderShape {
let di = self let di = self
.object_from_render .object_from_render
.apply_to_vector_interval(&Vector3fi::new_from_vector(r.d)); .apply_to_vector_interval(&Vector3fi::new_from_vector(r.d));
// Solve quadratic equation to find cylinder t0 and t1 values // Solve quadratic equation to find cylinder t0 and t1 values>>
let a: Interval = square(di.x()) + square(di.y()); let a: Interval = square(di.x()) + square(di.y()) + square(di.z());
let b: Interval = 2. * (di.x() * oi.x() + di.y() * oi.y()); let b: Interval = 2. * (di.x() * oi.x() + di.y() * oi.y() + di.z() * oi.z());
let c: Interval = square(oi.x()) + square(oi.y()) - square(Interval::new(self.radius)); let c: Interval =
square(oi.x()) + square(oi.y()) + square(oi.z()) - square(Interval::new(self.radius));
let f = b / (2. * a); let f = b / (2. * a);
let vx: Interval = oi.x() - f * di.x(); let vx: Interval = oi.x() - f * di.x();
let vy: Interval = oi.y() - f * di.y(); let vy: Interval = oi.y() - f * di.y();
let length: Interval = (square(vx) + square(vy)).sqrt_ext(); let length: Interval = (square(vx) + square(vy)).sqrt();
let discrim: Interval = let discrim: Interval =
4. * a * (Interval::new(self.radius) * length) * (Interval::new(self.radius) - length); 4. * a * (Interval::new(self.radius) * length) * (Interval::new(self.radius) - length);
if discrim.low < 0. { if discrim.low < 0. {
return None; return None;
} }
let root_discrim = discrim.sqrt_ext(); let root_discrim = discrim.sqrt();
let q = if Float::from(b) < 0. { let q = if Float::from(b) < 0. {
-0.5 * (b - root_discrim) -0.5 * (b - root_discrim)
} else { } else {

View file

@ -6,12 +6,11 @@ use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction
use crate::core::shape::{ use crate::core::shape::{
QuadricIntersection, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait, QuadricIntersection, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait,
}; };
use crate::utils::interval::Interval;
use crate::utils::math::{clamp, radians, square};
use crate::utils::sampling::sample_uniform_disk_concentric;
use crate::utils::Transform; use crate::utils::Transform;
use crate::utils::math::square;
use crate::utils::sampling::sample_uniform_disk_concentric;
use crate::{Float, PI}; use crate::{Float, PI};
use num_traits::Float as NumFloat; use std::sync::Arc;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -40,11 +39,7 @@ impl DiskShape {
radius, radius,
inner_radius, inner_radius,
height, height,
// pbrt: `phiMax(Radians(Clamp(phiMax, 0, 360)))`. The parameter arrives in phi_max,
// DEGREES (default 360); storing it raw made `area()` 360/2pi = 57.3x too
// large, so the sampling pdf was 57.3x too small and every direct-lighting
// contribution from a disk area light was 57.3x too bright.
phi_max: radians(clamp(phi_max, 0., 360.)),
render_from_object: render_from_object.clone(), render_from_object: render_from_object.clone(),
object_from_render, object_from_render,
reverse_orientation, reverse_orientation,
@ -53,38 +48,25 @@ impl DiskShape {
} }
fn basic_intersect(&self, r: &Ray, t_max: Float) -> Option<QuadricIntersection> { fn basic_intersect(&self, r: &Ray, t_max: Float) -> Option<QuadricIntersection> {
let oi = self let oi = self.object_from_render.apply_to_point(r.o);
.object_from_render let di = self.object_from_render.apply_to_vector(r.d);
.apply_to_interval(&Point3fi::new_from_point(r.o)); // Reject disk intersections for rays parallel to the disks plane
let di = self if di.z() == 0. {
.object_from_render
.apply_to_vector_interval(&Vector3fi::new_from_vector(r.d));
if Float::from(di.z()) == 0. {
return None;
}
// pbrt computes the plane hit as a SCALAR:
// Float tShapeHit = (height - Float(oi.z)) / Float(di.z);
// if (tShapeHit <= 0 || tShapeHit >= tMax) return {};
// Interval arithmetic here produced NaN, and neither `high <= 0` nor
// `low >= t_max` rejects a NaN (every NaN comparison is false). The NaN then
// flowed into p_hit -- so the `dist2 > radius^2` test could not reject it either,
// and the disk swallowed every ray it was tested against -- and on into dpdu/dpdv
// and the shading normal, poisoning the area-light MIS pdf.
let t_shape_hit: Float = (self.height - Float::from(oi.z())) / Float::from(di.z());
if t_shape_hit <= 0. || t_shape_hit >= t_max {
return None; return None;
} }
let oi_f = Point3f::from(oi); let t_shape_hit = (self.height - oi.z()) / di.z();
let di_f = Vector3f::from(di); if t_shape_hit == 0. || t_shape_hit >= t_max {
let t = t_shape_hit; return None;
let p_hit: Point3f = oi_f + di_f * t; }
// See if hit point is inside disk radii and phi_max
let p_hit: Point3f = oi + t_shape_hit * di;
let dist2 = square(p_hit.x()) + square(p_hit.y()); let dist2 = square(p_hit.x()) + square(p_hit.y());
if dist2 > square(self.radius) || dist2 < square(self.inner_radius) { if dist2 > square(self.radius) || dist2 < square(self.inner_radius) {
return None; return None;
} }
let mut phi = p_hit.y().atan2(p_hit.x()); let mut phi = p_hit.y().atan2(p_hit.x());
if phi < 0. { if phi < 0. {
phi += 2. * PI; phi += 2. * PI;
@ -94,7 +76,7 @@ impl DiskShape {
} }
Some(QuadricIntersection { Some(QuadricIntersection {
t_hit: t, t_hit: t_shape_hit,
p_obj: p_hit, p_obj: p_hit,
phi, phi,
}) })
@ -123,7 +105,7 @@ impl DiskShape {
let p_error = Vector3f::zero(); let p_error = Vector3f::zero();
let flip_normal = self.reverse_orientation ^ self.transform_swap_handedness; let flip_normal = self.reverse_orientation ^ self.transform_swap_handedness;
let wo_object = self.object_from_render.apply_to_vector(wo); let wo_object = self.object_from_render.apply_to_vector(wo);
let intr = SurfaceInteraction::new( SurfaceInteraction::new(
Point3fi::new_with_error(p_hit, p_error), Point3fi::new_with_error(p_hit, p_error),
Point2f::new(u, v), Point2f::new(u, v),
wo_object, wo_object,
@ -133,15 +115,7 @@ impl DiskShape {
dndv, dndv,
time, time,
flip_normal, flip_normal,
); )
match self
.render_from_object
.apply_to_interaction(&Interaction::Surface(intr))
{
Interaction::Surface(si) => si,
_ => unreachable!("Only surfaces need apply"),
}
} }
} }
@ -223,7 +197,7 @@ impl ShapeTrait for DiskShape {
} }
wi = wi.normalize(); wi = wi.normalize();
ss.pdf /= Vector3f::from(ss.intr.n()).abs_dot(-wi) / ctx.p().distance_squared(ss.intr.p()); ss.pdf = Vector3f::from(ss.intr.n()).dot(-wi).abs() / ctx.p().distance_squared(ss.intr.p());
if ss.pdf.is_infinite() { if ss.pdf.is_infinite() {
return None; return None;
} }

View file

@ -1,155 +0,0 @@
use crate::core::geometry::{Normal3f, Point2f, Point3f, Vector3f};
use crate::utils::sampling::PiecewiseConstant2D;
use crate::{gvec_from_slice, gvec_with_capacity, Float, GVec, Ptr, Transform};
#[repr(C)]
#[derive(Debug, Clone)]
pub struct TriangleMesh {
pub p: GVec<Point3f>,
pub n: GVec<Normal3f>,
pub s: GVec<Vector3f>,
pub uv: GVec<Point2f>,
pub vertex_indices: GVec<i32>,
pub face_indices: GVec<i32>,
pub n_triangles: u32,
pub n_vertices: u32,
pub reverse_orientation: bool,
pub transform_swaps_handedness: bool,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct BilinearPatchMesh {
pub p: GVec<Point3f>,
pub n: GVec<Normal3f>,
pub uv: GVec<Point2f>,
pub vertex_indices: GVec<i32>,
pub n_patches: u32,
pub n_vertices: u32,
pub reverse_orientation: bool,
pub transform_swaps_handedness: bool,
pub image_distribution: Ptr<PiecewiseConstant2D>,
}
impl TriangleMesh {
pub fn new(
render_from_object: &Transform,
reverse_orientation: bool,
vertex_indices: &[i32],
p: &[Point3f],
n: &[Normal3f],
s: &[Vector3f],
uv: &[Point2f],
face_indices: &[i32],
) -> Self {
let n_triangles = (vertex_indices.len() / 3) as u32;
let n_vertices = p.len() as u32;
let mut p_gvec = gvec_with_capacity(p.len());
for pt in p {
p_gvec.push(render_from_object.apply_to_point(*pt));
}
let mut n_gvec = gvec_with_capacity(n.len());
if !n.is_empty() {
assert_eq!(n_vertices as usize, n.len(), "Normal count mismatch");
for nn in n {
let mut transformed = render_from_object.apply_to_normal(*nn);
if reverse_orientation {
transformed = -transformed;
}
n_gvec.push(transformed);
}
}
let mut s_gvec = gvec_with_capacity(s.len());
if !s.is_empty() {
assert_eq!(n_vertices as usize, s.len(), "Tangent count mismatch");
for ss in s {
s_gvec.push(render_from_object.apply_to_vector(*ss));
}
}
assert!(
uv.is_empty() || uv.len() == n_vertices as usize,
"UV count mismatch"
);
assert!(
face_indices.is_empty() || face_indices.len() == n_triangles as usize,
"Face index count mismatch"
);
Self {
vertex_indices: gvec_from_slice(vertex_indices),
p: p_gvec,
n: n_gvec,
s: s_gvec,
uv: gvec_from_slice(uv),
face_indices: gvec_from_slice(face_indices),
n_triangles,
n_vertices,
reverse_orientation,
transform_swaps_handedness: render_from_object.swaps_handedness(),
}
}
pub fn positions(&self) -> &[Point3f] {
&self.p
}
pub fn indices(&self) -> &[i32] {
&self.vertex_indices
}
pub fn normals(&self) -> &[Normal3f] {
&self.n
}
pub fn uvs(&self) -> &[Point2f] {
&self.uv
}
}
impl BilinearPatchMesh {
pub fn new(
render_from_object: &Transform,
reverse_orientation: bool,
vertex_indices: &[i32],
p: &[Point3f],
n: &[Normal3f],
uv: &[Point2f],
image_distribution: Option<&PiecewiseConstant2D>,
) -> Self {
let n_patches = (vertex_indices.len() / 4) as u32;
let n_vertices = p.len() as u32;
let mut p_gvec = gvec_with_capacity(p.len());
for pt in p {
p_gvec.push(render_from_object.apply_to_point(*pt));
}
let mut n_gvec = gvec_with_capacity(n.len());
if !n.is_empty() {
assert_eq!(n_vertices as usize, n.len());
for nn in n {
let mut transformed = render_from_object.apply_to_normal(*nn);
if reverse_orientation {
transformed = -transformed;
}
n_gvec.push(transformed);
}
}
assert!(uv.is_empty() || uv.len() == n_vertices as usize);
Self {
vertex_indices: gvec_from_slice(vertex_indices),
p: p_gvec,
n: n_gvec,
uv: gvec_from_slice(uv),
image_distribution: Ptr::from(image_distribution),
n_patches,
n_vertices,
reverse_orientation,
transform_swaps_handedness: render_from_object.swaps_handedness(),
}
}
}

View file

@ -4,7 +4,6 @@ pub mod cylinder;
pub mod disk; pub mod disk;
pub mod sphere; pub mod sphere;
pub mod triangle; pub mod triangle;
pub mod mesh;
pub use bilinear::*; pub use bilinear::*;
pub use curves::*; pub use curves::*;
@ -12,4 +11,3 @@ pub use cylinder::*;
pub use disk::*; pub use disk::*;
pub use sphere::*; pub use sphere::*;
pub use triangle::*; pub use triangle::*;
pub use mesh::{TriangleMesh, BilinearPatchMesh};

View file

@ -1,21 +1,21 @@
use crate::core::geometry::{spherical_direction, Frame, SqrtExt};
use crate::core::geometry::{ use crate::core::geometry::{
Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f, Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
Vector3fi, VectorLike, Vector3fi, VectorLike,
}; };
use crate::core::geometry::{Frame, Sqrt, spherical_direction};
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::pbrt::gamma; use crate::core::pbrt::gamma;
use crate::core::shape::{ use crate::core::shape::{
QuadricIntersection, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait, QuadricIntersection, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait,
}; };
use crate::utils::Transform;
use crate::utils::interval::Interval; use crate::utils::interval::Interval;
use crate::utils::math::{clamp, difference_of_products, radians, safe_acos, safe_sqrt, square}; use crate::utils::math::{clamp, difference_of_products, radians, safe_acos, safe_sqrt, square};
use crate::utils::sampling::sample_uniform_sphere; use crate::utils::sampling::sample_uniform_sphere;
use crate::utils::Transform;
use crate::{Float, PI}; use crate::{Float, PI};
use num_traits::Float as NumFloat;
use core::mem; use std::mem;
use std::sync::Arc;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -57,7 +57,7 @@ impl SphereShape {
phi_max: Float, phi_max: Float,
) -> Self { ) -> Self {
let theta_z_min = clamp(z_min.min(z_max) / radius, -1., 1.).acos(); let theta_z_min = clamp(z_min.min(z_max) / radius, -1., 1.).acos();
let theta_z_max = clamp(z_min.max(z_max) / radius, -1., 1.).acos(); let theta_z_max = clamp(z_max.min(z_max) / radius, -1., 1.).acos();
let phi_max = radians(clamp(phi_max, 0., 360.0)); let phi_max = radians(clamp(phi_max, 0., 360.0));
Self { Self {
render_from_object: render_from_object.clone(), render_from_object: render_from_object.clone(),
@ -86,7 +86,7 @@ impl SphereShape {
let c: Interval = let c: Interval =
square(oi.x()) + square(oi.y()) + square(oi.z()) - square(Interval::new(self.radius)); square(oi.x()) + square(oi.y()) + square(oi.z()) - square(Interval::new(self.radius));
let v: Vector3fi = (oi - b / 2. * a * di).into(); let v: Vector3fi = (oi - b / Vector3fi::from((2. * a) * di)).into();
let length: Interval = v.norm(); let length: Interval = v.norm();
let discrim = let discrim =
4. * a * (Interval::new(self.radius) + length) * (Interval::new(self.radius) - length); 4. * a * (Interval::new(self.radius) + length) * (Interval::new(self.radius) - length);
@ -94,7 +94,7 @@ impl SphereShape {
return None; return None;
} }
let root_discrim = discrim.sqrt_ext(); let root_discrim = discrim.sqrt();
let q = if Float::from(b) < 0. { let q = if Float::from(b) < 0. {
-0.5 * (b - root_discrim) -0.5 * (b - root_discrim)
@ -108,7 +108,7 @@ impl SphereShape {
mem::swap(&mut t0, &mut t1); mem::swap(&mut t0, &mut t1);
} }
if t0.high >= t_max || t1.low <= 0. { if t0.high > t_max || t1.low < 0. {
return None; return None;
} }
let mut t_shape_hit = t0; let mut t_shape_hit = t0;
@ -120,9 +120,6 @@ impl SphereShape {
} }
let mut p_hit = Point3f::from(oi) + Float::from(t_shape_hit) * Vector3f::from(di); let mut p_hit = Point3f::from(oi) + Float::from(t_shape_hit) * Vector3f::from(di);
let scale = self.radius / p_hit.distance(Point3f::new(0., 0., 0.));
p_hit = Point3f::from(Vector3f::from(p_hit) * scale);
if p_hit.x() == 0. && p_hit.y() == 0. { if p_hit.x() == 0. && p_hit.y() == 0. {
p_hit[0] = 1e-5 * self.radius; p_hit[0] = 1e-5 * self.radius;
} }

View file

@ -1,19 +1,20 @@
use crate::Float;
use crate::core::geometry::{ use crate::core::geometry::{
Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3, Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3,
Vector3f, Vector3f,
}; };
use crate::core::geometry::{SqrtExt, Tuple, VectorLike, spherical_triangle_area}; use crate::core::geometry::{Sqrt, Tuple, VectorLike, spherical_triangle_area};
use crate::core::interaction::{ use crate::core::interaction::{
Interaction, InteractionBase, InteractionTrait, SimpleInteraction, SurfaceInteraction, Interaction, InteractionBase, InteractionTrait, SimpleInteraction, SurfaceInteraction,
}; };
use crate::core::pbrt::gamma;
use crate::core::shape::{ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait}; use crate::core::shape::{ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait};
use crate::shapes::mesh::TriangleMesh;
use crate::utils::math::{difference_of_products, square}; use crate::utils::math::{difference_of_products, square};
use crate::utils::mesh::TriangleMesh;
use crate::utils::sampling::{ use crate::utils::sampling::{
bilinear_pdf, invert_spherical_triangle_sample, sample_bilinear, sample_spherical_triangle, bilinear_pdf, invert_spherical_triangle_sample, sample_bilinear, sample_spherical_triangle,
sample_uniform_triangle, sample_uniform_triangle,
}; };
use crate::{Float, GVec, Ptr, gamma};
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -33,66 +34,92 @@ impl TriangleIntersection {
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct TriangleShape { pub struct TriangleShape {
pub mesh: Ptr<TriangleMesh>, pub mesh: TriangleMesh,
pub tri_index: i32, pub tri_index: u32,
} }
impl TriangleShape { impl TriangleShape {
pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 3e-4; pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 3e-4;
pub const MAX_SPHERICAL_SAMPLE_AREA: Float = 6.22; pub const MAX_SPHERICAL_SAMPLE_AREA: Float = 6.22;
fn mesh(&self) -> &TriangleMesh { #[inline(always)]
self.mesh.get().unwrap()
}
fn get_vertex_indices(&self) -> [usize; 3] { fn get_vertex_indices(&self) -> [usize; 3] {
let mesh = self.mesh(); unsafe {
let base = (self.tri_index as usize) * 3; let base_ptr = self
.mesh
.vertex_indices
.0
.add((self.tri_index as usize) * 3);
[ [
mesh.vertex_indices[base] as usize, *base_ptr.add(0) as usize,
mesh.vertex_indices[base + 1] as usize, *base_ptr.add(1) as usize,
mesh.vertex_indices[base + 2] as usize, *base_ptr.add(2) as usize,
] ]
} }
}
#[inline(always)]
fn get_points(&self) -> [Point3f; 3] { fn get_points(&self) -> [Point3f; 3] {
let mesh = self.mesh();
let [v0, v1, v2] = self.get_vertex_indices(); let [v0, v1, v2] = self.get_vertex_indices();
[mesh.p[v0], mesh.p[v1], mesh.p[v2]] unsafe {
} [
*self.mesh.p.0.add(v0),
fn get_shading_normals(&self) -> Option<[Normal3f; 3]> { *self.mesh.p.0.add(v1),
let mesh = self.mesh(); *self.mesh.p.0.add(v2),
if mesh.n.is_empty() { ]
return None; }
}
let [v0, v1, v2] = self.get_vertex_indices();
Some([mesh.n[v0], mesh.n[v1], mesh.n[v2]])
}
fn get_tangents(&self) -> Option<[Vector3f; 3]> {
let mesh = self.mesh();
if mesh.s.is_empty() {
return None;
}
let [v0, v1, v2] = self.get_vertex_indices();
Some([mesh.s[v0], mesh.s[v1], mesh.s[v2]])
} }
#[inline(always)]
fn get_uvs(&self) -> Option<[Point2f; 3]> { fn get_uvs(&self) -> Option<[Point2f; 3]> {
let mesh = self.mesh(); if self.mesh.uv.is_null() {
if mesh.uv.is_empty() {
return None; return None;
} }
let [v0, v1, v2] = self.get_vertex_indices(); let [v0, v1, v2] = self.get_vertex_indices();
Some([mesh.uv[v0], mesh.uv[v1], mesh.uv[v2]]) unsafe {
Some([
*self.mesh.uv.0.add(v0),
*self.mesh.uv.0.add(v1),
*self.mesh.uv.0.add(v2),
])
}
} }
pub fn new(mesh: Ptr<TriangleMesh>, tri_index: i32) -> Self { #[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.0.add(v0),
*self.mesh.s.0.add(v1),
*self.mesh.s.0.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.0.add(v0),
*self.mesh.n.0.add(v1),
*self.mesh.n.0.add(v2),
])
}
}
pub fn new(mesh: TriangleMesh, tri_index: u32) -> Self {
Self { mesh, tri_index } Self { mesh, tri_index }
} }
pub fn get_mesh(&self) -> Ptr<TriangleMesh> { pub fn get_mesh(&self) -> TriangleMesh {
self.mesh self.mesh
} }
@ -107,125 +134,13 @@ impl TriangleShape {
fn intersect_triangle( fn intersect_triangle(
&self, &self,
ray: &Ray, _ray: &Ray,
t_max: Float, _t_max: Float,
p0: Point3f, _p0: Point3f,
p1: Point3f, _p1: Point3f,
p2: Point3f, _p2: Point3f,
) -> Option<TriangleIntersection> { ) -> Option<TriangleIntersection> {
if (p2 - p0).cross(p1 - p0).norm_squared() == 0. { todo!()
return None;
}
// Transform triangle vertices to ray coordinate space
// Translate vertices based on ray origin
let mut p0t = p0 - Vector3f::from(ray.o);
let mut p1t = p1 - Vector3f::from(ray.o);
let mut p2t = p2 - Vector3f::from(ray.o);
// Permute components of triangle vertices and ray direction
let kz = ray.d.abs().max_component_index();
let mut kx = kz + 1;
if kx == 3 {
kx = 0;
}
let mut ky = kx + 1;
if ky == 3 {
ky = 0;
}
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[2];
p0t[1] += sy * p0t[2];
p1t[0] += sx * p1t[2];
p1t[1] += sy * p1t[2];
p2t[0] += sx * p2t[2];
p2t[1] += sy * p2t[2];
// Compute edge function coefficients _e0_, _e1_, and _e2_
let e0 = difference_of_products(p1t.x(), p2t.y(), p1t.y(), p2t.x());
let e1 = difference_of_products(p2t.x(), p0t.y(), p2t.y(), p0t.x());
let e2 = difference_of_products(p0t.x(), p1t.y(), p0t.y(), p1t.x());
// Fall back to double-precision test at triangle edges
// if sizeof(Float) == sizeof(float) && (e0 == 0.0f || e1 == 0.0f || e2 == 0.0f)) {
// double p2txp1ty = (double)p2t.x * (double)p1t.y;
// double p2typ1tx = (double)p2t.y * (double)p1t.x;
// e0 = (float)(p2typ1tx - p2txp1ty);
// double p0txp2ty = (double)p0t.x * (double)p2t.y;
// double p0typ2tx = (double)p0t.y * (double)p2t.x;
// e1 = (float)(p0typ2tx - p0txp2ty);
// double p1txp0ty = (double)p1t.x * (double)p0t.y;
// double p1typ0tx = (double)p1t.y * (double)p0t.x;
// e2 = (float)(p1typ0tx - p1txp0ty);
// }
// Perform triangle edge and determinant tests
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 $t$ range
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) {
return None;
} else if det > 0. && (t_scaled <= 0. || t_scaled > t_max * det) {
return None;
}
// Compute barycentric coordinates and $t$ 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;
debug_assert!(t.is_finite());
// Ensure that computed triangle $t$ is conservatively greater than zero
// Compute $\delta_z$ term for triangle $t$ error bounds
let max_zt = Vector3f::new(p0t.z(), p1t.z(), p2t.z())
.abs()
.max_component_value();
let delta_z = gamma(3) * max_zt;
// Compute $\delta_x$ and $\delta_y$ terms for triangle $t$ error bounds
let max_xt = Vector3f::new(p0t.x(), p1t.x(), p2t.x())
.abs()
.max_component_value();
let max_yt = Vector3f::new(p0t.y(), p1t.y(), p2t.y())
.abs()
.max_component_value();
let delta_x = gamma(5) * (max_xt + max_zt);
let delta_y = gamma(5) * (max_yt + max_zt);
// Compute $\delta_e$ term for triangle $t$ error bounds
let delta_e = 2. * (gamma(2) * max_xt * max_yt + delta_y * max_xt + delta_x * max_yt);
// Compute $\delta_t$ term for triangle $t$ error bounds and check _t_
let max_e = Vector3f::new(e0, e1, e2).abs().max_component_value();
let delta_t =
3. * (gamma(3) * max_e * max_zt + delta_e * max_zt + delta_z * max_e) * inv_det.abs();
if t <= delta_t {
return None;
}
// Return _TriangleIntersection_ for intersection
Some(TriangleIntersection { b0, b1, b2, t })
} }
fn interaction_from_intersection( fn interaction_from_intersection(
@ -297,8 +212,8 @@ impl TriangleShape {
flip_normal, flip_normal,
); );
isect.face_index = if !self.mesh.face_indices.is_empty() { isect.face_index = if !self.mesh.face_indices.is_null() {
unsafe { *self.mesh.face_indices.as_ptr().add(self.tri_index as usize) } unsafe { *self.mesh.face_indices.0.add(self.tri_index as usize) }
} else { } else {
0 0
}; };
@ -306,7 +221,7 @@ impl TriangleShape {
isect.common.n = ng; isect.common.n = ng;
isect.shading.n = ng; isect.shading.n = ng;
if !self.mesh.n.is_empty() || !self.mesh.s.is_empty() { if !self.mesh.p.is_null() || !self.mesh.s.is_null() {
self.compute_shading_geometry(&mut isect, &ti, uv, dpdu, determinant, degenerate); self.compute_shading_geometry(&mut isect, &ti, uv, dpdu, determinant, degenerate);
} }
isect isect
@ -321,6 +236,7 @@ impl TriangleShape {
determinant: Float, determinant: Float,
degenerate_uv: bool, degenerate_uv: bool,
) { ) {
// Interpolate vertex normals if they exist
let ns = if let Some(normals) = self.get_shading_normals() { let ns = if let Some(normals) = self.get_shading_normals() {
let n = ti.b0 * normals[0] + ti.b1 * normals[1] + ti.b2 * normals[2]; let n = ti.b0 * normals[0] + ti.b1 * normals[1] + ti.b2 * normals[2];
if n.norm_squared() > 0.0 { if n.norm_squared() > 0.0 {
@ -332,6 +248,7 @@ impl TriangleShape {
isect.n() isect.n()
}; };
// Interpolate tangents if they exist
let mut ss = if let Some(tangents) = self.get_tangents() { let mut ss = if let Some(tangents) = self.get_tangents() {
let s = ti.b0 * tangents[0] + ti.b1 * tangents[1] + ti.b2 * tangents[2]; let s = ti.b0 * tangents[0] + ti.b1 * tangents[1] + ti.b2 * tangents[2];
if s.norm_squared() > 0.0 { if s.norm_squared() > 0.0 {
@ -343,15 +260,17 @@ impl TriangleShape {
dpdu_geom dpdu_geom
}; };
// Ensure shading tangent (ss) is perpendicular to shading normal (ns)
let mut ts = ns.cross(ss.into()); let mut ts = ns.cross(ss.into());
if ts.norm_squared() > 0.0 { if ts.norm_squared() > 0.0 {
ss = ts.cross(ns).into(); ss = ts.cross(ns.into()).into();
} else { } else {
let (s, t) = ns.coordinate_system(); let (s, t) = ns.coordinate_system();
ss = s.into(); ss = s.into();
ts = t; ts = t.into();
} }
// How does the normal change as we move across UVs?
let (dndu, dndv) = if let Some(normals) = self.get_shading_normals() { let (dndu, dndv) = if let Some(normals) = self.get_shading_normals() {
if degenerate_uv { if degenerate_uv {
let dn = (normals[2] - normals[0]).cross(normals[1] - normals[0]); let dn = (normals[2] - normals[0]).cross(normals[1] - normals[0]);
@ -455,8 +374,13 @@ impl ShapeTrait for TriangleShape {
fn sample_from_context(&self, ctx: &ShapeSampleContext, mut u: Point2f) -> Option<ShapeSample> { fn sample_from_context(&self, ctx: &ShapeSampleContext, mut u: Point2f) -> Option<ShapeSample> {
let [p0, p1, p2] = self.get_points(); let [p0, p1, p2] = self.get_points();
let solid_angle = self.solid_angle(ctx.p());
let (b, tri_pdf) = sample_spherical_triangle(&[p0, p1, p2], ctx.p(), u)?;
if tri_pdf == 0. {
return None;
}
let solid_angle = self.solid_angle(ctx.p());
if solid_angle < Self::MIN_SPHERICAL_SAMPLE_AREA if solid_angle < Self::MIN_SPHERICAL_SAMPLE_AREA
|| solid_angle > Self::MAX_SPHERICAL_SAMPLE_AREA || solid_angle > Self::MAX_SPHERICAL_SAMPLE_AREA
{ {
@ -492,11 +416,6 @@ impl ShapeTrait for TriangleShape {
pdf = bilinear_pdf(u, &w); pdf = bilinear_pdf(u, &w);
} }
let (b, tri_pdf) = sample_spherical_triangle(&[p0, p1, p2], ctx.p(), u)?;
if tri_pdf == 0. {
return None;
}
let p0_v = Vector3f::from(p0); let p0_v = Vector3f::from(p0);
let p1_v = Vector3f::from(p1); let p1_v = Vector3f::from(p1);
let p2_v = Vector3f::from(p2); let p2_v = Vector3f::from(p2);
@ -539,15 +458,14 @@ impl ShapeTrait for TriangleShape {
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> { fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
let [p0, p1, p2] = self.get_points(); let [p0, p1, p2] = self.get_points();
let tri_isect = let tri_isect = self.intersect_triangle(ray, t_max.unwrap_or(0.), p0, p1, p2)?;
self.intersect_triangle(ray, t_max.unwrap_or(Float::INFINITY), p0, p1, p2)?;
let intr = self.interaction_from_intersection(tri_isect, ray.time, -ray.d); let intr = self.interaction_from_intersection(tri_isect, ray.time, -ray.d);
Some(ShapeIntersection::new(intr, tri_isect.t)) Some(ShapeIntersection::new(intr, tri_isect.t))
} }
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool { fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
let [p0, p1, p2] = self.get_points(); let [p0, p1, p2] = self.get_points();
let tri_isect = self.intersect_triangle(ray, t_max.unwrap_or(Float::INFINITY), p0, p1, p2); let tri_isect = self.intersect_triangle(ray, t_max.unwrap_or(0.), p0, p1, p2);
tri_isect.is_some() tri_isect.is_some()
} }

View file

@ -4,63 +4,18 @@ use crate::core::pbrt::Float;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum}; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum};
use crate::utils::math::SquareMatrix3f; use crate::utils::math::SquareMatrix3f;
use crate::utils::ptr::Ptr; use crate::utils::ptr::Ptr;
use core::cmp::{Eq, PartialEq};
use std::cmp::{Eq, PartialEq};
#[repr(C)] #[repr(C)]
#[derive(Copy, Debug, Clone)] #[derive(Copy, Debug, Clone)]
pub struct DeviceStandardColorSpaces { pub struct StandardColorSpaces {
pub srgb: Ptr<RGBColorSpace>, pub srgb: Ptr<RGBColorSpace>,
pub dci_p3: Ptr<RGBColorSpace>, pub dci_p3: Ptr<RGBColorSpace>,
pub rec2020: Ptr<RGBColorSpace>, pub rec2020: Ptr<RGBColorSpace>,
pub aces2065_1: Ptr<RGBColorSpace>, pub aces2065_1: Ptr<RGBColorSpace>,
} }
impl DeviceStandardColorSpaces {
#[cfg(not(target_arch = "nvptx64"))]
pub fn get_named(&self, name: &str) -> Option<Ptr<RGBColorSpace>> {
let lower = name.as_bytes();
match lower {
b if b.eq_ignore_ascii_case(b"srgb") => Some(self.srgb),
b if b.eq_ignore_ascii_case(b"dci-p3") => Some(self.dci_p3),
b if b.eq_ignore_ascii_case(b"rec2020") => Some(self.rec2020),
b if b.eq_ignore_ascii_case(b"aces2065-1") => Some(self.aces2065_1),
_ => None,
}
}
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> {
let lower = name.as_bytes();
match lower {
b if b.eq_ignore_ascii_case(b"srgb") => Some(Self::SRGB),
b if b.eq_ignore_ascii_case(b"dci-p3") => Some(Self::DciP3),
b if b.eq_ignore_ascii_case(b"rec2020") => Some(Self::Rec2020),
b if b.eq_ignore_ascii_case(b"aces2065-1") => Some(Self::Aces2065_1),
_ => None,
}
}
}
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct RGBColorSpace { pub struct RGBColorSpace {
@ -68,12 +23,14 @@ pub struct RGBColorSpace {
pub g: Point2f, pub g: Point2f,
pub b: Point2f, pub b: Point2f,
pub w: Point2f, pub w: Point2f,
pub illuminant: DenselySampledSpectrum,
pub rgb_to_spectrum_table: Ptr<RGBToSpectrumTable>,
pub xyz_from_rgb: SquareMatrix3f, pub xyz_from_rgb: SquareMatrix3f,
pub rgb_from_xyz: SquareMatrix3f, pub rgb_from_xyz: SquareMatrix3f,
pub illuminant: Ptr<DenselySampledSpectrum>,
pub rgb_to_spectrum_table: Ptr<RGBToSpectrumTable>,
} }
unsafe impl Send for RGBColorSpace {}
unsafe impl Sync for RGBColorSpace {}
impl RGBColorSpace { impl RGBColorSpace {
pub fn to_xyz(&self, rgb: RGB) -> XYZ { pub fn to_xyz(&self, rgb: RGB) -> XYZ {
@ -85,7 +42,7 @@ impl RGBColorSpace {
} }
pub fn to_rgb_coeffs(&self, rgb: RGB) -> RGBSigmoidPolynomial { pub fn to_rgb_coeffs(&self, rgb: RGB) -> RGBSigmoidPolynomial {
self.rgb_to_spectrum_table.evaluate(rgb) self.rgb_to_spectrum_table.to_polynomial(rgb)
} }
pub fn convert_colorspace(&self, other: &RGBColorSpace) -> SquareMatrix3f { pub fn convert_colorspace(&self, other: &RGBColorSpace) -> SquareMatrix3f {
@ -95,14 +52,6 @@ impl RGBColorSpace {
self.rgb_from_xyz * other.xyz_from_rgb self.rgb_from_xyz * other.xyz_from_rgb
} }
pub fn luminance_vector(&self) -> RGB {
RGB::new(
self.xyz_from_rgb[1][0],
self.xyz_from_rgb[1][1],
self.xyz_from_rgb[1][2],
)
}
} }
impl PartialEq for RGBColorSpace { impl PartialEq for RGBColorSpace {

View file

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

View file

@ -1,8 +1,8 @@
use super::{ use super::{
DenselySampledSpectrum, RGBColorSpace, SampledSpectrum, SampledWavelengths, LAMBDA_MAX, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, RGBColorSpace,
LAMBDA_MIN, N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths,
}; };
use crate::core::color::{RGBSigmoidPolynomial, RGB, XYZ}; use crate::core::color::{RGB, RGBSigmoidPolynomial, XYZ};
use crate::core::spectrum::SpectrumTrait; use crate::core::spectrum::SpectrumTrait;
use crate::utils::Ptr; use crate::utils::Ptr;
@ -77,7 +77,7 @@ impl RGBIlluminantSpectrum {
let illuminant = cs.illuminant; let illuminant = cs.illuminant;
let m = rgb.max_component_value(); let m = rgb.max_component_value();
let scale = 2. * m; let scale = 2. * m;
let rsp = cs.to_rgb_coeffs(if scale != 0. { let rsp = cs.to_rgb_coeffs(if scale == 1. {
rgb / scale rgb / scale
} else { } else {
RGB::new(0., 0., 0.) RGB::new(0., 0., 0.)
@ -85,7 +85,7 @@ impl RGBIlluminantSpectrum {
Self { Self {
scale, scale,
rsp, rsp,
illuminant, illuminant: Ptr::from(&illuminant),
} }
} }
} }

View file

@ -1,11 +1,9 @@
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::spectrum::{SpectrumTrait, StandardSpectra}; use crate::core::spectrum::{SpectrumTrait, StandardSpectra};
use crate::utils::gpu_array_from_fn; use crate::utils::math::{clamp, lerp};
use crate::utils::math::{clamp, lerp, square}; use std::ops::{
use core::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
}; };
use num_traits::Float as NumFloat;
pub const CIE_Y_INTEGRAL: Float = 106.856895; pub const CIE_Y_INTEGRAL: Float = 106.856895;
@ -34,17 +32,13 @@ impl SampledSpectrum {
} }
} }
pub fn zero() -> Self {
Self::default()
}
#[inline(always)] #[inline(always)]
pub fn from_fn<F>(cb: F) -> Self pub fn from_fn<F>(cb: F) -> Self
where where
F: FnMut(usize) -> Float, F: FnMut(usize) -> Float,
{ {
Self { Self {
values: gpu_array_from_fn(cb), values: std::array::from_fn(cb),
} }
} }
@ -132,7 +126,7 @@ impl SampledSpectrum {
impl<'a> IntoIterator for &'a SampledSpectrum { impl<'a> IntoIterator for &'a SampledSpectrum {
type Item = &'a Float; type Item = &'a Float;
type IntoIter = core::slice::Iter<'a, Float>; type IntoIter = std::slice::Iter<'a, Float>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
self.values.iter() self.values.iter()
@ -141,7 +135,7 @@ impl<'a> IntoIterator for &'a SampledSpectrum {
impl<'a> IntoIterator for &'a mut SampledSpectrum { impl<'a> IntoIterator for &'a mut SampledSpectrum {
type Item = &'a mut Float; type Item = &'a mut Float;
type IntoIter = core::slice::IterMut<'a, Float>; type IntoIter = std::slice::IterMut<'a, Float>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
self.values.iter_mut() self.values.iter_mut()
@ -338,19 +332,11 @@ impl SampledWavelengths {
} }
} }
pub fn terminate_secondary_inplace(&mut self) { pub fn terminate_secondary_inplace(&mut self) {}
if self.secondary_terminated() {
return;
}
self.pdf[0] /= N_SPECTRUM_SAMPLES as Float;
for i in 1..N_SPECTRUM_SAMPLES {
self.pdf[i] = 0.0;
}
}
pub fn sample_uniform(u: Float, lambda_min: Float, lambda_max: Float) -> Self { pub fn sample_uniform(u: Float, lambda_min: Float, lambda_max: Float) -> Self {
let mut lambda = [0.0; N_SPECTRUM_SAMPLES]; let mut lambda = [0.0; N_SPECTRUM_SAMPLES];
lambda[0] = lerp(u, lambda_min, lambda_max); lambda[0] = lerp(u, lambda_min, lambda_min);
let delta = (lambda_max - lambda_min) / N_SPECTRUM_SAMPLES as Float; let delta = (lambda_max - lambda_min) / N_SPECTRUM_SAMPLES as Float;
for i in 1..N_SPECTRUM_SAMPLES { for i in 1..N_SPECTRUM_SAMPLES {
lambda[i] = lambda[i - 1] + delta; lambda[i] = lambda[i - 1] + delta;
@ -365,14 +351,14 @@ impl SampledWavelengths {
} }
pub fn sample_visible_wavelengths(u: Float) -> Float { pub fn sample_visible_wavelengths(u: Float) -> Float {
(538.0_f64 - 138.888889_f64 * (0.85691062_f64 - 1.82750197_f64 * u as f64).atanh()) as Float 538.0 - 138.888889 * Float::atanh(0.85691062 - 1.82750197 * u)
} }
pub fn visible_wavelengths_pdf(lambda: Float) -> Float { pub fn visible_wavelengths_pdf(lambda: Float) -> Float {
if !(360.0..830.0).contains(&lambda) { if !(360.0..830.0).contains(&lambda) {
return 0.0; return 0.0;
} }
(0.0039398042_f64 / (0.0072_f64 * (lambda as f64 - 538.0)).cosh().powi(2)) as Float 0.0039398042 / (Float::cosh(0.0072 * (lambda - 538.0))).sqrt()
} }
pub fn sample_visible(u: Float) -> Self { pub fn sample_visible(u: Float) -> Self {

View file

@ -1,12 +1,12 @@
use super::cie::*; use super::cie::*;
use super::sampled::{LAMBDA_MAX, LAMBDA_MIN}; use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::spectra::{SampledSpectrum, SampledWavelengths, N_SPECTRUM_SAMPLES}; use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};
use crate::utils::find_interval; use crate::utils::ptr::Ptr;
use crate::utils::math::square; use crate::{Float, find_interval};
use crate::{gvec, gvec_from_slice, gvec_with_capacity, Float, GVec, Ptr}; use core::slice;
use core::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use num_traits::Float as NumFloat; use std::sync::LazyLock;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -31,106 +31,20 @@ impl SpectrumTrait for ConstantSpectrum {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Copy, Clone)]
pub struct DenselySampledSpectrum { pub struct DenselySampledSpectrum {
pub lambda_min: i32, pub lambda_min: i32,
pub lambda_max: i32, pub lambda_max: i32,
pub values: GVec<Float>, pub values: Ptr<Float>,
} }
unsafe impl Send for DenselySampledSpectrum {}
unsafe impl Sync for DenselySampledSpectrum {}
impl DenselySampledSpectrum { impl DenselySampledSpectrum {
pub fn new(lambda_min: i32, lambda_max: i32, values: GVec<Float>) -> Self {
let func_integral = 0.0;
Self {
lambda_min,
lambda_max,
values,
}
}
pub fn new_zero(lambda_min: i32, lambda_max: i32) -> Self {
let n = (lambda_max - lambda_min + 1).max(0) as usize;
let mut values = gvec_with_capacity(n);
values.resize(n, 0.0);
Self {
lambda_min,
lambda_max,
values,
}
}
pub fn from_spectrum(spec: &Spectrum) -> Self {
let mut values = gvec_with_capacity((LAMBDA_MAX - LAMBDA_MIN + 1) as usize);
for lambda in LAMBDA_MIN..=LAMBDA_MAX {
values.push(spec.evaluate(lambda as Float));
}
Self {
lambda_min: LAMBDA_MIN,
lambda_max: LAMBDA_MAX,
values,
}
}
pub fn from_function<F>(f: F, lambda_min: i32, lambda_max: i32) -> Self
where
F: Fn(Float) -> Float,
{
let mut values = gvec_with_capacity((lambda_max - lambda_min + 1) as usize);
for lambda in lambda_min..=lambda_max {
values.push(f(lambda as Float));
}
Self {
lambda_min,
lambda_max,
values,
}
}
pub fn generate_cie_d(temperature: Float) -> Self {
let cct = temperature * 1.4388 / 1.4380;
if cct < 4000.0 {
return Self::from_function(
|lambda| BlackbodySpectrum::new(cct).evaluate(lambda),
LAMBDA_MIN,
LAMBDA_MAX,
);
}
let x = if cct < 7000. {
-4.607 * 1e9 / cct.powi(3) + 2.9678 * 1e6 / square(cct) + 0.09911 * 1e3 / cct + 0.244063
} else {
-2.0064 * 1e9 / cct.powi(3) + 1.9018 * 1e6 / square(cct) + 0.24748 * 1e3 / cct + 0.23704
};
let y = -3. * x + 2.87 * x - 0.275;
let m = 0.0241 + 0.2562 * x - 0.7341 * y;
let m1 = (-1.3515 - 1.7703 * x + 5.9114 * y) / m;
let m2 = (0.0300 - 31.4424 * x + 30.0717 * y) / m;
let mut coarse_values = gvec_with_capacity(N_CIES);
for i in 0..N_CIES {
coarse_values.push((CIE_S0[i] + CIE_S1[i] * m1 + CIE_S2[i] * m2) * 0.01);
}
let temp_pls = PiecewiseLinearSpectrum {
lambdas: gvec_from_slice(&CIE_S_LAMBDA),
values: gvec_from_slice(&coarse_values),
count: N_CIES as u32,
};
Self::from_function(|lambda| temp_pls.evaluate(lambda), LAMBDA_MIN, LAMBDA_MAX)
}
pub fn scale(&mut self, s: Float) {
for v in &mut self.values {
*v *= s;
}
}
#[inline(always)] #[inline(always)]
pub fn count(&self) -> usize { pub fn count(&self) -> usize {
if self.values.is_empty() { if self.values.is_null() {
0 0
} else { } else {
(self.lambda_max - self.lambda_min + 1) as usize (self.lambda_max - self.lambda_min + 1) as usize
@ -138,8 +52,8 @@ impl DenselySampledSpectrum {
} }
#[inline(always)] #[inline(always)]
pub fn value(&self, idx: u32) -> Float { fn get(&self, idx: u32) -> Float {
unsafe { *self.values.as_ptr().add(idx as usize) } unsafe { *self.values.0.add(idx as usize) }
} }
} }
@ -147,47 +61,38 @@ impl PartialEq for DenselySampledSpectrum {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.lambda_min == other.lambda_min self.lambda_min == other.lambda_min
&& self.lambda_max == other.lambda_max && self.lambda_max == other.lambda_max
&& self.values == other.values && self.values.0 == other.values.0
} }
} }
impl Eq for DenselySampledSpectrum {} impl Eq for DenselySampledSpectrum {}
impl Hash for DenselySampledSpectrum { // impl Hash for DenselySampledSpectrum {
fn hash<H: Hasher>(&self, state: &mut H) { // fn hash<H: Hasher>(&self, state: &mut H) {
self.lambda_min.hash(state); // self.lambda_min.hash(state);
self.lambda_max.hash(state); // self.lambda_max.hash(state);
for &val in self.values.iter() { //
val.to_bits().hash(state); // for v in &self.values {
} // v.to_bits().hash(state);
} // }
} // }
// }
impl SpectrumTrait for DenselySampledSpectrum { impl SpectrumTrait for DenselySampledSpectrum {
fn max_value(&self) -> Float {
if self.values.is_empty() {
return 0.0;
}
let mut max_val = Float::NEG_INFINITY;
for i in 0..self.count() {
let val = self.value(i as u32);
if val > max_val {
max_val = val;
}
}
max_val
}
fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum { fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut s = SampledSpectrum::default(); let mut s = SampledSpectrum::default();
let n = self.count() as i32; let n = self.count() as i32;
for i in 0..N_SPECTRUM_SAMPLES { for i in 0..N_SPECTRUM_SAMPLES {
let offset = lambda[i].round() as i32 - self.lambda_min; let offset = lambda[i].round() as i32 - self.lambda_min;
s[i] = if offset < 0 || offset >= n {
0.0 if offset < 0 || offset >= n {
s[i] = 0.0;
} else { } else {
self.value(offset as u32) unsafe {
}; s[i] = *self.values.0.add(offset as usize);
}
}
} }
s s
} }
@ -198,107 +103,56 @@ impl SpectrumTrait for DenselySampledSpectrum {
if offset < 0 || offset >= n { if offset < 0 || offset >= n {
0.0 0.0
} else { } else {
self.value(offset as u32) unsafe { *self.values.0.add(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.0.add(i);
if val > max_val {
max_val = val;
}
}
}
max_val
}
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub struct PiecewiseLinearSpectrum { pub struct PiecewiseLinearSpectrum {
pub lambdas: GVec<Float>, pub lambdas: Ptr<Float>,
pub values: GVec<Float>, pub values: Ptr<Float>,
pub count: u32, pub count: u32,
} }
impl PiecewiseLinearSpectrum { impl PiecewiseLinearSpectrum {
#[inline(always)] #[inline(always)]
pub fn count(&self) -> usize { fn lambda(&self, i: u32) -> Float {
self.count.try_into().unwrap() unsafe { *self.lambdas.0.add(i as usize) }
} }
#[inline(always)] #[inline(always)]
pub fn lambda(&self, idx: u32) -> Float { fn value(&self, i: u32) -> Float {
unsafe { *self.lambdas.as_ptr().add(idx as usize) } unsafe { *self.values.0.add(i as usize) }
}
#[inline(always)]
pub fn value(&self, idx: u32) -> Float {
unsafe { *self.values.as_ptr().add(idx as usize) }
}
pub fn new(lambdas: GVec<Float>, values: GVec<Float>) -> Self {
assert_eq!(lambdas.len(), values.len());
let count = lambdas.len() as u32;
Self {
lambdas,
values,
count,
}
}
/// pbrt `PiecewiseLinearSpectrum::FromInterleaved` (`util/spectrum.cpp`): `(lambda, value)`
/// pairs, extended flat to cover the full visible range, and -- when `normalize` is set --
/// scaled so that `InnerProduct(spec, Y) == CIE_Y_integral` ("normalize to luminance 1").
pub fn from_interleaved(data: &[Float], normalize: bool) -> Self {
assert!(
data.len() % 2 == 0,
"Interleaved data must have even length"
);
let n = data.len() / 2;
let mut pairs: GVec<(Float, Float)> = gvec_with_capacity(n);
for chunk in data.chunks(2) {
pairs.push((chunk[0], chunk[1]));
}
pairs.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(core::cmp::Ordering::Equal));
let mut lambdas: GVec<Float> = gvec_with_capacity(n + 2);
let mut values: GVec<Float> = gvec_with_capacity(n + 2);
// Extend samples to cover the range of visible wavelengths if needed.
if pairs[0].0 > LAMBDA_MIN as Float {
lambdas.push(LAMBDA_MIN as Float - 1.0);
values.push(pairs[0].1);
}
for (l, v) in pairs.iter() {
lambdas.push(*l);
values.push(*v);
}
if *lambdas.last().unwrap() < LAMBDA_MAX as Float {
lambdas.push(LAMBDA_MAX as Float + 1.0);
values.push(*values.last().unwrap());
}
let mut spec = Self::new(lambdas, values);
if normalize {
// Normalize to have luminance of 1.
spec.scale(CIE_Y_INTEGRAL / spec.inner_product_with_cie_y());
}
spec
}
/// `InnerProduct(self, Spectra::Y())` -- pbrt sums over integer wavelengths across the
/// visible range, which is exactly the sampling of the tabulated `CIE_Y` curve.
pub fn inner_product_with_cie_y(&self) -> Float {
let mut integral = 0.0;
for (i, y) in CIE_Y.iter().enumerate() {
integral += *y * self.evaluate(LAMBDA_MIN as Float + i as Float);
}
integral
}
pub fn scale(&mut self, s: Float) {
for v in self.values.iter_mut() {
*v *= s;
}
} }
} }
unsafe impl Send for PiecewiseLinearSpectrum {}
unsafe impl Sync for PiecewiseLinearSpectrum {}
impl SpectrumTrait for PiecewiseLinearSpectrum { impl SpectrumTrait for PiecewiseLinearSpectrum {
fn evaluate(&self, lambda: Float) -> Float { fn evaluate(&self, lambda: Float) -> Float {
if self.lambdas.is_empty() { if self.lambdas.is_null() {
return 0.0; return 0.0;
} }
@ -322,7 +176,7 @@ impl SpectrumTrait for PiecewiseLinearSpectrum {
} }
fn max_value(&self) -> Float { fn max_value(&self) -> Float {
if self.values.is_empty() { if self.values.is_null() {
return 0.; return 0.;
} }
@ -331,7 +185,7 @@ impl SpectrumTrait for PiecewiseLinearSpectrum {
for i in 0..n { for i in 0..n {
unsafe { unsafe {
let val = *self.values.as_ptr().add(i as usize); let val = *self.values.0.add(i as usize);
if val > max_val { if val > max_val {
max_val = val; max_val = val;
} }

View file

@ -39,12 +39,8 @@ impl FloatBilerpTexture {
} }
} }
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { pub fn evaluate(&self, _ctx: &TextureEvalContext) -> Float {
let c = self.mapping.map(ctx); todo!()
(1. - c.st[0]) * (1. - c.st[1]) * self.v00
+ c.st[0] * (1. - c.st[1]) * self.v10
+ (1. - c.st[0]) * c.st[1] * self.v01
+ c.st[0] * c.st[1] * self.v11
} }
} }

View file

@ -1,23 +1,19 @@
use crate::Float; use crate::Float;
use crate::core::texture::{ use crate::core::texture::{
FloatTexture, SpectrumTexture, TextureEvalContext, TextureMapping2D, TextureMapping3D, GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext, TextureMapping2D, TextureMapping3D,
TextureMapping3DTrait, TextureMapping3DTrait,
}; };
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::{Ptr, math::square}; use crate::utils::{ArenaPtr, Ptr, math::square};
use num_traits::Float as NumFloat;
#[repr(C)] fn checkerboard(
#[derive(Debug, Copy, Clone)] ctx: &TextureEvalContext,
pub enum CheckerMap { map2d: Ptr<TextureMapping2D>,
D2(TextureMapping2D), map3d: Ptr<TextureMapping3D>,
D3(TextureMapping3D), ) -> Float {
}
fn checkerboard(ctx: &TextureEvalContext, checker_map: CheckerMap) -> Float {
let d = |x: Float| -> Float { let d = |x: Float| -> Float {
let y = x / 2. - (x / 2.).floor() - 0.5; let y = x / 2. - (x / 2.).floor() - 0.5;
x / 2. + y * (1. - 2. * y.abs()) return x / 2. + y * (1. - 2. * y.abs());
}; };
let bf = |x: Float, r: Float| -> Float { let bf = |x: Float, r: Float| -> Float {
@ -27,53 +23,49 @@ fn checkerboard(ctx: &TextureEvalContext, checker_map: CheckerMap) -> Float {
(d(x + r) - 2. * d(x) + d(x - r)) / square(r) (d(x + r) - 2. * d(x) + d(x - r)) / square(r)
}; };
match checker_map { if !map2d.is_null() {
CheckerMap::D2(map) => { assert!(map3d.is_null());
let c = map.map(ctx); let c = map2d.map(&ctx);
let ds = 1.5 * c.dsdx.abs().max(c.dsdy.abs()); let ds = 1.5 * c.dsdx.abs().max(c.dsdy.abs());
let dt = 1.5 * c.dtdx.abs().max(c.dtdy.abs()); let dt = 1.5 * c.dtdx.abs().max(c.dtdy.abs());
// Integrate product of 2D checkerboard function and triangle filter // Integrate product of 2D checkerboard function and triangle filter
0.5 - bf(c.st[0], ds) * bf(c.st[1], dt) / 2. 0.5 - bf(c.st[0], ds) * bf(c.st[1], dt) / 2.
} } else {
CheckerMap::D3(map) => { assert!(!map3d.is_null());
let c = map.map(ctx); let c = map3d.map(&ctx);
let dx = 1.5 * c.dpdx.x().abs().max(c.dpdy.x().abs()); 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 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()); 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) 0.5 - bf(c.p.x(), dx) * bf(c.p.y(), dy) * bf(c.p.z(), dz)
} }
} }
}
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct FloatCheckerboardTexture { pub struct FloatCheckerboardTexture {
pub map: CheckerMap, pub map2d: Ptr<TextureMapping2D>,
pub tex: [Ptr<FloatTexture>; 2], pub map3d: Ptr<TextureMapping3D>,
pub tex: [ArenaPtr<GPUFloatTexture>; 2],
} }
impl FloatCheckerboardTexture { impl FloatCheckerboardTexture {
pub fn new(map: CheckerMap, tex: [Ptr<FloatTexture>; 2]) -> Self {
Self { map, tex }
}
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
let w = checkerboard(ctx, self.map); let w = checkerboard(&ctx, self.map2d, self.map3d);
let mut t0 = 0.0; let mut t0 = 0.0;
let mut t1 = 0.0; let mut t1 = 0.0;
if w != 1.0 if w != 1.0 {
&& let Some(tex) = self.tex[0].get() if let Some(tex) = self.tex[0].get() {
{
t0 = tex.evaluate(ctx); t0 = tex.evaluate(ctx);
} }
}
if w != 0.0 if w != 0.0 {
&& let Some(tex) = self.tex[1].get() if let Some(tex) = self.tex[1].get() {
{
t1 = tex.evaluate(ctx); t1 = tex.evaluate(ctx);
} }
}
(1.0 - w) * t0 + w * t1 (1.0 - w) * t0 + w * t1
} }
@ -82,34 +74,31 @@ impl FloatCheckerboardTexture {
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct SpectrumCheckerboardTexture { pub struct SpectrumCheckerboardTexture {
pub map: CheckerMap, pub map2d: Ptr<TextureMapping2D>,
pub tex: [Ptr<SpectrumTexture>; 2], pub map3d: Ptr<TextureMapping3D>,
pub tex: [ArenaPtr<GPUSpectrumTexture>; 2],
} }
impl SpectrumCheckerboardTexture { impl SpectrumCheckerboardTexture {
pub fn new(map: CheckerMap, tex: [Ptr<SpectrumTexture>; 2]) -> Self {
Self { map, tex }
}
pub fn evaluate( pub fn evaluate(
&self, &self,
ctx: &TextureEvalContext, ctx: &TextureEvalContext,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum {
let w = checkerboard(ctx, self.map); let w = checkerboard(ctx, self.map2d, self.map3d);
let mut t0 = SampledSpectrum::new(0.); let mut t0 = SampledSpectrum::new(0.);
let mut t1 = SampledSpectrum::new(0.); let mut t1 = SampledSpectrum::new(0.);
if w != 1.0 if w != 1.0 {
&& let Some(tex) = self.tex[0].get() if let Some(tex) = self.tex[0].get() {
{
t0 = tex.evaluate(ctx, lambda); t0 = tex.evaluate(ctx, lambda);
} }
}
if w != 0.0 if w != 0.0 {
&& let Some(tex) = self.tex[1].get() if let Some(tex) = self.tex[1].get() {
{
t1 = tex.evaluate(ctx, lambda); t1 = tex.evaluate(ctx, lambda);
} }
}
t0 * (1.0 - w) + t1 * w t0 * (1.0 - w) + t1 * w
} }

View file

@ -1,11 +1,12 @@
use crate::Float; use crate::Float;
use crate::core::geometry::{Point2f, VectorLike}; use crate::core::geometry::{Point2f, VectorLike};
use crate::core::texture::{FloatTexture, SpectrumTexture, TextureEvalContext, TextureMapping2D}; use crate::core::texture::{
GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext, TextureMapping2D,
};
use crate::spectra::sampled::{SampledSpectrum, SampledWavelengths}; use crate::spectra::sampled::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::utils::math::square; use crate::utils::math::square;
use crate::utils::noise::noise_2d; use crate::utils::noise::noise_2d;
use num_traits::Float as NumFloat;
fn inside_polka_dot(st: Point2f) -> bool { fn inside_polka_dot(st: Point2f) -> bool {
let s_cell = (st[0] + 0.5).floor(); let s_cell = (st[0] + 0.5).floor();
@ -20,30 +21,18 @@ fn inside_polka_dot(st: Point2f) -> bool {
return true; return true;
} }
} }
false return false;
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct FloatDotsTexture { pub struct FloatDotsTexture {
pub mapping: TextureMapping2D, pub mapping: TextureMapping2D,
pub inside_dot: Ptr<FloatTexture>, pub outside_dot: Ptr<GPUFloatTexture>,
pub outside_dot: Ptr<FloatTexture>, pub inside_dot: Ptr<GPUFloatTexture>,
} }
impl FloatDotsTexture { impl FloatDotsTexture {
pub fn new(
mapping: TextureMapping2D,
inside_dot: Ptr<FloatTexture>,
outside_dot: Ptr<FloatTexture>,
) -> Self {
Self {
mapping,
inside_dot,
outside_dot,
}
}
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
let c = self.mapping.map(ctx); let c = self.mapping.map(ctx);
let target_texture = if inside_polka_dot(c.st) { let target_texture = if inside_polka_dot(c.st) {
@ -64,22 +53,11 @@ impl FloatDotsTexture {
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct SpectrumDotsTexture { pub struct SpectrumDotsTexture {
pub mapping: TextureMapping2D, pub mapping: TextureMapping2D,
pub inside_dot: Ptr<SpectrumTexture>, pub outside_dot: Ptr<GPUSpectrumTexture>,
pub outside_dot: Ptr<SpectrumTexture>, pub inside_dot: Ptr<GPUSpectrumTexture>,
} }
impl SpectrumDotsTexture { impl SpectrumDotsTexture {
pub fn new(
mapping: TextureMapping2D,
inside_dot: Ptr<SpectrumTexture>,
outside_dot: Ptr<SpectrumTexture>,
) -> Self {
Self {
mapping,
inside_dot,
outside_dot,
}
}
pub fn evaluate( pub fn evaluate(
&self, &self,
ctx: &TextureEvalContext, ctx: &TextureEvalContext,

View file

@ -5,18 +5,11 @@ use crate::utils::noise::fbm;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct FBmTexture { pub struct FBmTexture {
pub mapping: TextureMapping3D, pub mapping: TextureMapping3D,
pub octaves: u32,
pub omega: Float, pub omega: Float,
pub octaves: u32,
} }
impl FBmTexture { impl FBmTexture {
pub fn new(mapping: TextureMapping3D, octaves: u32, omega: Float) -> Self {
Self {
mapping,
omega,
octaves,
}
}
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
let c = self.mapping.map(ctx); let c = self.mapping.map(ctx);
fbm(c.p, c.dpdx, c.dpdy, self.omega, self.octaves) fbm(c.p, c.dpdx, c.dpdy, self.omega, self.octaves)

View file

@ -1,148 +1,105 @@
use crate::Float;
use crate::core::color::{RGB, XYZ}; use crate::core::color::{RGB, XYZ};
use crate::core::image::{Image, WrapMode, WrapMode2D};
use crate::core::spectrum::SpectrumTrait; use crate::core::spectrum::SpectrumTrait;
use crate::core::texture::{SpectrumType, TextureEvalContext, TextureMapping2D}; use crate::core::texture::{SpectrumType, TextureEvalContext, TextureMapping2D};
use crate::spectra::{ use crate::spectra::{
RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
SampledWavelengths, SampledWavelengths,
}; };
use crate::utils::Ptr;
use crate::Float;
use core::sync::atomic::{AtomicU32, Ordering};
pub static DIAG_IMG_COUNT: AtomicU32 = AtomicU32::new(0);
pub static DIAG_IMG_SCALE_BITS: AtomicU32 = AtomicU32::new(0);
pub static DIAG_IMG_PIXEL0_BITS: AtomicU32 = AtomicU32::new(0);
pub static DIAG_IMG_RGB0_BITS: AtomicU32 = AtomicU32::new(0);
pub static DIAG_IMG_RESULT0_BITS: AtomicU32 = AtomicU32::new(0);
/* GPU heavy code, dont know if this will ever work the way Im doing things. /* GPU heavy code, dont know if this will ever work the way Im doing things.
* Leaving it here isolated, for careful handling */ * Leaving it here isolated, for careful handling */
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, Copy)] #[derive(Clone, Debug, Copy)]
pub struct SpectrumImageTexture { pub struct GPUSpectrumImageTexture {
pub wrap_mode: WrapMode, pub mapping: TextureMapping2D,
pub tex_obj: u64, pub tex_obj: u64,
pub scale: Float, pub scale: Float,
pub spectrum_type: SpectrumType,
pub image: Ptr<Image>,
pub color_space: Ptr<RGBColorSpace>,
pub mapping: TextureMapping2D,
pub is_single_channel: bool,
pub invert: bool, pub invert: bool,
pub is_single_channel: bool,
pub color_space: RGBColorSpace,
pub spectrum_type: SpectrumType,
} }
impl SpectrumImageTexture { impl GPUSpectrumImageTexture {
pub fn evaluate( pub fn evaluate(
&self, &self,
ctx: &TextureEvalContext, ctx: &TextureEvalContext,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum {
#[cfg(feature = "cuda")] #[cfg(not(feature = "cuda"))]
if self.tex_obj != 0 { {
// FUTURE: hardware sampling path.
// let c = self.mapping.map(ctx);
// let rgb = tex2d_grad(self.tex_obj, c.st, [c.dsdx,c.dtdx], [c.dsdy,c.dtdy]);
// return spectrum_from_rgb(rgb * self.scale, self.invert, self.spectrum_type, ...);
// Until then, fall through to software path below.
}
let Some(image) = self.image.get() else {
return SampledSpectrum::zero(); return SampledSpectrum::zero();
}; }
let mut c = self.mapping.map(ctx);
c.st[1] = 1.0 - c.st[1]; // flip V to match pbrt convention
let wrap = WrapMode2D { #[cfg(feature = "cuda")]
uv: [self.wrap_mode; 2], {
}; use cuda_std::intrinsics;
let pixel0 = image.bilerp_channel_with_wrap(c.st, 0, wrap); let c = self.mapping.map(ctx);
let rgb = if image.n_channels == 1 { let u = c.st.x();
RGB::new(pixel0, pixel0, pixel0) 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: Float =
unsafe { intrinsics::tex2d_grad(self.tex_obj, u, v, d_p_dx, d_p_dy) };
RGB::new(val, val, val)
} else { } else {
RGB::new( let val: [Float; 4] =
pixel0, unsafe { intrinsics::tex2d_grad(self.tex_obj, u, v, d_p_dx, d_p_dy) };
image.bilerp_channel_with_wrap(c.st, 1, wrap), RGB::new(val[0], val[1], val[2])
image.bilerp_channel_with_wrap(c.st, 2, wrap),
)
}; };
let n = DIAG_IMG_COUNT.fetch_add(1, Ordering::Relaxed);
if n < 10 { let mut rgb = tex_color * self.scale;
DIAG_IMG_SCALE_BITS.store(self.scale.to_bits(), Ordering::Relaxed);
DIAG_IMG_PIXEL0_BITS.store(pixel0.to_bits(), Ordering::Relaxed);
DIAG_IMG_RGB0_BITS.store((rgb[0] as f32).to_bits(), Ordering::Relaxed);
}
let mut rgb = rgb * self.scale;
if self.invert { if self.invert {
rgb = (RGB::new(1.0, 1.0, 1.0) - rgb); rgb = (RGB::new(1.0, 1.0, 1.0) - rgb).clamp_zero();
}
match self.spectrum_type {
SpectrumType::Unbounded => {
RGBUnboundedSpectrum::new(&self.color_space, rgb).sample(lambda)
}
SpectrumType::Albedo => {
RGBAlbedoSpectrum::new(&self.color_space, rgb.clamp(0.0, 1.0)).sample(lambda)
}
_ => RGBIlluminantSpectrum::new(&self.color_space, rgb).sample(lambda),
} }
rgb = rgb.clamp_zero();
let cs = self
.color_space
.get()
.expect("color_space must not be null");
let result = match self.spectrum_type {
SpectrumType::Unbounded => RGBUnboundedSpectrum::new(cs, rgb).sample(lambda),
SpectrumType::Albedo => RGBAlbedoSpectrum::new(cs, rgb.clamp(0.0, 1.0)).sample(lambda),
_ => RGBIlluminantSpectrum::new(cs, rgb).sample(lambda),
};
if n < 10 {
DIAG_IMG_RESULT0_BITS.store(result[0].to_bits(), Ordering::Relaxed);
} }
result
} }
} }
#[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct FloatImageTexture { pub struct GPUFloatImageTexture {
pub image: Ptr<Image>,
pub mapping: TextureMapping2D, pub mapping: TextureMapping2D,
pub wrap_mode: WrapMode,
pub tex_obj: u64, pub tex_obj: u64,
pub scale: Float, pub scale: Float,
pub invert: bool, pub invert: bool,
} }
impl FloatImageTexture { impl GPUFloatImageTexture {
#[allow(unused_variables)]
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
#[cfg(not(feature = "cuda"))] #[cfg(not(feature = "cuda"))]
{ {
let wrap = WrapMode2D { return 0.;
uv: [self.wrap_mode; 2],
};
let mut c = self.mapping.map(ctx);
c.st[1] = 1.0 - c.st[1];
let Some(image) = self.image.get() else { return 0. };
let v = image.bilerp_channel_with_wrap(c.st, 0, wrap);
let v = if self.invert { (1.0 - v).max(0.0) } else { v };
return v * self.scale;
} }
#[cfg(feature = "cuda")] #[cfg(feature = "cuda")]
{ {
if self.tex_obj != 0 {
use cuda_std::intrinsics; use cuda_std::intrinsics;
let c = self.mapping.map(ctx); let c = self.mapping.map(ctx);
let u = c.st.x(); let u = c.st.x();
let v = 1.0 - c.st.y(); let v = 1.0 - c.st.y();
let d_p_dx = [c.dsdx, c.dtdx]; let d_p_dx = [c.dsdx, c.dtdx];
let d_p_dy = [c.dsdy, c.dtdy]; let d_p_dy = [c.dsdy, c.dtdy];
// let val: Float = unsafe { intrinsics::tex2d_grad(self.tex_obj, u, v, d_p_dx, d_p_dy) }; let val: Float = unsafe { intrinsics::tex2d_grad(self.tex_obj, u, v, d_p_dx, d_p_dy) };
let _ = (u, v, d_p_dx, d_p_dy);
let val: Float = 0.; if self.invert {
let result = if self.invert { (1.0 - val).max(0.0) } else { val }; return (1. - v).max(0.);
return result * self.scale; } else {
} return v;
// software path (no hardware texture object) }
let wrap = WrapMode2D { uv: [self.wrap_mode; 2] };
let mut c = self.mapping.map(ctx);
c.st[1] = 1.0 - c.st[1];
let Some(image) = self.image.get() else { return 0. };
let v = image.bilerp_channel_with_wrap(c.st, 0, wrap);
let v = if self.invert { (1.0 - v).max(0.0) } else { v };
return v * self.scale;
} }
} }
} }

View file

@ -8,7 +8,6 @@ use crate::utils::math::clamp;
use crate::utils::noise::fbm; use crate::utils::noise::fbm;
use crate::utils::ptr::Ptr; use crate::utils::ptr::Ptr;
use crate::utils::splines::evaluate_cubic_bezier; use crate::utils::splines::evaluate_cubic_bezier;
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, Copy)] #[derive(Clone, Debug, Copy)]
@ -22,25 +21,10 @@ pub struct MarbleTexture {
pub colorspace: Ptr<RGBColorSpace>, pub colorspace: Ptr<RGBColorSpace>,
} }
impl MarbleTexture { unsafe impl Send for MarbleTexture {}
pub fn new( unsafe impl Sync for MarbleTexture {}
mapping: TextureMapping3D,
octaves: i32,
omega: Float,
scale: Float,
variation: Float,
colorspace: Ptr<RGBColorSpace>,
) -> Self {
Self {
mapping,
octaves: octaves.try_into().unwrap(),
omega,
scale,
variation,
colorspace,
}
}
impl MarbleTexture {
pub fn evaluate( pub fn evaluate(
&self, &self,
ctx: &TextureEvalContext, ctx: &TextureEvalContext,
@ -78,6 +62,6 @@ impl MarbleTexture {
let (rgb_vec, _) = evaluate_cubic_bezier(&colors[first_idx..first_idx + 4], t_segment); let (rgb_vec, _) = evaluate_cubic_bezier(&colors[first_idx..first_idx + 4], t_segment);
let rgb = RGB::new(rgb_vec.x() * 1.5, rgb_vec.y() * 1.5, rgb_vec.z() * 1.5); let rgb = RGB::new(rgb_vec.x() * 1.5, rgb_vec.y() * 1.5, rgb_vec.z() * 1.5);
RGBAlbedoSpectrum::new(&self.colorspace, rgb).sample(lambda) RGBAlbedoSpectrum::new(&*self.colorspace, rgb).sample(lambda)
} }
} }

View file

@ -1,28 +1,28 @@
use crate::Float; use crate::Float;
use crate::core::geometry::{Vector3f, VectorLike}; use crate::core::geometry::{Vector3f, VectorLike};
use crate::core::texture::{FloatTexture, SpectrumTexture, TextureEvalContext}; use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext};
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr; use crate::utils::ArenaPtr;
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct FloatMixTexture { pub struct GPUFloatMixTexture {
pub tex1: Ptr<FloatTexture>, pub tex1: ArenaPtr<GPUFloatTexture>,
pub tex2: Ptr<FloatTexture>, pub tex2: ArenaPtr<GPUFloatTexture>,
pub amount: Ptr<FloatTexture>, pub amount: ArenaPtr<GPUFloatTexture>,
} }
impl FloatMixTexture { impl GPUFloatMixTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
let amt = self.amount.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0); let amt = self.amount.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0);
let t1 = if amt != 1.0 { let t1 = if amt != 1.0 {
self.tex1.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0) self.tex1.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0)
} else { } else {
0.0 0.0
}; };
let t2 = if amt != 0.0 { let t2 = if amt != 0.0 {
self.tex2.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0) self.tex2.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0)
} else { } else {
0.0 0.0
}; };
@ -33,23 +33,23 @@ impl FloatMixTexture {
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct FloatDirectionMixTexture { pub struct GPUFloatDirectionMixTexture {
pub tex1: Ptr<FloatTexture>, pub tex1: ArenaPtr<GPUFloatTexture>,
pub tex2: Ptr<FloatTexture>, pub tex2: ArenaPtr<GPUFloatTexture>,
pub dir: Vector3f, pub dir: Vector3f,
} }
impl FloatDirectionMixTexture { impl GPUFloatDirectionMixTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
let amt = self.dir.abs_dot(ctx.n.into()); let amt = self.dir.abs_dot(ctx.n.into());
let t1 = if amt != 1.0 { let t1 = if amt != 1.0 {
self.tex1.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0) self.tex1.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0)
} else { } else {
0.0 0.0
}; };
let t2 = if amt != 0.0 { let t2 = if amt != 0.0 {
self.tex2.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0) self.tex2.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0)
} else { } else {
0.0 0.0
}; };
@ -60,23 +60,23 @@ impl FloatDirectionMixTexture {
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct SpectrumMixTexture { pub struct GPUSpectrumMixTexture {
pub tex1: Ptr<SpectrumTexture>, pub tex1: ArenaPtr<GPUSpectrumTexture>,
pub tex2: Ptr<SpectrumTexture>, pub tex2: ArenaPtr<GPUSpectrumTexture>,
pub amount: Ptr<FloatTexture>, pub amount: ArenaPtr<GPUFloatTexture>,
} }
impl SpectrumMixTexture { impl GPUSpectrumMixTexture {
pub fn evaluate( pub fn evaluate(
&self, &self,
ctx: &TextureEvalContext, ctx: &TextureEvalContext,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum {
let amt = self.amount.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0); let amt = self.amount.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0);
let t1 = if amt != 1.0 { let t1 = if amt != 1.0 {
self.tex1 self.tex1
.get() .get()
.map(|t| t.evaluate(ctx, lambda)) .map(|t| t.evaluate(&ctx, &lambda))
.unwrap_or(SampledSpectrum::new(0.)) .unwrap_or(SampledSpectrum::new(0.))
} else { } else {
SampledSpectrum::new(0.) SampledSpectrum::new(0.)
@ -85,7 +85,7 @@ impl SpectrumMixTexture {
let t2 = if amt != 0.0 { let t2 = if amt != 0.0 {
self.tex2 self.tex2
.get() .get()
.map(|t| t.evaluate(ctx, lambda)) .map(|t| t.evaluate(&ctx, &lambda))
.unwrap_or(SampledSpectrum::new(0.)) .unwrap_or(SampledSpectrum::new(0.))
} else { } else {
SampledSpectrum::new(0.) SampledSpectrum::new(0.)
@ -97,13 +97,13 @@ impl SpectrumMixTexture {
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct SpectrumDirectionMixTexture { pub struct GPUSpectrumDirectionMixTexture {
pub tex1: Ptr<SpectrumTexture>, pub tex1: ArenaPtr<GPUSpectrumTexture>,
pub tex2: Ptr<SpectrumTexture>, pub tex2: ArenaPtr<GPUSpectrumTexture>,
pub dir: Vector3f, pub dir: Vector3f,
} }
impl SpectrumDirectionMixTexture { impl GPUSpectrumDirectionMixTexture {
pub fn evaluate( pub fn evaluate(
&self, &self,
ctx: &TextureEvalContext, ctx: &TextureEvalContext,
@ -113,7 +113,7 @@ impl SpectrumDirectionMixTexture {
let t1 = if amt != 1.0 { let t1 = if amt != 1.0 {
self.tex1 self.tex1
.get() .get()
.map(|t| t.evaluate(ctx, lambda)) .map(|t| t.evaluate(&ctx, &lambda))
.unwrap_or(SampledSpectrum::new(0.)) .unwrap_or(SampledSpectrum::new(0.))
} else { } else {
SampledSpectrum::new(0.) SampledSpectrum::new(0.)
@ -122,7 +122,7 @@ impl SpectrumDirectionMixTexture {
let t2 = if amt != 0.0 { let t2 = if amt != 0.0 {
self.tex2 self.tex2
.get() .get()
.map(|t| t.evaluate(ctx, lambda)) .map(|t| t.evaluate(&ctx, &lambda))
.unwrap_or(SampledSpectrum::new(0.)) .unwrap_or(SampledSpectrum::new(0.))
} else { } else {
SampledSpectrum::new(0.) SampledSpectrum::new(0.)

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