Wrap sampling logic/tracking in a struct.
This commit is contained in:
parent
1f7c412e25
commit
a12de4c3d7
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -352,6 +352,7 @@ dependencies = [
|
||||||
"openexr",
|
"openexr",
|
||||||
"png_encode_mini",
|
"png_encode_mini",
|
||||||
"rmath",
|
"rmath",
|
||||||
|
"rrand",
|
||||||
"rustc-serialize",
|
"rustc-serialize",
|
||||||
"scoped_threadpool",
|
"scoped_threadpool",
|
||||||
"sobol_burley",
|
"sobol_burley",
|
||||||
|
@ -565,6 +566,10 @@ dependencies = [
|
||||||
"rand 0.6.5",
|
"rand 0.6.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rrand"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-serialize"
|
name = "rustc-serialize"
|
||||||
version = "0.3.24"
|
version = "0.3.24"
|
||||||
|
|
|
@ -5,6 +5,7 @@ members = [
|
||||||
"sub_crates/compact",
|
"sub_crates/compact",
|
||||||
"sub_crates/halton",
|
"sub_crates/halton",
|
||||||
"sub_crates/rmath",
|
"sub_crates/rmath",
|
||||||
|
"sub_crates/rrand",
|
||||||
"sub_crates/spectral_upsampling",
|
"sub_crates/spectral_upsampling",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -48,12 +49,15 @@ path = "sub_crates/color"
|
||||||
|
|
||||||
[dependencies.compact]
|
[dependencies.compact]
|
||||||
path = "sub_crates/compact"
|
path = "sub_crates/compact"
|
||||||
[dependencies.halton]
|
|
||||||
|
|
||||||
|
[dependencies.halton]
|
||||||
path = "sub_crates/halton"
|
path = "sub_crates/halton"
|
||||||
|
|
||||||
[dependencies.rmath]
|
[dependencies.rmath]
|
||||||
path = "sub_crates/rmath"
|
path = "sub_crates/rmath"
|
||||||
|
|
||||||
|
[dependencies.rrand]
|
||||||
|
path = "sub_crates/rrand"
|
||||||
|
|
||||||
[dependencies.spectral_upsampling]
|
[dependencies.spectral_upsampling]
|
||||||
path = "sub_crates/spectral_upsampling"
|
path = "sub_crates/spectral_upsampling"
|
||||||
|
|
|
@ -5,10 +5,9 @@ use std::{
|
||||||
mem::MaybeUninit,
|
mem::MaybeUninit,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use rrand::mix_seed_u64;
|
||||||
hash::hash_u64,
|
|
||||||
lerp::{lerp_slice, Lerp},
|
use crate::lerp::{lerp_slice, Lerp};
|
||||||
};
|
|
||||||
|
|
||||||
/// Selects an item from a slice based on a weighting function and a
|
/// Selects an item from a slice based on a weighting function and a
|
||||||
/// number (n) between 0.0 and 1.0. Returns the index of the selected
|
/// number (n) between 0.0 and 1.0. Returns the index of the selected
|
||||||
|
@ -209,7 +208,7 @@ where
|
||||||
let mut seed = n as u64;
|
let mut seed = n as u64;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let i = left + (hash_u64(right as u64, seed) as usize % (right - left));
|
let i = left + (mix_seed_u64(right as u64, seed) as usize % (right - left));
|
||||||
|
|
||||||
slc.swap(i, right - 1);
|
slc.swap(i, right - 1);
|
||||||
let ii = left + {
|
let ii = left + {
|
||||||
|
|
45
src/hash.rs
45
src/hash.rs
|
@ -1,45 +0,0 @@
|
||||||
/// A fast seedable 32-bit hash function.
|
|
||||||
pub fn hash_u32(mut n: u32, seed: u32) -> u32 {
|
|
||||||
// We rotate the bits of `seed` so it's unlikely to interact with `n`
|
|
||||||
// in bad ways if they're both e.g. incrementing. The particular
|
|
||||||
// rotation constant used here isn't special.
|
|
||||||
n ^= seed.rotate_left(23);
|
|
||||||
|
|
||||||
// From https://github.com/skeeto/hash-prospector
|
|
||||||
n ^= n >> 16;
|
|
||||||
n = n.wrapping_mul(0x21f0aaad);
|
|
||||||
n ^= n >> 15;
|
|
||||||
n = n.wrapping_mul(0xd35a2d97);
|
|
||||||
n ^= n >> 15;
|
|
||||||
|
|
||||||
// Xor by a random number so input zero doesn't map to output zero.
|
|
||||||
// The particular number used here isn't special.
|
|
||||||
n ^ 0xe6fe3beb
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A fast seedable 64-bit hash function.
|
|
||||||
pub fn hash_u64(mut n: u64, seed: u64) -> u64 {
|
|
||||||
// We rotate the bits of `seed` so it's unlikely to interact with `n`
|
|
||||||
// in bad ways if they're both e.g. incrementing. The particular
|
|
||||||
// rotation constant used here isn't special.
|
|
||||||
n ^= seed.rotate_left(47);
|
|
||||||
|
|
||||||
// From https://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html
|
|
||||||
n ^= n >> 30;
|
|
||||||
n = n.wrapping_mul(0xbf58476d1ce4e5b9);
|
|
||||||
n ^= n >> 27;
|
|
||||||
n = n.wrapping_mul(0x94d049bb133111eb);
|
|
||||||
n ^= n >> 31;
|
|
||||||
|
|
||||||
// Xor by a random number so input zero doesn't map to output zero.
|
|
||||||
// The particular number used here isn't special.
|
|
||||||
n ^ 0x4acc3f27cc712c9d
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a random float in [0, 1] based on 'n' and a seed.
|
|
||||||
/// Generally use n for getting a bunch of different random
|
|
||||||
/// numbers, and use seed to vary between runs.
|
|
||||||
pub fn hash_u32_to_f32(n: u32, seed: u32) -> f32 {
|
|
||||||
const INV_MAX: f32 = 1.0 / std::u32::MAX as f32;
|
|
||||||
hash_u32(n, seed) as f32 * INV_MAX
|
|
||||||
}
|
|
|
@ -22,7 +22,6 @@ mod boundable;
|
||||||
mod camera;
|
mod camera;
|
||||||
mod color;
|
mod color;
|
||||||
mod fp_utils;
|
mod fp_utils;
|
||||||
mod hash;
|
|
||||||
mod image;
|
mod image;
|
||||||
mod lerp;
|
mod lerp;
|
||||||
mod light;
|
mod light;
|
||||||
|
|
110
src/renderer.rs
110
src/renderer.rs
|
@ -234,13 +234,16 @@ impl<'a> Renderer<'a> {
|
||||||
// the samplers themselves.
|
// the samplers themselves.
|
||||||
let si_offset =
|
let si_offset =
|
||||||
owen4(morton::encode(x, y), self.seed).wrapping_mul(self.spp as u32);
|
owen4(morton::encode(x, y), self.seed).wrapping_mul(self.spp as u32);
|
||||||
for si in 0..self.spp {
|
|
||||||
let si = (si as u32).wrapping_add(si_offset);
|
for si in 0..(self.spp as u32) {
|
||||||
|
let mut sample_gen = SampleGen::new(si_offset + si, self.seed);
|
||||||
|
|
||||||
// Raw sample numbers.
|
// Raw sample numbers.
|
||||||
let d0 = golden_ratio_sample(si);
|
let d0 = golden_ratio_sample(si.wrapping_add(si_offset));
|
||||||
let (d1, d2, d3, d4) = get_sample_4d(si, 0, self.seed);
|
let [d1, d2, d3, _] = sample_gen.next_dims();
|
||||||
let (d5, _, _, _) = get_sample_4d(si, 1, self.seed);
|
sample_gen.inc_padding();
|
||||||
|
let [d4, d5, _, _] = sample_gen.next_dims();
|
||||||
|
sample_gen.inc_padding();
|
||||||
|
|
||||||
// Calculate the values we need to generate a camera ray.
|
// Calculate the values we need to generate a camera ray.
|
||||||
let (img_x, img_y) = {
|
let (img_x, img_y) = {
|
||||||
|
@ -260,7 +263,7 @@ impl<'a> Renderer<'a> {
|
||||||
.camera
|
.camera
|
||||||
.generate_ray(img_x, img_y, time, wavelength, lens_uv.0, lens_uv.1);
|
.generate_ray(img_x, img_y, time, wavelength, lens_uv.0, lens_uv.1);
|
||||||
let path_col =
|
let path_col =
|
||||||
trace_camera_light_path(&mut tracer, &self.scene, ray, si, self.seed);
|
trace_camera_light_path(&mut sample_gen, &mut tracer, &self.scene, ray);
|
||||||
|
|
||||||
// Accummulate light path color to pixel.
|
// Accummulate light path color to pixel.
|
||||||
let mut col = img_bucket.get(x, y);
|
let mut col = img_bucket.get(x, y);
|
||||||
|
@ -322,11 +325,10 @@ impl<'a> Renderer<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trace_camera_light_path(
|
fn trace_camera_light_path(
|
||||||
|
sample_gen: &mut SampleGen,
|
||||||
tracer: &mut Tracer,
|
tracer: &mut Tracer,
|
||||||
scene: &Scene,
|
scene: &Scene,
|
||||||
camera_ray: Ray,
|
camera_ray: Ray,
|
||||||
sample_index: u32,
|
|
||||||
seed: u32,
|
|
||||||
) -> SpectralSample {
|
) -> SpectralSample {
|
||||||
use crate::shading::surface_closure::SurfaceClosure;
|
use crate::shading::surface_closure::SurfaceClosure;
|
||||||
use crate::surface::SurfaceIntersection;
|
use crate::surface::SurfaceIntersection;
|
||||||
|
@ -337,7 +339,6 @@ fn trace_camera_light_path(
|
||||||
let mut ray_pdf = 1.0; // PDF from generating the camera/bounce ray.
|
let mut ray_pdf = 1.0; // PDF from generating the camera/bounce ray.
|
||||||
let mut acc_color = Float4::splat(0.0); // Accumulated color.
|
let mut acc_color = Float4::splat(0.0); // Accumulated color.
|
||||||
let mut attenuation = Float4::splat(1.0); // Color attenuation along the path so far.
|
let mut attenuation = Float4::splat(1.0); // Color attenuation along the path so far.
|
||||||
let mut sampling_seed = seed + 1;
|
|
||||||
|
|
||||||
for bounce in 0..BOUNCE_COUNT {
|
for bounce in 0..BOUNCE_COUNT {
|
||||||
let isect = tracer.trace(ray);
|
let isect = tracer.trace(ray);
|
||||||
|
@ -370,8 +371,8 @@ fn trace_camera_light_path(
|
||||||
//-------------------------------------------------
|
//-------------------------------------------------
|
||||||
// Sample light sources.
|
// Sample light sources.
|
||||||
|
|
||||||
sampling_seed = sampling_seed.wrapping_add(1);
|
sample_gen.inc_padding();
|
||||||
let (light_n, d2, d3, d4) = get_sample_4d(sample_index, 0, sampling_seed);
|
let [light_n, d2, d3, d4] = sample_gen.next_dims();
|
||||||
|
|
||||||
let light_uvw = (d2, d3, d4);
|
let light_uvw = (d2, d3, d4);
|
||||||
let light_info = scene.sample_lights(
|
let light_info = scene.sample_lights(
|
||||||
|
@ -455,8 +456,8 @@ fn trace_camera_light_path(
|
||||||
|
|
||||||
// Sample closure
|
// Sample closure
|
||||||
let (dir, filter, pdf) = {
|
let (dir, filter, pdf) = {
|
||||||
sampling_seed = sampling_seed.wrapping_add(1);
|
sample_gen.inc_padding();
|
||||||
let (u, v, _, _) = get_sample_4d(sample_index, 0, sampling_seed);
|
let [u, v, _, _] = sample_gen.next_dims();
|
||||||
|
|
||||||
closure.sample(
|
closure.sample(
|
||||||
idata.incoming,
|
idata.incoming,
|
||||||
|
@ -509,28 +510,67 @@ fn trace_camera_light_path(
|
||||||
SpectralSample::from_parts(acc_color, ray.wavelength)
|
SpectralSample::from_parts(acc_color, ray.wavelength)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a sample, using LDS samples for lower dimensions,
|
/// Generates Owen-scrambled, padded Sobol samples.
|
||||||
/// and switching to random samples at higher dimensions where
|
#[derive(Debug, Copy, Clone)]
|
||||||
/// LDS samples aren't available.
|
struct SampleGen {
|
||||||
#[inline(always)]
|
padding_rng: rrand::Rng,
|
||||||
fn get_sample_4d(i: u32, dimension_set: u32, seed: u32) -> (f32, f32, f32, f32) {
|
dimension_rng: rrand::Rng,
|
||||||
match dimension_set {
|
padding_seed: u32,
|
||||||
ds if ds < sobol_burley::NUM_DIMENSION_SETS_4D as u32 => {
|
sample_i: u32,
|
||||||
// Sobol sampling.
|
sample_i_shuffled_rev: u32,
|
||||||
//
|
dimension_i: u32,
|
||||||
let n4 = sobol_burley::sample_4d(i, ds, seed);
|
}
|
||||||
(n4[0], n4[1], n4[2], n4[3])
|
|
||||||
}
|
impl SampleGen {
|
||||||
ds => {
|
pub fn new(sample_i: u32, seed: u32) -> Self {
|
||||||
// Random sampling.
|
let mut gen = Self {
|
||||||
use crate::hash::hash_u32_to_f32;
|
sample_i: sample_i,
|
||||||
(
|
sample_i_shuffled_rev: 0,
|
||||||
hash_u32_to_f32((ds * 4 + 0) ^ (i << 16), seed),
|
padding_seed: 0,
|
||||||
hash_u32_to_f32((ds * 4 + 1) ^ (i << 16), seed),
|
dimension_i: 0,
|
||||||
hash_u32_to_f32((ds * 4 + 2) ^ (i << 16), seed),
|
padding_rng: rrand::Rng::new(rrand::mix_u64(seed as u64)),
|
||||||
hash_u32_to_f32((ds * 4 + 3) ^ (i << 16), seed),
|
dimension_rng: rrand::Rng::new(0),
|
||||||
)
|
};
|
||||||
}
|
gen.inc_padding();
|
||||||
|
|
||||||
|
gen
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Increments the padding.
|
||||||
|
pub fn inc_padding(&mut self) {
|
||||||
|
use sobol_burley::parts::owen_scramble_rev;
|
||||||
|
|
||||||
|
self.padding_seed = self.padding_rng.u32();
|
||||||
|
self.sample_i_shuffled_rev =
|
||||||
|
owen_scramble_rev(self.sample_i.reverse_bits(), self.padding_seed);
|
||||||
|
|
||||||
|
self.dimension_rng = rrand::Rng::new(self.padding_rng.u64());
|
||||||
|
self.dimension_i = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the next four dimensions of the current sample.
|
||||||
|
pub fn next_dims(&mut self) -> [f32; 4] {
|
||||||
|
use sobol_burley::parts::{owen_scramble_int4_rev, sobol_4d_rev};
|
||||||
|
|
||||||
|
let sobol_int_rev = sobol_4d_rev(self.sample_i_shuffled_rev, self.dimension_i);
|
||||||
|
self.dimension_i += 1;
|
||||||
|
|
||||||
|
let rand1 = self.dimension_rng.u64();
|
||||||
|
let rand2 = self.dimension_rng.u64();
|
||||||
|
|
||||||
|
let sobol_owen_int = owen_scramble_int4_rev(
|
||||||
|
sobol_int_rev,
|
||||||
|
[
|
||||||
|
rand1 as u32,
|
||||||
|
(rand1 >> 32) as u32,
|
||||||
|
rand2 as u32,
|
||||||
|
(rand2 >> 32) as u32,
|
||||||
|
]
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
.reverse_bits();
|
||||||
|
|
||||||
|
sobol_owen_int.to_f32_norm()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
8
sub_crates/rrand/Cargo.toml
Normal file
8
sub_crates/rrand/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "rrand"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
121
sub_crates/rrand/src/lib.rs
Normal file
121
sub_crates/rrand/src/lib.rs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
//! Sources of deterministic "randomness" for rendering applications.
|
||||||
|
|
||||||
|
/// Convert a `u32` to a float in [0.0, 1.0).
|
||||||
|
///
|
||||||
|
/// Use for getting f32 values from random u32 sources.
|
||||||
|
///
|
||||||
|
/// Note: this is a linear mapping from [0, int_max] to [0.0, 1.0).
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn u32_to_f32_norm(n: u32) -> f32 {
|
||||||
|
f32::from_bits((n >> 9) | 0x3f800000) - 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
/// A fast RNG.
|
||||||
|
///
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct Rng {
|
||||||
|
state: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rng {
|
||||||
|
/// Creates a new Rng from a seed.
|
||||||
|
///
|
||||||
|
/// A seed of zero is perfectly fine, and does not affect the quality
|
||||||
|
/// of the generator.
|
||||||
|
#[inline]
|
||||||
|
pub fn new(seed: u64) -> Self {
|
||||||
|
Self { state: seed }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the nth relative RNG stream from this one.
|
||||||
|
///
|
||||||
|
/// The returned stream will be at the same point in its sequence as
|
||||||
|
/// this one.
|
||||||
|
#[inline]
|
||||||
|
pub fn nth_stream(&self, n: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
// We just jump forward 2^40*n states. This gives us 2^24
|
||||||
|
// unique streams, each of which is 2^40 numbers long.
|
||||||
|
state: self
|
||||||
|
.state
|
||||||
|
.wrapping_add(0xa0761d6478bd642f_u64.wrapping_mul(1 << 40).wrapping_mul(n)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a random u32 in [0, int_max].
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn u32(&mut self) -> u32 {
|
||||||
|
self.u64() as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a random u64 in [0, int_max].
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn u64(&mut self) -> u64 {
|
||||||
|
// The wyrand RNG.
|
||||||
|
self.state = self.state.wrapping_add(0xa0761d6478bd642f);
|
||||||
|
let t = (self.state as u128).wrapping_mul(self.state as u128 ^ 0xe7037ed1a0b428db);
|
||||||
|
((t >> 64) ^ t) as u64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
/// A fast 32-bit mixing function.
|
||||||
|
///
|
||||||
|
/// Scrambles the input number to produce a different deterministic
|
||||||
|
/// "random" number.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn mix_u32(mut n: u32) -> u32 {
|
||||||
|
// From https://github.com/skeeto/hash-prospector
|
||||||
|
n ^= n >> 16;
|
||||||
|
n = n.wrapping_mul(0x21f0aaad);
|
||||||
|
n ^= n >> 15;
|
||||||
|
n = n.wrapping_mul(0xd35a2d97);
|
||||||
|
n ^= n >> 15;
|
||||||
|
|
||||||
|
// Xor by a random number so input zero doesn't map to output zero.
|
||||||
|
// The particular number used here isn't special.
|
||||||
|
n ^ 0xe6fe3beb
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A fast seedable 32-bit mixing function.
|
||||||
|
///
|
||||||
|
/// Same as `mix_u32()` but takes a seed.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn mix_seed_u32(n: u32, seed: u32) -> u32 {
|
||||||
|
// We rotate the bits of `seed` so it's unlikely to interact with `n`
|
||||||
|
// in bad ways if they're both e.g. incrementing. The particular
|
||||||
|
// rotation constant used here isn't special.
|
||||||
|
mix_u32(n ^ seed.rotate_left(23))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A fast 64-bit mixing function.
|
||||||
|
///
|
||||||
|
/// Scrambles the input number to produce a different deterministic
|
||||||
|
/// "random" number.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn mix_u64(mut n: u64) -> u64 {
|
||||||
|
// From https://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html
|
||||||
|
n ^= n >> 30;
|
||||||
|
n = n.wrapping_mul(0xbf58476d1ce4e5b9);
|
||||||
|
n ^= n >> 27;
|
||||||
|
n = n.wrapping_mul(0x94d049bb133111eb);
|
||||||
|
n ^= n >> 31;
|
||||||
|
|
||||||
|
// Xor by a random number so input zero doesn't map to output zero.
|
||||||
|
// The particular number used here isn't special.
|
||||||
|
n ^ 0x4acc3f27cc712c9d
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A fast seedable 64-bit mixing function.
|
||||||
|
///
|
||||||
|
/// Same as `mix_u64()` but takes a seed.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn mix_seed_u64(n: u64, seed: u64) -> u64 {
|
||||||
|
// We rotate the bits of `seed` so it's unlikely to interact with `n`
|
||||||
|
// in bad ways if they're both e.g. incrementing. The particular
|
||||||
|
// rotation constant used here isn't special.
|
||||||
|
mix_u64(n ^ seed.rotate_left(47))
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user