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.
This commit is contained in:
Nathan Vegdahl 2020-03-15 14:47:40 +09:00
parent e0cb436209
commit 9b4781c81d
2 changed files with 30 additions and 15 deletions

View File

@ -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,13 +693,28 @@ 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 {
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);
sobol::sample_owen_scramble(dimension, i, hash_u32(dimension, scramble))
} else {
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)
}
}
}
#[derive(Debug)]

View File

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