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:
parent
8d16719cd2
commit
b081424ba6
|
@ -240,12 +240,14 @@ impl<'a> Renderer<'a> {
|
||||||
// Generate light paths and initial rays
|
// Generate light paths and initial rays
|
||||||
for y in bucket.y..(bucket.y + bucket.h) {
|
for y in bucket.y..(bucket.y + bucket.h) {
|
||||||
for x in bucket.x..(bucket.x + bucket.w) {
|
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 {
|
for si in 0..self.spp {
|
||||||
// Calculate image plane x and y coordinates
|
// Calculate image plane x and y coordinates
|
||||||
let (img_x, img_y) = {
|
let (img_x, img_y) = {
|
||||||
let filter_x = fast_logit(get_sample(4, si as u32, pix_id), 1.5) + 0.5;
|
let filter_x =
|
||||||
let filter_y = fast_logit(get_sample(5, si as u32, pix_id), 1.5) + 0.5;
|
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_x = (filter_x + x as f32) * cmpx;
|
||||||
let samp_y = (filter_y + y as f32) * cmpy;
|
let samp_y = (filter_y + y as f32) * cmpy;
|
||||||
((samp_x - 0.5) * x_extent, (0.5 - samp_y) * y_extent)
|
((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(
|
let (path, ray) = LightPath::new(
|
||||||
&self.scene,
|
&self.scene,
|
||||||
(x, y),
|
(x, y),
|
||||||
pix_id,
|
pix_scramble,
|
||||||
(img_x, img_y),
|
(img_x, img_y),
|
||||||
(
|
(
|
||||||
get_sample(0, si as u32, pix_id),
|
get_sample(0, si as u32, pix_scramble),
|
||||||
get_sample(1, si as u32, pix_id),
|
get_sample(1, si as u32, pix_scramble),
|
||||||
),
|
),
|
||||||
get_sample(2, 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_id)),
|
map_0_1_to_wavelength(get_sample(3, si as u32, pix_scramble)),
|
||||||
si as u32,
|
si as u32,
|
||||||
);
|
);
|
||||||
paths.push(path);
|
paths.push(path);
|
||||||
|
@ -369,7 +371,7 @@ pub struct LightPath {
|
||||||
bounce_count: u32,
|
bounce_count: u32,
|
||||||
|
|
||||||
pixel_co: (u32, u32),
|
pixel_co: (u32, u32),
|
||||||
pixel_id: u32,
|
pixel_scramble: u32,
|
||||||
sample_number: u32, // Which sample in the LDS sequence this is.
|
sample_number: u32, // Which sample in the LDS sequence this is.
|
||||||
dim_offset: Cell<u32>,
|
dim_offset: Cell<u32>,
|
||||||
time: f32,
|
time: f32,
|
||||||
|
@ -389,7 +391,7 @@ impl LightPath {
|
||||||
fn new(
|
fn new(
|
||||||
scene: &Scene,
|
scene: &Scene,
|
||||||
pixel_co: (u32, u32),
|
pixel_co: (u32, u32),
|
||||||
pixel_id: u32,
|
pixel_scramble: u32,
|
||||||
image_plane_co: (f32, f32),
|
image_plane_co: (f32, f32),
|
||||||
lens_uv: (f32, f32),
|
lens_uv: (f32, f32),
|
||||||
time: f32,
|
time: f32,
|
||||||
|
@ -402,7 +404,7 @@ impl LightPath {
|
||||||
bounce_count: 0,
|
bounce_count: 0,
|
||||||
|
|
||||||
pixel_co: pixel_co,
|
pixel_co: pixel_co,
|
||||||
pixel_id: pixel_id,
|
pixel_scramble: pixel_scramble,
|
||||||
sample_number: sample_number,
|
sample_number: sample_number,
|
||||||
dim_offset: Cell::new(6),
|
dim_offset: Cell::new(6),
|
||||||
time: time,
|
time: time,
|
||||||
|
@ -430,7 +432,7 @@ impl LightPath {
|
||||||
fn next_lds_samp(&self) -> f32 {
|
fn next_lds_samp(&self) -> f32 {
|
||||||
let dimension = self.dim_offset.get();
|
let dimension = self.dim_offset.get();
|
||||||
self.dim_offset.set(dimension + 1);
|
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(
|
fn next(
|
||||||
|
@ -688,9 +690,9 @@ 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_with_scramble(dimension, i, hash_u32(dimension, scramble))
|
sobol::sample_owen_scramble(dimension, i, scramble + dimension)
|
||||||
} else {
|
} else {
|
||||||
hash_u32_to_f32(dimension, i + scramble)
|
hash_u32_to_f32(dimension, i ^ (scramble << 16))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,22 +18,70 @@
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
// SOFTWARE.
|
// 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;
|
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
|
/// Compute one component of one sample from the Sobol'-sequence, where
|
||||||
/// corresponds to the dimension parameter, and the index specifies
|
/// `dimension` specifies the component and `index` specifies the sample
|
||||||
/// the point inside the sequence. The scramble parameter can be used
|
/// within the sequence.
|
||||||
/// to permute elementary intervals, and might be chosen randomly to
|
|
||||||
/// generate a randomized QMC sequence.
|
|
||||||
#[inline]
|
#[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);
|
assert!((dimension as usize) < NUM_DIMENSIONS);
|
||||||
|
|
||||||
let mut result = scramble;
|
let mut result = 0;
|
||||||
let mut i = (dimension as usize) * SIZE;
|
let mut i = (dimension as usize) * SIZE;
|
||||||
while index != 0 {
|
while index != 0 {
|
||||||
if (index & 1) != 0 {
|
if (index & 1) != 0 {
|
||||||
|
@ -44,10 +92,10 @@ pub fn sample_with_scramble(dimension: u32, mut index: u32, scramble: u32) -> f3
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
result as f32 * (1.0 / (1u64 << 32) as f32)
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline(always)]
|
||||||
pub fn sample(dimension: u32, index: u32) -> f32 {
|
fn u32_to_0_1_f32(n: u32) -> f32 {
|
||||||
sample_with_scramble(dimension, index, 0)
|
n as f32 * (1.0 / (1u64 << 32) as f32)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user