Changeset View
Changeset View
Standalone View
Standalone View
source/blender/nodes/composite/nodes/node_composite_glare.cc
| /* SPDX-License-Identifier: GPL-2.0-or-later | /* SPDX-License-Identifier: GPL-2.0-or-later | ||||
| * Copyright 2006 Blender Foundation. All rights reserved. */ | * Copyright 2006 Blender Foundation. All rights reserved. */ | ||||
| /** \file | /** \file | ||||
| * \ingroup cmpnodes | * \ingroup cmpnodes | ||||
| */ | */ | ||||
| #include <array> | #include <array> | ||||
| #include "BLI_assert.h" | #include "BLI_assert.h" | ||||
| #include "BLI_index_range.hh" | #include "BLI_index_range.hh" | ||||
| #include "BLI_math_base.h" | |||||
| #include "BLI_math_base.hh" | #include "BLI_math_base.hh" | ||||
| #include "BLI_math_vec_types.hh" | #include "BLI_math_vec_types.hh" | ||||
| #include "DNA_scene_types.h" | #include "DNA_scene_types.h" | ||||
| #include "RNA_access.h" | #include "RNA_access.h" | ||||
| #include "UI_interface.h" | #include "UI_interface.h" | ||||
| #include "UI_resources.h" | #include "UI_resources.h" | ||||
| #include "IMB_colormanagement.h" | #include "IMB_colormanagement.h" | ||||
| #include "GPU_shader.h" | #include "GPU_shader.h" | ||||
| #include "GPU_state.h" | #include "GPU_state.h" | ||||
| #include "GPU_texture.h" | #include "GPU_texture.h" | ||||
| #include "COM_algorithm_symmetric_separable_blur.hh" | #include "COM_algorithm_symmetric_separable_blur.hh" | ||||
| #include "COM_node_operation.hh" | #include "COM_node_operation.hh" | ||||
| #include "COM_utilities.hh" | #include "COM_utilities.hh" | ||||
| #include "node_composite_util.hh" | #include "node_composite_util.hh" | ||||
| #define MAX_GLARE_ITERATIONS 5 | |||||
| namespace blender::nodes::node_composite_glare_cc { | namespace blender::nodes::node_composite_glare_cc { | ||||
| NODE_STORAGE_FUNCS(NodeGlare) | NODE_STORAGE_FUNCS(NodeGlare) | ||||
| static void cmp_node_glare_declare(NodeDeclarationBuilder &b) | static void cmp_node_glare_declare(NodeDeclarationBuilder &b) | ||||
| { | { | ||||
| b.add_input<decl::Color>(N_("Image")) | b.add_input<decl::Color>(N_("Image")) | ||||
| .default_value({1.0f, 1.0f, 1.0f, 1.0f}) | .default_value({1.0f, 1.0f, 1.0f, 1.0f}) | ||||
| ▲ Show 20 Lines • Show All 81 Lines • ▼ Show 20 Lines | bool is_identity() | ||||
| } | } | ||||
| /* A mix factor of -1 indicates that the original image is returned as is. See the execute_mix | /* A mix factor of -1 indicates that the original image is returned as is. See the execute_mix | ||||
| * method for more information. */ | * method for more information. */ | ||||
| if (node_storage(bnode()).mix == -1.0f) { | if (node_storage(bnode()).mix == -1.0f) { | ||||
| return true; | return true; | ||||
| } | } | ||||
| /* Only the ghost and simple star operations are currently supported. */ | /* The fog glow mode is currently unsupported. */ | ||||
| switch (node_storage(bnode()).type) { | if (node_storage(bnode()).type == CMP_NODE_GLARE_FOG_GLOW) { | ||||
| case CMP_NODE_GLARE_SIMPLE_STAR: | |||||
| return false; | |||||
| case CMP_NODE_GLARE_FOG_GLOW: | |||||
| return true; | |||||
| case CMP_NODE_GLARE_STREAKS: | |||||
| return true; | |||||
| case CMP_NODE_GLARE_GHOST: | |||||
| return false; | |||||
| default: | |||||
| BLI_assert_unreachable(); | |||||
| return true; | return true; | ||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| Result execute_glare(Result &highlights_result) | Result execute_glare(Result &highlights_result) | ||||
| { | { | ||||
| switch (node_storage(bnode()).type) { | switch (node_storage(bnode()).type) { | ||||
| ▲ Show 20 Lines • Show All 177 Lines • ▼ Show 20 Lines | public: | ||||
| * used equation, see the compute_number_of_diagonals function in the following shader library | * used equation, see the compute_number_of_diagonals function in the following shader library | ||||
| * file: gpu_shader_compositor_image_diagonals.glsl */ | * file: gpu_shader_compositor_image_diagonals.glsl */ | ||||
| int compute_simple_star_diagonals_count() | int compute_simple_star_diagonals_count() | ||||
| { | { | ||||
| const int2 size = get_glare_size(); | const int2 size = get_glare_size(); | ||||
| return size.x + size.y - 1; | return size.x + size.y - 1; | ||||
| } | } | ||||
| /* --------------- | /* -------------- | ||||
| * Fog Glow Glare. | * Streaks Glare. | ||||
| * --------------- */ | * -------------- */ | ||||
| /* Not yet implemented. Unreachable code due to the is_identity method. */ | Result execute_streaks(Result &highlights_result) | ||||
| Result execute_fog_glow(Result & /*highlights_result*/) | |||||
| { | { | ||||
| BLI_assert_unreachable(); | /* Create an initially zero image where streaks will be accumulated. */ | ||||
| return Result(ResultType::Color, texture_pool()); | const float4 zero_color = float4(0.0f); | ||||
| const int2 glare_size = get_glare_size(); | |||||
| Result accumulated_streaks_result = Result::Temporary(ResultType::Color, texture_pool()); | |||||
| accumulated_streaks_result.allocate_texture(glare_size); | |||||
| GPU_texture_clear(accumulated_streaks_result.texture(), GPU_DATA_FLOAT, zero_color); | |||||
| /* For each streak, compute its direction and apply a streak filter in that direction, then | |||||
| * accumulate the result into the accumulated streaks result. */ | |||||
| for (const int streak_index : IndexRange(get_number_of_streaks())) { | |||||
| const float2 streak_direction = compute_streak_direction(streak_index); | |||||
| Result streak_result = apply_streak_filter(highlights_result, streak_direction); | |||||
| GPUShader *shader = shader_manager().get("compositor_glare_streaks_accumulate"); | |||||
| GPU_shader_bind(shader); | |||||
| const float attenuation_factor = compute_streak_attenuation_factor(); | |||||
| GPU_shader_uniform_1f(shader, "attenuation_factor", attenuation_factor); | |||||
| streak_result.bind_as_texture(shader, "streak_tx"); | |||||
| accumulated_streaks_result.bind_as_image(shader, "accumulated_streaks_img", true); | |||||
| compute_dispatch_threads_at_least(shader, glare_size); | |||||
| streak_result.unbind_as_texture(); | |||||
| accumulated_streaks_result.unbind_as_image(); | |||||
| streak_result.release(); | |||||
| GPU_shader_unbind(); | |||||
| } | } | ||||
| /* -------------- | return accumulated_streaks_result; | ||||
| * Streaks Glare. | } | ||||
| * -------------- */ | |||||
| /* Not yet implemented. Unreachable code due to the is_identity method. */ | Result apply_streak_filter(Result &highlights_result, const float2 &streak_direction) | ||||
| Result execute_streaks(Result & /*highlights_result*/) | |||||
| { | { | ||||
| BLI_assert_unreachable(); | GPUShader *shader = shader_manager().get("compositor_glare_streaks_filter"); | ||||
| return Result(ResultType::Color, texture_pool()); | GPU_shader_bind(shader); | ||||
| /* Copy the highlights result into a new image because the output will be copied to the input | |||||
| * after each iteration and the highlights result is still needed to compute other streaks. */ | |||||
| const int2 glare_size = get_glare_size(); | |||||
| Result input_streak_result = Result::Temporary(ResultType::Color, texture_pool()); | |||||
| input_streak_result.allocate_texture(glare_size); | |||||
| GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); | |||||
| GPU_texture_copy(input_streak_result.texture(), highlights_result.texture()); | |||||
| Result output_streak_result = Result::Temporary(ResultType::Color, texture_pool()); | |||||
| output_streak_result.allocate_texture(glare_size); | |||||
| /* For the given number of iterations, apply the streak filter in the given direction. The | |||||
| * result of the previous iteration is used as the input of the current iteration. */ | |||||
| const IndexRange iterations_range = IndexRange(get_number_of_iterations()); | |||||
| for (const int iteration : iterations_range) { | |||||
| const float color_modulator = compute_streak_color_modulator(iteration); | |||||
| const float iteration_magnitude = compute_streak_iteration_magnitude(iteration); | |||||
| const float3 fade_factors = compute_streak_fade_factors(iteration_magnitude); | |||||
| const float2 streak_vector = streak_direction * iteration_magnitude; | |||||
| GPU_shader_uniform_1f(shader, "color_modulator", color_modulator); | |||||
| GPU_shader_uniform_3fv(shader, "fade_factors", fade_factors); | |||||
| GPU_shader_uniform_2fv(shader, "streak_vector", streak_vector); | |||||
| input_streak_result.bind_as_texture(shader, "input_streak_tx"); | |||||
| GPU_texture_filter_mode(input_streak_result.texture(), true); | |||||
| GPU_texture_wrap_mode(input_streak_result.texture(), false, false); | |||||
| output_streak_result.bind_as_image(shader, "output_streak_img"); | |||||
| compute_dispatch_threads_at_least(shader, glare_size); | |||||
| input_streak_result.unbind_as_texture(); | |||||
| output_streak_result.unbind_as_image(); | |||||
| /* The accumulated result serves as the input for the next iteration, so copy the result to | |||||
| * the input result since it can't be used for reading and writing simultaneously. Skip | |||||
| * copying for the last iteration since it is not needed. */ | |||||
| if (iteration != iterations_range.last()) { | |||||
| GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); | |||||
| GPU_texture_copy(input_streak_result.texture(), output_streak_result.texture()); | |||||
| } | |||||
| } | |||||
| input_streak_result.release(); | |||||
| GPU_shader_unbind(); | |||||
| return output_streak_result; | |||||
| } | |||||
| /* As the number of iterations increase, the streaks spread farther and their intensity decrease. | |||||
| * To maintain similar intensities regardless of the number of iterations, streaks with lower | |||||
| * number of iteration are linearly attenuated. When the number of iterations is maximum, we need | |||||
| * not attenuate, so the denominator should be one, and when the number of iterations is one, we | |||||
| * need the attenuation to be maximum. This can be modeled as a simple decreasing linear equation | |||||
| * by substituting the two aforementioned cases. */ | |||||
| float compute_streak_attenuation_factor() | |||||
| { | |||||
| return 1.0f / (MAX_GLARE_ITERATIONS + 1 - get_number_of_iterations()); | |||||
| } | |||||
| /* Given the index of the streak in the [0, Number Of Streaks - 1] range, compute the unit | |||||
| * direction vector defining the streak. The streak directions should make angles with the | |||||
| * x-axis that are equally spaced and covers the whole two pi range, starting with the user | |||||
| * supplied angle. */ | |||||
| float2 compute_streak_direction(int streak_index) | |||||
| { | |||||
| const int number_of_streaks = get_number_of_streaks(); | |||||
| const float start_angle = get_streaks_start_angle(); | |||||
| const float angle = start_angle + (float(streak_index) / number_of_streaks) * (M_PI * 2.0f); | |||||
| return float2(math::cos(angle), math::sin(angle)); | |||||
| } | |||||
| /* Different color channels of the streaks can be modulated by being multiplied by the color | |||||
| * modulator computed by this method. The color modulation is expected to be maximum when the | |||||
| * modulation factor is 1 and non existent when it is zero. But since the color modulator is | |||||
| * multiplied to the channel and the multiplicative identity is 1, we invert the modulation | |||||
| * factor. Moreover, color modulation should be less visible on higher iterations because they | |||||
| * produce the farther more faded away parts of the streaks. To achieve that, the modulation | |||||
| * factor is raised to the power of the iteration, noting that the modulation value is in the | |||||
| * [0, 1] range so the higher the iteration the lower the resulting modulation factor. The plus | |||||
| * one makes sure the power starts at one. */ | |||||
| float compute_streak_color_modulator(int iteration) | |||||
| { | |||||
| return 1.0f - std::pow(get_color_modulation_factor(), iteration + 1); | |||||
| } | |||||
| /* Streaks are computed by iteratively applying a filter that samples 3 neighbouring pixels in | |||||
| * the direction of the streak. Those neighbouring pixels are then combined using a weighted sum. | |||||
| * The weights of the neighbours are the fade factors computed by this method. Farther neighbours | |||||
| * are expected to have lower weights because they contribute less to the combined result. Since | |||||
| * the iteration magnitude represents how far the neighbours are, as noted in the description of | |||||
| * the compute_streak_iteration_magnitude method, the fade factor for the closest neighbour is | |||||
| * computed as the user supplied fade parameter raised to the power of the magnitude, noting that | |||||
| * the fade value is in the [0, 1] range while the magnitude is larger than or equal one, so the | |||||
| * higher the power the lower the resulting fade factor. Furthermore, the other two neighbours | |||||
| * are just squared and cubed versions of the fade factor for the closest neighbour to get even | |||||
| * lower fade factors for those farther neighbours. */ | |||||
| float3 compute_streak_fade_factors(float iteration_magnitude) | |||||
| { | |||||
| const float fade_factor = std::pow(node_storage(bnode()).fade, iteration_magnitude); | |||||
| return float3(fade_factor, std::pow(fade_factor, 2.0f), std::pow(fade_factor, 3.0f)); | |||||
| } | |||||
| /* Streaks are computed by iteratively applying a filter that samples the neighbouring pixels in | |||||
| * the direction of the streak. Each higher iteration samples pixels that are farther away, the | |||||
| * magnitude computed by this method describes how farther away the neighbours are sampled. The | |||||
| * magnitude exponentially increase with the iteration. A base of 4, was chosen as compromise | |||||
| * between better quality and performance, since a lower base corresponds to more tightly spaced | |||||
| * neighbours but would require more iterations to produce a streak of the same length. */ | |||||
| float compute_streak_iteration_magnitude(int iteration) | |||||
| { | |||||
| return std::pow(4.0f, iteration); | |||||
| } | |||||
| float get_streaks_start_angle() | |||||
| { | |||||
| return node_storage(bnode()).angle_ofs; | |||||
| } | |||||
| int get_number_of_streaks() | |||||
| { | |||||
| return node_storage(bnode()).streaks; | |||||
| } | } | ||||
| /* ------------ | /* ------------ | ||||
| * Ghost Glare. | * Ghost Glare. | ||||
| * ------------ */ | * ------------ */ | ||||
| Result execute_ghost(Result &highlights_result) | Result execute_ghost(Result &highlights_result) | ||||
| { | { | ||||
| Result base_ghost_result = compute_base_ghost(highlights_result); | Result base_ghost_result = compute_base_ghost(highlights_result); | ||||
| GPUShader *shader = shader_manager().get("compositor_glare_ghost_accumulate"); | GPUShader *shader = shader_manager().get("compositor_glare_ghost_accumulate"); | ||||
| GPU_shader_bind(shader); | GPU_shader_bind(shader); | ||||
| /* Color modulators are constant across iterations. */ | /* Color modulators are constant across iterations. */ | ||||
| std::array<float4, 4> color_modulators = compute_ghost_color_modulators(); | std::array<float4, 4> color_modulators = compute_ghost_color_modulators(); | ||||
| GPU_shader_uniform_4fv_array(shader, | GPU_shader_uniform_4fv_array(shader, | ||||
| "color_modulators", | "color_modulators", | ||||
| color_modulators.size(), | color_modulators.size(), | ||||
| (const float(*)[4])color_modulators.data()); | (const float(*)[4])color_modulators.data()); | ||||
| /* Create an initially zero image where ghosts will be accumulated. */ | /* Create an initially zero image where ghosts will be accumulated. */ | ||||
| const float4 zero_color = float4(0.0f); | const float4 zero_color = float4(0.0f); | ||||
| const int2 glare_size = get_glare_size(); | const int2 glare_size = get_glare_size(); | ||||
| Result accumulated_ghost_result = Result::Temporary(ResultType::Color, texture_pool()); | Result accumulated_ghosts_result = Result::Temporary(ResultType::Color, texture_pool()); | ||||
| accumulated_ghost_result.allocate_texture(glare_size); | accumulated_ghosts_result.allocate_texture(glare_size); | ||||
| GPU_texture_clear(accumulated_ghost_result.texture(), GPU_DATA_FLOAT, zero_color); | GPU_texture_clear(accumulated_ghosts_result.texture(), GPU_DATA_FLOAT, zero_color); | ||||
| /* For the given number of iterations, accumulate four ghosts with different scales and color | /* For the given number of iterations, accumulate four ghosts with different scales and color | ||||
| * modulators. The result of the previous iteration is used as the input of the current | * modulators. The result of the previous iteration is used as the input of the current | ||||
| * iteration. We start from index 1 because we are not interested in the scales produced for | * iteration. We start from index 1 because we are not interested in the scales produced for | ||||
| * the first iteration according to visual judgment, see the compute_ghost_scales method. */ | * the first iteration according to visual judgment, see the compute_ghost_scales method. */ | ||||
| Result &input_ghost_result = base_ghost_result; | Result &input_ghost_result = base_ghost_result; | ||||
| const IndexRange iterations_range = IndexRange(get_number_of_iterations()).drop_front(1); | const IndexRange iterations_range = IndexRange(get_number_of_iterations()).drop_front(1); | ||||
| for (const int i : iterations_range) { | for (const int i : iterations_range) { | ||||
| std::array<float, 4> scales = compute_ghost_scales(i); | std::array<float, 4> scales = compute_ghost_scales(i); | ||||
| GPU_shader_uniform_4fv(shader, "scales", scales.data()); | GPU_shader_uniform_4fv(shader, "scales", scales.data()); | ||||
| input_ghost_result.bind_as_texture(shader, "input_ghost_tx"); | input_ghost_result.bind_as_texture(shader, "input_ghost_tx"); | ||||
| accumulated_ghost_result.bind_as_image(shader, "accumulated_ghost_img", true); | accumulated_ghosts_result.bind_as_image(shader, "accumulated_ghost_img", true); | ||||
| compute_dispatch_threads_at_least(shader, glare_size); | compute_dispatch_threads_at_least(shader, glare_size); | ||||
| input_ghost_result.unbind_as_texture(); | input_ghost_result.unbind_as_texture(); | ||||
| accumulated_ghost_result.unbind_as_image(); | accumulated_ghosts_result.unbind_as_image(); | ||||
| /* The accumulated result serves as the input for the next iteration, so copy the result to | /* The accumulated result serves as the input for the next iteration, so copy the result to | ||||
| * the input result since it can't be used for reading and writing simultaneously. Skip | * the input result since it can't be used for reading and writing simultaneously. Skip | ||||
| * copying for the last iteration since it is not needed. */ | * copying for the last iteration since it is not needed. */ | ||||
| if (i != iterations_range.last()) { | if (i != iterations_range.last()) { | ||||
| GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); | GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); | ||||
| GPU_texture_copy(input_ghost_result.texture(), accumulated_ghost_result.texture()); | GPU_texture_copy(input_ghost_result.texture(), accumulated_ghosts_result.texture()); | ||||
| } | } | ||||
| } | } | ||||
| GPU_shader_unbind(); | GPU_shader_unbind(); | ||||
| input_ghost_result.release(); | input_ghost_result.release(); | ||||
| return accumulated_ghost_result; | return accumulated_ghosts_result; | ||||
| } | } | ||||
| /* Computes two ghosts by blurring the highlights with two different radii, then adds them into a | /* Computes two ghosts by blurring the highlights with two different radii, then adds them into a | ||||
| * single base ghost image after scaling them by some factor and flipping the bigger ghost along | * single base ghost image after scaling them by some factor and flipping the bigger ghost along | ||||
| * the center of the image. */ | * the center of the image. */ | ||||
| Result compute_base_ghost(Result &highlights_result) | Result compute_base_ghost(Result &highlights_result) | ||||
| { | { | ||||
| Result small_ghost_result = Result::Temporary(ResultType::Color, texture_pool()); | Result small_ghost_result = Result::Temporary(ResultType::Color, texture_pool()); | ||||
| ▲ Show 20 Lines • Show All 116 Lines • ▼ Show 20 Lines | public: | ||||
| } | } | ||||
| /* The color channels of the glare can be modulated by being multiplied by this factor. In the | /* The color channels of the glare can be modulated by being multiplied by this factor. In the | ||||
| * user interface, 0 means no modulation and 1 means full modulation. But since the factor is | * user interface, 0 means no modulation and 1 means full modulation. But since the factor is | ||||
| * multiplied, 1 corresponds to no modulation and 0 corresponds to full modulation, so we | * multiplied, 1 corresponds to no modulation and 0 corresponds to full modulation, so we | ||||
| * subtract from one. */ | * subtract from one. */ | ||||
| float get_ghost_color_modulation_factor() | float get_ghost_color_modulation_factor() | ||||
| { | { | ||||
| return 1.0f - node_storage(bnode()).colmod; | return 1.0f - get_color_modulation_factor(); | ||||
| } | |||||
| /* --------------- | |||||
| * Fog Glow Glare. | |||||
| * --------------- */ | |||||
| /* Not yet implemented. Unreachable code due to the is_identity method. */ | |||||
| Result execute_fog_glow(Result & /*highlights_result*/) | |||||
| { | |||||
| BLI_assert_unreachable(); | |||||
| return Result(ResultType::Color, texture_pool()); | |||||
| } | } | ||||
| /* ---------- | /* ---------- | ||||
| * Glare Mix. | * Glare Mix. | ||||
| * ---------- */ | * ---------- */ | ||||
| void execute_mix(Result &glare_result) | void execute_mix(Result &glare_result) | ||||
| { | { | ||||
| Show All 34 Lines | int2 get_glare_size() | ||||
| return compute_domain().size / get_quality_factor(); | return compute_domain().size / get_quality_factor(); | ||||
| } | } | ||||
| int get_number_of_iterations() | int get_number_of_iterations() | ||||
| { | { | ||||
| return node_storage(bnode()).iter; | return node_storage(bnode()).iter; | ||||
| } | } | ||||
| float get_color_modulation_factor() | |||||
| { | |||||
| return node_storage(bnode()).colmod; | |||||
| } | |||||
| /* The glare node can compute the glare on a fraction of the input image size to improve | /* The glare node can compute the glare on a fraction of the input image size to improve | ||||
| * performance. The quality values and their corresponding quality factors are as follows: | * performance. The quality values and their corresponding quality factors are as follows: | ||||
| * | * | ||||
| * - High Quality => Quality Value: 0 => Quality Factor: 1. | * - High Quality => Quality Value: 0 => Quality Factor: 1. | ||||
| * - Medium Quality => Quality Value: 1 => Quality Factor: 2. | * - Medium Quality => Quality Value: 1 => Quality Factor: 2. | ||||
| * - Low Quality => Quality Value: 2 => Quality Factor: 4. | * - Low Quality => Quality Value: 2 => Quality Factor: 4. | ||||
| * | * | ||||
| * Dividing the image size by the quality factor gives the size where the glare should be | * Dividing the image size by the quality factor gives the size where the glare should be | ||||
| Show All 31 Lines | |||||