The first two dimensions of scrambled, shuffled Sobol and shuffled PMJ02 are equivalent, so this makes no real difference for the first two dimensions. But Sobol allows us to naturally extend to more dimensions. Pretabulated Sobol is now always used, and the sampling pattern settings is now only available as a debug option. This in turn allows the following two things (also implemented): * Use proper 3D samples for combined lens + motion blur sampling. This notably reduces the noise on objects that are simultaneously out-of-focus and motion blurred. * Use proper 3D samples for combined light selection + light sampling. Cycles was already doing something clever here with 2D samples, but using 3D samples is more straightforward and avoids overloading one of the dimensions. In the future this will also allow for proper sampling of e.g. volumetric light sources and other things that may need three or four dimensions. Differential Revision: https://developer.blender.org/D16443
175 lines
7.0 KiB
C++
175 lines
7.0 KiB
C++
/* SPDX-License-Identifier: Apache-2.0
|
|
* Copyright 2011-2022 Blender Foundation */
|
|
|
|
#include "kernel/sample/util.h"
|
|
#include "util/hash.h"
|
|
|
|
#pragma once
|
|
CCL_NAMESPACE_BEGIN
|
|
|
|
ccl_device uint tabulated_sobol_shuffled_sample_index(KernelGlobals kg,
|
|
uint sample,
|
|
uint dimension,
|
|
uint seed)
|
|
{
|
|
const uint sample_count = kernel_data.integrator.tabulated_sobol_sequence_size;
|
|
|
|
/* Shuffle the pattern order and sample index to decorrelate
|
|
* dimensions and make the most of the finite patterns we have.
|
|
* The funky sample mask stuff is to ensure that we only shuffle
|
|
* *within* the current sample pattern, which is necessary to avoid
|
|
* early repeat pattern use. */
|
|
const uint pattern_i = hash_shuffle_uint(dimension, NUM_TAB_SOBOL_PATTERNS, seed);
|
|
/* sample_count should always be a power of two, so this results in a mask. */
|
|
const uint sample_mask = sample_count - 1;
|
|
const uint sample_shuffled = nested_uniform_scramble(sample,
|
|
hash_wang_seeded_uint(dimension, seed));
|
|
sample = (sample & ~sample_mask) | (sample_shuffled & sample_mask);
|
|
|
|
return ((pattern_i * sample_count) + sample) % (sample_count * NUM_TAB_SOBOL_PATTERNS);
|
|
}
|
|
|
|
ccl_device float tabulated_sobol_sample_1D(KernelGlobals kg,
|
|
uint sample,
|
|
const uint rng_hash,
|
|
const uint dimension)
|
|
{
|
|
uint seed = rng_hash;
|
|
|
|
/* Use the same sample sequence seed for all pixels when using
|
|
* scrambling distance. */
|
|
if (kernel_data.integrator.scrambling_distance < 1.0f) {
|
|
seed = kernel_data.integrator.seed;
|
|
}
|
|
|
|
/* Fetch the sample. */
|
|
const uint index = tabulated_sobol_shuffled_sample_index(kg, sample, dimension, seed);
|
|
float x = kernel_data_fetch(sample_pattern_lut, index * NUM_TAB_SOBOL_DIMENSIONS);
|
|
|
|
/* Do limited Cranley-Patterson rotation when using scrambling distance. */
|
|
if (kernel_data.integrator.scrambling_distance < 1.0f) {
|
|
const float jitter_x = hash_wang_seeded_float(dimension, rng_hash) *
|
|
kernel_data.integrator.scrambling_distance;
|
|
x += jitter_x;
|
|
x -= floorf(x);
|
|
}
|
|
|
|
return x;
|
|
}
|
|
|
|
ccl_device float2 tabulated_sobol_sample_2D(KernelGlobals kg,
|
|
uint sample,
|
|
const uint rng_hash,
|
|
const uint dimension)
|
|
{
|
|
uint seed = rng_hash;
|
|
|
|
/* Use the same sample sequence seed for all pixels when using
|
|
* scrambling distance. */
|
|
if (kernel_data.integrator.scrambling_distance < 1.0f) {
|
|
seed = kernel_data.integrator.seed;
|
|
}
|
|
|
|
/* Fetch the sample. */
|
|
const uint index = tabulated_sobol_shuffled_sample_index(kg, sample, dimension, seed);
|
|
float x = kernel_data_fetch(sample_pattern_lut, index * NUM_TAB_SOBOL_DIMENSIONS);
|
|
float y = kernel_data_fetch(sample_pattern_lut, index * NUM_TAB_SOBOL_DIMENSIONS + 1);
|
|
|
|
/* Do limited Cranley-Patterson rotation when using scrambling distance. */
|
|
if (kernel_data.integrator.scrambling_distance < 1.0f) {
|
|
const float jitter_x = hash_wang_seeded_float(dimension, rng_hash) *
|
|
kernel_data.integrator.scrambling_distance;
|
|
const float jitter_y = hash_wang_seeded_float(dimension, rng_hash ^ 0xca0e1151) *
|
|
kernel_data.integrator.scrambling_distance;
|
|
x += jitter_x;
|
|
y += jitter_y;
|
|
x -= floorf(x);
|
|
y -= floorf(y);
|
|
}
|
|
|
|
return make_float2(x, y);
|
|
}
|
|
|
|
ccl_device float3 tabulated_sobol_sample_3D(KernelGlobals kg,
|
|
uint sample,
|
|
const uint rng_hash,
|
|
const uint dimension)
|
|
{
|
|
uint seed = rng_hash;
|
|
|
|
/* Use the same sample sequence seed for all pixels when using
|
|
* scrambling distance. */
|
|
if (kernel_data.integrator.scrambling_distance < 1.0f) {
|
|
seed = kernel_data.integrator.seed;
|
|
}
|
|
|
|
/* Fetch the sample. */
|
|
const uint index = tabulated_sobol_shuffled_sample_index(kg, sample, dimension, seed);
|
|
float x = kernel_data_fetch(sample_pattern_lut, index * NUM_TAB_SOBOL_DIMENSIONS);
|
|
float y = kernel_data_fetch(sample_pattern_lut, index * NUM_TAB_SOBOL_DIMENSIONS + 1);
|
|
float z = kernel_data_fetch(sample_pattern_lut, index * NUM_TAB_SOBOL_DIMENSIONS + 2);
|
|
|
|
/* Do limited Cranley-Patterson rotation when using scrambling distance. */
|
|
if (kernel_data.integrator.scrambling_distance < 1.0f) {
|
|
const float jitter_x = hash_wang_seeded_float(dimension, rng_hash) *
|
|
kernel_data.integrator.scrambling_distance;
|
|
const float jitter_y = hash_wang_seeded_float(dimension, rng_hash ^ 0xca0e1151) *
|
|
kernel_data.integrator.scrambling_distance;
|
|
const float jitter_z = hash_wang_seeded_float(dimension, rng_hash ^ 0xbf604c5a) *
|
|
kernel_data.integrator.scrambling_distance;
|
|
x += jitter_x;
|
|
y += jitter_y;
|
|
z += jitter_z;
|
|
x -= floorf(x);
|
|
y -= floorf(y);
|
|
z -= floorf(z);
|
|
}
|
|
|
|
return make_float3(x, y, z);
|
|
}
|
|
|
|
ccl_device float4 tabulated_sobol_sample_4D(KernelGlobals kg,
|
|
uint sample,
|
|
const uint rng_hash,
|
|
const uint dimension)
|
|
{
|
|
uint seed = rng_hash;
|
|
|
|
/* Use the same sample sequence seed for all pixels when using
|
|
* scrambling distance. */
|
|
if (kernel_data.integrator.scrambling_distance < 1.0f) {
|
|
seed = kernel_data.integrator.seed;
|
|
}
|
|
|
|
/* Fetch the sample. */
|
|
const uint index = tabulated_sobol_shuffled_sample_index(kg, sample, dimension, seed);
|
|
float x = kernel_data_fetch(sample_pattern_lut, index * NUM_TAB_SOBOL_DIMENSIONS);
|
|
float y = kernel_data_fetch(sample_pattern_lut, index * NUM_TAB_SOBOL_DIMENSIONS + 1);
|
|
float z = kernel_data_fetch(sample_pattern_lut, index * NUM_TAB_SOBOL_DIMENSIONS + 2);
|
|
float w = kernel_data_fetch(sample_pattern_lut, index * NUM_TAB_SOBOL_DIMENSIONS + 3);
|
|
|
|
/* Do limited Cranley-Patterson rotation when using scrambling distance. */
|
|
if (kernel_data.integrator.scrambling_distance < 1.0f) {
|
|
const float jitter_x = hash_wang_seeded_float(dimension, rng_hash) *
|
|
kernel_data.integrator.scrambling_distance;
|
|
const float jitter_y = hash_wang_seeded_float(dimension, rng_hash ^ 0xca0e1151) *
|
|
kernel_data.integrator.scrambling_distance;
|
|
const float jitter_z = hash_wang_seeded_float(dimension, rng_hash ^ 0xbf604c5a) *
|
|
kernel_data.integrator.scrambling_distance;
|
|
const float jitter_w = hash_wang_seeded_float(dimension, rng_hash ^ 0x99634d1d) *
|
|
kernel_data.integrator.scrambling_distance;
|
|
x += jitter_x;
|
|
y += jitter_y;
|
|
z += jitter_z;
|
|
w += jitter_w;
|
|
x -= floorf(x);
|
|
y -= floorf(y);
|
|
z -= floorf(z);
|
|
w -= floorf(w);
|
|
}
|
|
|
|
return make_float4(x, y, z, w);
|
|
}
|
|
|
|
CCL_NAMESPACE_END
|