From 402d8dac09d4efff151a503f7ca285c718adc794 Mon Sep 17 00:00:00 2001 From: Lukas Stockner Date: Tue, 20 Feb 2024 02:06:48 +0100 Subject: [PATCH 1/7] Cycles: Implement blue-noise dithered sampling --- intern/cycles/blender/addon/properties.py | 38 ++++++-- intern/cycles/blender/addon/ui.py | 23 +---- intern/cycles/blender/sync.cpp | 27 ++++-- intern/cycles/kernel/data_template.h | 2 + .../cycles/kernel/integrator/init_from_bake.h | 1 + .../integrator/subsurface_random_walk.h | 1 + intern/cycles/kernel/sample/pattern.h | 86 ++++++++++++++----- intern/cycles/kernel/sample/util.h | 42 +++++++++ intern/cycles/kernel/types.h | 5 ++ intern/cycles/scene/integrator.cpp | 20 ++++- 10 files changed, 187 insertions(+), 58 deletions(-) diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py index 01440ce95e9..789618b2d0c 100644 --- a/intern/cycles/blender/addon/properties.py +++ b/intern/cycles/blender/addon/properties.py @@ -70,10 +70,38 @@ enum_use_layer_samples = ( ('IGNORE', "Ignore", "Ignore per render layer number of samples"), ) -enum_sampling_pattern = ( - ('SOBOL_BURLEY', "Sobol-Burley", "Use on-the-fly computed Owen-scrambled Sobol for random sampling", 0), - ('TABULATED_SOBOL', "Tabulated Sobol", "Use pre-computed tables of Owen-scrambled Sobol for random sampling", 1), -) + +def enum_sampling_pattern(self, context): + prefs = context.preferences + use_debug = prefs.experimental.use_cycles_debug and prefs.view.show_developer_ui + + items = [ + ('AUTOMATIC', + "Automatic", + "Use a blue-noise sampling pattern, which optimizes the frequency distribution of noise, for random sampling. For viewport rendering, optimize first sample quality for interactive preview", + 5)] + + if use_debug: + items += [ + ('SOBOL_BURLEY', "Sobol-Burley", "Use on-the-fly computed Owen-scrambled Sobol for random sampling", 0), + ('TABULATED_SOBOL', "Tabulated Sobol", "Use pre-computed tables of Owen-scrambled Sobol for random sampling", 1), + ('BLUE_NOISE', "Blue-Noise (pure)", "Blue-Noise (pure)", 2), + ('BLUE_NOISE_FIRST', "Blue-Noise (first)", "Blue-Noise (first)", 3), + ('BLUE_NOISE_ROUND', "Blue-Noise (round)", "Blue-Noise (round)", 4), + ] + else: + items += [('TABULATED_SOBOL', + "Classic", + "Use pre-computed tables of Owen-scrambled Sobol for random sampling", + 1), + ('BLUE_NOISE', + "Blue-Noise", + "Use a blue-noise pattern, which optimizes the frequency distribution of noise, for random sampling", + 2), + ] + + return items + enum_emission_sampling = ( ('NONE', @@ -461,7 +489,7 @@ class CyclesRenderSettings(bpy.types.PropertyGroup): name="Sampling Pattern", description="Random sampling pattern used by the integrator", items=enum_sampling_pattern, - default='TABULATED_SOBOL', + default=6, ) scrambling_distance: FloatProperty( diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index 04b472e88b8..172dd1608fa 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -425,6 +425,9 @@ class CYCLES_RENDER_PT_sampling_advanced(CyclesButtonsPanel, Panel): scene = context.scene cscene = scene.cycles + row = layout.row(align=True) + row.prop(cscene, "sampling_pattern", text="Pattern") + row = layout.row(align=True) row.prop(cscene, "seed") row.prop(cscene, "use_animated_seed", text="", icon='TIME') @@ -436,7 +439,7 @@ class CYCLES_RENDER_PT_sampling_advanced(CyclesButtonsPanel, Panel): heading = layout.column(align=True, heading="Scrambling Distance") # Tabulated Sobol is used when the debug UI is turned off. - heading.active = cscene.sampling_pattern == 'TABULATED_SOBOL' or not CyclesDebugButtonsPanel.poll(context) + heading.active = cscene.sampling_pattern == 'TABULATED_SOBOL' heading.prop(cscene, "auto_scrambling_distance", text="Automatic") heading.prop(cscene, "preview_scrambling_distance", text="Viewport") heading.prop(cscene, "scrambling_distance", text="Multiplier") @@ -474,23 +477,6 @@ class CYCLES_RENDER_PT_sampling_lights(CyclesButtonsPanel, Panel): sub.active = not cscene.use_light_tree -class CYCLES_RENDER_PT_sampling_debug(CyclesDebugButtonsPanel, Panel): - bl_label = "Debug" - bl_parent_id = "CYCLES_RENDER_PT_sampling" - bl_options = {'DEFAULT_CLOSED'} - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False - - scene = context.scene - cscene = scene.cycles - - col = layout.column(align=True) - col.prop(cscene, "sampling_pattern", text="Pattern") - - class CYCLES_RENDER_PT_subdivision(CyclesButtonsPanel, Panel): bl_label = "Subdivision" bl_options = {'DEFAULT_CLOSED'} @@ -2576,7 +2562,6 @@ classes = ( CYCLES_RENDER_PT_sampling_path_guiding_debug, CYCLES_RENDER_PT_sampling_lights, CYCLES_RENDER_PT_sampling_advanced, - CYCLES_RENDER_PT_sampling_debug, CYCLES_RENDER_PT_light_paths, CYCLES_RENDER_PT_light_paths_max_bounces, CYCLES_RENDER_PT_light_paths_clamping, diff --git a/intern/cycles/blender/sync.cpp b/intern/cycles/blender/sync.cpp index 11faef434b0..adeb1ca5c83 100644 --- a/intern/cycles/blender/sync.cpp +++ b/intern/cycles/blender/sync.cpp @@ -359,13 +359,24 @@ void BlenderSync::sync_integrator(BL::ViewLayer &b_view_layer, scene->light_manager->tag_update(scene, LightManager::UPDATE_ALL); } - SamplingPattern sampling_pattern; - if (use_developer_ui) { - sampling_pattern = (SamplingPattern)get_enum( - cscene, "sampling_pattern", SAMPLING_NUM_PATTERNS, SAMPLING_PATTERN_TABULATED_SOBOL); - } - else { - sampling_pattern = SAMPLING_PATTERN_TABULATED_SOBOL; + SamplingPattern sampling_pattern = (SamplingPattern)get_enum( + cscene, "sampling_pattern", SAMPLING_NUM_PATTERNS, SAMPLING_PATTERN_TABULATED_SOBOL); + + switch (sampling_pattern) { + case SAMPLING_PATTERN_AUTOMATIC: + sampling_pattern = background ? SAMPLING_PATTERN_BLUE_NOISE_PURE : + SAMPLING_PATTERN_BLUE_NOISE_FIRST; + break; + case SAMPLING_PATTERN_TABULATED_SOBOL: + case SAMPLING_PATTERN_BLUE_NOISE_PURE: + /* Always allowed. */ + break; + default: + /* If not using developer UI, default to blue noise for "advanced" patterns. */ + if (!use_developer_ui) { + sampling_pattern = SAMPLING_PATTERN_BLUE_NOISE_PURE; + } + break; } integrator->set_sampling_pattern(sampling_pattern); @@ -409,7 +420,7 @@ void BlenderSync::sync_integrator(BL::ViewLayer &b_view_layer, /* Only use scrambling distance in the viewport if user wants to. */ bool preview_scrambling_distance = get_boolean(cscene, "preview_scrambling_distance"); if ((preview && !preview_scrambling_distance) || - sampling_pattern == SAMPLING_PATTERN_SOBOL_BURLEY) + sampling_pattern != SAMPLING_PATTERN_TABULATED_SOBOL) { scrambling_distance = 1.0f; } diff --git a/intern/cycles/kernel/data_template.h b/intern/cycles/kernel/data_template.h index 29ca68f84e7..3ecce93bdad 100644 --- a/intern/cycles/kernel/data_template.h +++ b/intern/cycles/kernel/data_template.h @@ -195,6 +195,8 @@ KERNEL_STRUCT_MEMBER_DONT_SPECIALIZE KERNEL_STRUCT_MEMBER(integrator, int, tabulated_sobol_sequence_size) KERNEL_STRUCT_MEMBER_DONT_SPECIALIZE KERNEL_STRUCT_MEMBER(integrator, int, sobol_index_mask) +KERNEL_STRUCT_MEMBER_DONT_SPECIALIZE +KERNEL_STRUCT_MEMBER(integrator, int, blue_noise_sequence_length) /* Volume render. */ KERNEL_STRUCT_MEMBER(integrator, int, use_volumes) KERNEL_STRUCT_MEMBER(integrator, int, volume_max_steps) diff --git a/intern/cycles/kernel/integrator/init_from_bake.h b/intern/cycles/kernel/integrator/init_from_bake.h index 5666fd3e75a..1668b50199b 100644 --- a/intern/cycles/kernel/integrator/init_from_bake.h +++ b/intern/cycles/kernel/integrator/init_from_bake.h @@ -141,6 +141,7 @@ ccl_device bool integrator_init_from_bake(KernelGlobals kg, prim += kernel_data.bake.tri_offset; /* Random number generator. */ + // TODO const uint rng_hash = hash_uint(seed) ^ kernel_data.integrator.seed; const float2 rand_filter = (sample == 0) ? make_float2(0.5f, 0.5f) : diff --git a/intern/cycles/kernel/integrator/subsurface_random_walk.h b/intern/cycles/kernel/integrator/subsurface_random_walk.h index d0dc7082aa9..182310423d2 100644 --- a/intern/cycles/kernel/integrator/subsurface_random_walk.h +++ b/intern/cycles/kernel/integrator/subsurface_random_walk.h @@ -222,6 +222,7 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg, const float phase_log = logf((diffusion_length + 1.0f) / (diffusion_length - 1.0f)); /* Modify state for RNGs, decorrelated from other paths. */ + // TODO rng_state.rng_hash = hash_hp_seeded_uint(rng_state.rng_hash + rng_state.rng_offset, 0xdeadbeef); /* Random walk until we hit the surface again. */ diff --git a/intern/cycles/kernel/sample/pattern.h b/intern/cycles/kernel/sample/pattern.h index 929dd43e5f3..dab98678124 100644 --- a/intern/cycles/kernel/sample/pattern.h +++ b/intern/cycles/kernel/sample/pattern.h @@ -28,22 +28,55 @@ CCL_NAMESPACE_BEGIN * x,y over x,z. */ +ccl_device_forceinline uint3 blue_noise_indexing(KernelGlobals kg, uint pixel_index, uint sample) +{ + if (kernel_data.integrator.sampling_pattern == SAMPLING_PATTERN_SOBOL_BURLEY) { + /* One sequence per pixel, using the length mask optimization. */ + return make_uint3(sample, pixel_index, kernel_data.integrator.sobol_index_mask); + } + else if (kernel_data.integrator.sampling_pattern == SAMPLING_PATTERN_BLUE_NOISE_PURE) { + /* For blue-noise samples, we use a single sequence (seed 0) with each pixel receiving + * a section of it. + * The total length is expected to get very large (effectively pixel count times sample count), + * so we don't use the length mask optimization here. */ + pixel_index *= kernel_data.integrator.blue_noise_sequence_length; + return make_uint3(sample + pixel_index, 0, 0xffffffff); + } + else if (kernel_data.integrator.sampling_pattern == SAMPLING_PATTERN_BLUE_NOISE_FIRST) { + /* The "first" pattern uses a 1SPP blue-noise sequence for the first sample, and a separate + * N-1 SPP sequence for the remaining pixels. The purpose of this is to get blue-noise + * properties during viewport navigation, which will generally use 1 SPP. + * Unfortunately using just the first sample of a full blue-noise sequence doesn't give + * its benefits, so we combine the two as a tradeoff between quality at 1 SPP and full SPP. */ + if (sample == 0) { + return make_uint3(pixel_index, 0x0cd0519f, 0xffffffff); + } + else { + pixel_index *= kernel_data.integrator.blue_noise_sequence_length; + return make_uint3((sample - 1) + pixel_index, 0, 0xffffffff); + } + } + else { + kernel_assert(false); + return make_uint3(0, 0, 0); + } +} + ccl_device_forceinline float path_rng_1D(KernelGlobals kg, uint rng_hash, - int sample, + uint sample, int dimension) { #ifdef __DEBUG_CORRELATION__ return (float)drand48(); #endif - if (kernel_data.integrator.sampling_pattern == SAMPLING_PATTERN_SOBOL_BURLEY) { - const uint index_mask = kernel_data.integrator.sobol_index_mask; - return sobol_burley_sample_1D(sample, dimension, rng_hash, index_mask); - } - else { + if (kernel_data.integrator.sampling_pattern == SAMPLING_PATTERN_TABULATED_SOBOL) { return tabulated_sobol_sample_1D(kg, sample, rng_hash, dimension); } + + uint3 index = blue_noise_indexing(kg, rng_hash, sample); + return sobol_burley_sample_1D(index.x, dimension, index.y, index.z); } ccl_device_forceinline float2 path_rng_2D(KernelGlobals kg, @@ -55,13 +88,12 @@ ccl_device_forceinline float2 path_rng_2D(KernelGlobals kg, return make_float2((float)drand48(), (float)drand48()); #endif - if (kernel_data.integrator.sampling_pattern == SAMPLING_PATTERN_SOBOL_BURLEY) { - const uint index_mask = kernel_data.integrator.sobol_index_mask; - return sobol_burley_sample_2D(sample, dimension, rng_hash, index_mask); - } - else { + if (kernel_data.integrator.sampling_pattern == SAMPLING_PATTERN_TABULATED_SOBOL) { return tabulated_sobol_sample_2D(kg, sample, rng_hash, dimension); } + + uint3 index = blue_noise_indexing(kg, rng_hash, sample); + return sobol_burley_sample_2D(index.x, dimension, index.y, index.z); } ccl_device_forceinline float3 path_rng_3D(KernelGlobals kg, @@ -73,13 +105,12 @@ ccl_device_forceinline float3 path_rng_3D(KernelGlobals kg, return make_float3((float)drand48(), (float)drand48(), (float)drand48()); #endif - if (kernel_data.integrator.sampling_pattern == SAMPLING_PATTERN_SOBOL_BURLEY) { - const uint index_mask = kernel_data.integrator.sobol_index_mask; - return sobol_burley_sample_3D(sample, dimension, rng_hash, index_mask); - } - else { + if (kernel_data.integrator.sampling_pattern == SAMPLING_PATTERN_TABULATED_SOBOL) { return tabulated_sobol_sample_3D(kg, sample, rng_hash, dimension); } + + uint3 index = blue_noise_indexing(kg, rng_hash, sample); + return sobol_burley_sample_3D(index.x, dimension, index.y, index.z); } ccl_device_forceinline float4 path_rng_4D(KernelGlobals kg, @@ -91,13 +122,12 @@ ccl_device_forceinline float4 path_rng_4D(KernelGlobals kg, return make_float4((float)drand48(), (float)drand48(), (float)drand48(), (float)drand48()); #endif - if (kernel_data.integrator.sampling_pattern == SAMPLING_PATTERN_SOBOL_BURLEY) { - const uint index_mask = kernel_data.integrator.sobol_index_mask; - return sobol_burley_sample_4D(sample, dimension, rng_hash, index_mask); - } - else { + if (kernel_data.integrator.sampling_pattern == SAMPLING_PATTERN_TABULATED_SOBOL) { return tabulated_sobol_sample_4D(kg, sample, rng_hash, dimension); } + + uint3 index = blue_noise_indexing(kg, rng_hash, sample); + return sobol_burley_sample_4D(index.x, dimension, index.y, index.z); } ccl_device_inline uint path_rng_hash_init(KernelGlobals kg, @@ -105,7 +135,19 @@ ccl_device_inline uint path_rng_hash_init(KernelGlobals kg, const int x, const int y) { - const uint rng_hash = hash_iqnt2d(x, y) ^ kernel_data.integrator.seed; + uint rng_hash = kernel_data.integrator.seed; + const uint pattern = kernel_data.integrator.sampling_pattern; + if (pattern == SAMPLING_PATTERN_TABULATED_SOBOL || pattern == SAMPLING_PATTERN_SOBOL_BURLEY) { + rng_hash ^= hash_iqnt2d(x, y); + } + else { + /* Perform blue-noise dithered sampling by distributing the base sequence across pixels + * following a hierarchically shuffled 2D morton curve. + * Based on: + * https://psychopath.io/post/2022_07_24_owen_scrambling_based_dithered_blue_noise_sampling. + */ + rng_hash = nested_uniform_scramble_base4(morton2d(x, y), rng_hash); + } #ifdef __DEBUG_CORRELATION__ srand48(rng_hash + sample); diff --git a/intern/cycles/kernel/sample/util.h b/intern/cycles/kernel/sample/util.h index 2537f385c70..7eab6bb0649 100644 --- a/intern/cycles/kernel/sample/util.h +++ b/intern/cycles/kernel/sample/util.h @@ -25,6 +25,25 @@ ccl_device_inline uint reversed_bit_owen(uint n, uint seed) return n; } +/* + * Performs base-4 Owen scrambling on a reversed-bit unsigned integer. + * + * See https://psychopath.io/post/2022_08_14_a_fast_hash_for_base_4_owen_scrambling + */ + +ccl_device_inline uint reversed_bit_owen_base4(uint n, uint seed) +{ + n ^= n * 0x3d20adea; + n ^= (n >> 1) & (n << 1) & 0x55555555; + n += seed; + n *= (seed >> 16) | 1; + n ^= (n >> 1) & (n << 1) & 0x55555555; + n ^= n * 0x05526c56; + n ^= n * 0x53a22864; + + return n; +} + /* * Performs base-2 Owen scrambling on an unsigned integer. */ @@ -33,4 +52,27 @@ ccl_device_inline uint nested_uniform_scramble(uint i, uint seed) return reverse_integer_bits(reversed_bit_owen(reverse_integer_bits(i), seed)); } +/* + * Performs base-4 Owen scrambling on an unsigned integer. + */ +ccl_device_inline uint nested_uniform_scramble_base4(uint i, uint seed) +{ + return reverse_integer_bits(reversed_bit_owen_base4(reverse_integer_bits(i), seed)); +} + +ccl_device_inline uint expand_bits(uint x) +{ + x &= 0x0000ffff; + x = (x ^ (x << 8)) & 0x00ff00ff; + x = (x ^ (x << 4)) & 0x0f0f0f0f; + x = (x ^ (x << 2)) & 0x33333333; + x = (x ^ (x << 1)) & 0x55555555; + return x; +} + +ccl_device_inline uint morton2d(uint x, uint y) +{ + return (expand_bits(x) << 1) | expand_bits(y); +} + CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h index 7952d384680..016d2a08629 100644 --- a/intern/cycles/kernel/types.h +++ b/intern/cycles/kernel/types.h @@ -340,6 +340,11 @@ enum PathTraceDimension { enum SamplingPattern { SAMPLING_PATTERN_SOBOL_BURLEY = 0, SAMPLING_PATTERN_TABULATED_SOBOL = 1, + SAMPLING_PATTERN_BLUE_NOISE_PURE = 2, + SAMPLING_PATTERN_BLUE_NOISE_FIRST = 3, + SAMPLING_PATTERN_BLUE_NOISE_ROUND = 4, + /* Never used in kernel. */ + SAMPLING_PATTERN_AUTOMATIC = 5, SAMPLING_NUM_PATTERNS, }; diff --git a/intern/cycles/scene/integrator.cpp b/intern/cycles/scene/integrator.cpp index ad37c071e0c..f70b16ca053 100644 --- a/intern/cycles/scene/integrator.cpp +++ b/intern/cycles/scene/integrator.cpp @@ -121,6 +121,9 @@ NODE_DEFINE(Integrator) static NodeEnum sampling_pattern_enum; sampling_pattern_enum.insert("sobol_burley", SAMPLING_PATTERN_SOBOL_BURLEY); sampling_pattern_enum.insert("tabulated_sobol", SAMPLING_PATTERN_TABULATED_SOBOL); + sampling_pattern_enum.insert("blue_noise_pure", SAMPLING_PATTERN_BLUE_NOISE_PURE); + sampling_pattern_enum.insert("blue_noise_round", SAMPLING_PATTERN_BLUE_NOISE_ROUND); + sampling_pattern_enum.insert("blue_noise_first", SAMPLING_PATTERN_BLUE_NOISE_FIRST); SOCKET_ENUM(sampling_pattern, "Sampling Pattern", sampling_pattern_enum, @@ -274,6 +277,16 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene kintegrator->sampling_pattern = sampling_pattern; kintegrator->scrambling_distance = scrambling_distance; kintegrator->sobol_index_mask = reverse_integer_bits(next_power_of_two(aa_samples - 1) - 1); + kintegrator->blue_noise_sequence_length = aa_samples; + if (kintegrator->sampling_pattern == SAMPLING_PATTERN_BLUE_NOISE_ROUND) { + if (!is_power_of_two(aa_samples)) { + kintegrator->blue_noise_sequence_length = next_power_of_two(aa_samples); + } + kintegrator->sampling_pattern = SAMPLING_PATTERN_BLUE_NOISE_PURE; + } + if (kintegrator->sampling_pattern == SAMPLING_PATTERN_BLUE_NOISE_FIRST) { + kintegrator->blue_noise_sequence_length -= 1; + } /* NOTE: The kintegrator->use_light_tree is assigned to the efficient value in the light manager, * and the synchronization code is expected to tag the light manager for update when the @@ -288,17 +301,16 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene /* Build pre-tabulated Sobol samples if needed. */ int sequence_size = clamp( next_power_of_two(aa_samples - 1), MIN_TAB_SOBOL_SAMPLES, MAX_TAB_SOBOL_SAMPLES); + const int table_size = sequence_size * NUM_TAB_SOBOL_PATTERNS * NUM_TAB_SOBOL_DIMENSIONS; if (kintegrator->sampling_pattern == SAMPLING_PATTERN_TABULATED_SOBOL && - dscene->sample_pattern_lut.size() != - (sequence_size * NUM_TAB_SOBOL_PATTERNS * NUM_TAB_SOBOL_DIMENSIONS)) + dscene->sample_pattern_lut.size() != table_size) { kintegrator->tabulated_sobol_sequence_size = sequence_size; if (dscene->sample_pattern_lut.size() != 0) { dscene->sample_pattern_lut.free(); } - float4 *directions = (float4 *)dscene->sample_pattern_lut.alloc( - sequence_size * NUM_TAB_SOBOL_PATTERNS * NUM_TAB_SOBOL_DIMENSIONS); + float4 *directions = (float4 *)dscene->sample_pattern_lut.alloc(table_size); TaskPool pool; for (int j = 0; j < NUM_TAB_SOBOL_PATTERNS; ++j) { float4 *sequence = directions + j * sequence_size; -- 2.30.2 From c3cdc04966312b810c6416d4a963b2d50a983570 Mon Sep 17 00:00:00 2001 From: Lukas Stockner Date: Sun, 2 Jun 2024 19:56:42 +0200 Subject: [PATCH 2/7] Handle baking --- intern/cycles/blender/sync.cpp | 23 +++++++++++++++---- .../cycles/kernel/integrator/init_from_bake.h | 10 +++++--- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/intern/cycles/blender/sync.cpp b/intern/cycles/blender/sync.cpp index adeb1ca5c83..f925e739b59 100644 --- a/intern/cycles/blender/sync.cpp +++ b/intern/cycles/blender/sync.cpp @@ -359,13 +359,28 @@ void BlenderSync::sync_integrator(BL::ViewLayer &b_view_layer, scene->light_manager->tag_update(scene, LightManager::UPDATE_ALL); } + const bool is_vertex_baking = scene->bake_manager->get_baking() && + b_scene.render().bake().target() != BL::BakeSettings::target_IMAGE_TEXTURES; + SamplingPattern sampling_pattern = (SamplingPattern)get_enum( cscene, "sampling_pattern", SAMPLING_NUM_PATTERNS, SAMPLING_PATTERN_TABULATED_SOBOL); switch (sampling_pattern) { case SAMPLING_PATTERN_AUTOMATIC: - sampling_pattern = background ? SAMPLING_PATTERN_BLUE_NOISE_PURE : - SAMPLING_PATTERN_BLUE_NOISE_FIRST; + if (is_vertex_baking) { + /* When baking vertex colors, the "pixels" in the output are unrelated to their neighbors, + * so blue-noise sampling makes no sense. */ + sampling_pattern = SAMPLING_PATTERN_TABULATED_SOBOL; + } + else if (!background) { + /* For interactive rendering, ensure that the first sample is in itself + * blue-noise-distributed for smooth viewport navigation. */ + sampling_pattern = SAMPLING_PATTERN_BLUE_NOISE_FIRST; + } + else { + /* For non-interactive rendering, default to a full blue-noise pattern. */ + sampling_pattern = SAMPLING_PATTERN_BLUE_NOISE_PURE; + } break; case SAMPLING_PATTERN_TABULATED_SOBOL: case SAMPLING_PATTERN_BLUE_NOISE_PURE: @@ -476,9 +491,7 @@ void BlenderSync::sync_integrator(BL::ViewLayer &b_view_layer, /* No denoising support for vertex color baking, vertices packed into image * buffer have no relation to neighbors. */ - if (scene->bake_manager->get_baking() && - b_scene.render().bake().target() != BL::BakeSettings::target_IMAGE_TEXTURES) - { + if (is_vertex_baking) { denoise_params.use = false; } diff --git a/intern/cycles/kernel/integrator/init_from_bake.h b/intern/cycles/kernel/integrator/init_from_bake.h index 1668b50199b..02ecc792af6 100644 --- a/intern/cycles/kernel/integrator/init_from_bake.h +++ b/intern/cycles/kernel/integrator/init_from_bake.h @@ -130,7 +130,6 @@ ccl_device bool integrator_init_from_bake(KernelGlobals kg, ccl_global float *primitive = buffer + kernel_data.film.pass_bake_primitive; ccl_global float *differential = buffer + kernel_data.film.pass_bake_differential; - const int seed = __float_as_uint(primitive[0]); int prim = __float_as_uint(primitive[1]); if (prim == -1) { /* Accumulate transparency for empty pixels. */ @@ -141,8 +140,13 @@ ccl_device bool integrator_init_from_bake(KernelGlobals kg, prim += kernel_data.bake.tri_offset; /* Random number generator. */ - // TODO - const uint rng_hash = hash_uint(seed) ^ kernel_data.integrator.seed; + uint rng_hash = __float_as_uint(primitive[0]); + if (kernel_data.integrator.sampling_pattern == SAMPLING_PATTERN_TABULATED_SOBOL) { + rng_hash = hash_uint(rng_hash) ^ kernel_data.integrator.seed; + } + else { + rng_hash = path_rng_hash_init(kg, sample, x, y); + } const float2 rand_filter = (sample == 0) ? make_float2(0.5f, 0.5f) : path_rng_2D(kg, rng_hash, sample, PRNG_FILTER); -- 2.30.2 From 572b43002e185da58ceb33176be94d00e1a11155 Mon Sep 17 00:00:00 2001 From: Lukas Stockner Date: Mon, 3 Jun 2024 18:45:29 +0200 Subject: [PATCH 3/7] Rename rng_hash to rng_pixel --- .../cycles/kernel/integrator/init_from_bake.h | 10 ++-- .../kernel/integrator/init_from_camera.h | 12 ++-- .../integrator/intersect_dedicated_light.h | 2 +- intern/cycles/kernel/integrator/path_state.h | 22 +++---- .../cycles/kernel/integrator/shade_surface.h | 10 ++-- .../cycles/kernel/integrator/shade_volume.h | 4 +- .../kernel/integrator/shadow_state_template.h | 4 +- .../cycles/kernel/integrator/state_template.h | 4 +- .../kernel/integrator/subsurface_disk.h | 2 +- .../integrator/subsurface_random_walk.h | 2 +- intern/cycles/kernel/sample/pattern.h | 59 ++++++++++--------- intern/cycles/kernel/svm/bevel.h | 2 +- 12 files changed, 68 insertions(+), 65 deletions(-) diff --git a/intern/cycles/kernel/integrator/init_from_bake.h b/intern/cycles/kernel/integrator/init_from_bake.h index 02ecc792af6..d5f83ed62a6 100644 --- a/intern/cycles/kernel/integrator/init_from_bake.h +++ b/intern/cycles/kernel/integrator/init_from_bake.h @@ -140,19 +140,19 @@ ccl_device bool integrator_init_from_bake(KernelGlobals kg, prim += kernel_data.bake.tri_offset; /* Random number generator. */ - uint rng_hash = __float_as_uint(primitive[0]); + uint rng_pixel = __float_as_uint(primitive[0]); if (kernel_data.integrator.sampling_pattern == SAMPLING_PATTERN_TABULATED_SOBOL) { - rng_hash = hash_uint(rng_hash) ^ kernel_data.integrator.seed; + rng_pixel = hash_uint(rng_pixel) ^ kernel_data.integrator.seed; } else { - rng_hash = path_rng_hash_init(kg, sample, x, y); + rng_pixel = path_rng_pixel_init(kg, sample, x, y); } const float2 rand_filter = (sample == 0) ? make_float2(0.5f, 0.5f) : - path_rng_2D(kg, rng_hash, sample, PRNG_FILTER); + path_rng_2D(kg, rng_pixel, sample, PRNG_FILTER); /* Initialize path state for path integration. */ - path_state_init_integrator(kg, state, sample, rng_hash); + path_state_init_integrator(kg, state, sample, rng_pixel); /* Barycentric UV. */ float u = primitive[2]; diff --git a/intern/cycles/kernel/integrator/init_from_camera.h b/intern/cycles/kernel/integrator/init_from_camera.h index b0b14bcfed6..524da472b0a 100644 --- a/intern/cycles/kernel/integrator/init_from_camera.h +++ b/intern/cycles/kernel/integrator/init_from_camera.h @@ -20,17 +20,17 @@ ccl_device_inline void integrate_camera_sample(KernelGlobals kg, const int sample, const int x, const int y, - const uint rng_hash, + const uint rng_pixel, ccl_private Ray *ray) { /* Filter sampling. */ const float2 rand_filter = (sample == 0) ? make_float2(0.5f, 0.5f) : - path_rng_2D(kg, rng_hash, sample, PRNG_FILTER); + path_rng_2D(kg, rng_pixel, sample, PRNG_FILTER); /* Motion blur (time) and depth of field (lens) sampling. (time, lens_x, lens_y) */ const float3 rand_time_lens = (kernel_data.cam.shuttertime != -1.0f || kernel_data.cam.aperturesize > 0.0f) ? - path_rng_3D(kg, rng_hash, sample, PRNG_LENS_TIME) : + path_rng_3D(kg, rng_pixel, sample, PRNG_LENS_TIME) : zero_float3(); /* We use x for time and y,z for lens because in practice with Sobol @@ -78,12 +78,12 @@ ccl_device bool integrator_init_from_camera(KernelGlobals kg, kg, state, render_buffer, scheduled_sample, tile->sample_offset); /* Initialize random number seed for path. */ - const uint rng_hash = path_rng_hash_init(kg, sample, x, y); + const uint rng_pixel = path_rng_pixel_init(kg, sample, x, y); { /* Generate camera ray. */ Ray ray; - integrate_camera_sample(kg, sample, x, y, rng_hash, &ray); + integrate_camera_sample(kg, sample, x, y, rng_pixel, &ray); if (ray.tmax == 0.0f) { return true; } @@ -93,7 +93,7 @@ ccl_device bool integrator_init_from_camera(KernelGlobals kg, } /* Initialize path state for path integration. */ - path_state_init_integrator(kg, state, sample, rng_hash); + path_state_init_integrator(kg, state, sample, rng_pixel); /* Continue with intersect_closest kernel, optionally initializing volume * stack before that if the camera may be inside a volume. */ diff --git a/intern/cycles/kernel/integrator/intersect_dedicated_light.h b/intern/cycles/kernel/integrator/intersect_dedicated_light.h index 91d602b7503..572a99e3207 100644 --- a/intern/cycles/kernel/integrator/intersect_dedicated_light.h +++ b/intern/cycles/kernel/integrator/intersect_dedicated_light.h @@ -129,7 +129,7 @@ ccl_device bool shadow_linking_pick_light_intersection(KernelGlobals kg, const int object_receiver = light_link_receiver_forward(kg, state); - uint lcg_state = lcg_state_init(INTEGRATOR_STATE(state, path, rng_hash), + uint lcg_state = lcg_state_init(INTEGRATOR_STATE(state, path, rng_pixel), INTEGRATOR_STATE(state, path, rng_offset), INTEGRATOR_STATE(state, path, sample), 0x68bc21eb); diff --git a/intern/cycles/kernel/integrator/path_state.h b/intern/cycles/kernel/integrator/path_state.h index 8fe107f4dc3..9b631f959a0 100644 --- a/intern/cycles/kernel/integrator/path_state.h +++ b/intern/cycles/kernel/integrator/path_state.h @@ -38,7 +38,7 @@ ccl_device_inline void path_state_init(IntegratorState state, ccl_device_inline void path_state_init_integrator(KernelGlobals kg, IntegratorState state, const int sample, - const uint rng_hash) + const uint rng_pixel) { INTEGRATOR_STATE_WRITE(state, path, sample) = sample; INTEGRATOR_STATE_WRITE(state, path, bounce) = 0; @@ -48,7 +48,7 @@ ccl_device_inline void path_state_init_integrator(KernelGlobals kg, INTEGRATOR_STATE_WRITE(state, path, transparent_bounce) = 0; INTEGRATOR_STATE_WRITE(state, path, volume_bounce) = 0; INTEGRATOR_STATE_WRITE(state, path, volume_bounds_bounce) = 0; - INTEGRATOR_STATE_WRITE(state, path, rng_hash) = rng_hash; + INTEGRATOR_STATE_WRITE(state, path, rng_pixel) = rng_pixel; INTEGRATOR_STATE_WRITE(state, path, rng_offset) = PRNG_BOUNCE_NUM; INTEGRATOR_STATE_WRITE(state, path, flag) = PATH_RAY_CAMERA | PATH_RAY_MIS_SKIP | PATH_RAY_TRANSPARENT_BACKGROUND; @@ -307,7 +307,7 @@ ccl_device_inline bool path_state_ao_bounce(KernelGlobals kg, ConstIntegratorSta /* RNG State loaded onto stack. */ typedef struct RNGState { - uint rng_hash; + uint rng_pixel; uint rng_offset; int sample; } RNGState; @@ -315,7 +315,7 @@ typedef struct RNGState { ccl_device_inline void path_state_rng_load(ConstIntegratorState state, ccl_private RNGState *rng_state) { - rng_state->rng_hash = INTEGRATOR_STATE(state, path, rng_hash); + rng_state->rng_pixel = INTEGRATOR_STATE(state, path, rng_pixel); rng_state->rng_offset = INTEGRATOR_STATE(state, path, rng_offset); rng_state->sample = INTEGRATOR_STATE(state, path, sample); } @@ -323,7 +323,7 @@ ccl_device_inline void path_state_rng_load(ConstIntegratorState state, ccl_device_inline void shadow_path_state_rng_load(ConstIntegratorShadowState state, ccl_private RNGState *rng_state) { - rng_state->rng_hash = INTEGRATOR_STATE(state, shadow_path, rng_hash); + rng_state->rng_pixel = INTEGRATOR_STATE(state, shadow_path, rng_pixel); rng_state->rng_offset = INTEGRATOR_STATE(state, shadow_path, rng_offset); rng_state->sample = INTEGRATOR_STATE(state, shadow_path, sample); } @@ -333,7 +333,7 @@ ccl_device_inline float path_state_rng_1D(KernelGlobals kg, const int dimension) { return path_rng_1D( - kg, rng_state->rng_hash, rng_state->sample, rng_state->rng_offset + dimension); + kg, rng_state->rng_pixel, rng_state->sample, rng_state->rng_offset + dimension); } ccl_device_inline float2 path_state_rng_2D(KernelGlobals kg, @@ -341,7 +341,7 @@ ccl_device_inline float2 path_state_rng_2D(KernelGlobals kg, const int dimension) { return path_rng_2D( - kg, rng_state->rng_hash, rng_state->sample, rng_state->rng_offset + dimension); + kg, rng_state->rng_pixel, rng_state->sample, rng_state->rng_offset + dimension); } ccl_device_inline float3 path_state_rng_3D(KernelGlobals kg, @@ -349,7 +349,7 @@ ccl_device_inline float3 path_state_rng_3D(KernelGlobals kg, const int dimension) { return path_rng_3D( - kg, rng_state->rng_hash, rng_state->sample, rng_state->rng_offset + dimension); + kg, rng_state->rng_pixel, rng_state->sample, rng_state->rng_offset + dimension); } ccl_device_inline float path_branched_rng_1D(KernelGlobals kg, @@ -359,7 +359,7 @@ ccl_device_inline float path_branched_rng_1D(KernelGlobals kg, const int dimension) { return path_rng_1D(kg, - rng_state->rng_hash, + rng_state->rng_pixel, rng_state->sample * num_branches + branch, rng_state->rng_offset + dimension); } @@ -371,7 +371,7 @@ ccl_device_inline float2 path_branched_rng_2D(KernelGlobals kg, const int dimension) { return path_rng_2D(kg, - rng_state->rng_hash, + rng_state->rng_pixel, rng_state->sample * num_branches + branch, rng_state->rng_offset + dimension); } @@ -383,7 +383,7 @@ ccl_device_inline float3 path_branched_rng_3D(KernelGlobals kg, const int dimension) { return path_rng_3D(kg, - rng_state->rng_hash, + rng_state->rng_pixel, rng_state->sample * num_branches + branch, rng_state->rng_offset + dimension); } diff --git a/intern/cycles/kernel/integrator/shade_surface.h b/intern/cycles/kernel/integrator/shade_surface.h index 185ec4a427d..6977847a87d 100644 --- a/intern/cycles/kernel/integrator/shade_surface.h +++ b/intern/cycles/kernel/integrator/shade_surface.h @@ -232,8 +232,8 @@ integrate_direct_light_shadow_init_common(KernelGlobals kg, state, path, render_pixel_index); INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, rng_offset) = INTEGRATOR_STATE( state, path, rng_offset); - INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, rng_hash) = INTEGRATOR_STATE( - state, path, rng_hash); + INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, rng_pixel) = INTEGRATOR_STATE( + state, path, rng_pixel); INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, sample) = INTEGRATOR_STATE( state, path, sample); @@ -675,8 +675,8 @@ ccl_device_forceinline void integrate_surface_ao(KernelGlobals kg, state, path, render_pixel_index); INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, rng_offset) = INTEGRATOR_STATE( state, path, rng_offset); - INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, rng_hash) = INTEGRATOR_STATE( - state, path, rng_hash); + INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, rng_pixel) = INTEGRATOR_STATE( + state, path, rng_pixel); INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, sample) = INTEGRATOR_STATE( state, path, sample); INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, flag) = shadow_flag; @@ -724,7 +724,7 @@ ccl_device int integrate_surface(KernelGlobals kg, /* Initialize additional RNG for BSDFs. */ if (sd.flag & SD_BSDF_NEEDS_LCG) { - sd.lcg_state = lcg_state_init(INTEGRATOR_STATE(state, path, rng_hash), + sd.lcg_state = lcg_state_init(INTEGRATOR_STATE(state, path, rng_pixel), INTEGRATOR_STATE(state, path, rng_offset), INTEGRATOR_STATE(state, path, sample), 0xb4bc3953); diff --git a/intern/cycles/kernel/integrator/shade_volume.h b/intern/cycles/kernel/integrator/shade_volume.h index e77a69ea94c..7d29bf7b600 100644 --- a/intern/cycles/kernel/integrator/shade_volume.h +++ b/intern/cycles/kernel/integrator/shade_volume.h @@ -850,8 +850,8 @@ ccl_device_forceinline void integrate_volume_direct_light( state, path, render_pixel_index); INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, rng_offset) = INTEGRATOR_STATE( state, path, rng_offset); - INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, rng_hash) = INTEGRATOR_STATE( - state, path, rng_hash); + INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, rng_pixel) = INTEGRATOR_STATE( + state, path, rng_pixel); INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, sample) = INTEGRATOR_STATE( state, path, sample); INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, flag) = shadow_flag; diff --git a/intern/cycles/kernel/integrator/shadow_state_template.h b/intern/cycles/kernel/integrator/shadow_state_template.h index cd16d27dea0..8bb577d0cfa 100644 --- a/intern/cycles/kernel/integrator/shadow_state_template.h +++ b/intern/cycles/kernel/integrator/shadow_state_template.h @@ -9,8 +9,8 @@ KERNEL_STRUCT_BEGIN(shadow_path) KERNEL_STRUCT_MEMBER(shadow_path, uint32_t, render_pixel_index, KERNEL_FEATURE_PATH_TRACING) /* Current sample number. */ KERNEL_STRUCT_MEMBER(shadow_path, uint32_t, sample, KERNEL_FEATURE_PATH_TRACING) -/* Random number generator seed. */ -KERNEL_STRUCT_MEMBER(shadow_path, uint32_t, rng_hash, KERNEL_FEATURE_PATH_TRACING) +/* Random number generator per-pixel info. */ +KERNEL_STRUCT_MEMBER(shadow_path, uint32_t, rng_pixel, KERNEL_FEATURE_PATH_TRACING) /* Random number dimension offset. */ KERNEL_STRUCT_MEMBER(shadow_path, uint16_t, rng_offset, KERNEL_FEATURE_PATH_TRACING) /* Current ray bounce depth. */ diff --git a/intern/cycles/kernel/integrator/state_template.h b/intern/cycles/kernel/integrator/state_template.h index 87317842f86..bec718326d5 100644 --- a/intern/cycles/kernel/integrator/state_template.h +++ b/intern/cycles/kernel/integrator/state_template.h @@ -29,8 +29,8 @@ KERNEL_STRUCT_MEMBER(path, uint16_t, volume_bounce, KERNEL_FEATURE_PATH_TRACING) KERNEL_STRUCT_MEMBER(path, uint16_t, volume_bounds_bounce, KERNEL_FEATURE_PATH_TRACING) /* DeviceKernel bit indicating queued kernels. */ KERNEL_STRUCT_MEMBER(path, uint16_t, queued_kernel, KERNEL_FEATURE_PATH_TRACING) -/* Random number generator seed. */ -KERNEL_STRUCT_MEMBER(path, uint32_t, rng_hash, KERNEL_FEATURE_PATH_TRACING) +/* Random number generator per-pixel info. */ +KERNEL_STRUCT_MEMBER(path, uint32_t, rng_pixel, KERNEL_FEATURE_PATH_TRACING) /* Random number dimension offset. */ KERNEL_STRUCT_MEMBER(path, uint16_t, rng_offset, KERNEL_FEATURE_PATH_TRACING) /* enum PathRayFlag */ diff --git a/intern/cycles/kernel/integrator/subsurface_disk.h b/intern/cycles/kernel/integrator/subsurface_disk.h index 6902df850cf..0c33d5caa2d 100644 --- a/intern/cycles/kernel/integrator/subsurface_disk.h +++ b/intern/cycles/kernel/integrator/subsurface_disk.h @@ -98,7 +98,7 @@ ccl_device_inline bool subsurface_disk(KernelGlobals kg, /* Intersect with the same object. if multiple intersections are found it * will use at most BSSRDF_MAX_HITS hits, a random subset of all hits. */ uint lcg_state = lcg_state_init( - rng_state.rng_hash, rng_state.rng_offset, rng_state.sample, 0x68bc21eb); + rng_state.rng_pixel, rng_state.rng_offset, rng_state.sample, 0x68bc21eb); const int max_hits = BSSRDF_MAX_HITS; scene_intersect_local(kg, &ray, &ss_isect, object, &lcg_state, max_hits); diff --git a/intern/cycles/kernel/integrator/subsurface_random_walk.h b/intern/cycles/kernel/integrator/subsurface_random_walk.h index 182310423d2..78152bd6dda 100644 --- a/intern/cycles/kernel/integrator/subsurface_random_walk.h +++ b/intern/cycles/kernel/integrator/subsurface_random_walk.h @@ -223,7 +223,7 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg, /* Modify state for RNGs, decorrelated from other paths. */ // TODO - rng_state.rng_hash = hash_hp_seeded_uint(rng_state.rng_hash + rng_state.rng_offset, 0xdeadbeef); + rng_state.rng_pixel = hash_hp_seeded_uint(rng_state.rng_pixel + rng_state.rng_offset, 0xdeadbeef); /* Random walk until we hit the surface again. */ bool hit = false; diff --git a/intern/cycles/kernel/sample/pattern.h b/intern/cycles/kernel/sample/pattern.h index dab98678124..45d8e6eb12a 100644 --- a/intern/cycles/kernel/sample/pattern.h +++ b/intern/cycles/kernel/sample/pattern.h @@ -63,7 +63,7 @@ ccl_device_forceinline uint3 blue_noise_indexing(KernelGlobals kg, uint pixel_in } ccl_device_forceinline float path_rng_1D(KernelGlobals kg, - uint rng_hash, + uint rng_pixel, uint sample, int dimension) { @@ -72,15 +72,15 @@ ccl_device_forceinline float path_rng_1D(KernelGlobals kg, #endif if (kernel_data.integrator.sampling_pattern == SAMPLING_PATTERN_TABULATED_SOBOL) { - return tabulated_sobol_sample_1D(kg, sample, rng_hash, dimension); + return tabulated_sobol_sample_1D(kg, sample, rng_pixel, dimension); } - uint3 index = blue_noise_indexing(kg, rng_hash, sample); + uint3 index = blue_noise_indexing(kg, rng_pixel, sample); return sobol_burley_sample_1D(index.x, dimension, index.y, index.z); } ccl_device_forceinline float2 path_rng_2D(KernelGlobals kg, - uint rng_hash, + uint rng_pixel, int sample, int dimension) { @@ -89,15 +89,15 @@ ccl_device_forceinline float2 path_rng_2D(KernelGlobals kg, #endif if (kernel_data.integrator.sampling_pattern == SAMPLING_PATTERN_TABULATED_SOBOL) { - return tabulated_sobol_sample_2D(kg, sample, rng_hash, dimension); + return tabulated_sobol_sample_2D(kg, sample, rng_pixel, dimension); } - uint3 index = blue_noise_indexing(kg, rng_hash, sample); + uint3 index = blue_noise_indexing(kg, rng_pixel, sample); return sobol_burley_sample_2D(index.x, dimension, index.y, index.z); } ccl_device_forceinline float3 path_rng_3D(KernelGlobals kg, - uint rng_hash, + uint rng_pixel, int sample, int dimension) { @@ -106,15 +106,15 @@ ccl_device_forceinline float3 path_rng_3D(KernelGlobals kg, #endif if (kernel_data.integrator.sampling_pattern == SAMPLING_PATTERN_TABULATED_SOBOL) { - return tabulated_sobol_sample_3D(kg, sample, rng_hash, dimension); + return tabulated_sobol_sample_3D(kg, sample, rng_pixel, dimension); } - uint3 index = blue_noise_indexing(kg, rng_hash, sample); + uint3 index = blue_noise_indexing(kg, rng_pixel, sample); return sobol_burley_sample_3D(index.x, dimension, index.y, index.z); } ccl_device_forceinline float4 path_rng_4D(KernelGlobals kg, - uint rng_hash, + uint rng_pixel, int sample, int dimension) { @@ -123,39 +123,42 @@ ccl_device_forceinline float4 path_rng_4D(KernelGlobals kg, #endif if (kernel_data.integrator.sampling_pattern == SAMPLING_PATTERN_TABULATED_SOBOL) { - return tabulated_sobol_sample_4D(kg, sample, rng_hash, dimension); + return tabulated_sobol_sample_4D(kg, sample, rng_pixel, dimension); } - uint3 index = blue_noise_indexing(kg, rng_hash, sample); + uint3 index = blue_noise_indexing(kg, rng_pixel, sample); return sobol_burley_sample_4D(index.x, dimension, index.y, index.z); } -ccl_device_inline uint path_rng_hash_init(KernelGlobals kg, +ccl_device_inline uint path_rng_pixel_init(KernelGlobals kg, const int sample, const int x, const int y) { - uint rng_hash = kernel_data.integrator.seed; const uint pattern = kernel_data.integrator.sampling_pattern; if (pattern == SAMPLING_PATTERN_TABULATED_SOBOL || pattern == SAMPLING_PATTERN_SOBOL_BURLEY) { - rng_hash ^= hash_iqnt2d(x, y); - } - else { - /* Perform blue-noise dithered sampling by distributing the base sequence across pixels - * following a hierarchically shuffled 2D morton curve. - * Based on: - * https://psychopath.io/post/2022_07_24_owen_scrambling_based_dithered_blue_noise_sampling. - */ - rng_hash = nested_uniform_scramble_base4(morton2d(x, y), rng_hash); - } - #ifdef __DEBUG_CORRELATION__ - srand48(rng_hash + sample); + return srand48(rng_pixel + sample); #else - (void)sample; + (void)sample; #endif - return rng_hash; + /* The white-noise samplers use a random per-pixel hash to generate independent sequences. */ + return hash_iqnt2d(x, y) ^ kernel_data.integrator.seed; + } + else { + /* The blue-noise samplers use a single sequence for all pixels, but offset the index within + * the sequence for each pixel. We use a hierarchically shuffled 2D morton curve to determine + * each pixel's offset along the sequence. + * + * Based on: + * https://psychopath.io/post/2022_07_24_owen_scrambling_based_dithered_blue_noise_sampling. + * + * TODO(lukas): Use a precomputed Hilbert curve to avoid directionality bias in the noise + * distribution. We can just precompute a small-ish tile and repeat it in morton code order. + */ + return nested_uniform_scramble_base4(morton2d(x, y), kernel_data.integrator.seed); + } } /** diff --git a/intern/cycles/kernel/svm/bevel.h b/intern/cycles/kernel/svm/bevel.h index 3babf1ef342..fa718336fd0 100644 --- a/intern/cycles/kernel/svm/bevel.h +++ b/intern/cycles/kernel/svm/bevel.h @@ -118,7 +118,7 @@ ccl_device float3 svm_bevel( /* Setup for multi intersection. */ LocalIntersection isect; - uint lcg_state = lcg_state_init(INTEGRATOR_STATE(state, path, rng_hash), + uint lcg_state = lcg_state_init(INTEGRATOR_STATE(state, path, rng_pixel), INTEGRATOR_STATE(state, path, rng_offset), INTEGRATOR_STATE(state, path, sample), 0x64c6a40e); -- 2.30.2 From f09b5d512e7795b7e9181e1fe0331024f0327929 Mon Sep 17 00:00:00 2001 From: Lukas Stockner Date: Mon, 3 Jun 2024 23:56:28 +0200 Subject: [PATCH 4/7] Use dimension offset to scramble subsurface random walk instead --- intern/cycles/kernel/integrator/path_state.h | 8 ++++++++ intern/cycles/kernel/integrator/subsurface_random_walk.h | 3 +-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/intern/cycles/kernel/integrator/path_state.h b/intern/cycles/kernel/integrator/path_state.h index 9b631f959a0..639501db736 100644 --- a/intern/cycles/kernel/integrator/path_state.h +++ b/intern/cycles/kernel/integrator/path_state.h @@ -328,6 +328,14 @@ ccl_device_inline void shadow_path_state_rng_load(ConstIntegratorShadowState sta rng_state->sample = INTEGRATOR_STATE(state, shadow_path, sample); } +ccl_device_inline void path_state_rng_scramble(ccl_private RNGState *rng_state, const int seed) +{ + /* To get an uncorrelated sequence of samples (e.g. for subsurface random walk), just change + * the dimension offset since all implemented samplers can generate unlimited numbers of + * dimensions anyways. The only thing to ensure is that the offset is divisible by 4. */ + rng_state->rng_offset = hash_hp_seeded_uint(rng_state->rng_offset, seed) & ~0x3; +} + ccl_device_inline float path_state_rng_1D(KernelGlobals kg, ccl_private const RNGState *rng_state, const int dimension) diff --git a/intern/cycles/kernel/integrator/subsurface_random_walk.h b/intern/cycles/kernel/integrator/subsurface_random_walk.h index 78152bd6dda..50b1139d7bd 100644 --- a/intern/cycles/kernel/integrator/subsurface_random_walk.h +++ b/intern/cycles/kernel/integrator/subsurface_random_walk.h @@ -222,8 +222,7 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg, const float phase_log = logf((diffusion_length + 1.0f) / (diffusion_length - 1.0f)); /* Modify state for RNGs, decorrelated from other paths. */ - // TODO - rng_state.rng_pixel = hash_hp_seeded_uint(rng_state.rng_pixel + rng_state.rng_offset, 0xdeadbeef); + path_state_rng_scramble(&rng_state, 0xdeadbeef); /* Random walk until we hit the surface again. */ bool hit = false; -- 2.30.2 From dc4718aebb041bb4dd40fa501c1c1adc06267c2e Mon Sep 17 00:00:00 2001 From: Lukas Stockner Date: Tue, 4 Jun 2024 00:38:50 +0200 Subject: [PATCH 5/7] Fix enum default --- intern/cycles/blender/addon/properties.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py index 789618b2d0c..36515c90b8b 100644 --- a/intern/cycles/blender/addon/properties.py +++ b/intern/cycles/blender/addon/properties.py @@ -489,7 +489,7 @@ class CyclesRenderSettings(bpy.types.PropertyGroup): name="Sampling Pattern", description="Random sampling pattern used by the integrator", items=enum_sampling_pattern, - default=6, + default=5, ) scrambling_distance: FloatProperty( -- 2.30.2 From 38c0eea557379b9e39e9088b819bd9655968e4b4 Mon Sep 17 00:00:00 2001 From: Lukas Stockner Date: Wed, 5 Jun 2024 02:21:47 +0200 Subject: [PATCH 6/7] Add versioning to continue using Tabulated Sobol for existing files --- intern/cycles/blender/addon/version_update.py | 9 ++++++++- source/blender/blenkernel/BKE_blender_version.h | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/intern/cycles/blender/addon/version_update.py b/intern/cycles/blender/addon/version_update.py index c2983d81a36..fa50423083c 100644 --- a/intern/cycles/blender/addon/version_update.py +++ b/intern/cycles/blender/addon/version_update.py @@ -101,7 +101,7 @@ def do_versions(self): library_versions.setdefault(library.version, []).append(library) # Do versioning per library, since they might have different versions. - max_need_versioning = (3, 5, 2) + max_need_versioning = (4, 2, 52) for version, libraries in library_versions.items(): if version > max_need_versioning: continue @@ -253,6 +253,13 @@ def do_versions(self): # Tabulated Sobol. cscene.sampling_pattern = 'TABULATED_SOBOL' + if version <= (4, 2, 52): + cscene = scene.cycles + # Previous versions defaulted to Tabulated Sobol unless debugging options + # were enabled, so keep this behavior instead of suddenly defaulting to + # blue noise if the file happens to contain a different option for the enum. + cscene.sampling_pattern = 'TABULATED_SOBOL' + # Lamps for light in bpy.data.lights: if light.library not in libraries: diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 0cad239c3f8..7af9e7e4457 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -29,7 +29,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 52 +#define BLENDER_FILE_SUBVERSION 53 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to -- 2.30.2 From 86fe14f839a60b7b7d1e5d1a151a5a213487e097 Mon Sep 17 00:00:00 2001 From: Lukas Stockner Date: Wed, 5 Jun 2024 02:22:16 +0200 Subject: [PATCH 7/7] Bump tests for subsurface changes --- tests/data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/data b/tests/data index 363d42173a7..dcdda07d27c 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit 363d42173a72ff8e9d0bc7c3be17b9739559b74c +Subproject commit dcdda07d27cb95e8b388ab80cbf2bae62778d24d -- 2.30.2