Compare commits
No commits in common. "ed2caf094d522bcc9e6c2384b3b89f042a3047e7" and "f7c47be077bacb4ed6afb653d0582ca5ab468f45" have entirely different histories.
ed2caf094d
...
f7c47be077
233 changed files with 12172 additions and 20138 deletions
|
|
@ -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
17
.gitignore
vendored
|
|
@ -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
|
|
||||||
|
|
|
||||||
61
Cargo.toml
61
Cargo.toml
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
43
build.rs
43
build.rs
|
|
@ -1,43 +0,0 @@
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
println!("cargo:rerun-if-changed=kernels/");
|
|
||||||
|
|
||||||
if std::env::var("CARGO_FEATURE_CUDA").is_ok() {
|
|
||||||
compile_kernels();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compile_kernels() {
|
|
||||||
let out_dir = std::env::var("OUT_DIR").unwrap();
|
|
||||||
|
|
||||||
let kernels = ["test_kernels"];
|
|
||||||
|
|
||||||
for name in kernels {
|
|
||||||
let src = format!("kernels/{}.cu", name);
|
|
||||||
let dst = format!("{}/{}.ptx", out_dir, name);
|
|
||||||
|
|
||||||
println!("cargo:rerun-if-changed={}", src);
|
|
||||||
|
|
||||||
let status = Command::new("nvcc")
|
|
||||||
.args([
|
|
||||||
"-ptx",
|
|
||||||
"-o",
|
|
||||||
&dst,
|
|
||||||
&src,
|
|
||||||
"--gpu-architecture=sm_75", // Adjust for your GPU
|
|
||||||
"-O3",
|
|
||||||
"--use_fast_math",
|
|
||||||
])
|
|
||||||
.status()
|
|
||||||
.expect("Failed to run nvcc");
|
|
||||||
|
|
||||||
if !status.success() {
|
|
||||||
panic!("nvcc failed on {}", src);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("cargo:warning=Compiled {} -> {}", src, dst);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("cargo:rustc-env=KERNEL_PTX_DIR={}", out_dir);
|
|
||||||
}
|
|
||||||
|
|
@ -1,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"
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
fn main() {
|
|
||||||
println!("cargo:rerun-if-changed=cpp/ptex_filter_wrapper.cpp");
|
|
||||||
|
|
||||||
let home = std::env::var("HOME").unwrap();
|
|
||||||
let ptex_include = format!("{}/.local/include", home);
|
|
||||||
let ptex_lib = format!("{}/.local/lib", home);
|
|
||||||
|
|
||||||
cc::Build::new()
|
|
||||||
.cpp(true)
|
|
||||||
.file("cpp/ptex_filter_wrapper.cpp")
|
|
||||||
.include(&ptex_include)
|
|
||||||
.flag("-std=c++17")
|
|
||||||
.compile("ptex_filter_wrapper");
|
|
||||||
|
|
||||||
println!("cargo:rustc-link-search=native={}", ptex_lib);
|
|
||||||
println!("cargo:rustc-link-lib=Ptex");
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
#include "ptex_filter_wrapper.h"
|
|
||||||
#include <Ptexture.h>
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
PtexFilterHandle ptex_filter_create(PtexTextureHandle texture, const PtexFilterOptions* opts) {
|
|
||||||
Ptex::PtexTexture* tex = static_cast<Ptex::PtexTexture*>(texture);
|
|
||||||
if (!tex || !opts) return nullptr;
|
|
||||||
|
|
||||||
Ptex::PtexFilter::Options ptex_opts;
|
|
||||||
ptex_opts.filter = static_cast<Ptex::PtexFilter::FilterType>(opts->filter);
|
|
||||||
ptex_opts.lerp = opts->lerp;
|
|
||||||
ptex_opts.sharpness = opts->sharpness;
|
|
||||||
ptex_opts.noedgeblend = opts->noedgeblend != 0;
|
|
||||||
|
|
||||||
return Ptex::PtexFilter::getFilter(tex, ptex_opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ptex_filter_eval(
|
|
||||||
PtexFilterHandle filter,
|
|
||||||
float* result,
|
|
||||||
int32_t first_channel,
|
|
||||||
int32_t num_channels,
|
|
||||||
int32_t face_id,
|
|
||||||
float u, float v,
|
|
||||||
float dudx, float dvdx,
|
|
||||||
float dudy, float dvdy
|
|
||||||
) {
|
|
||||||
Ptex::PtexFilter* f = static_cast<Ptex::PtexFilter*>(filter);
|
|
||||||
if (f && result) {
|
|
||||||
f->eval(result, first_channel, num_channels, face_id, u, v, dudx, dvdx, dudy, dvdy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ptex_filter_release(PtexFilterHandle filter) {
|
|
||||||
Ptex::PtexFilter* f = static_cast<Ptex::PtexFilter*>(filter);
|
|
||||||
if (f) {
|
|
||||||
f->release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PtexTextureHandle ptex_texture_open(const char* filename, char** error_str) {
|
|
||||||
Ptex::String error;
|
|
||||||
Ptex::PtexTexture* tex = Ptex::PtexTexture::open(filename, error);
|
|
||||||
|
|
||||||
if (!tex && error_str) {
|
|
||||||
*error_str = strdup(error.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
return tex;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ptex_texture_release(PtexTextureHandle texture) {
|
|
||||||
Ptex::PtexTexture* tex = static_cast<Ptex::PtexTexture*>(texture);
|
|
||||||
if (tex) {
|
|
||||||
tex->release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t ptex_texture_num_channels(PtexTextureHandle texture) {
|
|
||||||
Ptex::PtexTexture* tex = static_cast<Ptex::PtexTexture*>(texture);
|
|
||||||
return tex ? tex->numChannels() : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
typedef void* PtexTextureHandle;
|
|
||||||
typedef void* PtexFilterHandle;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
PTEX_FILTER_POINT = 0,
|
|
||||||
PTEX_FILTER_BILINEAR = 1,
|
|
||||||
PTEX_FILTER_BOX = 2,
|
|
||||||
PTEX_FILTER_GAUSSIAN = 3,
|
|
||||||
PTEX_FILTER_BICUBIC = 4,
|
|
||||||
PTEX_FILTER_BSPLINE = 5,
|
|
||||||
PTEX_FILTER_CATMULLROM = 6,
|
|
||||||
PTEX_FILTER_MITCHELL = 7
|
|
||||||
} PtexFilterType;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
PtexFilterType filter;
|
|
||||||
int32_t lerp;
|
|
||||||
float sharpness;
|
|
||||||
int32_t noedgeblend;
|
|
||||||
} PtexFilterOptions;
|
|
||||||
|
|
||||||
PtexTextureHandle ptex_texture_open(const char* filename, char** error_str);
|
|
||||||
void ptex_filter_release(PtexFilterHandle filter);
|
|
||||||
int32_t ptex_texture_num_channels(PtexTextureHandle texture);
|
|
||||||
PtexFilterHandle ptex_filter_create(PtexTextureHandle texture, const PtexFilterOptions* opts);
|
|
||||||
|
|
||||||
|
|
||||||
void ptex_filter_eval(
|
|
||||||
PtexFilterHandle filter,
|
|
||||||
float* result,
|
|
||||||
int32_t first_channel,
|
|
||||||
int32_t num_channels,
|
|
||||||
int32_t face_id,
|
|
||||||
float u, float v,
|
|
||||||
float dudx, float dvdx,
|
|
||||||
float dudy, float dvdy
|
|
||||||
);
|
|
||||||
void ptex_filter_release(PtexFilterHandle filter);
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
use std::ffi::c_void;
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum PtexFilterType {
|
|
||||||
Point = 0,
|
|
||||||
Bilinear = 1,
|
|
||||||
Box = 2,
|
|
||||||
Gaussian = 3,
|
|
||||||
Bicubic = 4,
|
|
||||||
BSpline = 5,
|
|
||||||
CatmullRom = 6,
|
|
||||||
Mitchell = 7,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct PtexFilterOptions {
|
|
||||||
pub filter: PtexFilterType,
|
|
||||||
pub lerp: i32,
|
|
||||||
pub sharpness: f32,
|
|
||||||
pub noedgeblend: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PtexFilterOptions {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
filter: PtexFilterType::BSpline,
|
|
||||||
lerp: 1,
|
|
||||||
sharpness: 0.0,
|
|
||||||
noedgeblend: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
pub fn ptex_filter_create(texture: *mut c_void, opts: *const PtexFilterOptions) -> *mut c_void;
|
|
||||||
|
|
||||||
pub fn ptex_filter_eval(
|
|
||||||
filter: *mut c_void,
|
|
||||||
result: *mut f32,
|
|
||||||
first_channel: i32,
|
|
||||||
num_channels: i32,
|
|
||||||
face_id: i32,
|
|
||||||
u: f32,
|
|
||||||
v: f32,
|
|
||||||
dudx: f32,
|
|
||||||
dvdx: f32,
|
|
||||||
dudy: f32,
|
|
||||||
dvdy: f32,
|
|
||||||
);
|
|
||||||
|
|
||||||
pub fn ptex_filter_release(filter: *mut c_void);
|
|
||||||
pub fn ptex_texture_open(
|
|
||||||
filename: *const std::ffi::c_char,
|
|
||||||
error_str: *mut *mut std::ffi::c_char,
|
|
||||||
) -> *mut c_void;
|
|
||||||
pub fn ptex_texture_release(texture: *mut c_void);
|
|
||||||
pub fn ptex_texture_num_channels(texture: *mut c_void) -> i32;
|
|
||||||
}
|
|
||||||
|
|
@ -1,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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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"]
|
||||||
|
|
|
||||||
|
|
@ -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>>>(
|
|
||||||
¶ms
|
|
||||||
)
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
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>>>(
|
|
||||||
¶ms
|
|
||||||
)
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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,
|
}
|
||||||
) {
|
|
||||||
scale_kernel_logic(id.x as usize, input, output, *scale);
|
impl_math_gpu_traits!(Vector);
|
||||||
|
impl_math_gpu_traits!(Normal);
|
||||||
|
impl_math_gpu_traits!(Point);
|
||||||
|
impl_gpu_traits!(Interval);
|
||||||
|
impl_gpu_traits!(Float4);
|
||||||
|
impl_gpu_traits!(SampledSpectrum);
|
||||||
|
impl_gpu_traits!(SampledWavelengths);
|
||||||
|
|
||||||
|
struct KernelStats {
|
||||||
|
description: String,
|
||||||
|
num_launches: usize,
|
||||||
|
sum_ms: f32,
|
||||||
|
min_ms: f32,
|
||||||
|
max_ms: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KernelStats {
|
||||||
|
fn new(description: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
description: description.to_string(),
|
||||||
|
num_launches: 0,
|
||||||
|
sum_ms: 0.0,
|
||||||
|
min_ms: 0.0,
|
||||||
|
max_ms: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ProfilerEvent {
|
||||||
|
start: Event,
|
||||||
|
stop: Event,
|
||||||
|
active: bool,
|
||||||
|
stats: Option<Arc<Mutex<KernelStats>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProfilerEvent {
|
||||||
|
fn new() -> Result<Self, cust::error::CudaError> {
|
||||||
|
let start = Event::new(EventFlags::DEFAULT)?;
|
||||||
|
let stop = Event::new(EventFlags::DEFAULT)?;
|
||||||
|
Ok(Self {
|
||||||
|
start,
|
||||||
|
stop,
|
||||||
|
active: false,
|
||||||
|
stats: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync(&mut self) {
|
||||||
|
if !self.active {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.stop.synchronize().is_ok() {
|
||||||
|
// Check timing between start and stop
|
||||||
|
match self.stop.elapsed_time_f32(&self.start) {
|
||||||
|
Ok(ms) => {
|
||||||
|
if let Some(stats_arc) = &self.stats {
|
||||||
|
let mut stats = stats_arc.lock();
|
||||||
|
stats.num_launches += 1;
|
||||||
|
if stats.num_launches == 1 {
|
||||||
|
stats.sum_ms = ms;
|
||||||
|
stats.min_ms = ms;
|
||||||
|
stats.max_ms = ms;
|
||||||
|
} else {
|
||||||
|
stats.sum_ms += ms;
|
||||||
|
stats.min_ms = stats.min_ms.min(ms);
|
||||||
|
stats.max_ms = stats.max_ms.max(ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => log::error!("Failed to get elapsed time: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Profiler Manager ---
|
||||||
|
|
||||||
|
struct Profiler {
|
||||||
|
kernel_stats: Vec<Arc<Mutex<KernelStats>>>,
|
||||||
|
event_pool: Vec<ProfilerEvent>,
|
||||||
|
pool_offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Profiler {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
kernel_stats: Vec::new(),
|
||||||
|
event_pool: Vec::new(),
|
||||||
|
pool_offset: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepares an event from the pool.
|
||||||
|
/// Returns a mutable reference to the event, valid as long as the borrow of self.
|
||||||
|
fn prepare<'a>(&'a mut self, description: &str) -> &'a mut ProfilerEvent {
|
||||||
|
// Grow pool if empty or needed (simple heuristic)
|
||||||
|
if self.event_pool.is_empty() {
|
||||||
|
for _ in 0..128 {
|
||||||
|
if let Ok(e) = ProfilerEvent::new() {
|
||||||
|
self.event_pool.push(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.pool_offset >= self.event_pool.len() {
|
||||||
|
self.pool_offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let idx = self.pool_offset;
|
||||||
|
self.pool_offset += 1;
|
||||||
|
|
||||||
|
let pe = &mut self.event_pool[idx];
|
||||||
|
|
||||||
|
if pe.active {
|
||||||
|
pe.sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
pe.active = true;
|
||||||
|
pe.stats = None;
|
||||||
|
|
||||||
|
// Find or create stats
|
||||||
|
let mut found = None;
|
||||||
|
for s in &self.kernel_stats {
|
||||||
|
if s.lock().description == description {
|
||||||
|
found = Some(s.clone());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.is_none() {
|
||||||
|
let new_stats = Arc::new(Mutex::new(KernelStats::new(description)));
|
||||||
|
self.kernel_stats.push(new_stats.clone());
|
||||||
|
found = Some(new_stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
pe.stats = found;
|
||||||
|
pe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GpuState {
|
||||||
|
context: Context,
|
||||||
|
stream: Stream,
|
||||||
|
profiler: Profiler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GpuState {
|
||||||
|
fn init(device_index: u32) -> Result<Self, Box<dyn Error>> {
|
||||||
|
cust::init(CudaFlags::empty())?;
|
||||||
|
|
||||||
|
let device = Device::get_device(device_index)?;
|
||||||
|
|
||||||
|
let name = device.name().unwrap_or_else(|_| "Unknown".into());
|
||||||
|
let memory = device.total_memory().unwrap_or(0);
|
||||||
|
let memory_gb = memory as f64 / (1024.0 * 1024.0 * 1024.0);
|
||||||
|
|
||||||
|
let major = device
|
||||||
|
.get_attribute(DeviceAttribute::ComputeCapabilityMajor)
|
||||||
|
.unwrap_or(0);
|
||||||
|
let minor = device
|
||||||
|
.get_attribute(DeviceAttribute::ComputeCapabilityMinor)
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"Selected GPU: {} ({:.2} GB, SM {}.{})",
|
||||||
|
name,
|
||||||
|
memory_gb,
|
||||||
|
major,
|
||||||
|
minor
|
||||||
|
);
|
||||||
|
|
||||||
|
let has_unified = device
|
||||||
|
.get_attribute(DeviceAttribute::UnifiedAddressing)
|
||||||
|
.unwrap_or(0);
|
||||||
|
if has_unified == 0 {
|
||||||
|
panic!("Selected GPU does not support unified addressing.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let context = Context::new(device)?;
|
||||||
|
|
||||||
|
CurrentContext::set_resource_limit(ResourceLimit::StackSize, 8192)?;
|
||||||
|
let stack_size = CurrentContext::get_resource_limit(ResourceLimit::StackSize)?;
|
||||||
|
log::info!("Reset stack size to {}", stack_size);
|
||||||
|
|
||||||
|
CurrentContext::set_resource_limit(ResourceLimit::PrintfFifoSize, 32 * 1024 * 1024)?;
|
||||||
|
CurrentContext::set_cache_config(CacheConfig::PreferL1)?;
|
||||||
|
|
||||||
|
let stream = Stream::new(StreamFlags::DEFAULT, None)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
context,
|
||||||
|
stream,
|
||||||
|
profiler: Profiler::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref GPU_STATE: Mutex<Option<GpuState>> = Mutex::new(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gpu_init() {
|
||||||
|
if !get_options().use_gpu {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let device_id = get_options().gpu_device.unwrap_or(0);
|
||||||
|
log::info!("Initializing GPU Device {}", device_id);
|
||||||
|
|
||||||
|
match GpuState::init(device_id) {
|
||||||
|
Ok(state) => {
|
||||||
|
#[cfg(feature = "use_nvtx")]
|
||||||
|
nvtx::name_thread("MAIN_THREAD");
|
||||||
|
*GPU_STATE.lock() = Some(state);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
panic!("Failed to initialize GPU: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gpu_thread_init() {
|
||||||
|
if let Some(state) = GPU_STATE.lock().as_ref() {
|
||||||
|
if let Err(e) = CurrentContext::set_current(&state.context) {
|
||||||
|
log::error!("Failed to set CUDA context for thread: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gpu_wait() {
|
||||||
|
let mut guard = GPU_STATE.lock();
|
||||||
|
if let Some(state) = guard.as_mut() {
|
||||||
|
if let Err(e) = state.stream.synchronize() {
|
||||||
|
log::error!("GPU Wait failed: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Launches a parallel for loop on the GPU.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `description`: Name for profiling.
|
||||||
|
/// * `n_items`: Total items (threads).
|
||||||
|
/// * `function`: Compiled kernel function handle.
|
||||||
|
/// * `params`: Kernel parameters (must be DeviceCopy).
|
||||||
|
pub fn gpu_parallel_for<T: DeviceCopy>(
|
||||||
|
description: &str,
|
||||||
|
n_items: i32,
|
||||||
|
function: &Function,
|
||||||
|
params: &T,
|
||||||
|
) {
|
||||||
|
#[cfg(feature = "use_nvtx")]
|
||||||
|
nvtx::range_push(description);
|
||||||
|
|
||||||
|
let mut guard = GPU_STATE.lock();
|
||||||
|
let state = guard.as_mut().expect("GPU not initialized");
|
||||||
|
|
||||||
|
let (_, block_size) = match function.suggested_launch_configuration(0, 0.into()) {
|
||||||
|
Ok(cfg) => cfg,
|
||||||
|
Err(e) => panic!(
|
||||||
|
"Failed to calculate launch config for {}: {:?}",
|
||||||
|
description, e
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
log::debug!("[{}] Block size: {}", description, block_size);
|
||||||
|
|
||||||
|
let grid_size = (n_items as u32 + block_size - 1) / block_size;
|
||||||
|
|
||||||
|
let stream = &state.stream;
|
||||||
|
let profiler = &mut state.profiler;
|
||||||
|
|
||||||
|
// Save the index we are about to use so we can retrieve the STOP event later
|
||||||
|
let event_idx = profiler.pool_offset;
|
||||||
|
|
||||||
|
{
|
||||||
|
let pe = profiler.prepare(description);
|
||||||
|
if let Err(e) = pe.start.record(stream) {
|
||||||
|
log::error!("Failed to record start event: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let params_ptr = params as *const T as *mut c_void;
|
||||||
|
let n_items_ptr = &n_items as *const i32 as *mut c_void;
|
||||||
|
let args = [params_ptr, n_items_ptr];
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
if let Err(e) =
|
||||||
|
state
|
||||||
|
.stream
|
||||||
|
.launch(function, (grid_size, 1, 1), (block_size, 1, 1), 0, &args)
|
||||||
|
{
|
||||||
|
panic!("CUDA Launch failed for {}: {:?}", description, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the specific event we just set up.
|
||||||
|
// Pool_offset was incremented in prepare().
|
||||||
|
// If event_idx was the one used, the event is at event_idx.
|
||||||
|
if event_idx < profiler.event_pool.len() {
|
||||||
|
let pe = &mut profiler.event_pool[event_idx];
|
||||||
|
if let Err(e) = pe.stop.record(stream) {
|
||||||
|
log::error!("Failed to record stop event: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let _ = state.stream.synchronize();
|
||||||
|
|
||||||
|
#[cfg(feature = "use_nvtx")]
|
||||||
|
nvtx::range_pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn report_kernel_stats() {
|
||||||
|
let mut guard = GPU_STATE.lock();
|
||||||
|
if let Some(state) = guard.as_mut() {
|
||||||
|
let _ = state.stream.synchronize();
|
||||||
|
|
||||||
|
// Process all pending events
|
||||||
|
for pe in &mut state.profiler.event_pool {
|
||||||
|
if pe.active {
|
||||||
|
pe.sync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut total_ms = 0.0;
|
||||||
|
for s in &state.profiler.kernel_stats {
|
||||||
|
total_ms += s.lock().sum_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Wavefront Kernel Profile:");
|
||||||
|
for s in &state.profiler.kernel_stats {
|
||||||
|
let stats = s.lock();
|
||||||
|
let percent = if total_ms > 0.0 {
|
||||||
|
100.0 * stats.sum_ms / total_ms
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
println!(
|
||||||
|
" {:<45} {:5} launches {:9.2} ms / {:5.1}% (avg {:6.3})",
|
||||||
|
stats.description,
|
||||||
|
stats.num_launches,
|
||||||
|
stats.sum_ms,
|
||||||
|
percent,
|
||||||
|
if stats.num_launches > 0 {
|
||||||
|
stats.sum_ms / stats.num_launches as f32
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
println!("\nTotal: {:.2} ms", total_ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gpu_memset<T: DeviceCopy>(dst: &mut DeviceSlice<T>, value: u8) {
|
||||||
|
unsafe {
|
||||||
|
let ptr = dst.as_raw_ptr(); // Returns CUdeviceptr (u64)
|
||||||
|
let len = dst.len() * std::mem::size_of::<T>();
|
||||||
|
|
||||||
|
// We need the `cust::external::cuda` or equivalent sys crate function
|
||||||
|
|
||||||
|
log::warn!("gpu_memset requested but raw memset not exposed via safe cust API yet.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! impl_gpu_traits {
|
||||||
|
($name:ty) => {
|
||||||
|
unsafe impl cust::memory::DeviceCopy for $name {}
|
||||||
|
unsafe impl bytemuck::Zeroable for $name {}
|
||||||
|
unsafe impl bytemuck::Pod for $name {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! impl_math_gpu_traits {
|
||||||
|
($Struct:ident) => {
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
unsafe impl<T, const N: usize> cust::memory::DeviceCopy for $Struct<T, N> where
|
||||||
|
T: cust::memory::DeviceCopy + Copy
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T, const N: usize> bytemuck::Zeroable for $Struct<T, N> where
|
||||||
|
T: bytemuck::Zeroable
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T, const N: usize> bytemuck::Pod for $Struct<T, N> where T: bytemuck::Pod {}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
47
kernels/src/wavefront/mod.rs
Normal file
47
kernels/src/wavefront/mod.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
use image_rs::Pixel;
|
||||||
|
|
||||||
|
use crate::camera::Camera;
|
||||||
|
use crate::core::film::Film;
|
||||||
|
use crate::core::filter::Filter;
|
||||||
|
use crate::core::sampler::Sampler;
|
||||||
|
use crate::core::scene::BasicScene;
|
||||||
|
use crate::lights::Light;
|
||||||
|
use crate::lights::LightSampler;
|
||||||
|
use crate::{
|
||||||
|
EscapedRayQueue, GetBSSRDFAndProbeRayQueue, HitAreaLightQueue, MaterialEvalQueue,
|
||||||
|
MediumSampleQueue, MediumScatterQueue, PixelSampleStateStorage, RayQueue, ShadowRayQueue,
|
||||||
|
SubsurfaceScatterQueue,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct WavefrontPathIntegrator {
|
||||||
|
pub film: Film,
|
||||||
|
pub filter: Filter,
|
||||||
|
pub sampler: Sampler,
|
||||||
|
pub camera: Arc<Camera>,
|
||||||
|
pub light_sampler: LightSampler,
|
||||||
|
pub infinite_lights: Option<Vec<Arc<Light>>>,
|
||||||
|
pub max_depth: i32,
|
||||||
|
pub samples_per_pixel: i32,
|
||||||
|
pub regularize: bool,
|
||||||
|
pub scanlines_per_pixel: i32,
|
||||||
|
pub max_queue_size: i32,
|
||||||
|
pub pixel_sample_state: PixelSampleStateStorage,
|
||||||
|
pub ray_queue: [RayQueue; 2],
|
||||||
|
pub hit_area_light_queue: HitAreaLightQueue,
|
||||||
|
pub shadow_ray_queue: ShadowRayQueue,
|
||||||
|
pub escaped_ray_queue: Option<EscapedRayQueue>,
|
||||||
|
pub basic_material_queue: Option<MaterialEvalQueue>,
|
||||||
|
pub universal_material_queue: Option<MaterialEvalQueue>,
|
||||||
|
pub medium_sample_queue: Option<MediumSampleQueue>,
|
||||||
|
pub medium_scatter_queue: Option<MediumScatterQueue>,
|
||||||
|
pub bssrf_queue: Option<GetBSSRDFAndProbeRayQueue>,
|
||||||
|
pub subsurface_queue: Option<SubsurfaceScatterQueue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
impl WavefrontPathIntegrator {
|
||||||
|
pub fn new(scene: BasicScene) -> Self {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
535
kernels/src/workitem.rs
Normal file
535
kernels/src/workitem.rs
Normal file
|
|
@ -0,0 +1,535 @@
|
||||||
|
#![allow(clippy::too_many_arguments)]
|
||||||
|
use super::Float4;
|
||||||
|
use crate::Float;
|
||||||
|
use crate::core::geometry::{Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray, Vector3f};
|
||||||
|
use crate::lights::LightSampleContext;
|
||||||
|
use crate::soa_struct;
|
||||||
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
|
use cust::memory::{CopyDestination, DeviceMemory};
|
||||||
|
use cust::prelude::*;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! soa_struct {
|
||||||
|
(
|
||||||
|
$(#[$outer:meta])*
|
||||||
|
pub struct $name:ident {
|
||||||
|
$(
|
||||||
|
pub $field:ident : $type:ty
|
||||||
|
),* $(,)?
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
$(#[$outer])*
|
||||||
|
pub struct $name {
|
||||||
|
capacity: u32,
|
||||||
|
pub count: cust::memory::DeviceBuffer<u32>,
|
||||||
|
$(
|
||||||
|
pub $field: cust::memory::DeviceBuffer<$type>,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
impl $name {
|
||||||
|
pub fn new(capacity: usize) -> cust::error::CudaResult<Self> {
|
||||||
|
use cust::memory::DeviceBuffer;
|
||||||
|
Ok(Self {
|
||||||
|
capacity: capacity as u32,
|
||||||
|
count: DeviceBuffer::zeroed(1)?,
|
||||||
|
$(
|
||||||
|
$field: DeviceBuffer::zeroed(capacity)?,
|
||||||
|
)*
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> cust::error::CudaResult<u32> {
|
||||||
|
let mut host_count = [0u32; 1];
|
||||||
|
self.count.copy_to(&mut host_count)?;
|
||||||
|
Ok(host_count[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) -> cust::error::CudaResult<()> {
|
||||||
|
self.count.copy_from(&[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the View name
|
||||||
|
pub fn as_view(&mut self) -> paste::paste! { [<$name View>] } {
|
||||||
|
paste::paste! {
|
||||||
|
[<$name View>] {
|
||||||
|
capacity: self.capacity,
|
||||||
|
count: self.count.as_device_ptr().as_mut_ptr(),
|
||||||
|
$(
|
||||||
|
$field: self.$field.as_device_ptr().as_raw() as *mut $type,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paste::paste! {
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct [<$name View>] {
|
||||||
|
pub capacity: u32,
|
||||||
|
pub count: *mut u32,
|
||||||
|
$(
|
||||||
|
pub $field: *mut $type,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl cust::memory::DeviceCopy for [<$name View>] {}
|
||||||
|
|
||||||
|
impl [<$name View>] {
|
||||||
|
// The raw push that fills every field
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
pub unsafe fn push(&self, $( $field : $type ),* ) -> Option<u32> {
|
||||||
|
use core::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
|
||||||
|
let index = unsafe {
|
||||||
|
let counter_ptr = self.count as *mut AtomicU32;
|
||||||
|
(*counter_ptr).fetch_add(1, Ordering::Relaxed)
|
||||||
|
};
|
||||||
|
|
||||||
|
if index >= self.capacity {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
$(
|
||||||
|
*self.$field.add(index as usize) = $field;
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
pub unsafe fn size(&self) -> u32 {
|
||||||
|
use core::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
unsafe {
|
||||||
|
(*(self.count as *const AtomicU32)).load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
pub fn [<$field _ptr>](&self) -> *mut $type {
|
||||||
|
self.$field
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub struct RaySamplesDirect {
|
||||||
|
pub u: Point2f,
|
||||||
|
pub uc: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub struct RaySamplesIndirect {
|
||||||
|
pub uc: Float,
|
||||||
|
pub rr: Float,
|
||||||
|
pub u: Point2f,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub struct RaySamplesSubsurface {
|
||||||
|
pub uc: Float,
|
||||||
|
pub u: Point2f,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub struct RaySamples {
|
||||||
|
pub direct: RaySamplesDirect,
|
||||||
|
pub indirect: RaySamplesIndirect,
|
||||||
|
pub have_subsurface: bool,
|
||||||
|
pub subsurface: RaySamplesSubsurface,
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct RayQueue {
|
||||||
|
pub ray_o: Point3f,
|
||||||
|
pub ray_d: Vector3f,
|
||||||
|
|
||||||
|
pub depth: i32,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub r_l: SampledSpectrum,
|
||||||
|
|
||||||
|
pub ctx_pi: Point3f,
|
||||||
|
pub ctx_n: Normal3f,
|
||||||
|
pub ctx_ns: Normal3f,
|
||||||
|
|
||||||
|
pub eta_scale: Float,
|
||||||
|
pub specular_bounce: u32,
|
||||||
|
pub any_non_specular_bounces: u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct PixelSampleStateStorage {
|
||||||
|
pub p_pixel: Point2i,
|
||||||
|
pub l: SampledSpectrum,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub filter_weight: Float,
|
||||||
|
pub visible_surface: u32,
|
||||||
|
pub camera_ray_weight: SampledSpectrum,
|
||||||
|
|
||||||
|
pub rs_direct_packed: Float4,
|
||||||
|
pub rs_indirect_packed: Float4,
|
||||||
|
pub rs_subsurface_packed: Float4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct EscapedRayQueue {
|
||||||
|
pub ray_o: Point3f,
|
||||||
|
pub ray_d: Vector3f,
|
||||||
|
pub depth: i32,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub specular_bounce: u32,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub r_l: SampledSpectrum,
|
||||||
|
pub ctx_pi: Point3f,
|
||||||
|
pub ctx_n: Normal3f,
|
||||||
|
pub ctx_ns: Normal3f,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct HitAreaLightQueue {
|
||||||
|
pub area_light_id: u32, // Light ID
|
||||||
|
pub p: Point3f,
|
||||||
|
pub n: Normal3f,
|
||||||
|
pub uv: Point2f,
|
||||||
|
pub wo: Vector3f,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub depth: i32,
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub r_l: SampledSpectrum,
|
||||||
|
pub ctx_pi: Point3f,
|
||||||
|
pub ctx_n: Normal3f,
|
||||||
|
pub ctx_ns: Normal3f,
|
||||||
|
pub specular_bounce: u32,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct ShadowRayQueue {
|
||||||
|
pub ray_o: Point3f,
|
||||||
|
pub ray_d: Vector3f,
|
||||||
|
pub t_max: Float,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub ld: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub r_l: SampledSpectrum,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct GetBSSRDFAndProbeRayQueue {
|
||||||
|
pub material_id: u32,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub p: Point3f,
|
||||||
|
pub wo: Vector3f,
|
||||||
|
pub n: Normal3f,
|
||||||
|
pub ns: Normal3f,
|
||||||
|
pub dpdus: Vector3f,
|
||||||
|
pub uv: Point2f,
|
||||||
|
pub depth: i32,
|
||||||
|
pub mi_inside: u32,
|
||||||
|
pub mi_outside: u32,
|
||||||
|
pub eta_scale: Float,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct SubsurfaceScatterQueue {
|
||||||
|
pub p0: Point3f,
|
||||||
|
pub p1: Point3f,
|
||||||
|
pub depth: i32,
|
||||||
|
pub material_id: u32,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub mi_inside: u32,
|
||||||
|
pub mi_outside: u32,
|
||||||
|
pub eta_scale: Float,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct MediumSampleQueue {
|
||||||
|
pub ray_o: Point3f,
|
||||||
|
pub ray_d: Vector3f,
|
||||||
|
pub t_max: Float,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub r_l: SampledSpectrum,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
|
||||||
|
pub ctx_pi: Point3f,
|
||||||
|
pub ctx_n: Normal3f,
|
||||||
|
pub ctx_ns: Normal3f,
|
||||||
|
|
||||||
|
pub specular_bounce: u32,
|
||||||
|
pub any_non_specular_bounces: u32,
|
||||||
|
pub eta_scale: Float,
|
||||||
|
|
||||||
|
pub area_light_id: u32,
|
||||||
|
pub pi: Point3fi,
|
||||||
|
pub n: Normal3f,
|
||||||
|
pub dpdu: Vector3f,
|
||||||
|
pub dpdv: Vector3f,
|
||||||
|
pub wo: Vector3f,
|
||||||
|
pub uv: Point2f,
|
||||||
|
pub material_id: u32,
|
||||||
|
pub ns: Normal3f,
|
||||||
|
pub dpdus: Vector3f,
|
||||||
|
pub dpdvs: Vector3f,
|
||||||
|
pub dndus: Normal3f,
|
||||||
|
pub dndvs: Normal3f,
|
||||||
|
pub face_index: i32,
|
||||||
|
pub mi_inside: u32,
|
||||||
|
pub mi_outside: u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct MaterialEvalQueue {
|
||||||
|
pub material_id: u32,
|
||||||
|
pub pi: Point3fi,
|
||||||
|
pub n: Normal3f,
|
||||||
|
pub dpdu: Vector3f,
|
||||||
|
pub dpdv: Vector3f,
|
||||||
|
pub time: Float,
|
||||||
|
pub depth: i32,
|
||||||
|
pub ns: Normal3f,
|
||||||
|
pub dpdus: Vector3f,
|
||||||
|
pub dpdvs: Vector3f,
|
||||||
|
pub dndus: Normal3f,
|
||||||
|
pub dndvs: Normal3f,
|
||||||
|
pub uv: Point2f,
|
||||||
|
pub face_index: i32,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
pub any_non_specular_bounces: u32,
|
||||||
|
pub wo: Vector3f,
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub eta_scale: Float,
|
||||||
|
pub mi_inside: u32,
|
||||||
|
pub mi_outside: u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct MediumScatterQueue {
|
||||||
|
pub p: Point3f,
|
||||||
|
pub depth: usize,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub wo: Vector3f,
|
||||||
|
pub time: Float,
|
||||||
|
pub eta_scale: Float,
|
||||||
|
pub pixel_index: usize,
|
||||||
|
|
||||||
|
// ID
|
||||||
|
pub phase_function: u32,
|
||||||
|
pub medium: u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct RayWorkItem {
|
||||||
|
pub ray: Ray,
|
||||||
|
pub depth: i32,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub r_l: SampledSpectrum,
|
||||||
|
pub prev_intr_ctx: LightSampleContext,
|
||||||
|
pub eta_scale: Float,
|
||||||
|
pub specular_bounce: bool,
|
||||||
|
pub any_non_specular_bounces: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct EscapedRayWorkItem {
|
||||||
|
pub ray_o: Point3f,
|
||||||
|
pub ray_d: Vector3f,
|
||||||
|
pub depth: i32,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub specular_bounce: bool,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub r_l: SampledSpectrum,
|
||||||
|
pub prev_intr_ctx: LightSampleContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct ShadowRayWorkItem {
|
||||||
|
pub ray: Ray,
|
||||||
|
pub t_max: Float,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub ld: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub r_l: SampledSpectrum,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RayQueueView {
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
pub unsafe fn push_work_item(&self, item: RayWorkItem) -> Option<u32> {
|
||||||
|
unsafe {
|
||||||
|
self.push(
|
||||||
|
item.ray.o,
|
||||||
|
item.ray.d,
|
||||||
|
item.depth,
|
||||||
|
item.lambda,
|
||||||
|
item.pixel_index,
|
||||||
|
item.beta,
|
||||||
|
item.r_u,
|
||||||
|
item.r_l,
|
||||||
|
item.prev_intr_ctx.pi.into(),
|
||||||
|
item.prev_intr_ctx.n,
|
||||||
|
item.prev_intr_ctx.ns,
|
||||||
|
item.eta_scale,
|
||||||
|
if item.specular_bounce { 1 } else { 0 },
|
||||||
|
if item.any_non_specular_bounces { 1 } else { 0 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EscapedRayQueueView {
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
pub unsafe fn push_work_item(&self, r: &RayWorkItem) -> Option<u32> {
|
||||||
|
unsafe {
|
||||||
|
self.push(
|
||||||
|
r.ray.o,
|
||||||
|
r.ray.d,
|
||||||
|
r.depth,
|
||||||
|
r.lambda,
|
||||||
|
r.pixel_index,
|
||||||
|
r.beta,
|
||||||
|
if r.specular_bounce { 1 } else { 0 },
|
||||||
|
r.r_u,
|
||||||
|
r.r_l,
|
||||||
|
r.prev_intr_ctx.pi.into(),
|
||||||
|
r.prev_intr_ctx.n,
|
||||||
|
r.prev_intr_ctx.ns,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PixelSampleStateStorageView {
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
pub unsafe fn get_samples(&self, index: u32) -> RaySamples {
|
||||||
|
let i = index as usize;
|
||||||
|
|
||||||
|
let (dir, ind, ss) = unsafe {
|
||||||
|
(
|
||||||
|
*self.rs_direct_packed.add(i),
|
||||||
|
*self.rs_indirect_packed.add(i),
|
||||||
|
*self.rs_subsurface_packed.add(i),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let direct_u = Point2f::new(dir.v[0], dir.v[1]);
|
||||||
|
let direct_uc = dir.v[2];
|
||||||
|
let flags = dir.v[3] as i32;
|
||||||
|
let have_subsurface = (flags & 1) != 0;
|
||||||
|
|
||||||
|
let indirect_uc = ind.v[0];
|
||||||
|
let indirect_rr = ind.v[1];
|
||||||
|
let indirect_u = Point2f::new(ind.v[2], ind.v[3]);
|
||||||
|
|
||||||
|
let subsurface_uc = ss.v[0];
|
||||||
|
let subsurface_u = Point2f::new(ss.v[1], ss.v[2]);
|
||||||
|
|
||||||
|
RaySamples {
|
||||||
|
direct: RaySamplesDirect {
|
||||||
|
u: direct_u,
|
||||||
|
uc: direct_uc,
|
||||||
|
},
|
||||||
|
indirect: RaySamplesIndirect {
|
||||||
|
uc: indirect_uc,
|
||||||
|
rr: indirect_rr,
|
||||||
|
u: indirect_u,
|
||||||
|
},
|
||||||
|
have_subsurface,
|
||||||
|
subsurface: RaySamplesSubsurface {
|
||||||
|
uc: subsurface_uc,
|
||||||
|
u: subsurface_u,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
pub unsafe fn set_samples(&self, index: u32, rs: RaySamples) {
|
||||||
|
if index >= self.capacity {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let i = index as usize;
|
||||||
|
|
||||||
|
let flags = if rs.have_subsurface { 1.0 } else { 0.0 };
|
||||||
|
let dir = Float4 {
|
||||||
|
v: [rs.direct.u.0[0], rs.direct.u.0[1], rs.direct.uc, flags],
|
||||||
|
};
|
||||||
|
|
||||||
|
let ind = Float4 {
|
||||||
|
v: [
|
||||||
|
rs.indirect.uc,
|
||||||
|
rs.indirect.rr,
|
||||||
|
rs.indirect.u.0[0],
|
||||||
|
rs.indirect.u.0[1],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
*self.rs_direct_packed.add(i) = dir;
|
||||||
|
*self.rs_indirect_packed.add(i) = ind;
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.have_subsurface {
|
||||||
|
let ss = Float4 {
|
||||||
|
v: [
|
||||||
|
rs.subsurface.uc,
|
||||||
|
rs.subsurface.u.0[0],
|
||||||
|
rs.subsurface.u.0[1],
|
||||||
|
0.0,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
*self.rs_subsurface_packed.add(i) = ss;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
extern "C" __global__ void scale_array(float* data, unsigned int len, float scale) {
|
|
||||||
unsigned int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
|
||||||
if (idx >= len) return;
|
|
||||||
data[idx] *= scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" __global__ void add_arrays(
|
|
||||||
const float* __restrict__ a,
|
|
||||||
const float* __restrict__ b,
|
|
||||||
float* __restrict__ c,
|
|
||||||
unsigned int len
|
|
||||||
) {
|
|
||||||
unsigned int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
|
||||||
if (idx >= len) return;
|
|
||||||
c[idx] = a[idx] + b[idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" __global__ void saxpy(
|
|
||||||
float a,
|
|
||||||
const float* __restrict__ x,
|
|
||||||
float* __restrict__ y,
|
|
||||||
unsigned int len
|
|
||||||
) {
|
|
||||||
unsigned int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
|
||||||
if (idx >= len) return;
|
|
||||||
y[idx] = a * x[idx] + y[idx];
|
|
||||||
}
|
|
||||||
|
|
@ -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"]
|
|
||||||
|
|
|
||||||
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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,44 +163,49 @@ pub trait CameraTrait {
|
||||||
sample: CameraSample,
|
sample: CameraSample,
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
) -> Option<CameraRay> {
|
) -> Option<CameraRay> {
|
||||||
let mut central_cam_ray = self.generate_ray(sample, lambda)?;
|
match self {
|
||||||
let mut rd = RayDifferential::default();
|
Camera::Orthographic(c) => c.generate_ray_differential(sample, lambda),
|
||||||
let mut rx_found = false;
|
_ => {
|
||||||
let mut ry_found = false;
|
let mut central_cam_ray = self.generate_ray(sample, lambda)?;
|
||||||
|
let mut rd = RayDifferential::default();
|
||||||
|
let mut rx_found = false;
|
||||||
|
let mut ry_found = false;
|
||||||
|
|
||||||
for eps in [0.05, -0.05] {
|
for eps in [0.05, -0.05] {
|
||||||
let mut s_shift = sample;
|
let mut s_shift = sample;
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for eps in [0.05, -0.05] {
|
||||||
|
let mut s_shift = sample;
|
||||||
|
s_shift.p_film[1] += eps;
|
||||||
|
|
||||||
|
if let Some(ry_cam_ray) = self.generate_ray(s_shift, lambda) {
|
||||||
|
rd.ry_origin = central_cam_ray.ray.o
|
||||||
|
+ (ry_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
|
||||||
|
rd.ry_direction = central_cam_ray.ray.d
|
||||||
|
+ (ry_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
|
||||||
|
ry_found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rx_found && ry_found {
|
||||||
|
central_cam_ray.ray.differential = rd;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(central_cam_ray)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for eps in [0.05, -0.05] {
|
|
||||||
let mut s_shift = sample;
|
|
||||||
s_shift.p_film[1] += eps;
|
|
||||||
|
|
||||||
if let Some(ry_cam_ray) = self.generate_ray(s_shift, lambda) {
|
|
||||||
rd.ry_origin =
|
|
||||||
central_cam_ray.ray.o + (ry_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
|
|
||||||
rd.ry_direction =
|
|
||||||
central_cam_ray.ray.d + (ry_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
|
|
||||||
ry_found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rx_found && ry_found {
|
|
||||||
central_cam_ray.ray.differential = rd;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(central_cam_ray)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn approximate_dp_dxy(
|
fn approximate_dp_dxy(
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
|
||||||
2
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if self.g < self.b {
|
if self.g < self.b { 1 } else { 2 }
|
||||||
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 {
|
|
||||||
2
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if self.g > self.b {
|
if self.g > self.b { 1 } else { 2 }
|
||||||
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);
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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};
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
|
||||||
}
|
|
||||||
|
|
@ -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.;
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = self.pixel_offset(p) + c;
|
||||||
|
unsafe {
|
||||||
|
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 pixel_offset(&self, p: Point2i) -> usize {
|
pub fn get_channel(&self, p: Point2i, c: i32) -> Float {
|
||||||
let width = self.resolution.x() as usize;
|
self.get_channel_with_wrap(p, c, WrapMode::Clamp.into())
|
||||||
(p.y() as usize * width + p.x() as usize) * self.n_channels as usize
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
BlackbodySpectrum::new(temp).sample(lambda)
|
let temp = grid.lookup(p);
|
||||||
} else {
|
BlackbodySpectrum::new(temp).sample(lambda)
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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};
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -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,57 +92,70 @@ 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 {
|
pub static RARE_EVENT_TOTAL_CALLS: AtomicU64 = AtomicU64::new(0);
|
||||||
use core::sync::atomic::AtomicU64;
|
pub static RARE_EVENT_CONDITION_MET: AtomicU64 = AtomicU64::new(0);
|
||||||
use core::sync::atomic::Ordering as SyncOrdering;
|
|
||||||
pub static RARE_EVENT_TOTAL_CALLS: AtomicU64 = AtomicU64::new(0);
|
|
||||||
pub static RARE_EVENT_CONDITION_MET: AtomicU64 = AtomicU64::new(0);
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! check_rare {
|
macro_rules! check_rare {
|
||||||
($frequency_threshold:expr, $condition:expr) => {
|
($frequency_threshold:expr, $condition:expr) => {
|
||||||
use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
|
use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
|
||||||
const CHECK_INTERVAL: u64 = 4096;
|
const CHECK_INTERVAL: u64 = 4096;
|
||||||
|
|
||||||
let total_calls = RARE_EVENT_TOTAL_CALLS.fetch_add(1, SyncOrdering::Relaxed);
|
let total_calls = RARE_EVENT_TOTAL_CALLS.fetch_add(1, SyncOrdering::Relaxed);
|
||||||
|
|
||||||
if $condition {
|
if $condition {
|
||||||
RARE_EVENT_CONDITION_MET.fetch_add(1, SyncOrdering::Relaxed);
|
RARE_EVENT_CONDITION_MET.fetch_add(1, SyncOrdering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (total_calls + 1) % CHECK_INTERVAL == 0 {
|
if (total_calls + 1) % CHECK_INTERVAL == 0 {
|
||||||
let met_count = RARE_EVENT_CONDITION_MET.load(SyncOrdering::Relaxed);
|
let met_count = RARE_EVENT_CONDITION_MET.load(SyncOrdering::Relaxed);
|
||||||
if met_count > 0 {
|
if met_count > 0 {
|
||||||
let frequency = met_count as f64 / (total_calls + 1) as f64;
|
let frequency = met_count as f64 / (total_calls + 1) as f64;
|
||||||
if frequency > $frequency_threshold {
|
if frequency > $frequency_threshold {
|
||||||
panic!(
|
panic!(
|
||||||
"Rare event occurred with frequency {} which is > threshold {}",
|
"Rare event occurred with frequency {} which is > threshold {}",
|
||||||
frequency, $frequency_threshold
|
frequency, $frequency_threshold
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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))
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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]);
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,28 @@
|
||||||
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 }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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,29 +11,14 @@ pub struct MitchellFilter {
|
||||||
pub sampler: FilterSampler,
|
pub sampler: FilterSampler,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mitchell_1d_eval(b: Float, c: Float, x: Float) -> Float {
|
|
||||||
let x = x.abs();
|
|
||||||
if x <= 1.0 {
|
|
||||||
((12.0 - 9.0 * b - 6.0 * c) * x.powi(3)
|
|
||||||
+ (-18.0 + 12.0 * b + 6.0 * c) * x.powi(2)
|
|
||||||
+ (6.0 - 2.0 * b))
|
|
||||||
* (1.0 / 6.0)
|
|
||||||
} else if x <= 2.0 {
|
|
||||||
((-b - 6.0 * c) * x.powi(3)
|
|
||||||
+ (6.0 * b + 30.0 * c) * x.powi(2)
|
|
||||||
+ (-12.0 * b - 48.0 * c) * x
|
|
||||||
+ (8.0 * b + 24.0 * c))
|
|
||||||
* (1.0 / 6.0)
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MitchellFilter {
|
impl MitchellFilter {
|
||||||
pub fn new(radius: Vector2f, b: Float, c: Float) -> Self {
|
pub fn new(radius: Vector2f, b: Float, c: Float) -> Self {
|
||||||
let sampler = FilterSampler::new(radius, move |p: Point2f| {
|
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)
|
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 {
|
Self {
|
||||||
radius,
|
radius,
|
||||||
b,
|
b,
|
||||||
|
|
@ -43,8 +27,26 @@ impl MitchellFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mitchell_1d_eval(b: Float, c: Float, x: Float) -> Float {
|
||||||
|
let x = x.abs();
|
||||||
|
if x <= 1.0 {
|
||||||
|
((12.0 - 9.0 * b - 6.0 * c) * x.powi(3)
|
||||||
|
+ (-18.0 + 12.0 * b + 6.0 * c) * x.powi(2)
|
||||||
|
+ (6.0 - 2.0 * b))
|
||||||
|
* (1.0 / 6.0)
|
||||||
|
} else if x <= 2.0 {
|
||||||
|
((-b - 6.0 * c) * x.powi(3)
|
||||||
|
+ (6.0 * b + 30.0 * c) * x.powi(2)
|
||||||
|
+ (-12.0 * b - 48.0 * c) * x
|
||||||
|
+ (8.0 * b + 24.0 * c))
|
||||||
|
* (1.0 / 6.0)
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)]
|
||||||
|
|
|
||||||
|
|
@ -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};
|
|
||||||
|
|
|
||||||
|
|
@ -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,11 +149,13 @@ 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(),
|
||||||
.sample(&lambda);
|
rgb.clamp_zero(),
|
||||||
|
)
|
||||||
|
.sample(&lambda);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
l *= self.scale / (self.image.resolution().x() * self.image.resolution().y()) as Float;
|
l *= self.scale / (self.image.resolution().x() * self.image.resolution().y()) as Float;
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
fn bounds(&self) -> Option<LightBounds> {
|
_p: Point3f,
|
||||||
let mut sum_y = 0.;
|
_n: Normal3f,
|
||||||
for x in 0..self.image.resolution().x() {
|
_uv: Point2f,
|
||||||
for y in 0..self.image.resolution().y() {
|
_w: Vector3f,
|
||||||
sum_y += self.image.get_channel(Point2i::new(x, y), 0);
|
_lambda: &SampledWavelengths,
|
||||||
}
|
) -> SampledSpectrum {
|
||||||
}
|
todo!()
|
||||||
let phi = self.scale * self.iemit.max_value() * 4. * PI * sum_y
|
}
|
||||||
/ (self.image.resolution().x() * self.image.resolution().y()) as f32;
|
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||||
let p = self
|
todo!()
|
||||||
.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 preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
fn bounds(&self) -> Option<LightBounds> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
Point2i::new(u, v),
|
for c in 0..3 {
|
||||||
WrapMode::OctahedralSphere.into(),
|
rgb[c] = self.image.get_channel_with_wrap(
|
||||||
));
|
Point2i::new(u, v),
|
||||||
sum_l += RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero())
|
c as i32,
|
||||||
|
WrapMode::OctahedralSphere.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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(gpu))]
|
#[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();
|
(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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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>(
|
||||||
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 {
|
||||||
|
|
|
||||||
|
|
@ -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!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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."
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 disk’s 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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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};
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
mesh.vertex_indices[base] as usize,
|
.vertex_indices
|
||||||
mesh.vertex_indices[base + 1] as usize,
|
.0
|
||||||
mesh.vertex_indices[base + 2] as usize,
|
.add((self.tri_index as usize) * 3);
|
||||||
]
|
[
|
||||||
|
*base_ptr.add(0) as usize,
|
||||||
|
*base_ptr.add(1) 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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};
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,52 +23,48 @@ 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,33 +74,30 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
} else {
|
|
||||||
RGB::new(
|
let d_p_dx = [c.dsdx, c.dtdx];
|
||||||
pixel0,
|
let d_p_dy = [c.dsdy, c.dtdy];
|
||||||
image.bilerp_channel_with_wrap(c.st, 1, wrap),
|
|
||||||
image.bilerp_channel_with_wrap(c.st, 2, wrap),
|
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) };
|
||||||
let n = DIAG_IMG_COUNT.fetch_add(1, Ordering::Relaxed);
|
RGB::new(val, val, val)
|
||||||
if n < 10 {
|
} else {
|
||||||
DIAG_IMG_SCALE_BITS.store(self.scale.to_bits(), Ordering::Relaxed);
|
let val: [Float; 4] =
|
||||||
DIAG_IMG_PIXEL0_BITS.store(pixel0.to_bits(), Ordering::Relaxed);
|
unsafe { intrinsics::tex2d_grad(self.tex_obj, u, v, d_p_dx, d_p_dy) };
|
||||||
DIAG_IMG_RGB0_BITS.store((rgb[0] as f32).to_bits(), Ordering::Relaxed);
|
RGB::new(val[0], val[1], val[2])
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut rgb = tex_color * self.scale;
|
||||||
|
if self.invert {
|
||||||
|
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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let mut rgb = rgb * self.scale;
|
|
||||||
if self.invert {
|
|
||||||
rgb = (RGB::new(1.0, 1.0, 1.0) - rgb);
|
|
||||||
}
|
|
||||||
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);
|
if self.invert {
|
||||||
let val: Float = 0.;
|
return (1. - v).max(0.);
|
||||||
let result = if self.invert { (1.0 - val).max(0.0) } else { val };
|
} else {
|
||||||
return result * self.scale;
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
Loading…
Reference in a new issue