diff --git a/source/blender/compositor/shaders/compositor_glare_sun_beams.glsl b/source/blender/compositor/shaders/compositor_glare_sun_beams.glsl index 2e8a4bb0269..cb26656b022 100644 --- a/source/blender/compositor/shaders/compositor_glare_sun_beams.glsl +++ b/source/blender/compositor/shaders/compositor_glare_sun_beams.glsl @@ -2,9 +2,27 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ +#include "gpu_shader_common_hash.glsl" #include "gpu_shader_compositor_texture_utilities.glsl" #include "gpu_shader_math_base_lib.glsl" +/* Returns an index for a position along the path between the texel and the source. + * + * When jitter is enabled, the position index is computed using the Global Shift + * sampling technique: a hash-based global shift is applied to the indices which is then + * factored to cover the range [0, steps]. + * Without jitter, the integer index `i` is returned + * directly. + */ +float get_sample_position(int i, float random_offset) +{ +#if defined(JITTER) + return safe_divide(i + random_offset, 1.0f - jitter_factor); +#else + return i; +#endif +} + void main() { int2 texel = int2(gl_GlobalInvocationID.xy); @@ -26,8 +44,17 @@ void main() float accumulated_weight = 0.0f; float4 accumulated_color = float4(0.0f); - for (int i = 0; i <= steps; i++) { - float2 position = coordinates + i * step_vector; + +#if defined(JITTER) + int number_of_steps = int((1.0f - jitter_factor) * steps); +#else + int number_of_steps = steps; +#endif + float random_offset = hash_uint2_to_float(uint(texel.x), uint(texel.y)); + + for (int i = 0; i <= number_of_steps; i++) { + float position_index = get_sample_position(i, random_offset); + float2 position = coordinates + position_index * step_vector; /* We are already past the image boundaries, and any future steps are also past the image * boundaries, so break. */ @@ -39,12 +66,17 @@ void main() /* Attenuate the contributions of pixels that are further away from the source using a * quadratic falloff. */ - float weight = square(1.0f - i / float(steps)); + float weight = square(1.0f - position_index / float(steps)); accumulated_weight += weight; accumulated_color += sample_color * weight; } - accumulated_color /= accumulated_weight != 0.0f ? accumulated_weight : 1.0f; + if (accumulated_weight != 0.0f) { + accumulated_color /= accumulated_weight; + } + else { + accumulated_color = texture(input_tx, coordinates); + } imageStore(output_img, texel, accumulated_color); } diff --git a/source/blender/compositor/shaders/infos/compositor_glare_info.hh b/source/blender/compositor/shaders/infos/compositor_glare_info.hh index b02da6f1ed9..76b8a1052db 100644 --- a/source/blender/compositor/shaders/infos/compositor_glare_info.hh +++ b/source/blender/compositor/shaders/infos/compositor_glare_info.hh @@ -173,12 +173,23 @@ GPU_SHADER_CREATE_END() * Sun Beams * ----- */ -GPU_SHADER_CREATE_INFO(compositor_glare_sun_beams) +GPU_SHADER_CREATE_INFO(compositor_glare_sun_beams_shared) LOCAL_GROUP_SIZE(16, 16) PUSH_CONSTANT(float2, source) PUSH_CONSTANT(int, max_steps) SAMPLER(0, sampler2D, input_tx) IMAGE(0, SFLOAT_16_16_16_16, write, image2D, output_img) COMPUTE_SOURCE("compositor_glare_sun_beams.glsl") +GPU_SHADER_CREATE_END() + +GPU_SHADER_CREATE_INFO(compositor_glare_sun_beams) +ADDITIONAL_INFO(compositor_glare_sun_beams_shared) +DO_STATIC_COMPILATION() +GPU_SHADER_CREATE_END() + +GPU_SHADER_CREATE_INFO(compositor_glare_sun_beams_jitter) +ADDITIONAL_INFO(compositor_glare_sun_beams_shared) +DEFINE("JITTER") +PUSH_CONSTANT(float, jitter_factor) DO_STATIC_COMPILATION() GPU_SHADER_CREATE_END() diff --git a/source/blender/nodes/composite/nodes/node_composite_glare.cc b/source/blender/nodes/composite/nodes/node_composite_glare.cc index c282921dccf..8efb5501aca 100644 --- a/source/blender/nodes/composite/nodes/node_composite_glare.cc +++ b/source/blender/nodes/composite/nodes/node_composite_glare.cc @@ -26,6 +26,7 @@ #include "BLI_math_color.h" #include "BLI_math_vector.hh" #include "BLI_math_vector_types.hh" +#include "BLI_noise.hh" #include "BLI_task.hh" #include "DNA_scene_types.h" @@ -171,6 +172,14 @@ static void cmp_node_glare_declare(NodeDeclarationBuilder &b) .description( "The position of the source of the rays in normalized coordinates. 0 means lower left " "corner and 1 means upper right corner"); + glare_panel.add_input("Jitter") + .default_value(0.0f) + .min(0.0f) + .max(1.0) + .subtype(PROP_FACTOR) + .description( + "The amount of jitter to introduce while computing rays, higher jitter can be faster " + "but can produce grainy or noisy results"); } static void node_composit_init_glare(bNodeTree * /*ntree*/, bNode *node) @@ -222,6 +231,10 @@ static void node_update(bNodeTree *ntree, bNode *node) bNodeSocket *source_input = bke::node_find_socket(*node, SOCK_IN, "Sun Position"); blender::bke::node_set_socket_availability( *ntree, *source_input, glare_type == CMP_NODE_GLARE_SUN_BEAMS); + + bNodeSocket *jitter_steps = bke::node_find_socket(*node, SOCK_IN, "Jitter"); + blender::bke::node_set_socket_availability( + *ntree, *jitter_steps, glare_type == CMP_NODE_GLARE_SUN_BEAMS); } class SocketSearchOp { @@ -2247,10 +2260,11 @@ class GlareOperation : public NodeOperation { Result execute_sun_beams_gpu(Result &highlights, const int max_steps) { - gpu::Shader *shader = context().get_shader("compositor_glare_sun_beams"); + gpu::Shader *shader = context().get_shader(this->get_compositor_sun_beams_shader()); GPU_shader_bind(shader); GPU_shader_uniform_2fv(shader, "source", this->get_sun_position()); + GPU_shader_uniform_1f(shader, "jitter_factor", this->get_jitter_factor()); GPU_shader_uniform_1i(shader, "max_steps", max_steps); GPU_texture_filter_mode(highlights, true); @@ -2270,6 +2284,14 @@ class GlareOperation : public NodeOperation { return output_image; } + const char *get_compositor_sun_beams_shader() + { + if (this->get_use_jitter()) { + return "compositor_glare_sun_beams_jitter"; + } + return "compositor_glare_sun_beams"; + } + Result execute_sun_beams_cpu(Result &highlights, const int max_steps) { const float2 source = this->get_sun_position(); @@ -2278,6 +2300,8 @@ class GlareOperation : public NodeOperation { output.allocate_texture(highlights.domain()); const int2 input_size = highlights.domain().size; + float jitter_factor = this->get_jitter_factor(); + bool use_jitter = this->get_use_jitter(); parallel_for(input_size, [&](const int2 texel) { /* The number of steps is the distance in pixels from the source to the current texel. With * at least a single step and at most the user specified maximum ray length, which is @@ -2296,8 +2320,14 @@ class GlareOperation : public NodeOperation { float accumulated_weight = 0.0f; float4 accumulated_color = float4(0.0f); - for (int i = 0; i <= steps; i++) { - float2 position = coordinates + i * step_vector; + + int number_of_steps = (1.0f - jitter_factor) * steps; + float random_offset = noise::hash_to_float(texel.x, texel.y); + + for (int i = 0; i <= number_of_steps; i++) { + float position_index = this->get_sample_position( + i, use_jitter, jitter_factor, random_offset); + float2 position = coordinates + position_index * step_vector; /* We are already past the image boundaries, and any future steps are also past the image * boundaries, so break. */ @@ -2309,18 +2339,53 @@ class GlareOperation : public NodeOperation { /* Attenuate the contributions of pixels that are further away from the source using a * quadratic falloff. */ - float weight = math::square(1.0f - i / float(steps)); + float weight = math::square(1.0f - position_index / float(steps)); accumulated_weight += weight; accumulated_color += sample_color * weight; } - accumulated_color /= accumulated_weight != 0.0f ? accumulated_weight : 1.0f; + if (accumulated_weight != 0.0f) { + accumulated_color /= accumulated_weight; + } + else { + accumulated_color = highlights.sample_bilinear_zero(coordinates); + } output.store_pixel(texel, accumulated_color); }); return output; } + /* Returns an index for a position along the path between the texel and the source. + * + * When jitter is enabled, the position index is computed using the Global Shift + * sampling technique: a hash-based global shift is applied to the indices which is then + * factored to cover the range [0, steps]. + * Without jitter, the integer index `i` is returned + * directly. + */ + + float get_sample_position(const int i, + const bool use_jitter, + float jitter_factor, + const float random_offset) + { + if (use_jitter) { + return math::safe_divide(i + random_offset, 1.0f - jitter_factor); + } + return i; + } + + bool get_use_jitter() + { + return this->get_jitter_factor() != 0.0f; + } + + float get_jitter_factor() + { + return math::clamp(this->get_input("Jitter").get_single_value_default(0.0f), 0.0f, 1.0f); + } + /* ---------- * Glare Mix. * ---------- */