807 lines
23 KiB
Rust
807 lines
23 KiB
Rust
use crate::core::filter::FilterTrait;
|
|
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::utils::math::{
|
|
BinaryPermuteScrambler, DigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler,
|
|
PRIME_TABLE_SIZE, Scrambler, clamp, encode_morton_2, inverse_radical_inverse, lerp, log2_int,
|
|
owen_scrambled_radical_inverse, permutation_element, radical_inverse, round_up_pow2,
|
|
scrambled_radical_inverse, sobol_interval_to_index, sobol_sample,
|
|
};
|
|
use crate::utils::rng::Rng;
|
|
use crate::utils::sobol::N_SOBOL_DIMENSIONS;
|
|
use crate::utils::{hash::*, sobol};
|
|
use crate::{GVec, Ptr, gvec};
|
|
use enum_dispatch::enum_dispatch;
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug, Default, Clone, Copy)]
|
|
pub struct CameraSample {
|
|
pub p_film: Point2f,
|
|
pub p_lens: Point2f,
|
|
pub time: Float,
|
|
pub filter_weight: Float,
|
|
}
|
|
|
|
pub fn get_camera_sample<S, F>(sampler: &mut S, p_pixel: Point2i, filter: &F) -> CameraSample
|
|
where
|
|
S: SamplerTrait,
|
|
F: FilterTrait,
|
|
{
|
|
let fs = filter.sample(sampler.get_pixel2d());
|
|
CameraSample {
|
|
p_film: Point2f::from(p_pixel) + Vector2f::from(fs.p) + Vector2f::new(0.5, 0.5),
|
|
time: sampler.get1d(),
|
|
p_lens: sampler.get2d(),
|
|
filter_weight: fs.weight,
|
|
}
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Default, Debug, Clone, Copy)]
|
|
pub struct IndependentSampler {
|
|
pub samples_per_pixel: i32,
|
|
pub seed: u64,
|
|
pub rng: Rng,
|
|
}
|
|
|
|
impl IndependentSampler {
|
|
pub fn new(samples_per_pixel: i32, seed: u64) -> Self {
|
|
Self {
|
|
samples_per_pixel,
|
|
seed,
|
|
rng: Rng::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SamplerTrait for IndependentSampler {
|
|
fn samples_per_pixel(&self) -> i32 {
|
|
self.samples_per_pixel
|
|
}
|
|
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
|
|
let hash_input = [p.x() as u64, p.y() as u64, self.seed];
|
|
let 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 {
|
|
self.rng.uniform::<Float>()
|
|
}
|
|
fn get2d(&mut self) -> Point2f {
|
|
Point2f::new(self.rng.uniform::<Float>(), self.rng.uniform::<Float>())
|
|
}
|
|
fn get_pixel2d(&mut self) -> Point2f {
|
|
self.get2d()
|
|
}
|
|
}
|
|
|
|
pub const MAX_HALTON_RESOLUTION: i32 = 128;
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Copy)]
|
|
pub enum RandomizeStrategy {
|
|
#[default]
|
|
None,
|
|
PermuteDigits,
|
|
FastOwen,
|
|
Owen,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct HaltonSampler {
|
|
pub samples_per_pixel: i32,
|
|
pub randomize: RandomizeStrategy,
|
|
pub base_scales: [u64; 2],
|
|
pub base_exponents: [u64; 2],
|
|
pub mult_inverse: [u64; 2],
|
|
pub halton_index: u64,
|
|
pub dim: u32,
|
|
pub digit_permutations: Ptr<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 {
|
|
pub fn sample_dimension(&self, dimension: u32) -> Float {
|
|
if self.randomize == RandomizeStrategy::None {
|
|
radical_inverse(dimension, self.halton_index)
|
|
} else if self.randomize == RandomizeStrategy::PermuteDigits {
|
|
let digit_perm = unsafe { &*self.digit_permutations.add(dimension as usize) };
|
|
scrambled_radical_inverse(dimension, self.halton_index, digit_perm)
|
|
} else {
|
|
owen_scrambled_radical_inverse(
|
|
dimension,
|
|
self.halton_index,
|
|
mix_bits(1 + ((dimension as u64) << 4)) as u32,
|
|
)
|
|
}
|
|
}
|
|
|
|
pub fn multiplicative_inverse(a: i64, n: i64) -> u64 {
|
|
let (x, _) = Self::extended_gcd(a as u64, n as u64);
|
|
x.rem_euclid(n) as u64
|
|
}
|
|
|
|
pub fn extended_gcd(a: u64, b: u64) -> (i64, i64) {
|
|
if b == 0 {
|
|
return (1, 0);
|
|
}
|
|
|
|
let (xp, yp) = Self::extended_gcd(b, a % b);
|
|
|
|
let d = (a / b) as i64;
|
|
|
|
(yp, xp - d * yp)
|
|
}
|
|
}
|
|
|
|
impl SamplerTrait for HaltonSampler {
|
|
fn samples_per_pixel(&self) -> i32 {
|
|
self.samples_per_pixel
|
|
}
|
|
|
|
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
|
|
self.halton_index = 0;
|
|
|
|
let sample_stride = self.base_scales[0] * self.base_scales[1];
|
|
if sample_stride > 1 {
|
|
let pm_x = p.x().rem_euclid(MAX_HALTON_RESOLUTION) as u64;
|
|
let pm_y = p.y().rem_euclid(MAX_HALTON_RESOLUTION) as u64;
|
|
|
|
let dim_offset_x = inverse_radical_inverse(pm_x, 2, self.base_exponents[0]);
|
|
|
|
self.halton_index = self.halton_index.wrapping_add(
|
|
dim_offset_x
|
|
.wrapping_mul(sample_stride / self.base_scales[0])
|
|
.wrapping_mul(self.mult_inverse[0]),
|
|
);
|
|
|
|
let dim_offset_y = inverse_radical_inverse(pm_y, 3, self.base_exponents[1]);
|
|
|
|
self.halton_index = self.halton_index.wrapping_add(
|
|
dim_offset_y
|
|
.wrapping_mul(sample_stride / self.base_scales[1])
|
|
.wrapping_mul(self.mult_inverse[1]),
|
|
);
|
|
|
|
self.halton_index %= sample_stride;
|
|
}
|
|
|
|
self.halton_index = self
|
|
.halton_index
|
|
.wrapping_add((sample_index as u64).wrapping_mul(sample_stride));
|
|
|
|
self.dim = 2.max(dim.unwrap_or(0));
|
|
}
|
|
|
|
fn get1d(&mut self) -> Float {
|
|
// pbrt: `SampleDimension(dimension++)` -- POST-increment. Pre-incrementing makes
|
|
// the next Get2D() reuse the dimension this call just consumed.
|
|
if self.dim >= PRIME_TABLE_SIZE as u32 {
|
|
self.dim = 2;
|
|
}
|
|
let dim = self.dim;
|
|
self.dim += 1;
|
|
self.sample_dimension(dim)
|
|
}
|
|
|
|
fn get2d(&mut self) -> Point2f {
|
|
if self.dim + 1 >= PRIME_TABLE_SIZE as u32 {
|
|
self.dim = 2;
|
|
}
|
|
let dim = self.dim;
|
|
self.dim += 2;
|
|
Point2f::new(self.sample_dimension(dim), self.sample_dimension(dim + 1))
|
|
}
|
|
|
|
fn get_pixel2d(&mut self) -> Point2f {
|
|
Point2f::new(
|
|
radical_inverse(0, self.halton_index >> self.base_exponents[0]),
|
|
radical_inverse(1, self.halton_index / self.base_scales[1]),
|
|
)
|
|
}
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Default, Debug, Clone, Copy)]
|
|
pub struct StratifiedSampler {
|
|
x_pixel_samples: i32,
|
|
y_pixel_samples: i32,
|
|
jitter: bool,
|
|
seed: u64,
|
|
rng: Rng,
|
|
pixel: Point2i,
|
|
sample_index: i32,
|
|
dim: u32,
|
|
}
|
|
|
|
impl StratifiedSampler {
|
|
pub fn new(
|
|
x_pixel_samples: i32,
|
|
y_pixel_samples: i32,
|
|
seed: Option<u64>,
|
|
jitter: bool,
|
|
) -> Self {
|
|
Self {
|
|
x_pixel_samples,
|
|
y_pixel_samples,
|
|
jitter,
|
|
seed: seed.unwrap_or(0),
|
|
rng: Rng::default(),
|
|
pixel: Point2i::default(),
|
|
sample_index: 0,
|
|
dim: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SamplerTrait for StratifiedSampler {
|
|
fn samples_per_pixel(&self) -> i32 {
|
|
self.x_pixel_samples * self.y_pixel_samples
|
|
}
|
|
|
|
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
|
|
self.pixel = p;
|
|
self.sample_index = sample_index;
|
|
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 {
|
|
let hash_input = [
|
|
self.pixel.x() as u64,
|
|
self.pixel.y() as u64,
|
|
self.dim as u64,
|
|
self.seed,
|
|
];
|
|
let hash = hash_buffer(&hash_input, 0);
|
|
let stratum = permutation_element(
|
|
self.sample_index as u32,
|
|
self.samples_per_pixel() as u32,
|
|
hash as u32,
|
|
);
|
|
self.dim += 1;
|
|
|
|
let delta = if self.jitter {
|
|
self.rng.uniform::<Float>()
|
|
} else {
|
|
0.5
|
|
};
|
|
(stratum as Float + delta) / (self.samples_per_pixel() as Float)
|
|
}
|
|
|
|
fn get2d(&mut self) -> Point2f {
|
|
let hash_input = [
|
|
self.pixel.x() as u64,
|
|
self.pixel.y() as u64,
|
|
self.dim as u64,
|
|
self.seed,
|
|
];
|
|
let hash = hash_buffer(&hash_input, 0);
|
|
let stratum = permutation_element(
|
|
self.sample_index as u32,
|
|
self.samples_per_pixel() as u32,
|
|
hash as u32,
|
|
);
|
|
self.dim += 2;
|
|
// pbrt: both the modulus and the divisor are xPixelSamples.
|
|
let x = stratum % self.x_pixel_samples as u32;
|
|
let y = stratum / self.x_pixel_samples as u32;
|
|
let dx = if self.jitter {
|
|
self.rng.uniform::<Float>()
|
|
} else {
|
|
0.5
|
|
};
|
|
let dy = if self.jitter {
|
|
self.rng.uniform::<Float>()
|
|
} else {
|
|
0.5
|
|
};
|
|
Point2f::new(
|
|
(x as Float + dx) / self.x_pixel_samples as Float,
|
|
(y as Float + dy) / self.y_pixel_samples as Float,
|
|
)
|
|
}
|
|
|
|
fn get_pixel2d(&mut self) -> Point2f {
|
|
self.get2d()
|
|
}
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Default, Debug, Clone, Copy)]
|
|
pub struct PaddedSobolSampler {
|
|
samples_per_pixel: i32,
|
|
seed: u64,
|
|
randomize: RandomizeStrategy,
|
|
pixel: Point2i,
|
|
sample_index: i32,
|
|
dim: u32,
|
|
}
|
|
|
|
impl PaddedSobolSampler {
|
|
pub fn new(samples_per_pixel: i32, randomize: RandomizeStrategy, seed: Option<u64>) -> Self {
|
|
Self {
|
|
samples_per_pixel,
|
|
seed: seed.unwrap_or(0),
|
|
randomize,
|
|
pixel: Point2i::default(),
|
|
sample_index: 0,
|
|
dim: 0,
|
|
}
|
|
}
|
|
|
|
fn sample_dimension(&self, dimension: u32, a: u32, hash: u32) -> Float {
|
|
if self.randomize == RandomizeStrategy::None {
|
|
return sobol_sample(a as u64, dimension, NoRandomizer);
|
|
}
|
|
match self.randomize {
|
|
RandomizeStrategy::PermuteDigits => {
|
|
sobol_sample(a as u64, dimension, BinaryPermuteScrambler::new(hash))
|
|
}
|
|
RandomizeStrategy::FastOwen => {
|
|
sobol_sample(a as u64, dimension, FastOwenScrambler::new(hash))
|
|
}
|
|
RandomizeStrategy::Owen => sobol_sample(a as u64, dimension, OwenScrambler::new(hash)),
|
|
RandomizeStrategy::None => unreachable!(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SamplerTrait for PaddedSobolSampler {
|
|
fn samples_per_pixel(&self) -> i32 {
|
|
self.samples_per_pixel
|
|
}
|
|
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
|
|
self.pixel = p;
|
|
self.sample_index = sample_index;
|
|
self.dim = dim.unwrap_or(0);
|
|
}
|
|
|
|
fn get1d(&mut self) -> Float {
|
|
let hash_input = [
|
|
self.pixel.x() as u64,
|
|
self.pixel.y() as u64,
|
|
self.dim as u64,
|
|
self.seed,
|
|
];
|
|
let hash = hash_buffer(&hash_input, 0);
|
|
let index = permutation_element(
|
|
self.sample_index as u32,
|
|
self.samples_per_pixel as u32,
|
|
hash as u32,
|
|
);
|
|
self.dim += 1;
|
|
self.sample_dimension(0, index, (hash >> 32) as u32)
|
|
}
|
|
fn get2d(&mut self) -> Point2f {
|
|
let hash_input = [
|
|
self.pixel.x() as u64,
|
|
self.pixel.y() as u64,
|
|
self.dim as u64,
|
|
self.seed,
|
|
];
|
|
let hash = hash_buffer(&hash_input, 0);
|
|
let index = permutation_element(
|
|
self.sample_index as u32,
|
|
self.samples_per_pixel as u32,
|
|
hash as u32,
|
|
);
|
|
self.dim += 2;
|
|
Point2f::new(
|
|
self.sample_dimension(0, index, hash as u32),
|
|
self.sample_dimension(1, index, (hash >> 32) as u32),
|
|
)
|
|
}
|
|
|
|
fn get_pixel2d(&mut self) -> Point2f {
|
|
self.get2d()
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone)]
|
|
pub struct SobolSampler {
|
|
samples_per_pixel: i32,
|
|
scale: i32,
|
|
seed: u64,
|
|
randomize: RandomizeStrategy,
|
|
pixel: Point2i,
|
|
dim: u32,
|
|
sobol_index: u64,
|
|
}
|
|
|
|
impl SobolSampler {
|
|
pub fn new(
|
|
samples_per_pixel: i32,
|
|
full_resolution: Point2i,
|
|
randomize: RandomizeStrategy,
|
|
seed: Option<u64>,
|
|
) -> Self {
|
|
let scale = round_up_pow2(full_resolution.x().max(full_resolution.y()));
|
|
Self {
|
|
samples_per_pixel,
|
|
scale,
|
|
seed: seed.unwrap_or(0),
|
|
randomize,
|
|
pixel: Point2i::default(),
|
|
dim: 0,
|
|
sobol_index: 0,
|
|
}
|
|
}
|
|
|
|
fn sample_dimension(&self, dimension: u32) -> Float {
|
|
if self.randomize == RandomizeStrategy::None {
|
|
return sobol_sample(self.sobol_index, dimension, NoRandomizer);
|
|
}
|
|
let hash_input = [self.pixel.x() as u64, self.pixel.y() as u64, self.seed];
|
|
let hash = hash_buffer(&hash_input, 0) as u32;
|
|
match self.randomize {
|
|
RandomizeStrategy::PermuteDigits => sobol_sample(
|
|
self.sobol_index,
|
|
dimension,
|
|
BinaryPermuteScrambler::new(hash),
|
|
),
|
|
RandomizeStrategy::FastOwen => {
|
|
sobol_sample(self.sobol_index, dimension, FastOwenScrambler::new(hash))
|
|
}
|
|
RandomizeStrategy::Owen => {
|
|
sobol_sample(self.sobol_index, dimension, OwenScrambler::new(hash))
|
|
}
|
|
RandomizeStrategy::None => unreachable!(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SamplerTrait for SobolSampler {
|
|
fn samples_per_pixel(&self) -> i32 {
|
|
self.samples_per_pixel
|
|
}
|
|
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
|
|
self.pixel = p;
|
|
self.dim = 2.max(dim.unwrap_or(0));
|
|
self.sobol_index =
|
|
sobol_interval_to_index(log2_int(self.scale as Float) as u32, sample_index as u64, p)
|
|
}
|
|
|
|
fn get1d(&mut self) -> Float {
|
|
if self.dim >= N_SOBOL_DIMENSIONS as u32 {
|
|
self.dim = 2;
|
|
}
|
|
|
|
let dim = self.dim;
|
|
self.dim += 1;
|
|
self.sample_dimension(dim)
|
|
}
|
|
|
|
fn get2d(&mut self) -> Point2f {
|
|
if self.dim >= N_SOBOL_DIMENSIONS as u32 {
|
|
self.dim = 2;
|
|
}
|
|
let u = Point2f::new(
|
|
self.sample_dimension(self.dim),
|
|
self.sample_dimension(self.dim + 1),
|
|
);
|
|
self.dim += 2;
|
|
u
|
|
}
|
|
|
|
fn get_pixel2d(&mut self) -> Point2f {
|
|
let mut u = Point2f::new(
|
|
sobol_sample(self.sobol_index, 0, NoRandomizer),
|
|
sobol_sample(self.sobol_index, 1, NoRandomizer),
|
|
);
|
|
u[0] = clamp(
|
|
u[0] * self.scale as Float - self.pixel[0] as Float,
|
|
0.,
|
|
ONE_MINUS_EPSILON,
|
|
) as Float;
|
|
u[1] = clamp(
|
|
u[1] * self.scale as Float - self.pixel[1] as Float,
|
|
0.,
|
|
ONE_MINUS_EPSILON,
|
|
) as Float;
|
|
u
|
|
}
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Default, Copy, Debug, Clone)]
|
|
pub struct ZSobolSampler {
|
|
randomize: RandomizeStrategy,
|
|
seed: u64,
|
|
log2_samples_per_pixel: i32,
|
|
n_base4_digits: u32,
|
|
morton_index: u64,
|
|
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 {
|
|
pub fn new(
|
|
samples_per_pixel: i32,
|
|
full_resolution: Point2i,
|
|
randomize: RandomizeStrategy,
|
|
seed: Option<u64>,
|
|
) -> Self {
|
|
// pbrt calls the integer Log2Int overload; the float one disagrees for
|
|
// 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 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;
|
|
Self {
|
|
randomize,
|
|
seed: seed.unwrap_or(0),
|
|
log2_samples_per_pixel: log2_samples_per_pixel as i32,
|
|
n_base4_digits,
|
|
morton_index: 0,
|
|
dim: 0,
|
|
}
|
|
}
|
|
|
|
fn get_sample_index(&self) -> u64 {
|
|
const PERMUTATIONS: [[u8; 4]; 24] = [
|
|
[0, 1, 2, 3],
|
|
[0, 1, 3, 2],
|
|
[0, 2, 1, 3],
|
|
[0, 2, 3, 1],
|
|
[0, 3, 2, 1],
|
|
[0, 3, 1, 2],
|
|
[1, 0, 2, 3],
|
|
[1, 0, 3, 2],
|
|
[1, 2, 0, 3],
|
|
[1, 2, 3, 0],
|
|
[1, 3, 2, 0],
|
|
[1, 3, 0, 2],
|
|
[2, 1, 0, 3],
|
|
[2, 1, 3, 0],
|
|
[2, 0, 1, 3],
|
|
[2, 0, 3, 1],
|
|
[2, 3, 0, 1],
|
|
[2, 3, 1, 0],
|
|
[3, 1, 2, 0],
|
|
[3, 1, 0, 2],
|
|
[3, 2, 1, 0],
|
|
[3, 2, 0, 1],
|
|
[3, 0, 2, 1],
|
|
[3, 0, 1, 2],
|
|
];
|
|
|
|
let mut sample_index = 0;
|
|
let pow2_samples = (self.log2_samples_per_pixel & 1) != 0;
|
|
let last_digit = if pow2_samples { 1 } else { 0 };
|
|
|
|
for i in (last_digit..self.n_base4_digits).rev() {
|
|
let digit_shift = (2 * i) - if pow2_samples { 1 } else { 0 };
|
|
|
|
let mut digit = (self.morton_index >> digit_shift) & 3;
|
|
|
|
let higher_digits = self.morton_index >> (digit_shift + 2);
|
|
|
|
let mix_input = higher_digits ^ scramble_seed(self.dim);
|
|
let p = (mix_bits(mix_input) >> 24) % 24;
|
|
|
|
digit = PERMUTATIONS[p as usize][digit as usize] as u64;
|
|
|
|
sample_index |= digit << digit_shift;
|
|
}
|
|
|
|
if pow2_samples {
|
|
let digit = self.morton_index & 1;
|
|
sample_index |=
|
|
digit ^ (mix_bits((self.morton_index >> 1) ^ scramble_seed(self.dim)) & 1);
|
|
}
|
|
|
|
sample_index
|
|
}
|
|
}
|
|
|
|
impl SamplerTrait for ZSobolSampler {
|
|
fn samples_per_pixel(&self) -> i32 {
|
|
1 << self.log2_samples_per_pixel
|
|
}
|
|
|
|
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
|
|
self.dim = dim.unwrap_or(0);
|
|
self.morton_index = (encode_morton_2(p.x() as u32, p.y() as u32)
|
|
<< self.log2_samples_per_pixel)
|
|
| sample_index as u64
|
|
}
|
|
|
|
fn get1d(&mut self) -> Float {
|
|
let sample_index = self.get_sample_index();
|
|
self.dim += 1;
|
|
let hash = dim_seed_hash(self.dim, self.seed) as u32;
|
|
// Always Sobol dimension 0 -- decorrelation comes from the hash.
|
|
match self.randomize {
|
|
RandomizeStrategy::None => sobol_sample(sample_index, 0, NoRandomizer),
|
|
RandomizeStrategy::PermuteDigits => {
|
|
sobol_sample(sample_index, 0, BinaryPermuteScrambler::new(hash))
|
|
}
|
|
RandomizeStrategy::FastOwen => {
|
|
sobol_sample(sample_index, 0, FastOwenScrambler::new(hash))
|
|
}
|
|
RandomizeStrategy::Owen => sobol_sample(sample_index, 0, OwenScrambler::new(hash)),
|
|
}
|
|
}
|
|
|
|
fn get2d(&mut self) -> Point2f {
|
|
let sample_index = self.get_sample_index();
|
|
self.dim += 2;
|
|
let hash = dim_seed_hash(self.dim, self.seed);
|
|
let sample_hash = [hash as u32, (hash >> 32) as u32];
|
|
if self.randomize == RandomizeStrategy::None {
|
|
return Point2f::new(
|
|
sobol_sample(sample_index, 0, NoRandomizer),
|
|
sobol_sample(sample_index, 1, NoRandomizer),
|
|
);
|
|
}
|
|
match self.randomize {
|
|
RandomizeStrategy::PermuteDigits => Point2f::new(
|
|
sobol_sample(sample_index, 0, BinaryPermuteScrambler::new(sample_hash[0])),
|
|
sobol_sample(sample_index, 1, BinaryPermuteScrambler::new(sample_hash[1])),
|
|
),
|
|
RandomizeStrategy::FastOwen => Point2f::new(
|
|
sobol_sample(sample_index, 0, FastOwenScrambler::new(sample_hash[0])),
|
|
sobol_sample(sample_index, 1, FastOwenScrambler::new(sample_hash[1])),
|
|
),
|
|
RandomizeStrategy::Owen => Point2f::new(
|
|
sobol_sample(sample_index, 0, OwenScrambler::new(sample_hash[0])),
|
|
sobol_sample(sample_index, 1, OwenScrambler::new(sample_hash[1])),
|
|
),
|
|
RandomizeStrategy::None => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn get_pixel2d(&mut self) -> Point2f {
|
|
self.get2d()
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone)]
|
|
struct PrimarySample {
|
|
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 {
|
|
fn samples_per_pixel(&self) -> i32 {
|
|
self.mutations_per_pixel
|
|
}
|
|
|
|
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
|
|
let hash_input = [p.x() as u64, p.y() as u64, self.seed];
|
|
let 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 {
|
|
#[cfg(not(any(feature = "cuda", feature = "vulkan")))]
|
|
{
|
|
return 0.;
|
|
}
|
|
|
|
let ind = self.get_next_index();
|
|
}
|
|
fn get2d(&mut self) -> Point2f {
|
|
todo!()
|
|
}
|
|
fn get_pixel2d(&mut self) -> Point2f {
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
#[enum_dispatch]
|
|
pub trait SamplerTrait {
|
|
fn samples_per_pixel(&self) -> i32;
|
|
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>);
|
|
fn get1d(&mut self) -> Float;
|
|
fn get2d(&mut self) -> Point2f;
|
|
fn get_pixel2d(&mut self) -> Point2f;
|
|
}
|
|
|
|
#[enum_dispatch(SamplerTrait)]
|
|
#[derive(Debug, Clone)]
|
|
pub enum Sampler {
|
|
Independent(IndependentSampler),
|
|
Stratified(StratifiedSampler),
|
|
Halton(HaltonSampler),
|
|
PaddedSobol(PaddedSobolSampler),
|
|
Sobol(SobolSampler),
|
|
ZSobol(ZSobolSampler),
|
|
MLT(MLTSampler),
|
|
}
|