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
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user