From b081424ba6bc2f16943f4f008353d234b901934d Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Wed, 11 Mar 2020 18:29:46 +0900 Subject: [PATCH] Implemented Owen scrambling for the Sobol sampler. This gives better variance than random digit scrambling, at a very tiny runtime cost (so tiny it's lost in the noise of the rest of the rendering process). --- src/renderer.rs | 30 ++++++++------- sub_crates/sobol/src/lib.rs | 74 ++++++++++++++++++++++++++++++------- 2 files changed, 77 insertions(+), 27 deletions(-) diff --git a/src/renderer.rs b/src/renderer.rs index bbe6a40..fc4330d 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -240,12 +240,14 @@ impl<'a> Renderer<'a> { // Generate light paths and initial rays for y in bucket.y..(bucket.y + bucket.h) { for x in bucket.x..(bucket.x + bucket.w) { - let pix_id = pixel_id(x, y); + let pix_scramble = hash_u32(pixel_id(x, y), self.seed); for si in 0..self.spp { // Calculate image plane x and y coordinates let (img_x, img_y) = { - let filter_x = fast_logit(get_sample(4, si as u32, pix_id), 1.5) + 0.5; - let filter_y = fast_logit(get_sample(5, si as u32, pix_id), 1.5) + 0.5; + let filter_x = + fast_logit(get_sample(4, si as u32, pix_scramble), 1.5) + 0.5; + let filter_y = + fast_logit(get_sample(5, si as u32, pix_scramble), 1.5) + 0.5; let samp_x = (filter_x + x as f32) * cmpx; let samp_y = (filter_y + y as f32) * cmpy; ((samp_x - 0.5) * x_extent, (0.5 - samp_y) * y_extent) @@ -255,14 +257,14 @@ impl<'a> Renderer<'a> { let (path, ray) = LightPath::new( &self.scene, (x, y), - pix_id, + pix_scramble, (img_x, img_y), ( - get_sample(0, si as u32, pix_id), - get_sample(1, si as u32, pix_id), + get_sample(0, si as u32, pix_scramble), + get_sample(1, si as u32, pix_scramble), ), - get_sample(2, si as u32, pix_id), - map_0_1_to_wavelength(get_sample(3, si as u32, pix_id)), + get_sample(2, si as u32, pix_scramble), + map_0_1_to_wavelength(get_sample(3, si as u32, pix_scramble)), si as u32, ); paths.push(path); @@ -369,7 +371,7 @@ pub struct LightPath { bounce_count: u32, pixel_co: (u32, u32), - pixel_id: u32, + pixel_scramble: u32, sample_number: u32, // Which sample in the LDS sequence this is. dim_offset: Cell, time: f32, @@ -389,7 +391,7 @@ impl LightPath { fn new( scene: &Scene, pixel_co: (u32, u32), - pixel_id: u32, + pixel_scramble: u32, image_plane_co: (f32, f32), lens_uv: (f32, f32), time: f32, @@ -402,7 +404,7 @@ impl LightPath { bounce_count: 0, pixel_co: pixel_co, - pixel_id: pixel_id, + pixel_scramble: pixel_scramble, sample_number: sample_number, dim_offset: Cell::new(6), time: time, @@ -430,7 +432,7 @@ impl LightPath { fn next_lds_samp(&self) -> f32 { let dimension = self.dim_offset.get(); self.dim_offset.set(dimension + 1); - get_sample(dimension, self.sample_number, self.pixel_id) + get_sample(dimension, self.sample_number, self.pixel_scramble) } fn next( @@ -688,9 +690,9 @@ impl LightPath { fn get_sample(dimension: u32, i: u32, scramble: u32) -> f32 { use crate::hash::hash_u32_to_f32; if dimension < sobol::NUM_DIMENSIONS as u32 { - sobol::sample_with_scramble(dimension, i, hash_u32(dimension, scramble)) + sobol::sample_owen_scramble(dimension, i, scramble + dimension) } else { - hash_u32_to_f32(dimension, i + scramble) + hash_u32_to_f32(dimension, i ^ (scramble << 16)) } } diff --git a/sub_crates/sobol/src/lib.rs b/sub_crates/sobol/src/lib.rs index 9a7c5f4..283a762 100644 --- a/sub_crates/sobol/src/lib.rs +++ b/sub_crates/sobol/src/lib.rs @@ -18,22 +18,70 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -// Adapted to Rust by Nathan Vegdahl (2017) +// Adapted to Rust by Nathan Vegdahl (2017). +// Owen scrambling implementation also by Nathan Vegdahl (2020). mod matrices; -pub use crate::matrices::{MATRICES, NUM_DIMENSIONS, SIZE}; +pub use crate::matrices::NUM_DIMENSIONS; +use crate::matrices::{MATRICES, SIZE}; -/// Compute one component of the Sobol'-sequence, where the component -/// corresponds to the dimension parameter, and the index specifies -/// the point inside the sequence. The scramble parameter can be used -/// to permute elementary intervals, and might be chosen randomly to -/// generate a randomized QMC sequence. +/// Compute one component of one sample from the Sobol'-sequence, where +/// `dimension` specifies the component and `index` specifies the sample +/// within the sequence. #[inline] -pub fn sample_with_scramble(dimension: u32, mut index: u32, scramble: u32) -> f32 { +pub fn sample(dimension: u32, index: u32) -> f32 { + u32_to_0_1_f32(sample_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. +#[inline] +pub fn sample_rd_scramble(dimension: u32, index: u32, scramble: u32) -> f32 { + u32_to_0_1_f32(sample_u32(dimension, index) ^ scramble) +} + +/// Same as `sample()` except applies Owen scrambling using the given seed. +/// +/// To get proper Owen scrambling, you need to use a different seed for each +/// dimension. +#[inline] +pub fn sample_owen_scramble(dimension: u32, index: u32, seed: u32) -> f32 { + // Get the sobol point. + let mut n = sample_u32(dimension, index); + + // We first apply the 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. 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 hash function on the final valuw, + // but which only allows avalanche to happen upwards (e.g. a bit is never + // affected by higher bits). This is acheived by only using multiplies by + // even numbers. Normally this would be considered a poor hash function, + // but in this case that behavior is exactly what we want. + for _ in 0..4 { + // The constant here is a large prime * 2. + n ^= n * 0xa97774e6; + } + + u32_to_0_1_f32(n) +} + +//---------------------------------------------------------------------- + +/// The actual core Sobol samplng code. Used by the other functions. +#[inline(always)] +fn sample_u32(dimension: u32, mut index: u32) -> u32 { assert!((dimension as usize) < NUM_DIMENSIONS); - let mut result = scramble; + let mut result = 0; let mut i = (dimension as usize) * SIZE; while index != 0 { if (index & 1) != 0 { @@ -44,10 +92,10 @@ pub fn sample_with_scramble(dimension: u32, mut index: u32, scramble: u32) -> f3 i += 1; } - result as f32 * (1.0 / (1u64 << 32) as f32) + result } -#[inline] -pub fn sample(dimension: u32, index: u32) -> f32 { - sample_with_scramble(dimension, index, 0) +#[inline(always)] +fn u32_to_0_1_f32(n: u32) -> f32 { + n as f32 * (1.0 / (1u64 << 32) as f32) }