Fixed broken Owen scrambling.

The previous implementation was fundamentally broken because it
was mixing the bits in the wrong direction.  This fixes that.

The constants have also been updated.  I created a (temporary)
implementation of slow but full owen scrambling to test against,
and these constants appear to give results consistent with that
on all the test scenes I rendered on.  It is still, of course,
possible that my full implementation was flawed, so more validation
in the future would be a good idea.
This commit is contained in:
Nathan Vegdahl 2020-03-12 20:20:56 +09:00
parent 9ba51cd43a
commit a3ea90afdc
2 changed files with 36 additions and 36 deletions

View File

@ -690,7 +690,7 @@ impl LightPath {
fn get_sample(dimension: u32, i: u32, scramble: u32) -> f32 { fn get_sample(dimension: u32, i: u32, scramble: u32) -> f32 {
use crate::hash::hash_u32_to_f32; use crate::hash::hash_u32_to_f32;
if dimension < sobol::NUM_DIMENSIONS as u32 { if dimension < sobol::NUM_DIMENSIONS as u32 {
sobol::sample_owen_scramble(dimension, i, scramble + dimension) sobol::sample_owen_scramble(dimension, i, hash_u32(dimension, scramble))
} else { } else {
hash_u32_to_f32(dimension, i ^ (scramble << 16)) hash_u32_to_f32(dimension, i ^ (scramble << 16))
} }

View File

@ -31,7 +31,7 @@ use crate::matrices::{MATRICES, SIZE};
/// within the sequence. /// within the sequence.
#[inline] #[inline]
pub fn sample(dimension: u32, index: u32) -> f32 { pub fn sample(dimension: u32, index: u32) -> f32 {
u32_to_0_1_f32(sample_u32(dimension, index)) u32_to_0_1_f32(sobol_u32(dimension, index))
} }
/// Same as `sample()` except applies random digit scrambling using the /// Same as `sample()` except applies random digit scrambling using the
@ -43,49 +43,49 @@ pub fn sample(dimension: u32, index: u32) -> f32 {
/// works well. /// works well.
#[inline] #[inline]
pub fn sample_rd_scramble(dimension: u32, index: u32, scramble: u32) -> f32 { pub fn sample_rd_scramble(dimension: u32, index: u32, scramble: u32) -> f32 {
u32_to_0_1_f32(sample_u32(dimension, index) ^ scramble) u32_to_0_1_f32(sobol_u32(dimension, index) ^ scramble)
} }
/// Same as `sample()` except applies Owen scrambling using the given seed. /// Same as `sample()` except applies Owen scrambling using the given scramble
/// parameter.
/// ///
/// To get proper Owen scrambling, you need to use a different seed for each /// To get proper Owen scrambling, you need to use a different scramble
/// dimension. For example, reusing the dimension parameter itself works well. /// 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] #[inline]
pub fn sample_owen_scramble(dimension: u32, index: u32, seed: u32) -> f32 { pub fn sample_owen_scramble(dimension: u32, index: u32, scramble: u32) -> f32 {
// Do a weak "hash" on the seed. This isn't meant to be a real hash,
// we're just mixing the higher bits down into the lower bits so that
// naive seeds still work.
let mut seed = seed;
seed ^= seed.wrapping_mul(0x54bbba73);
seed ^= seed.wrapping_shr(16);
seed ^= seed.wrapping_mul(0x736caf6f);
seed ^= seed.wrapping_shr(16);
// Get the sobol point. // Get the sobol point.
let mut n = sample_u32(dimension, index); let mut n = sobol_u32(dimension, index);
// Apply the "hashed" seed as if doing random digit scrambling.
// This is valid because random digit scrambling is a strict subset of
// Owen scrambling, and therefore does not invalidate the Owen scrambling
// below. Instead, this simply serves to seed the Owen scrambling.
n ^= seed;
// Do Owen scrambling. // Do Owen scrambling.
//
// 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
// that only allows avalanche to happen upwards (i.e. a bit is only // that only allows avalanche to happen downwards (i.e. a bit is only
// affected by the bits lower than it). This is achieved by only doing // affected by the bits higher than it). This is achieved by first
// mixing via multiplication by even numbers. Normally this would be // reversing the bits and then doing mixing via multiplication by even
// considered a poor hash function, because normally you want all bits to // numbers.
// have an equal chance of affecting all other bits. But in this case that //
// only-upwards behavior is exactly what we want, because it ends up being // Normally this would be considered a poor hash function, because normally
// equivalent to Owen scrambling. // you want all bits to have an equal chance of affecting all other bits.
// The constants here are large primes. // But in this case that only-downward behavior is exactly what we want,
n ^= n.wrapping_mul(0x54bbba73 * 2); // because it ends up being equivalent to Owen scrambling.
n ^= n.wrapping_mul(0x736caf6f * 2); //
n ^= n.wrapping_mul(0x54bbba73 * 2); // Note that the application of the scramble parameter here is equivalent
n ^= n.wrapping_mul(0x736caf6f * 2); // to doing random digit scrambling. This is valid because random digit
// scrambling is a strict subset of Owen scrambling, and therefore does
// not invalidate the Owen scrambling itself.
//
// The constants here are large primes, selected semi-carefully to maximize
// avalanche between bits.
n = n.reverse_bits();
n ^= scramble; // Apply the scramble parameter.
n ^= n.wrapping_mul(0x2d2c6e5d << 1);
n ^= n.wrapping_mul(0x52d391b3 << 1);
n ^= n.wrapping_mul(0x736caf6f << 1);
n = n.reverse_bits();
u32_to_0_1_f32(n) u32_to_0_1_f32(n)
} }
@ -94,7 +94,7 @@ pub fn sample_owen_scramble(dimension: u32, index: u32, seed: u32) -> f32 {
/// The actual core Sobol samplng code. Used by the other functions. /// The actual core Sobol samplng code. Used by the other functions.
#[inline(always)] #[inline(always)]
fn sample_u32(dimension: u32, mut index: u32) -> u32 { fn sobol_u32(dimension: u32, mut index: u32) -> u32 {
assert!((dimension as usize) < NUM_DIMENSIONS); assert!((dimension as usize) < NUM_DIMENSIONS);
let mut result = 0; let mut result = 0;