From 7daa133e152847cc2d1cf489e955e41a2b92eec8 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Thu, 19 Mar 2020 08:58:42 +0900 Subject: [PATCH] Only use 16 bit integers for generating Sobol samples. This limits the number of samples per dimension to 2^16, but that should be more than enough for any rendering situation. And this reduces the direction numbers table size by a factor of 4. This commit also takes advantage of the reduced bit space to provide even better Owen scrambling, by utilizing the unused 16 bits for better mixing. --- sub_crates/sobol/build.rs | 21 +++++++++++---------- sub_crates/sobol/src/lib.rs | 29 ++++++++++++++++++----------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/sub_crates/sobol/build.rs b/sub_crates/sobol/build.rs index 142cd18..333e1fb 100644 --- a/sub_crates/sobol/build.rs +++ b/sub_crates/sobol/build.rs @@ -4,7 +4,7 @@ use std::{env, fs::File, io::Write, path::Path}; /// How many components to generate. -const NUM_DIMENSIONS: usize = 256; +const NUM_DIMENSIONS: usize = 1024; /// What file to generate the numbers from. const DIRECTION_NUMBERS_TEXT: &str = include_str!("direction_numbers/joe-kuo-cessen-3.1024.txt"); @@ -22,7 +22,7 @@ fn main() { .unwrap(); // Write the vectors. - f.write_all("pub const VECTORS: &[[u32; 32]] = &[\n".as_bytes()) + f.write_all(format!("pub const VECTORS: &[[u{0}; {0}]] = &[\n", SOBOL_BITS).as_bytes()) .unwrap(); for v in vectors.iter() { f.write_all(" [\n".as_bytes()).unwrap(); @@ -85,22 +85,23 @@ fn main() { // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -const SOBOL_BITS: usize = 32; +type SobolInt = u16; +const SOBOL_BITS: usize = std::mem::size_of::() * 8; -pub fn generate_direction_vectors(dimensions: usize) -> Vec<[u32; SOBOL_BITS]> { +pub fn generate_direction_vectors(dimensions: usize) -> Vec<[SobolInt; SOBOL_BITS]> { let mut vectors = Vec::new(); // Calculate first dimension, which is just the van der Corput sequence. - let mut dim_0 = [0u32; SOBOL_BITS]; + let mut dim_0 = [0 as SobolInt; SOBOL_BITS]; for i in 0..SOBOL_BITS { - dim_0[i] = 1 << (31 - i); + dim_0[i] = 1 << (SOBOL_BITS - 1 - i); } vectors.push(dim_0); // Do the rest of the dimensions. let mut lines = DIRECTION_NUMBERS_TEXT.lines(); for _ in 1..dimensions { - let mut v = [0u32; SOBOL_BITS]; + let mut v = [0 as SobolInt; SOBOL_BITS]; // Get data from the next valid line from the direction numbers text // file. @@ -117,18 +118,18 @@ pub fn generate_direction_vectors(dimensions: usize) -> Vec<[u32; SOBOL_BITS]> { // Generate the direction numbers for this dimension. if SOBOL_BITS <= s as usize { for i in 0..SOBOL_BITS { - v[i] = m[i] << (31 - i); + v[i] = (m[i] << (SOBOL_BITS - 1 - i)) as SobolInt; } } else { for i in 0..(s as usize) { - v[i] = m[i] << (31 - i); + v[i] = (m[i] << (SOBOL_BITS - 1 - i)) as SobolInt; } for i in (s as usize)..SOBOL_BITS { v[i] = v[i - s as usize] ^ (v[i - s as usize] >> s); for k in 1..s { - v[i] ^= ((a >> (s - 1 - k)) & 1) * v[i - k as usize]; + v[i] ^= ((a >> (s - 1 - k)) & 1) as SobolInt * v[i - k as usize]; } } } diff --git a/sub_crates/sobol/src/lib.rs b/sub_crates/sobol/src/lib.rs index 1d105fb..086f282 100644 --- a/sub_crates/sobol/src/lib.rs +++ b/sub_crates/sobol/src/lib.rs @@ -10,6 +10,9 @@ include!(concat!(env!("OUT_DIR"), "/vectors.inc")); /// Compute one component of one sample from the Sobol'-sequence, where /// `dimension` specifies the component and `index` specifies the sample /// within the sequence. +/// +/// 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)) @@ -74,10 +77,14 @@ pub fn sample_owen_cranley(dimension: u32, index: u32, scramble: u32) -> f32 { //---------------------------------------------------------------------- /// The actual core Sobol samplng code. Used by the other functions. +/// +/// Note: if the `index` parameter exceeds 2^16-1, the sample set will start +/// repeating. #[inline(always)] -fn sobol_u32(dimension: u32, mut index: u32) -> u32 { +fn sobol_u32(dimension: u32, index: u32) -> u32 { assert!(dimension < MAX_DIMENSION); let vecs = &VECTORS[dimension as usize]; + let mut index = index as u16; let mut result = 0; let mut i = 0; @@ -88,17 +95,17 @@ fn sobol_u32(dimension: u32, mut index: u32) -> u32 { index >>= j + 1; } - result + (result as u32) << 16 } /// Scrambles `n` using Owen scrambling and the given scramble parameter. #[inline(always)] fn owen_scramble_u32(mut n: u32, scramble: u32) -> u32 { - // We don't need the lowest 8 bits because we're converting to an f32 at - // the end which only has 24 bits of precision anyway. And doing this - // allows the seed to affect the mixing of the higher bits to make them - // more random in the Owen scrambling below. - n >>= 8; + // The lower 16 bits aren't generated by our sobol sampler anyway, + // and shifting them out allows the seed to better affect the mixing + // of the higher bits in a more randomized way, which improves the + // scrambling. + n >>= 16; // Do Owen scrambling. // @@ -122,20 +129,20 @@ 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. - n = n.reverse_bits(); n ^= scramble; - let perms = [0xa56bb1c6, 0xef577134, 0xd0e5e808, 0x200bd50a]; + let perms = [0x1313e844, 0xa14a177e, 0x18c8e432]; for p in perms.iter() { n ^= n.wrapping_mul(*p); } n = n.reverse_bits(); // Return the scrambled value, shifted back into place. - n << 8 + n << 16 } #[inline(always)] fn u32_to_0_1_f32(n: u32) -> f32 { - n as f32 * (1.0 / (1u64 << 32) as f32) + const ONE_OVER_32BITS: f32 = 1.0 / (1u64 << 32) as f32; + n as f32 * ONE_OVER_32BITS }