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).
This commit is contained in:
Nathan Vegdahl 2020-03-11 18:29:46 +09:00
parent 8d16719cd2
commit b081424ba6
2 changed files with 77 additions and 27 deletions

View File

@ -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<u32>,
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))
}
}

View File

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