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:
Nathan Vegdahl 2020-04-19 01:11:43 +09:00
parent f36b71184a
commit 085d1d655e
3 changed files with 50 additions and 80 deletions

View File

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

View File

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

View File

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