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.
This commit is contained in:
Nathan Vegdahl 2020-03-19 08:58:42 +09:00
parent 9e14e164e7
commit 7daa133e15
2 changed files with 29 additions and 21 deletions

View File

@ -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::<SobolInt>() * 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];
}
}
}

View File

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