From 085d1d655e11b5c1236229d07b9383a2248f449b Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Sun, 19 Apr 2020 01:11:43 +0900 Subject: [PATCH] A single unified Sobol implementation. This version of Sobol implements both Owen scrambling and index permutation, allowing for multiple statistically independent Sobol sequences. --- src/hash.rs | 2 +- src/renderer.rs | 11 ++-- sub_crates/sobol/src/lib.rs | 117 +++++++++++++----------------------- 3 files changed, 50 insertions(+), 80 deletions(-) diff --git a/src/hash.rs b/src/hash.rs index d923da2..6790092 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -1,7 +1,7 @@ pub fn hash_u32(n: u32, seed: u32) -> u32 { let mut hash = n; for _ in 0..3 { - hash = hash.wrapping_mul(1_936_502_639); + hash = hash.wrapping_mul(0x736caf6f); hash ^= hash.wrapping_shr(16); hash ^= seed; } diff --git a/src/renderer.rs b/src/renderer.rs index f546089..dcd2c2a 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -694,21 +694,22 @@ impl LightPath { /// LDS samples aren't available. #[inline(always)] 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 resolution of 65536 x 65536. Also further randomized by a seed. - let scramble = hash_u32(pixel_co.0 ^ (pixel_co.1 << 16), seed); + // A unique seed for every pixel coordinate up to a resolution of + // 65536 x 65536. Also incorperating the seed. + let seed = hash_u32(pixel_co.0 ^ (pixel_co.1 << 16), seed); match dimension { d if d < sobol::MAX_DIMENSION as u32 => { // Sobol sampling. // We skip the first 4 samples, because that mitigates some poor // 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 => { // Random sampling. use crate::hash::hash_u32_to_f32; - hash_u32_to_f32(d ^ (i << 16), scramble) + hash_u32_to_f32(d ^ (i << 16), seed) } } } diff --git a/sub_crates/sobol/src/lib.rs b/sub_crates/sobol/src/lib.rs index 3feaf97..a51a723 100644 --- a/sub_crates/sobol/src/lib.rs +++ b/sub_crates/sobol/src/lib.rs @@ -1,7 +1,4 @@ -//! An implementation of the Sobol low discrepancy sequence. -//! -//! Includes variants with random digit scrambling, Cranley-Patterson rotation, -//! and Owen scrambling. +//! An implementation of the Sobol sequence with Owen scrambling. // The following `include` provides `MAX_DIMENSION` and `VECTORS`. // 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 /// 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` /// parameter exceeds 2^16-1, the sample set will start repeating. #[inline] -pub fn sample(dimension: u32, index: u32) -> f32 { - u32_to_0_1_f32(sobol_u32(dimension, index)) -} - -/// Same as `sample()` except applies random digit scrambling using the -/// scramble parameter. -/// -/// 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) +pub fn sample(dimension: u32, index: u32, seed: u32) -> f32 { + let scramble = hash(dimension ^ seed); + let shuffled_index = owen_scramble(index, seed); + u32_to_0_1_f32(owen_scramble( + sobol_u32(dimension, shuffled_index), + scramble, + )) } //---------------------------------------------------------------------- @@ -110,7 +51,7 @@ fn sobol_u32(dimension: u32, index: u32) -> u32 { /// Scrambles `n` using Owen scrambling and the given scramble parameter. #[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 // Stochastic Transparency" by Laine and Karras. // 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 // process to maximize low-bias avalanche between bits. + const PERMS: [u32; 3] = [0x97b756bc, 0x4b0a8a12, 0x75c77e36]; n = n.reverse_bits(); 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.reverse_bits(); @@ -143,6 +84,34 @@ fn owen_scramble_u32(mut n: u32, scramble: u32) -> u32 { 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)] fn u32_to_0_1_f32(n: u32) -> f32 { const ONE_OVER_32BITS: f32 = 1.0 / (1u64 << 32) as f32;