From 9b4781c81d22c0671b2bdf5f9ef0caf5ccae6c2e Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Sun, 15 Mar 2020 14:47:40 +0900 Subject: [PATCH] Multiple improvements to sampling. 1. Use better constants for the hash-based Owen scrambling. 2. Use golden ratio sampling for the wavelength dimension. On the use of golden ratio sampling: Since hero wavelength sampling uses multiple equally-spaced wavelengths, and most samplers only consider the spacing of individual samples, those samplers weren't actually doing a good job of distributing all the wavelengths evenly. Golden ratio sampling, on the other hand, does this effortlessly by its nature, and the resulting reduction of color noise is huge. --- src/renderer.rs | 35 +++++++++++++++++++++++++---------- sub_crates/sobol/src/lib.rs | 10 +++++----- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/renderer.rs b/src/renderer.rs index baec6de..58a7adb 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -244,9 +244,9 @@ impl<'a> Renderer<'a> { // Calculate image plane x and y coordinates let (img_x, img_y) = { let filter_x = - fast_logit(get_sample(0, si as u32, (x, y), self.seed), 1.5) + 0.5; + fast_logit(get_sample(4, si as u32, (x, y), self.seed), 1.5) + 0.5; let filter_y = - fast_logit(get_sample(1, si as u32, (x, y), self.seed), 1.5) + 0.5; + fast_logit(get_sample(5, si as u32, (x, y), self.seed), 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) @@ -262,8 +262,8 @@ impl<'a> Renderer<'a> { get_sample(2, si as u32, (x, y), self.seed), get_sample(3, si as u32, (x, y), self.seed), ), - get_sample(4, si as u32, (x, y), self.seed), - map_0_1_to_wavelength(get_sample(5, si as u32, (x, y), self.seed)), + get_sample(1, si as u32, (x, y), self.seed), + map_0_1_to_wavelength(get_sample(0, si as u32, (x, y), self.seed)), si as u32, ); paths.push(path); @@ -693,12 +693,27 @@ impl LightPath { #[inline(always)] fn get_sample(dimension: u32, i: u32, pixel_co: (u32, u32), seed: u32) -> f32 { let pixel_id = pixel_co.0 ^ (pixel_co.1 << 16); - if dimension < sobol::NUM_DIMENSIONS as u32 { - let scramble = hash_u32(pixel_id, seed); - sobol::sample_owen_scramble(dimension, i, hash_u32(dimension, scramble)) - } else { - use crate::hash::hash_u32_to_f32; - hash_u32_to_f32(dimension ^ (i << 16), pixel_id) + match dimension { + 0 => { + // Golden ratio sampling. + // NOTE: use this for the wavelength dimension, because + // due to the nature of hero wavelength sampling this ends up + // being crazily more efficient than pretty much any other sampler, + // and reduces variance by a huge amount. + let scramble = hash_u32(pixel_id, seed); + let n = (i + scramble) * 2654435769; + n as f32 * (1.0 / (1u64 << 32) as f32) + } + n if (n - 1) < sobol::NUM_DIMENSIONS as u32 => { + // Sobol sampling. + let scramble = hash_u32(pixel_id, seed); + sobol::sample_owen_scramble(dimension - 1, i, hash_u32(dimension, scramble)) + } + _ => { + // Random sampling. + use crate::hash::hash_u32_to_f32; + hash_u32_to_f32(dimension ^ (i << 16), pixel_id) + } } } diff --git a/sub_crates/sobol/src/lib.rs b/sub_crates/sobol/src/lib.rs index 19804f9..07e93bd 100644 --- a/sub_crates/sobol/src/lib.rs +++ b/sub_crates/sobol/src/lib.rs @@ -78,13 +78,13 @@ pub fn sample_owen_scramble(dimension: u32, index: u32, scramble: u32) -> f32 { // 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. + // The constants here were selected through an optimization process + // to maximize unidirectional low-bias 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.wrapping_mul(0x08afbbe0); + n ^= n.wrapping_mul(0xa7389b46); + n ^= n.wrapping_mul(0x42bf6dbc); n = n.reverse_bits(); u32_to_0_1_f32(n)