A single unified Sobol implementation.
This version of Sobol implements both Owen scrambling and index permutation, allowing for multiple statistically independent Sobol sequences.
This commit is contained in:
parent
f36b71184a
commit
085d1d655e
|
@ -1,7 +1,7 @@
|
||||||
pub fn hash_u32(n: u32, seed: u32) -> u32 {
|
pub fn hash_u32(n: u32, seed: u32) -> u32 {
|
||||||
let mut hash = n;
|
let mut hash = n;
|
||||||
for _ in 0..3 {
|
for _ in 0..3 {
|
||||||
hash = hash.wrapping_mul(1_936_502_639);
|
hash = hash.wrapping_mul(0x736caf6f);
|
||||||
hash ^= hash.wrapping_shr(16);
|
hash ^= hash.wrapping_shr(16);
|
||||||
hash ^= seed;
|
hash ^= seed;
|
||||||
}
|
}
|
||||||
|
|
|
@ -694,21 +694,22 @@ impl LightPath {
|
||||||
/// LDS samples aren't available.
|
/// LDS samples aren't available.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn get_sample(dimension: u32, i: u32, pixel_co: (u32, u32), seed: u32) -> f32 {
|
fn get_sample(dimension: u32, i: u32, pixel_co: (u32, u32), seed: u32) -> f32 {
|
||||||
// A unique random scramble value for every pixel coordinate up to
|
// A unique seed for every pixel coordinate up to a resolution of
|
||||||
// a resolution of 65536 x 65536. Also further randomized by a seed.
|
// 65536 x 65536. Also incorperating the seed.
|
||||||
let scramble = hash_u32(pixel_co.0 ^ (pixel_co.1 << 16), seed);
|
let seed = hash_u32(pixel_co.0 ^ (pixel_co.1 << 16), seed);
|
||||||
|
|
||||||
match dimension {
|
match dimension {
|
||||||
d if d < sobol::MAX_DIMENSION as u32 => {
|
d if d < sobol::MAX_DIMENSION as u32 => {
|
||||||
// Sobol sampling.
|
// Sobol sampling.
|
||||||
// We skip the first 4 samples, because that mitigates some poor
|
// We skip the first 4 samples, because that mitigates some poor
|
||||||
// sampling at low sample counts like 16.
|
// sampling at low sample counts like 16.
|
||||||
sobol::sample_owen(d, i + 4, hash_u32(d, scramble))
|
sobol::sample(d, i + 4, seed)
|
||||||
|
// halton::sample(d, i + seed)
|
||||||
}
|
}
|
||||||
d => {
|
d => {
|
||||||
// Random sampling.
|
// Random sampling.
|
||||||
use crate::hash::hash_u32_to_f32;
|
use crate::hash::hash_u32_to_f32;
|
||||||
hash_u32_to_f32(d ^ (i << 16), scramble)
|
hash_u32_to_f32(d ^ (i << 16), seed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
//! An implementation of the Sobol low discrepancy sequence.
|
//! An implementation of the Sobol sequence with Owen scrambling.
|
||||||
//!
|
|
||||||
//! Includes variants with random digit scrambling, Cranley-Patterson rotation,
|
|
||||||
//! and Owen scrambling.
|
|
||||||
|
|
||||||
// The following `include` provides `MAX_DIMENSION` and `VECTORS`.
|
// The following `include` provides `MAX_DIMENSION` and `VECTORS`.
|
||||||
// See the build.rs file for how this included file is generated.
|
// See the build.rs file for how this included file is generated.
|
||||||
|
@ -11,76 +8,20 @@ include!(concat!(env!("OUT_DIR"), "/vectors.inc"));
|
||||||
/// `dimension` specifies the component and `index` specifies the sample
|
/// `dimension` specifies the component and `index` specifies the sample
|
||||||
/// within the sequence.
|
/// within the sequence.
|
||||||
///
|
///
|
||||||
|
/// A different `seed` parameter results in a statistically independent Sobol
|
||||||
|
/// sequence, uncorrelated to others with different seeds. However, seed
|
||||||
|
/// itself needs to be sufficiently random: you can't just pass 1, 2, 3, etc.
|
||||||
|
///
|
||||||
/// Note: generates a maximum of 2^16 samples per dimension. If the `index`
|
/// Note: generates a maximum of 2^16 samples per dimension. If the `index`
|
||||||
/// parameter exceeds 2^16-1, the sample set will start repeating.
|
/// parameter exceeds 2^16-1, the sample set will start repeating.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn sample(dimension: u32, index: u32) -> f32 {
|
pub fn sample(dimension: u32, index: u32, seed: u32) -> f32 {
|
||||||
u32_to_0_1_f32(sobol_u32(dimension, index))
|
let scramble = hash(dimension ^ seed);
|
||||||
}
|
let shuffled_index = owen_scramble(index, seed);
|
||||||
|
u32_to_0_1_f32(owen_scramble(
|
||||||
/// Same as `sample()` except applies random digit scrambling using the
|
sobol_u32(dimension, shuffled_index),
|
||||||
/// scramble parameter.
|
scramble,
|
||||||
///
|
))
|
||||||
/// To get proper random digit scrambling, you need to use a different scramble
|
|
||||||
/// value for each dimension, and those values should be generated more-or-less
|
|
||||||
/// randomly. For example, using a 32-bit hash of the dimension parameter
|
|
||||||
/// works well.
|
|
||||||
#[inline]
|
|
||||||
pub fn sample_rd(dimension: u32, index: u32, scramble: u32) -> f32 {
|
|
||||||
u32_to_0_1_f32(sobol_u32(dimension, index) ^ scramble)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Same as `sample()` except applies Cranley Patterson rotation using the
|
|
||||||
/// given scramble parameter.
|
|
||||||
///
|
|
||||||
/// To get proper Cranley Patterson rotation, you need to use a different
|
|
||||||
/// scramble value for each dimension, and those values should be generated
|
|
||||||
/// more-or-less randomly. For example, using a 32-bit hash of the dimension
|
|
||||||
/// parameter works well.
|
|
||||||
#[inline]
|
|
||||||
pub fn sample_cranley(dimension: u32, index: u32, scramble: u32) -> f32 {
|
|
||||||
u32_to_0_1_f32(sobol_u32(dimension, index).wrapping_add(scramble))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Same as `sample()` except applies Owen scrambling using the given scramble
|
|
||||||
/// parameter.
|
|
||||||
///
|
|
||||||
/// To get proper Owen scrambling, you need to use a different scramble
|
|
||||||
/// value for each dimension, and those values should be generated more-or-less
|
|
||||||
/// randomly. For example, using a 32-bit hash of the dimension parameter
|
|
||||||
/// works well.
|
|
||||||
#[inline]
|
|
||||||
pub fn sample_owen(dimension: u32, index: u32, scramble: u32) -> f32 {
|
|
||||||
u32_to_0_1_f32(owen_scramble_u32(sobol_u32(dimension, index), scramble))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Same as `sample_owen()` except uses a slower more full version of
|
|
||||||
/// Owen scrambling.
|
|
||||||
///
|
|
||||||
/// This is mainly intended to help validate the faster Owen scrambling,
|
|
||||||
/// and likely shouldn't be used for real things. It is significantly
|
|
||||||
/// slower.
|
|
||||||
#[inline]
|
|
||||||
pub fn sample_owen_slow(dimension: u32, index: u32, scramble: u32) -> f32 {
|
|
||||||
let mut n = sobol_u32(dimension, index);
|
|
||||||
n = n.reverse_bits().wrapping_add(scramble).reverse_bits();
|
|
||||||
for i in 0..16 {
|
|
||||||
let mask = (1 << (31 - i)) - 1;
|
|
||||||
let hash = {
|
|
||||||
let mut hash = n & (!mask);
|
|
||||||
let seed = scramble + i;
|
|
||||||
let perms = [0x29aaaaa7, 0x736caf6f, 0x54aad35b, 0x2ab35aaa];
|
|
||||||
for p in perms.iter().cycle().take(6) {
|
|
||||||
hash = hash.wrapping_mul(*p);
|
|
||||||
hash ^= hash.wrapping_shr(16);
|
|
||||||
hash ^= seed;
|
|
||||||
}
|
|
||||||
hash
|
|
||||||
};
|
|
||||||
n ^= hash & mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32_to_0_1_f32(n)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------
|
//----------------------------------------------------------------------
|
||||||
|
@ -110,7 +51,7 @@ fn sobol_u32(dimension: u32, index: u32) -> u32 {
|
||||||
|
|
||||||
/// Scrambles `n` using Owen scrambling and the given scramble parameter.
|
/// Scrambles `n` using Owen scrambling and the given scramble parameter.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn owen_scramble_u32(mut n: u32, scramble: u32) -> u32 {
|
fn owen_scramble(mut n: u32, scramble: u32) -> u32 {
|
||||||
// This uses the technique presented in the paper "Stratified Sampling for
|
// This uses the technique presented in the paper "Stratified Sampling for
|
||||||
// Stochastic Transparency" by Laine and Karras.
|
// Stochastic Transparency" by Laine and Karras.
|
||||||
// The basic idea is that we're running a special kind of hash function
|
// The basic idea is that we're running a special kind of hash function
|
||||||
|
@ -131,10 +72,10 @@ fn owen_scramble_u32(mut n: u32, scramble: u32) -> u32 {
|
||||||
// The permutation constants here were selected through an optimization
|
// The permutation constants here were selected through an optimization
|
||||||
// process to maximize low-bias avalanche between bits.
|
// process to maximize low-bias avalanche between bits.
|
||||||
|
|
||||||
|
const PERMS: [u32; 3] = [0x97b756bc, 0x4b0a8a12, 0x75c77e36];
|
||||||
n = n.reverse_bits();
|
n = n.reverse_bits();
|
||||||
n = n.wrapping_add(scramble);
|
n = n.wrapping_add(scramble);
|
||||||
let perms = [0x97b756bc, 0x4b0a8a12, 0x75c77e36];
|
for &p in PERMS.iter() {
|
||||||
for &p in perms.iter() {
|
|
||||||
n ^= n.wrapping_mul(p);
|
n ^= n.wrapping_mul(p);
|
||||||
}
|
}
|
||||||
n = n.reverse_bits();
|
n = n.reverse_bits();
|
||||||
|
@ -143,6 +84,34 @@ fn owen_scramble_u32(mut n: u32, scramble: u32) -> u32 {
|
||||||
n
|
n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Same as `owen_scramble()` except uses a slower more full version of
|
||||||
|
/// Owen scrambling.
|
||||||
|
///
|
||||||
|
/// This is mainly intended to help validate the faster Owen scrambling,
|
||||||
|
/// and likely shouldn't be used for real things. It is significantly
|
||||||
|
/// slower.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[inline]
|
||||||
|
fn owen_scramble_slow(mut n: u32, scramble: u32) -> u32 {
|
||||||
|
n = n.reverse_bits().wrapping_add(scramble).reverse_bits();
|
||||||
|
for i in 0..31 {
|
||||||
|
let mask = (1 << (31 - i)) - 1;
|
||||||
|
let high_bits_hash = hash((n & (!mask)) ^ hash(i));
|
||||||
|
n ^= high_bits_hash & mask;
|
||||||
|
}
|
||||||
|
n
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn hash(n: u32) -> u32 {
|
||||||
|
let mut hash = n;
|
||||||
|
for _ in 0..3 {
|
||||||
|
hash = hash.wrapping_mul(0x736caf6f);
|
||||||
|
hash ^= hash.wrapping_shr(16);
|
||||||
|
}
|
||||||
|
hash
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn u32_to_0_1_f32(n: u32) -> f32 {
|
fn u32_to_0_1_f32(n: u32) -> f32 {
|
||||||
const ONE_OVER_32BITS: f32 = 1.0 / (1u64 << 32) as f32;
|
const ONE_OVER_32BITS: f32 = 1.0 / (1u64 << 32) as f32;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user