Compositor: Improve Glare node UX

This patch redesigns the Glare node to improve the user experience. The
improvements are as follows.

Two new outputs were added, Glare and Highlights. The Glare output gives
the generated glare without the input, and is useful when the user wants
to adjust the glare before adding it to the image. The Highlights output
gives the areas that are considered highlights when computing the glare,
and is useful if the user wants to temporally check the highlights while
doing adjustments or wants to use those as a base for creating a custom
glare setup.

The Mix node option was removed and a new Strength single value input
was added to serve the same functionality. The Mix option had a range of
[-1, 1], where the [-1, 0] sub-range essentially controlled the strength
of the glare, 0 being full strength and -1 being zero strength. While
the [0, 1] range returned the generated glare with an attenuated version
of the image added, that is, it was useless except for the value of 1,
which returned the generate glare only.
Aside from being a very intuitive range, it also meant that the power of
glare can't be boosted beyond the full strength of, you guessed it, 0.
The newly added Strength input has a soft range of [0, 1] and can be
boosted beyond 1. If the users want the glare only, they can use the
newly provided Glare output.

The Size node option used for Bloom and Fog Glow was removed and a new
Size single value input was added. The Size node option had yet another
very intuitive range of [1, 9], and it was related exponentially to the
actual size of the Glare. For Bloom, the actual bloom size relative to
the image was 2^(Size-9), so a Size of 8 means the bloom covers half of
the image. For Fog Glow, the actual bloom size in pixels is 2^Size, so
the glare size is not relative to the image size and would thus change
as the image resolution change. Furthermore, the maximum possible glare
size was 512 pixels, and the user couldn't make fine adjustments to the
size.
The newly added Size input has a range [0, 1], where 1 means the glare
covers the entire image, 0.5 means it covers half the image, and so on.
That means it is consistent between Bloom and Fog Glow, it is relative
to the image size, it allows as large of a glare as possible, it is
continuous for Fog Glow, but not for Bloom because that requires an
algorithmic change that will be implemented separately.

The Threshold, Streaks, Streaks Angle, Iterations, Fade, and Color
Modulation node option was turned into a single value node input to
allow the option to be used in node groups.

---

Versioning was added to transfer node options into sockets, but it is
not all 1:1 versioning, since the old Size option was not relative to
the image size, so it depends on runtime information of the input size.
As a guess, we assume the render size in that case. Versioning the
[0, 1] range of the Mix option intentionally omits the attenuation of
the image input, because that is almost certainly not what the user
wants and was probably done thinking it controls the strength.

Glare code now sets the alpha channel to 1, that's because it was
already ignored in the mixing step, but now that we expose the Glare
output, we need to set it to 1. So this is not a functional change.

The get_glare_size() method was renamed for clarity since it now
conflicts with the newly added Size input.

---

This is a partial implementation of #124176 to address #131325. In
particular, it adjust existing functionality, it doesn't add any new
ones. Those will be added in separate patches.

Pull Request: https://projects.blender.org/blender/blender/pulls/132499
This commit is contained in:
Omar Emara
2025-01-07 11:15:26 +01:00
committed by Omar Emara
parent 0c2ec3dcf9
commit 004e3d39fa
15 changed files with 851 additions and 248 deletions

View File

@@ -31,7 +31,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 17
#define BLENDER_FILE_SUBVERSION 18
/* 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

View File

@@ -10,6 +10,9 @@
#include <algorithm>
#include <cmath>
#include <string>
#include <fmt/format.h>
/* Define macros in `DNA_genfile.h`. */
#define DNA_GENFILE_VERSIONING_MACROS
@@ -44,8 +47,10 @@
#include "BLI_assert.h"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_math_base.hh"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_math_vector_types.hh"
#include "BLI_set.hh"
#include "BLI_string.h"
#include "BLI_string_ref.hh"
@@ -62,6 +67,7 @@
#include "BKE_curve.hh"
#include "BKE_customdata.hh"
#include "BKE_effect.h"
#include "BKE_fcurve.hh"
#include "BKE_file_handler.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_idprop.hh"
@@ -1048,6 +1054,203 @@ static bool versioning_clear_strip_unused_flag(Strip *seq, void * /*user_data*/)
return true;
}
/* Adjust the values of the given FCurve key frames by applying the given function. The function is
* expected to get and return a float representing the value of the key frame. The FCurve is
* potentially changed to have the given property type, if not already the case. */
template<typename Function>
static void adjust_fcurve_key_frame_values(FCurve *fcurve,
const PropertyType property_type,
const Function &function)
{
/* Adjust key frames. */
if (fcurve->bezt) {
for (int i = 0; i < fcurve->totvert; i++) {
fcurve->bezt[i].vec[0][1] = function(fcurve->bezt[i].vec[0][1]);
fcurve->bezt[i].vec[1][1] = function(fcurve->bezt[i].vec[1][1]);
fcurve->bezt[i].vec[2][1] = function(fcurve->bezt[i].vec[2][1]);
}
}
/* Adjust baked key frames. */
if (fcurve->fpt) {
for (int i = 0; i < fcurve->totvert; i++) {
fcurve->fpt[i].vec[1] = function(fcurve->fpt[i].vec[1]);
}
}
/* Setup the flags based on the property type. */
fcurve->flag &= ~(FCURVE_INT_VALUES | FCURVE_DISCRETE_VALUES);
switch (property_type) {
case PROP_FLOAT:
break;
case PROP_INT:
fcurve->flag |= FCURVE_INT_VALUES;
break;
default:
fcurve->flag |= (FCURVE_DISCRETE_VALUES | FCURVE_INT_VALUES);
break;
}
/* Recalculate the automatic handles of the FCurve after adjustments. */
BKE_fcurve_handles_recalc(fcurve);
}
/* The Threshold, Mix, and Size properties of the node were converted into node inputs, and
* two new outputs were added.
*
* A new Highlights output was added to expose the extracted highlights, this is not relevant for
* versioning.
*
* A new Glare output was added to expose just the generated glare without the input image itself.
* this relevant for versioning the Mix property as will be shown.
*
* The Threshold, Iterations, Fade, Color Modulation, Streaks, and Streaks Angle Offset properties
* were converted into node inputs, maintaining its type and range, so we just transfer its value
* as is.
*
* The Mix property was converted into a Strength input, but its range changed from [-1, 1] to [0,
* 1]. For the [-1, 0] sub-range, -1 used to mean zero strength and 0 used to mean full strength,
* so we can convert between the two ranges by negating the mix factor and subtracting it from 1.
* The [0, 1] sub-range on the other hand was useless except for the value 1, because it linearly
* interpolates between Image + Glare and Glare, so it essentially adds an attenuated version of
* the input image to the glare. When it is 1, only the glare is returned. So we split that range
* in half as a heuristic and for values in the range [0.5, 1], we just reconnect the output to the
* newly added Glare output.
*
* The Size property was converted into a float node input, and its range was changed from [1, 9]
* to [0, 1]. For Bloom, the [1, 9] range was related exponentially to the actual size of the
* glare, that is, 9 meant the glare covers the entire image, 8 meant it covers half, 7 meant it
* covers quarter and so on. The new range is linear and relative to the image size, that is, 1
* means the entire image and 0 means nothing. So we can convert from the [1, 9] range to [0, 1]
* range using the relation 2^(x-9).
* For Fog Glow, the [1, 9] range was related to the absolute size of the Fog Glow kernel in
* pixels, where it is 2^size pixels in size. There is no way to version this accurately, since the
* new size is relative to the input image size, which is runtime information. But we can assume
* the render size as a guess and compute the size relative to that. */
static void do_version_glare_node_options_to_inputs(const Scene *scene,
bNodeTree *node_tree,
bNode *node)
{
NodeGlare *storage = static_cast<NodeGlare *>(node->storage);
if (!storage) {
return;
}
/* Get the newly added inputs. */
bNodeSocket *threshold = version_node_add_socket_if_not_exist(
node_tree, node, SOCK_IN, SOCK_FLOAT, PROP_NONE, "Threshold", "Threshold");
bNodeSocket *strength = version_node_add_socket_if_not_exist(
node_tree, node, SOCK_IN, SOCK_FLOAT, PROP_FACTOR, "Strength", "Strength");
bNodeSocket *size = version_node_add_socket_if_not_exist(
node_tree, node, SOCK_IN, SOCK_FLOAT, PROP_FACTOR, "Size", "Size");
bNodeSocket *streaks = version_node_add_socket_if_not_exist(
node_tree, node, SOCK_IN, SOCK_INT, PROP_NONE, "Streaks", "Streaks");
bNodeSocket *streaks_angle = version_node_add_socket_if_not_exist(
node_tree, node, SOCK_IN, SOCK_FLOAT, PROP_ANGLE, "Streaks Angle", "Streaks Angle");
bNodeSocket *iterations = version_node_add_socket_if_not_exist(
node_tree, node, SOCK_IN, SOCK_INT, PROP_NONE, "Iterations", "Iterations");
bNodeSocket *fade = version_node_add_socket_if_not_exist(
node_tree, node, SOCK_IN, SOCK_FLOAT, PROP_FACTOR, "Fade", "Fade");
bNodeSocket *color_modulation = version_node_add_socket_if_not_exist(
node_tree, node, SOCK_IN, SOCK_FLOAT, PROP_FACTOR, "Color Modulation", "Color Modulation");
/* Function to remap the Mix property to the range of the new Strength input. See function
* description. */
auto mix_to_strength = [](const float mix) {
return 1.0f - blender::math::clamp(-mix, 0.0f, 1.0f);
};
/* Function to remap the Size property to its new range. See function description. */
blender::int2 render_size;
BKE_render_resolution(&scene->r, true, &render_size.x, &render_size.y);
const int max_render_size = blender::math::reduce_max(render_size);
auto size_to_linear = [&](const int size) {
if (storage->type == CMP_NODE_GLARE_BLOOM) {
return blender::math::pow(2.0f, float(size - 9));
}
return blender::math::min(1.0f, float((1 << size) + 1) / float(max_render_size));
};
/* Assign the inputs the values from the old deprecated properties. */
threshold->default_value_typed<bNodeSocketValueFloat>()->value = storage->threshold;
strength->default_value_typed<bNodeSocketValueFloat>()->value = mix_to_strength(storage->mix);
size->default_value_typed<bNodeSocketValueFloat>()->value = size_to_linear(storage->size);
streaks->default_value_typed<bNodeSocketValueInt>()->value = storage->streaks;
streaks_angle->default_value_typed<bNodeSocketValueFloat>()->value = storage->angle_ofs;
iterations->default_value_typed<bNodeSocketValueInt>()->value = storage->iter;
fade->default_value_typed<bNodeSocketValueFloat>()->value = storage->fade;
color_modulation->default_value_typed<bNodeSocketValueFloat>()->value = storage->colmod;
/* Compute the RNA path of the node. */
char escaped_node_name[sizeof(node->name) * 2 + 1];
BLI_str_escape(escaped_node_name, node->name, sizeof(escaped_node_name));
const std::string node_rna_path = fmt::format("nodes[\"{}\"]", escaped_node_name);
BKE_fcurves_id_cb(&node_tree->id, [&](ID * /*id*/, FCurve *fcurve) {
/* The FCurve does not belong to the node since its RNA path doesn't start with the node's RNA
* path. */
if (!blender::StringRef(fcurve->rna_path).startswith(node_rna_path)) {
return;
}
/* Change the RNA path of the FCurve from the old properties to the new inputs, adjusting the
* values of the FCurves frames when needed. */
char *old_rna_path = fcurve->rna_path;
if (BLI_str_endswith(fcurve->rna_path, "threshold")) {
fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[1].default_value");
}
else if (BLI_str_endswith(fcurve->rna_path, "mix")) {
fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[2].default_value");
adjust_fcurve_key_frame_values(
fcurve, PROP_FLOAT, [&](const float value) { return mix_to_strength(value); });
}
else if (BLI_str_endswith(fcurve->rna_path, "size")) {
fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[3].default_value");
adjust_fcurve_key_frame_values(
fcurve, PROP_FLOAT, [&](const float value) { return size_to_linear(value); });
}
else if (BLI_str_endswith(fcurve->rna_path, "streaks")) {
fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[4].default_value");
}
else if (BLI_str_endswith(fcurve->rna_path, "angle_offset")) {
fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[5].default_value");
}
else if (BLI_str_endswith(fcurve->rna_path, "iterations")) {
fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[6].default_value");
}
else if (BLI_str_endswith(fcurve->rna_path, "fade")) {
fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[7].default_value");
}
else if (BLI_str_endswith(fcurve->rna_path, "color_modulation")) {
fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[8].default_value");
}
/* The RNA path was changed, free the old path. */
if (fcurve->rna_path != old_rna_path) {
MEM_freeN(old_rna_path);
}
});
/* If the Mix factor is between [0.5, 1], then the user actually wants the Glare output, so
* reconnect the output to the newly created Glare output. */
if (storage->mix > 0.5f) {
bNodeSocket *image_output = version_node_add_socket_if_not_exist(
node_tree, node, SOCK_OUT, SOCK_RGBA, PROP_NONE, "Image", "Image");
bNodeSocket *glare_output = version_node_add_socket_if_not_exist(
node_tree, node, SOCK_OUT, SOCK_RGBA, PROP_NONE, "Glare", "Glare");
LISTBASE_FOREACH_BACKWARD_MUTABLE (bNodeLink *, link, &node_tree->links) {
if (link->fromsock != image_output) {
continue;
}
/* Relink from the Image output to the Glare output. */
blender::bke::node_add_link(node_tree, node, glare_output, link->tonode, link->tosock);
blender::bke::node_remove_link(node_tree, link);
}
}
}
static bool all_scenes_use(Main *bmain, const blender::Span<const char *> engines)
{
if (!bmain->scenes.first) {
@@ -1283,6 +1486,22 @@ void do_versions_after_linking_400(FileData *fd, Main *bmain)
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 404, 18)) {
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
if (ntree->type != NTREE_COMPOSIT) {
continue;
}
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->type != CMP_NODE_GLARE) {
continue;
}
do_version_glare_node_options_to_inputs(reinterpret_cast<Scene *>(id), ntree, node);
}
}
FOREACH_NODETREE_END;
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

View File

@@ -193,6 +193,8 @@ set(GLSL_SRC
shaders/compositor_glare_simple_star_vertical_pass.glsl
shaders/compositor_glare_streaks_accumulate.glsl
shaders/compositor_glare_streaks_filter.glsl
shaders/compositor_glare_write_glare_output.glsl
shaders/compositor_glare_write_highlights_output.glsl
shaders/compositor_id_mask.glsl
shaders/compositor_image_crop.glsl
shaders/compositor_inpaint_compute_boundary.glsl

View File

@@ -37,5 +37,6 @@ void main()
upsampled += (1.0 / 16.0) * texture(input_tx, coordinates + pixel_size * vec2(1.0, -1.0));
upsampled += (1.0 / 16.0) * texture(input_tx, coordinates + pixel_size * vec2(1.0, 1.0));
imageStore(output_img, texel, imageLoad(output_img, texel) + upsampled);
vec4 combined = imageLoad(output_img, texel) + upsampled;
imageStore(output_img, texel, vec4(combined.rgb, 1.0f));
}

View File

@@ -37,5 +37,6 @@ void main()
}
vec4 current_accumulated_ghost = imageLoad(accumulated_ghost_img, texel);
imageStore(accumulated_ghost_img, texel, current_accumulated_ghost + accumulated_ghost);
vec4 combined_ghost = current_accumulated_ghost + accumulated_ghost;
imageStore(accumulated_ghost_img, texel, vec4(combined_ghost.rgb, 1.0f));
}

View File

@@ -15,18 +15,7 @@ void main()
vec4 glare_color = texture(glare_tx, normalized_coordinates);
vec4 input_color = max(vec4(0.0), texture_load(input_tx, texel));
/* The mix factor is in the range [-1, 1] and linearly interpolate between the three values such
* that:
* 1 => Glare only.
* 0 => Input + Glare.
* -1 => Input only.
* We implement that as a weighted sum as follows. When the mix factor is 1, the glare weight
* should be 1 and the input weight should be 0. When the mix factor is -1, the glare weight
* should be 0 and the input weight should be 1. When the mix factor is 0, both weights should
* be 1. This can be expressed using the following compact min max expressions. */
float input_weight = 1.0 - max(0.0, mix_factor);
float glare_weight = 1.0 + min(0.0, mix_factor);
vec3 highlights = input_weight * input_color.rgb + glare_weight * glare_color.rgb;
vec3 highlights = input_color.rgb + glare_color.rgb * strength;
imageStore(output_img, texel, vec4(highlights, input_color.a));
}

View File

@@ -54,6 +54,7 @@ void main()
ivec2 texel = start + j * direction;
vec4 horizontal = texture_load(diagonal_tx, texel);
vec4 vertical = imageLoad(anti_diagonal_img, texel);
imageStore(anti_diagonal_img, texel, horizontal + vertical);
vec4 combined = horizontal + vertical;
imageStore(anti_diagonal_img, texel, vec4(combined.rgb, 1.0));
}
}

View File

@@ -48,6 +48,7 @@ void main()
ivec2 texel = ivec2(gl_GlobalInvocationID.x, y);
vec4 horizontal = texture_load(horizontal_tx, texel);
vec4 vertical = imageLoad(vertical_img, texel);
imageStore(vertical_img, texel, horizontal + vertical);
vec4 combined = horizontal + vertical;
imageStore(vertical_img, texel, vec4(combined.rgb, 1.0));
}
}

View File

@@ -9,5 +9,6 @@ void main()
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
vec4 attenuated_streak = texture_load(streak_tx, texel) * attenuation_factor;
vec4 current_accumulated_streaks = imageLoad(accumulated_streaks_img, texel);
imageStore(accumulated_streaks_img, texel, current_accumulated_streaks + attenuated_streak);
vec4 combined_streaks = current_accumulated_streaks + attenuated_streak;
imageStore(accumulated_streaks_img, texel, vec4(combined_streaks.rgb, 1.0));
}

View File

@@ -0,0 +1,13 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
vec2 normalized_coordinates = (vec2(texel) + vec2(0.5)) / vec2(imageSize(output_img));
vec4 glare_value = texture(input_tx, normalized_coordinates);
vec4 adjusted_glare_value = glare_value * strength;
imageStore(output_img, texel, vec4(adjusted_glare_value.rgb, 1.0));
}

View File

@@ -0,0 +1,11 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
vec2 normalized_coordinates = (vec2(texel) + vec2(0.5)) / vec2(imageSize(output_img));
imageStore(output_img, texel, texture(input_tx, normalized_coordinates));
}

View File

@@ -19,7 +19,7 @@ GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(compositor_glare_mix)
LOCAL_GROUP_SIZE(16, 16)
PUSH_CONSTANT(FLOAT, mix_factor)
PUSH_CONSTANT(FLOAT, strength)
SAMPLER(0, FLOAT_2D, input_tx)
SAMPLER(1, FLOAT_2D, glare_tx)
IMAGE(0, GPU_RGBA16F, WRITE, FLOAT_2D, output_img)
@@ -27,6 +27,23 @@ COMPUTE_SOURCE("compositor_glare_mix.glsl")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(compositor_glare_write_glare_output)
LOCAL_GROUP_SIZE(16, 16)
PUSH_CONSTANT(FLOAT, strength)
SAMPLER(0, FLOAT_2D, input_tx)
IMAGE(0, GPU_RGBA16F, WRITE, FLOAT_2D, output_img)
COMPUTE_SOURCE("compositor_glare_write_glare_output.glsl")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(compositor_glare_write_highlights_output)
LOCAL_GROUP_SIZE(16, 16)
SAMPLER(0, FLOAT_2D, input_tx)
IMAGE(0, GPU_RGBA16F, WRITE, FLOAT_2D, output_img)
COMPUTE_SOURCE("compositor_glare_write_highlights_output.glsl")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
/* ------------
* Ghost Glare.
* ------------ */

View File

@@ -1223,12 +1223,19 @@ typedef struct NodeScriptDict {
/** glare node. */
typedef struct NodeGlare {
char quality, type, iter;
/* XXX angle is only kept for backward/forward compatibility,
* was used for two different things, see #50736. */
char angle DNA_DEPRECATED, _pad0, size, star_45, streaks;
float colmod, mix, threshold, fade;
float angle_ofs;
char type;
char quality;
char iter DNA_DEPRECATED;
char angle DNA_DEPRECATED;
char _pad0;
char size DNA_DEPRECATED;
char star_45;
char streaks DNA_DEPRECATED;
float colmod DNA_DEPRECATED;
float mix DNA_DEPRECATED;
float threshold DNA_DEPRECATED;
float fade DNA_DEPRECATED;
float angle_ofs DNA_DEPRECATED;
char _pad1[4];
} NodeGlare;

View File

@@ -7,6 +7,7 @@
*/
#include <climits>
#include <cmath>
#include <cstdlib>
#include <cstring>
@@ -14,6 +15,7 @@
#include "BLI_function_ref.hh"
#include "BLI_linear_allocator.hh"
#include "BLI_math_base.hh"
#include "BLI_math_rotation.h"
#include "BLI_string.h"
#include "BLI_string_utf8_symbols.h"
@@ -3379,6 +3381,152 @@ static void rna_NodeColorBalance_update_cdl(Main *bmain, Scene *scene, PointerRN
rna_Node_update(bmain, scene, ptr);
}
/* --------------------------------------------------------------------
* Glare Node Compatibility Setters/Getters.
*
* The Glare node properties are now deprecated and replaced corresponding inputs. So we provide
* setters/getters for compatibility until those are removed in 5.0. See the
* do_version_glare_node_options_to_inputs function for conversion
*/
static float rna_NodeGlare_threshold_get(PointerRNA *ptr)
{
bNode *node = static_cast<bNode *>(ptr->data);
bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Threshold");
PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input);
return RNA_float_get(&input_rna_pointer, "default_value");
}
static void rna_NodeGlare_threshold_set(PointerRNA *ptr, const float value)
{
bNode *node = static_cast<bNode *>(ptr->data);
bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Threshold");
PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input);
RNA_float_set(&input_rna_pointer, "default_value", value);
}
static float rna_NodeGlare_mix_get(PointerRNA *ptr)
{
bNode *node = static_cast<bNode *>(ptr->data);
bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Strength");
PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input);
return RNA_float_get(&input_rna_pointer, "default_value") - 1;
}
static void rna_NodeGlare_mix_set(PointerRNA *ptr, const float value)
{
bNode *node = static_cast<bNode *>(ptr->data);
bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Strength");
PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input);
const float mix_value = 1.0f - blender::math::clamp(-value, 0.0f, 1.0f);
RNA_float_set(&input_rna_pointer, "default_value", mix_value);
}
static int rna_NodeGlare_size_get(PointerRNA *ptr)
{
bNode *node = static_cast<bNode *>(ptr->data);
bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Size");
PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input);
const float size_value = RNA_float_get(&input_rna_pointer, "default_value");
if (size_value == 0.0f) {
return 1;
}
return blender::math::max(1, 9 - int(-std::log2(size_value)));
}
static void rna_NodeGlare_size_set(PointerRNA *ptr, const int value)
{
bNode *node = static_cast<bNode *>(ptr->data);
bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Size");
PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input);
const float size_value = blender::math::pow(2.0f, float(value - 9));
RNA_float_set(&input_rna_pointer, "default_value", size_value);
}
static int rna_NodeGlare_streaks_get(PointerRNA *ptr)
{
bNode *node = static_cast<bNode *>(ptr->data);
bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Streaks");
PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input);
return blender::math::clamp(RNA_int_get(&input_rna_pointer, "default_value"), 1, 16);
}
static void rna_NodeGlare_streaks_set(PointerRNA *ptr, const int value)
{
bNode *node = static_cast<bNode *>(ptr->data);
bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Streaks");
PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input);
RNA_int_set(&input_rna_pointer, "default_value", blender::math::clamp(value, 1, 16));
}
static float rna_NodeGlare_angle_offset_get(PointerRNA *ptr)
{
bNode *node = static_cast<bNode *>(ptr->data);
bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Streaks Angle");
PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input);
return RNA_float_get(&input_rna_pointer, "default_value");
}
static void rna_NodeGlare_angle_offset_set(PointerRNA *ptr, const float value)
{
bNode *node = static_cast<bNode *>(ptr->data);
bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Streaks Angle");
PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input);
RNA_float_set(&input_rna_pointer, "default_value", value);
}
static int rna_NodeGlare_iterations_get(PointerRNA *ptr)
{
bNode *node = static_cast<bNode *>(ptr->data);
bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Iterations");
PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input);
return blender::math::clamp(RNA_int_get(&input_rna_pointer, "default_value"), 2, 5);
}
static void rna_NodeGlare_iterations_set(PointerRNA *ptr, const int value)
{
bNode *node = static_cast<bNode *>(ptr->data);
bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Iterations");
PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input);
RNA_int_set(&input_rna_pointer, "default_value", blender::math::clamp(value, 2, 5));
}
static float rna_NodeGlare_fade_get(PointerRNA *ptr)
{
bNode *node = static_cast<bNode *>(ptr->data);
bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Fade");
PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input);
return blender::math::clamp(RNA_float_get(&input_rna_pointer, "default_value"), 0.75f, 1.0f);
}
static void rna_NodeGlare_fade_set(PointerRNA *ptr, const float value)
{
bNode *node = static_cast<bNode *>(ptr->data);
bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Fade");
PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input);
RNA_float_set(&input_rna_pointer, "default_value", blender::math::clamp(value, 0.75f, 1.0f));
}
static float rna_NodeGlare_color_modulation_get(PointerRNA *ptr)
{
bNode *node = static_cast<bNode *>(ptr->data);
bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Color Modulation");
PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input);
return blender::math::clamp(RNA_float_get(&input_rna_pointer, "default_value"), 0.0f, 1.0f);
}
static void rna_NodeGlare_color_modulation_set(PointerRNA *ptr, const float value)
{
bNode *node = static_cast<bNode *>(ptr->data);
bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Color Modulation");
PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input);
RNA_float_set(&input_rna_pointer, "default_value", blender::math::clamp(value, 0.0f, 1.0f));
}
/* --------------------------------------------------------------------
* White Balance Node.
*/
static void rna_NodeColorBalance_input_whitepoint_get(PointerRNA *ptr, float value[3])
{
bNode *node = static_cast<bNode *>(ptr->data);
@@ -7835,54 +7983,57 @@ static void def_cmp_glare(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
prop = RNA_def_property(srna, "iterations", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, nullptr, "iter");
RNA_def_property_int_funcs(
prop, "rna_NodeGlare_iterations_get", "rna_NodeGlare_iterations_set", nullptr);
RNA_def_property_range(prop, 2, 5);
RNA_def_property_ui_text(prop, "Iterations", "");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
RNA_def_property_ui_text(prop, "Iterations", "(Deprecated: Use Iterations input instead)");
prop = RNA_def_property(srna, "color_modulation", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, nullptr, "colmod");
RNA_def_property_float_funcs(
prop, "rna_NodeGlare_color_modulation_get", "rna_NodeGlare_color_modulation_set", nullptr);
RNA_def_property_range(prop, 0.0f, 1.0f);
RNA_def_property_ui_text(
prop,
"Color Modulation",
"Amount of Color Modulation, modulates colors of streaks and ghosts for "
"a spectral dispersion effect");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
"a spectral dispersion effect. (Deprecated: Use Color Modulation input instead)");
prop = RNA_def_property(srna, "mix", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, nullptr, "mix");
RNA_def_property_float_funcs(prop, "rna_NodeGlare_mix_get", "rna_NodeGlare_mix_set", nullptr);
RNA_def_property_range(prop, -1.0f, 1.0f);
RNA_def_property_ui_text(
prop, "Mix", "-1 is original image only, 0 is exact 50/50 mix, 1 is processed image only");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
RNA_def_property_ui_text(prop,
"Mix",
"1 is original image only, 0 is exact 50/50 mix, 1 is processed image "
"only. (Deprecated: Use Strength input instead)");
prop = RNA_def_property(srna, "threshold", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, nullptr, "threshold");
RNA_def_property_float_funcs(
prop, "rna_NodeGlare_threshold_get", "rna_NodeGlare_threshold_set", nullptr);
RNA_def_property_range(prop, 0.0f, FLT_MAX);
RNA_def_property_ui_text(
prop,
"Threshold",
"The glare filter will only be applied to pixels brighter than this value");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
RNA_def_property_ui_text(prop,
"Threshold",
"The glare filter will only be applied to pixels brighter than this "
"value. (Deprecated: Use Threshold input instead)");
prop = RNA_def_property(srna, "streaks", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, nullptr, "streaks");
RNA_def_property_int_funcs(
prop, "rna_NodeGlare_streaks_get", "rna_NodeGlare_streaks_set", nullptr);
RNA_def_property_range(prop, 1, 16);
RNA_def_property_ui_text(prop, "Streaks", "Total number of streaks");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
RNA_def_property_ui_text(
prop, "Streaks", "Total number of streaks. (Deprecated: Use Streaks input instead)");
prop = RNA_def_property(srna, "angle_offset", PROP_FLOAT, PROP_ANGLE);
RNA_def_property_float_sdna(prop, nullptr, "angle_ofs");
RNA_def_property_float_funcs(
prop, "rna_NodeGlare_angle_offset_get", "rna_NodeGlare_angle_offset_set", nullptr);
RNA_def_property_range(prop, 0.0f, DEG2RADF(180.0f));
RNA_def_property_ui_text(prop, "Angle Offset", "Streak angle offset");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
RNA_def_property_ui_text(
prop, "Angle Offset", "Streak angle offset. (Deprecated: Use Streaks Angle input instead)");
prop = RNA_def_property(srna, "fade", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, nullptr, "fade");
RNA_def_property_float_funcs(prop, "rna_NodeGlare_fade_get", "rna_NodeGlare_fade_set", nullptr);
RNA_def_property_range(prop, 0.75f, 1.0f);
RNA_def_property_ui_text(prop, "Fade", "Streak fade-out factor");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
RNA_def_property_ui_text(
prop, "Fade", "Streak fade-out factor. (Deprecated: Use Fade input instead)");
prop = RNA_def_property(srna, "use_rotate_45", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "star_45", 0);
@@ -7890,15 +8041,12 @@ static void def_cmp_glare(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
prop = RNA_def_property(srna, "size", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, nullptr, "size");
RNA_def_property_int_funcs(prop, "rna_NodeGlare_size_get", "rna_NodeGlare_size_set", nullptr);
RNA_def_property_range(prop, 1, 9);
RNA_def_property_ui_text(
prop,
"Size",
"Glow/glare size (not actual size; relative to initial size of bright area of pixels)");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
/* TODO */
RNA_def_property_ui_text(prop,
"Size",
"Glow/glare size (not actual size; relative to initial size of bright "
"area of pixels). (Deprecated: Use Size input instead)");
}
static void def_cmp_tonemap(StructRNA *srna)

View File

@@ -7,6 +7,7 @@
*/
#include <array>
#include <cmath>
#include <complex>
#include <memory>
@@ -46,7 +47,6 @@
#include "node_composite_util.hh"
#define MAX_GLARE_ITERATIONS 5
#define MAX_GLARE_SIZE 9
namespace blender::nodes::node_composite_glare_cc {
@@ -57,7 +57,67 @@ static void cmp_node_glare_declare(NodeDeclarationBuilder &b)
b.add_input<decl::Color>("Image")
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
.compositor_domain_priority(0);
b.add_output<decl::Color>("Image");
b.add_input<decl::Float>("Threshold")
.default_value(1.0f)
.min(0.0f)
.description(
"Defines the luminance at which pixels start to be considered part of the highlights "
"that will produce a glare")
.compositor_expects_single_value();
b.add_input<decl::Float>("Strength")
.default_value(1.0f)
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR)
.description("The strength of the glare that will be added to the image")
.compositor_expects_single_value();
b.add_input<decl::Float>("Size")
.default_value(0.5f)
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR)
.description(
"The size of the glare relative to the image. 1 means the glare covers the entire "
"image, 0.5 means the glare covers half the image, and so on")
.compositor_expects_single_value();
b.add_input<decl::Int>("Streaks")
.default_value(4)
.min(1)
.max(16)
.description("The number of streaks")
.compositor_expects_single_value();
b.add_input<decl::Float>("Streaks Angle")
.default_value(0.0f)
.subtype(PROP_ANGLE)
.description("The angle that the first streak makes with the horizontal axis")
.compositor_expects_single_value();
b.add_input<decl::Int>("Iterations")
.default_value(3)
.min(2)
.max(5)
.description(
"The number of ghosts for Ghost glare or the spread of Glare for Streaks and Simple "
"Star")
.compositor_expects_single_value();
b.add_input<decl::Float>("Fade")
.default_value(0.9f)
.min(0.75f)
.max(1.0f)
.subtype(PROP_FACTOR)
.description("Streak fade-out factor")
.compositor_expects_single_value();
b.add_input<decl::Float>("Color Modulation")
.default_value(0.25)
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR)
.description("Modulates colors of streaks and ghosts for a spectral dispersion effect")
.compositor_expects_single_value();
b.add_output<decl::Color>("Image").description("The image with the generated glare added");
b.add_output<decl::Color>("Glare").description("The generated glare");
b.add_output<decl::Color>("Highlights")
.description("The extracted highlights from which the glare was generated");
}
static void node_composit_init_glare(bNodeTree * /*ntree*/, bNode *node)
@@ -65,15 +125,7 @@ static void node_composit_init_glare(bNodeTree * /*ntree*/, bNode *node)
NodeGlare *ndg = MEM_cnew<NodeGlare>(__func__);
ndg->quality = 1;
ndg->type = CMP_NODE_GLARE_STREAKS;
ndg->iter = 3;
ndg->colmod = 0.25;
ndg->mix = 0;
ndg->threshold = 1;
ndg->star_45 = true;
ndg->streaks = 4;
ndg->angle_ofs = 0.0f;
ndg->fade = 0.9;
ndg->size = 8;
node->storage = ndg;
}
@@ -89,43 +141,42 @@ static void node_composit_buts_glare(uiLayout *layout, bContext * /*C*/, Pointer
uiItemR(layout, ptr, "glare_type", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
uiItemR(layout, ptr, "quality", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
if (ELEM(glare_type, CMP_NODE_GLARE_SIMPLE_STAR, CMP_NODE_GLARE_GHOST, CMP_NODE_GLARE_STREAKS)) {
uiItemR(layout, ptr, "iterations", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
}
if (ELEM(glare_type, CMP_NODE_GLARE_GHOST, CMP_NODE_GLARE_STREAKS)) {
uiItemR(layout,
ptr,
"color_modulation",
UI_ITEM_R_SPLIT_EMPTY_NAME | UI_ITEM_R_SLIDER,
std::nullopt,
ICON_NONE);
}
uiItemR(layout, ptr, "mix", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
uiItemR(layout, ptr, "threshold", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
if (glare_type == CMP_NODE_GLARE_STREAKS) {
uiItemR(layout, ptr, "streaks", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
uiItemR(layout, ptr, "angle_offset", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
}
if (ELEM(glare_type, CMP_NODE_GLARE_SIMPLE_STAR, CMP_NODE_GLARE_STREAKS)) {
uiItemR(layout,
ptr,
"fade",
UI_ITEM_R_SPLIT_EMPTY_NAME | UI_ITEM_R_SLIDER,
std::nullopt,
ICON_NONE);
}
if (glare_type == CMP_NODE_GLARE_SIMPLE_STAR) {
uiItemR(layout, ptr, "use_rotate_45", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
}
}
if (ELEM(glare_type, CMP_NODE_GLARE_FOG_GLOW, CMP_NODE_GLARE_BLOOM)) {
uiItemR(layout, ptr, "size", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
}
static void node_update(bNodeTree *ntree, bNode *node)
{
const CMPNodeGlareType glare_type = static_cast<CMPNodeGlareType>(node_storage(*node).type);
bNodeSocket *size_input = bke::node_find_socket(node, SOCK_IN, "Size");
blender::bke::node_set_socket_availability(
ntree, size_input, ELEM(glare_type, CMP_NODE_GLARE_FOG_GLOW, CMP_NODE_GLARE_BLOOM));
bNodeSocket *iterations_input = bke::node_find_socket(node, SOCK_IN, "Iterations");
blender::bke::node_set_socket_availability(
ntree,
iterations_input,
ELEM(glare_type, CMP_NODE_GLARE_SIMPLE_STAR, CMP_NODE_GLARE_GHOST, CMP_NODE_GLARE_STREAKS));
bNodeSocket *fade_input = bke::node_find_socket(node, SOCK_IN, "Fade");
blender::bke::node_set_socket_availability(
ntree, fade_input, ELEM(glare_type, CMP_NODE_GLARE_SIMPLE_STAR, CMP_NODE_GLARE_STREAKS));
bNodeSocket *color_modulation_input = bke::node_find_socket(node, SOCK_IN, "Color Modulation");
blender::bke::node_set_socket_availability(
ntree,
color_modulation_input,
ELEM(glare_type, CMP_NODE_GLARE_GHOST, CMP_NODE_GLARE_STREAKS));
bNodeSocket *streaks_input = bke::node_find_socket(node, SOCK_IN, "Streaks");
blender::bke::node_set_socket_availability(
ntree, streaks_input, glare_type == CMP_NODE_GLARE_STREAKS);
bNodeSocket *streaks_angle_input = bke::node_find_socket(node, SOCK_IN, "Streaks Angle");
blender::bke::node_set_socket_availability(
ntree, streaks_angle_input, glare_type == CMP_NODE_GLARE_STREAKS);
}
using namespace blender::compositor;
@@ -136,57 +187,54 @@ class GlareOperation : public NodeOperation {
void execute() override
{
if (is_identity()) {
get_input("Image").pass_through(get_result("Image"));
Result &image_input = this->get_input("Image");
Result &image_output = this->get_result("Image");
Result &glare_output = this->get_result("Glare");
Result &highlights_output = this->get_result("Highlights");
if (image_input.is_single_value()) {
if (image_output.should_compute()) {
image_input.pass_through(image_output);
}
if (glare_output.should_compute()) {
glare_output.allocate_invalid();
}
if (highlights_output.should_compute()) {
highlights_output.allocate_invalid();
}
return;
}
Result highlights_result = execute_highlights();
Result glare_result = execute_glare(highlights_result);
highlights_result.release();
execute_mix(glare_result);
glare_result.release();
}
Result highlights = this->compute_highlights();
Result glare = this->compute_glare(highlights);
bool is_identity()
{
if (get_input("Image").is_single_value()) {
return true;
if (highlights_output.should_compute()) {
if (highlights.domain().size != image_input.domain().size) {
/* The highlights were computed on a fraction of the image size, see the get_quality_factor
* method. So we need to upsample them while writing as opposed to just stealing the
* existing data. */
this->write_highlights_output(highlights);
}
else {
highlights_output.steal_data(highlights);
}
}
highlights.release();
/* A mix factor of -1 indicates that the original image is returned as is. See the execute_mix
* method for more information. */
if (node_storage(bnode()).mix == -1.0f) {
return true;
}
return false;
}
Result execute_glare(Result &highlights_result)
{
switch (node_storage(bnode()).type) {
case CMP_NODE_GLARE_SIMPLE_STAR:
return execute_simple_star(highlights_result);
case CMP_NODE_GLARE_FOG_GLOW:
return execute_fog_glow(highlights_result);
case CMP_NODE_GLARE_STREAKS:
return execute_streaks(highlights_result);
case CMP_NODE_GLARE_GHOST:
return execute_ghost(highlights_result);
case CMP_NODE_GLARE_BLOOM:
return execute_bloom(highlights_result);
default:
BLI_assert_unreachable();
return context().create_result(ResultType::Color);
/* Combine the original input and the generated glare. */
execute_mix(glare);
if (glare_output.should_compute()) {
this->write_glare_output(glare);
}
glare.release();
}
/* -----------------
* Glare Highlights.
* ----------------- */
Result execute_highlights()
Result compute_highlights()
{
if (this->context().use_gpu()) {
return this->execute_highlights_gpu();
@@ -199,18 +247,18 @@ class GlareOperation : public NodeOperation {
GPUShader *shader = context().get_shader("compositor_glare_highlights");
GPU_shader_bind(shader);
GPU_shader_uniform_1f(shader, "threshold", node_storage(bnode()).threshold);
GPU_shader_uniform_1f(shader, "threshold", this->get_threshold());
const Result &input_image = get_input("Image");
GPU_texture_filter_mode(input_image, true);
input_image.bind_as_texture(shader, "input_tx");
const int2 glare_size = get_glare_size();
const int2 highlights_size = get_highlights_size();
Result highlights_result = context().create_result(ResultType::Color);
highlights_result.allocate_texture(glare_size);
highlights_result.allocate_texture(highlights_size);
highlights_result.bind_as_image(shader, "output_img");
compute_dispatch_threads_at_least(shader, glare_size);
compute_dispatch_threads_at_least(shader, highlights_size);
GPU_shader_unbind();
input_image.unbind_as_texture();
@@ -221,21 +269,21 @@ class GlareOperation : public NodeOperation {
Result execute_highlights_cpu()
{
const float threshold = node_storage(bnode()).threshold;
const float threshold = this->get_threshold();
const Result &input = get_input("Image");
const int2 glare_size = this->get_glare_size();
const int2 highlights_size = this->get_highlights_size();
Result output = context().create_result(ResultType::Color);
output.allocate_texture(glare_size);
output.allocate_texture(highlights_size);
/* The dispatch domain covers the output image size, which might be a fraction of the input
* image size, so you will notice the glare size used throughout the code instead of the input
* one. */
parallel_for(glare_size, [&](const int2 texel) {
parallel_for(highlights_size, [&](const int2 texel) {
/* Add 0.5 to evaluate the input sampler at the center of the pixel and divide by the image
* size to get the coordinates into the sampler's expected [0, 1] range. */
float2 normalized_coordinates = (float2(texel) + float2(0.5f)) / float2(glare_size);
float2 normalized_coordinates = (float2(texel) + float2(0.5f)) / float2(highlights_size);
float4 hsva;
rgb_to_hsv_v(input.sample_bilinear_extended(normalized_coordinates), hsva);
@@ -254,6 +302,95 @@ class GlareOperation : public NodeOperation {
return output;
}
/* As a performance optimization, the operation can compute the glare on a fraction of the input
* image size, so we extract the highlights to a smaller result, whose size is returned by this
* method. */
int2 get_highlights_size()
{
return this->compute_domain().size / this->get_quality_factor();
}
/* Writes the given input highlights by upsampling it using bilinear interpolation to match the
* size of the original input, allocating the highlights output and writing the result to it. */
void write_highlights_output(const Result &highlights)
{
if (this->context().use_gpu()) {
this->write_highlights_output_gpu(highlights);
}
else {
this->write_highlights_output_cpu(highlights);
}
}
void write_highlights_output_gpu(const Result &highlights)
{
GPUShader *shader = this->context().get_shader("compositor_glare_write_highlights_output");
GPU_shader_bind(shader);
GPU_texture_filter_mode(highlights, true);
GPU_texture_extend_mode(highlights, GPU_SAMPLER_EXTEND_MODE_EXTEND);
highlights.bind_as_texture(shader, "input_tx");
const Result &image_input = this->get_input("Image");
Result &output = this->get_result("Highlights");
output.allocate_texture(image_input.domain());
output.bind_as_image(shader, "output_img");
compute_dispatch_threads_at_least(shader, output.domain().size);
GPU_shader_unbind();
output.unbind_as_image();
highlights.unbind_as_texture();
}
void write_highlights_output_cpu(const Result &highlights)
{
const Result &image_input = this->get_input("Image");
Result &output = this->get_result("Highlights");
output.allocate_texture(image_input.domain());
const int2 size = output.domain().size;
parallel_for(size, [&](const int2 texel) {
float2 normalized_coordinates = (float2(texel) + float2(0.5f)) / float2(size);
output.store_pixel(texel, highlights.sample_bilinear_extended(normalized_coordinates));
});
}
/* ------
* Glare.
* ------ */
Result compute_glare(Result &highlights_result)
{
if (!this->should_compute_glare()) {
return this->context().create_result(ResultType::Color);
}
switch (node_storage(bnode()).type) {
case CMP_NODE_GLARE_SIMPLE_STAR:
return this->execute_simple_star(highlights_result);
case CMP_NODE_GLARE_FOG_GLOW:
return this->execute_fog_glow(highlights_result);
case CMP_NODE_GLARE_STREAKS:
return this->execute_streaks(highlights_result);
case CMP_NODE_GLARE_GHOST:
return this->execute_ghost(highlights_result);
case CMP_NODE_GLARE_BLOOM:
return this->execute_bloom(highlights_result);
default:
BLI_assert_unreachable();
return this->context().create_result(ResultType::Color);
}
}
/* Glare should be computed either because the glare output is needed directly or the image
* output is needed. */
bool should_compute_glare()
{
return this->get_result("Glare").should_compute() ||
this->get_result("Image").should_compute();
}
/* ------------------
* Simple Star Glare.
* ------------------ */
@@ -289,7 +426,7 @@ class GlareOperation : public NodeOperation {
{
/* First, copy the highlights result to the output since we will be doing the computation
* in-place. */
const int2 size = get_glare_size();
const int2 size = highlights.domain().size;
Result vertical_pass_result = context().create_result(ResultType::Color);
vertical_pass_result.allocate_texture(size);
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
@@ -299,7 +436,7 @@ class GlareOperation : public NodeOperation {
GPU_shader_bind(shader);
GPU_shader_uniform_1i(shader, "iterations", get_number_of_iterations());
GPU_shader_uniform_1f(shader, "fade_factor", node_storage(bnode()).fade);
GPU_shader_uniform_1f(shader, "fade_factor", this->get_fade());
horizontal_pass_result.bind_as_texture(shader, "horizontal_tx");
@@ -321,7 +458,7 @@ class GlareOperation : public NodeOperation {
{
/* First, copy the highlights result to the output since we will be doing the computation
* in-place. */
const int2 size = get_glare_size();
const int2 size = highlights.domain().size;
Result output = this->context().create_result(ResultType::Color);
output.allocate_texture(size);
parallel_for(size, [&](const int2 texel) {
@@ -329,7 +466,7 @@ class GlareOperation : public NodeOperation {
});
const int iterations = this->get_number_of_iterations();
const float fade_factor = node_storage(this->bnode()).fade;
const float fade_factor = this->get_fade();
/* Dispatch a thread for each column in the image. */
const int width = size.x;
@@ -378,7 +515,8 @@ class GlareOperation : public NodeOperation {
int2 texel = int2(x, y);
float4 horizontal = horizontal_pass_result.load_pixel<float4>(texel);
float4 vertical = output.load_pixel<float4>(texel);
output.store_pixel(texel, horizontal + vertical);
float4 combined = horizontal + vertical;
output.store_pixel(texel, float4(combined.xyz(), 1.0f));
}
}
});
@@ -398,7 +536,7 @@ class GlareOperation : public NodeOperation {
{
/* First, copy the highlights result to the output since we will be doing the computation
* in-place. */
const int2 size = get_glare_size();
const int2 size = highlights.domain().size;
Result horizontal_pass_result = context().create_result(ResultType::Color);
horizontal_pass_result.allocate_texture(size);
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
@@ -408,7 +546,7 @@ class GlareOperation : public NodeOperation {
GPU_shader_bind(shader);
GPU_shader_uniform_1i(shader, "iterations", get_number_of_iterations());
GPU_shader_uniform_1f(shader, "fade_factor", node_storage(bnode()).fade);
GPU_shader_uniform_1f(shader, "fade_factor", this->get_fade());
horizontal_pass_result.bind_as_image(shader, "horizontal_img");
@@ -425,7 +563,7 @@ class GlareOperation : public NodeOperation {
{
/* First, copy the highlights result to the output since we will be doing the computation
* in-place. */
const int2 size = get_glare_size();
const int2 size = highlights.domain().size;
Result horizontal_pass_result = context().create_result(ResultType::Color);
horizontal_pass_result.allocate_texture(size);
parallel_for(size, [&](const int2 texel) {
@@ -433,7 +571,7 @@ class GlareOperation : public NodeOperation {
});
const int iterations = this->get_number_of_iterations();
const float fade_factor = node_storage(this->bnode()).fade;
const float fade_factor = this->get_fade();
/* Dispatch a thread for each row in the image. */
const int width = size.x;
@@ -504,9 +642,9 @@ class GlareOperation : public NodeOperation {
{
/* First, copy the highlights result to the output since we will be doing the computation
* in-place. */
const int2 glare_size = get_glare_size();
const int2 size = highlights.domain().size;
Result anti_diagonal_pass_result = context().create_result(ResultType::Color);
anti_diagonal_pass_result.allocate_texture(glare_size);
anti_diagonal_pass_result.allocate_texture(size);
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
GPU_texture_copy(anti_diagonal_pass_result, highlights);
@@ -514,14 +652,14 @@ class GlareOperation : public NodeOperation {
GPU_shader_bind(shader);
GPU_shader_uniform_1i(shader, "iterations", get_number_of_iterations());
GPU_shader_uniform_1f(shader, "fade_factor", node_storage(bnode()).fade);
GPU_shader_uniform_1f(shader, "fade_factor", this->get_fade());
diagonal_pass_result.bind_as_texture(shader, "diagonal_tx");
anti_diagonal_pass_result.bind_as_image(shader, "anti_diagonal_img");
/* Dispatch a thread for each diagonal in the image. */
compute_dispatch_threads_at_least(shader, int2(compute_number_of_diagonals(glare_size), 1));
compute_dispatch_threads_at_least(shader, int2(compute_number_of_diagonals(size), 1));
diagonal_pass_result.unbind_as_texture();
anti_diagonal_pass_result.unbind_as_image();
@@ -535,7 +673,7 @@ class GlareOperation : public NodeOperation {
{
/* First, copy the highlights result to the output since we will be doing the computation
* in-place. */
const int2 size = get_glare_size();
const int2 size = highlights.domain().size;
Result output = this->context().create_result(ResultType::Color);
output.allocate_texture(size);
parallel_for(size, [&](const int2 texel) {
@@ -543,7 +681,7 @@ class GlareOperation : public NodeOperation {
});
const int iterations = this->get_number_of_iterations();
const float fade_factor = node_storage(this->bnode()).fade;
const float fade_factor = this->get_fade();
/* Dispatch a thread for each diagonal in the image. */
const int diagonals_count = compute_number_of_diagonals(size);
@@ -595,7 +733,8 @@ class GlareOperation : public NodeOperation {
int2 texel = start + j * direction;
float4 horizontal = diagonal_pass_result.load_pixel<float4>(texel);
float4 vertical = output.load_pixel<float4>(texel);
output.store_pixel(texel, horizontal + vertical);
float4 combined = horizontal + vertical;
output.store_pixel(texel, float4(combined.xyz(), 1.0f));
}
}
});
@@ -615,9 +754,9 @@ class GlareOperation : public NodeOperation {
{
/* First, copy the highlights result to the output since we will be doing the computation
* in-place. */
const int2 glare_size = get_glare_size();
const int2 size = highlights.domain().size;
Result diagonal_pass_result = context().create_result(ResultType::Color);
diagonal_pass_result.allocate_texture(glare_size);
diagonal_pass_result.allocate_texture(size);
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
GPU_texture_copy(diagonal_pass_result, highlights);
@@ -625,12 +764,12 @@ class GlareOperation : public NodeOperation {
GPU_shader_bind(shader);
GPU_shader_uniform_1i(shader, "iterations", get_number_of_iterations());
GPU_shader_uniform_1f(shader, "fade_factor", node_storage(bnode()).fade);
GPU_shader_uniform_1f(shader, "fade_factor", this->get_fade());
diagonal_pass_result.bind_as_image(shader, "diagonal_img");
/* Dispatch a thread for each diagonal in the image. */
compute_dispatch_threads_at_least(shader, int2(compute_number_of_diagonals(glare_size), 1));
compute_dispatch_threads_at_least(shader, int2(compute_number_of_diagonals(size), 1));
diagonal_pass_result.unbind_as_image();
GPU_shader_unbind();
@@ -642,7 +781,7 @@ class GlareOperation : public NodeOperation {
{
/* First, copy the highlights result to the output since we will be doing the computation
* in-place. */
const int2 size = get_glare_size();
const int2 size = highlights.domain().size;
Result diagonal_pass_result = this->context().create_result(ResultType::Color);
diagonal_pass_result.allocate_texture(size);
parallel_for(size, [&](const int2 texel) {
@@ -650,7 +789,7 @@ class GlareOperation : public NodeOperation {
});
const int iterations = this->get_number_of_iterations();
const float fade_factor = node_storage(this->bnode()).fade;
const float fade_factor = this->get_fade();
/* Dispatch a thread for each diagonal in the image. */
const int diagonals_count = compute_number_of_diagonals(size);
@@ -712,7 +851,7 @@ class GlareOperation : public NodeOperation {
Result execute_streaks(const Result &highlights)
{
/* Create an initially zero image where streaks will be accumulated. */
const int2 size = get_glare_size();
const int2 size = highlights.domain().size;
Result accumulated_streaks_result = context().create_result(ResultType::Color);
accumulated_streaks_result.allocate_texture(size);
if (this->context().use_gpu()) {
@@ -752,14 +891,14 @@ class GlareOperation : public NodeOperation {
/* Copy the highlights result into a new result because the output will be copied to the input
* after each iteration. */
const int2 glare_size = get_glare_size();
const int2 size = highlights.domain().size;
Result input_streak_result = context().create_result(ResultType::Color);
input_streak_result.allocate_texture(glare_size);
input_streak_result.allocate_texture(size);
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
GPU_texture_copy(input_streak_result, highlights);
Result output_streak_result = context().create_result(ResultType::Color);
output_streak_result.allocate_texture(glare_size);
output_streak_result.allocate_texture(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. */
@@ -780,7 +919,7 @@ class GlareOperation : public NodeOperation {
output_streak_result.bind_as_image(shader, "output_streak_img");
compute_dispatch_threads_at_least(shader, glare_size);
compute_dispatch_threads_at_least(shader, size);
input_streak_result.unbind_as_texture();
output_streak_result.unbind_as_image();
@@ -804,7 +943,7 @@ class GlareOperation : public NodeOperation {
{
/* Copy the highlights result into a new result because the output will be copied to the input
* after each iteration. */
const int2 size = this->get_glare_size();
const int2 size = highlights.domain().size;
Result input = this->context().create_result(ResultType::Color);
input.allocate_texture(size);
parallel_for(size, [&](const int2 texel) {
@@ -894,8 +1033,7 @@ class GlareOperation : public NodeOperation {
streak_result.bind_as_texture(shader, "streak_tx");
accumulated_streaks_result.bind_as_image(shader, "accumulated_streaks_img", true);
const int2 glare_size = get_glare_size();
compute_dispatch_threads_at_least(shader, glare_size);
compute_dispatch_threads_at_least(shader, streak_result.domain().size);
streak_result.unbind_as_texture();
accumulated_streaks_result.unbind_as_image();
@@ -906,11 +1044,12 @@ class GlareOperation : public NodeOperation {
{
const float attenuation_factor = this->compute_streak_attenuation_factor();
const int2 size = get_glare_size();
const int2 size = streak.domain().size;
parallel_for(size, [&](const int2 texel) {
float4 attenuated_streak = streak.load_pixel<float4>(texel) * attenuation_factor;
float4 current_accumulated_streaks = accumulated_streaks.load_pixel<float4>(texel);
accumulated_streaks.store_pixel(texel, current_accumulated_streaks + attenuated_streak);
float4 combined_streaks = current_accumulated_streaks + attenuated_streak;
accumulated_streaks.store_pixel(texel, float4(combined_streaks.xyz(), 1.0f));
});
}
@@ -932,7 +1071,7 @@ class GlareOperation : public NodeOperation {
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 start_angle = this->get_streaks_angle();
const float angle = start_angle + (float(streak_index) / number_of_streaks) * (M_PI * 2.0f);
return float2(math::cos(angle), math::sin(angle));
}
@@ -948,7 +1087,7 @@ class GlareOperation : public NodeOperation {
* 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);
return 1.0f - std::pow(this->get_color_modulation(), iteration + 1);
}
/* Streaks are computed by iteratively applying a filter that samples 3 neighboring pixels in the
@@ -964,7 +1103,7 @@ class GlareOperation : public NodeOperation {
* fade factors for those farther neighbors. */
float3 compute_streak_fade_factors(float iteration_magnitude)
{
const float fade_factor = std::pow(node_storage(bnode()).fade, iteration_magnitude);
const float fade_factor = std::pow(this->get_fade(), iteration_magnitude);
return float3(fade_factor, std::pow(fade_factor, 2.0f), std::pow(fade_factor, 3.0f));
}
@@ -979,14 +1118,14 @@ class GlareOperation : public NodeOperation {
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;
return math::clamp(this->get_input("Streaks").get_single_value_default(4), 1, 16);
}
float get_streaks_angle()
{
return this->get_input("Streaks Angle").get_single_value_default(0.0f);
}
/* ------------
@@ -1022,14 +1161,14 @@ class GlareOperation : public NodeOperation {
/* Zero initialize output image where ghosts will be accumulated. */
const float4 zero_color = float4(0.0f);
const int2 glare_size = get_glare_size();
accumulated_ghosts_result.allocate_texture(glare_size);
const int2 size = base_ghost_result.domain().size;
accumulated_ghosts_result.allocate_texture(size);
GPU_texture_clear(accumulated_ghosts_result, GPU_DATA_FLOAT, zero_color);
/* Copy the highlights result into a new result because the output will be copied to the input
* after each iteration. */
Result input_ghost_result = context().create_result(ResultType::Color);
input_ghost_result.allocate_texture(glare_size);
input_ghost_result.allocate_texture(size);
GPU_texture_copy(input_ghost_result, base_ghost_result);
/* For the given number of iterations, accumulate four ghosts with different scales and color
@@ -1044,7 +1183,7 @@ class GlareOperation : public NodeOperation {
input_ghost_result.bind_as_texture(shader, "input_ghost_tx");
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, size);
input_ghost_result.unbind_as_texture();
accumulated_ghosts_result.unbind_as_image();
@@ -1068,7 +1207,7 @@ class GlareOperation : public NodeOperation {
std::array<float4, 4> color_modulators = this->compute_ghost_color_modulators();
/* Zero initialize output image where ghosts will be accumulated. */
const int2 size = get_glare_size();
const int2 size = base_ghost.domain().size;
accumulated_ghosts_result.allocate_texture(size);
parallel_for(size, [&](const int2 texel) {
accumulated_ghosts_result.store_pixel(texel, float4(0.0f));
@@ -1119,8 +1258,8 @@ class GlareOperation : public NodeOperation {
}
float4 current_accumulated_ghost = accumulated_ghosts_result.load_pixel<float4>(texel);
accumulated_ghosts_result.store_pixel(texel,
current_accumulated_ghost + accumulated_ghost);
float4 combined_ghost = current_accumulated_ghost + accumulated_ghost;
accumulated_ghosts_result.store_pixel(texel, float4(combined_ghost.xyz(), 1.0f));
});
/* The accumulated result serves as the input for the next iteration, so copy the result to
@@ -1186,11 +1325,10 @@ class GlareOperation : public NodeOperation {
GPU_texture_extend_mode(big_ghost_result, GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER);
big_ghost_result.bind_as_texture(shader, "big_ghost_tx");
const int2 glare_size = get_glare_size();
base_ghost_result.allocate_texture(glare_size);
base_ghost_result.allocate_texture(small_ghost_result.domain());
base_ghost_result.bind_as_image(shader, "combined_ghost_img");
compute_dispatch_threads_at_least(shader, glare_size);
compute_dispatch_threads_at_least(shader, base_ghost_result.domain().size);
GPU_shader_unbind();
small_ghost_result.unbind_as_texture();
@@ -1202,7 +1340,7 @@ class GlareOperation : public NodeOperation {
const Result &big_ghost_result,
Result &combined_ghost)
{
const int2 size = get_glare_size();
const int2 size = small_ghost_result.domain().size;
combined_ghost.allocate_texture(size);
parallel_for(size, [&](const int2 texel) {
@@ -1320,7 +1458,7 @@ class GlareOperation : public NodeOperation {
* subtract from one. */
float get_ghost_color_modulation_factor()
{
return 1.0f - get_color_modulation_factor();
return 1.0f - this->get_color_modulation();
}
/* ------------
@@ -1357,11 +1495,9 @@ class GlareOperation : public NodeOperation {
* smaller dimension of the size of the highlights.
*
* However, as users might want a smaller glare size, we reduce the chain length by the
* halving count supplied by the user. */
const int2 glare_size = get_glare_size();
const int smaller_glare_dimension = math::min(glare_size.x, glare_size.y);
const int chain_length = int(std::log2(smaller_glare_dimension)) -
compute_bloom_size_halving_count();
* size supplied by the user. */
const int smaller_dimension = math::reduce_min(highlights.domain().size);
const int chain_length = int(std::log2(smaller_dimension * this->get_size()));
/* If the chain length is less than 2, that means no down-sampling will happen, so we just
* return a copy of the highlights. This is a sanitization of a corner case, so no need to
@@ -1460,7 +1596,8 @@ class GlareOperation : public NodeOperation {
upsampled += (1.0f / 16.0f) *
input.sample_bilinear_extended(coordinates + pixel_size * float2(1.0f, 1.0f));
output.store_pixel(texel, output.load_pixel<float4>(texel) + upsampled);
float4 combined = output.load_pixel<float4>(texel) + upsampled;
output.store_pixel(texel, float4(combined.xyz(), 1.0f));
});
}
@@ -1660,21 +1797,6 @@ class GlareOperation : public NodeOperation {
math::safe_rcp(math::reduce_add(weights));
}
/* The bloom has a maximum possible size when the bloom size is equal to MAX_GLARE_SIZE and
* halves for every unit decrement of the bloom size. This method computes the number of halving
* that should take place, which is simply the difference to MAX_GLARE_SIZE. */
int compute_bloom_size_halving_count()
{
return MAX_GLARE_SIZE - get_bloom_size();
}
/* The size of the bloom relative to its maximum possible size, see the
* compute_bloom_size_halving_count() method for more information. */
int get_bloom_size()
{
return node_storage(bnode()).size;
}
/* ---------------
* Fog Glow Glare.
* --------------- */
@@ -1684,7 +1806,7 @@ class GlareOperation : public NodeOperation {
#if defined(WITH_FFTW3)
fftw::initialize_float();
const int kernel_size = compute_fog_glow_kernel_size();
const int kernel_size = compute_fog_glow_kernel_size(highlights);
/* Since we will be doing a circular convolution, we need to zero pad our input image by half
* the kernel size to avoid the kernel affecting the pixels at the other side of image.
@@ -1850,11 +1972,11 @@ class GlareOperation : public NodeOperation {
/* Computes the size of the fog glow kernel that will be convolved with the image, which is
* essentially the extent of the glare in pixels. */
int compute_fog_glow_kernel_size()
int compute_fog_glow_kernel_size(const Result &highlights)
{
/* We use an odd sized kernel since an even one will typically introduce a tiny offset as it
* has no exact center value. */
return (1 << node_storage(bnode()).size) + 1;
/* make sure the kernel size is odd since an even one will typically introduce a tiny offset as
* it has no exact center value. */
return (int(math::reduce_max(highlights.domain().size) * this->get_size()) / 2) * 2 + 1;
}
/* ----------
@@ -1863,6 +1985,17 @@ class GlareOperation : public NodeOperation {
void execute_mix(const Result &glare_result)
{
Result &image_output = this->get_result("Image");
if (!image_output.should_compute()) {
return;
}
Result &image_input = this->get_input("Image");
if (this->get_strength() == 0.0f) {
image_input.pass_through(image_output);
return;
}
if (this->context().use_gpu()) {
this->execute_mix_gpu(glare_result);
}
@@ -1876,7 +2009,7 @@ class GlareOperation : public NodeOperation {
GPUShader *shader = context().get_shader("compositor_glare_mix");
GPU_shader_bind(shader);
GPU_shader_uniform_1f(shader, "mix_factor", node_storage(bnode()).mix);
GPU_shader_uniform_1f(shader, "strength", this->get_strength());
const Result &input_image = get_input("Image");
input_image.bind_as_texture(shader, "input_tx");
@@ -1899,7 +2032,7 @@ class GlareOperation : public NodeOperation {
void execute_mix_cpu(const Result &glare_result)
{
const float mix_factor = node_storage(bnode()).mix;
const float strength = this->get_strength();
const Result &input = get_input("Image");
@@ -1910,46 +2043,104 @@ class GlareOperation : public NodeOperation {
parallel_for(domain.size, [&](const int2 texel) {
/* Add 0.5 to evaluate the input sampler at the center of the pixel and divide by the input
* image size to get the relevant coordinates into the sampler's expected [0, 1] range.
* Make sure the input color is not negative to avoid a subtractive effect when mixing the
* glare.
*/
* Make sure the input color is not negative to avoid a subtractive effect when adding the
* glare. */
float2 normalized_coordinates = (float2(texel) + float2(0.5f)) / float2(input.domain().size);
float4 glare_color = glare_result.sample_bilinear_extended(normalized_coordinates);
float4 input_color = math::max(float4(0.0f), input.load_pixel<float4>(texel));
/* The mix factor is in the range [-1, 1] and linearly interpolate between the three values
* such that: 1 => Glare only. 0 => Input + Glare. -1 => Input only. We implement that as a
* weighted sum as follows. When the mix factor is 1, the glare weight should be 1 and the
* input weight should be 0. When the mix factor is -1, the glare weight should be 0 and
* the input weight should be 1. When the mix factor is 0, both weights should be 1. This
* can be expressed using the following compact min max expressions. */
float input_weight = 1.0f - math::max(0.0f, mix_factor);
float glare_weight = 1.0f + math::min(0.0f, mix_factor);
float3 highlights = input_weight * input_color.xyz() + glare_weight * glare_color.xyz();
float3 highlights = input_color.xyz() + glare_color.xyz() * strength;
output.store_pixel(texel, float4(highlights, input_color.w));
});
}
/* Writes the given input glare by adjusting it as needed and upsampling it using bilinear
* interpolation to match the size of the original input, allocating the glare output and writing
* the result to it. */
void write_glare_output(const Result &glare)
{
if (this->context().use_gpu()) {
this->write_glare_output_gpu(glare);
}
else {
this->write_glare_output_cpu(glare);
}
}
void write_glare_output_gpu(const Result &glare)
{
GPUShader *shader = this->context().get_shader("compositor_glare_write_glare_output");
GPU_shader_bind(shader);
GPU_shader_uniform_1f(shader, "strength", this->get_strength());
GPU_texture_filter_mode(glare, true);
GPU_texture_extend_mode(glare, GPU_SAMPLER_EXTEND_MODE_EXTEND);
glare.bind_as_texture(shader, "input_tx");
const Result &image_input = this->get_input("Image");
Result &output = this->get_result("Glare");
output.allocate_texture(image_input.domain());
output.bind_as_image(shader, "output_img");
compute_dispatch_threads_at_least(shader, output.domain().size);
GPU_shader_unbind();
output.unbind_as_image();
glare.unbind_as_texture();
}
void write_glare_output_cpu(const Result &glare)
{
const float strength = this->get_strength();
const Result &image_input = this->get_input("Image");
Result &output = this->get_result("Glare");
output.allocate_texture(image_input.domain());
const int2 size = output.domain().size;
parallel_for(size, [&](const int2 texel) {
float2 normalized_coordinates = (float2(texel) + float2(0.5f)) / float2(size);
float4 glare_value = glare.sample_bilinear_extended(normalized_coordinates);
float4 adjusted_glare_value = glare_value * strength;
output.store_pixel(texel, float4(adjusted_glare_value.xyz(), 1.0f));
});
}
/* -------
* Common.
* ------- */
/* As a performance optimization, the operation can compute the glare on a fraction of the input
* image size, which is what this method returns. */
int2 get_glare_size()
float get_threshold()
{
return compute_domain().size / get_quality_factor();
return math::max(0.0f, this->get_input("Threshold").get_single_value_default(1.0f));
}
float get_strength()
{
return math::max(0.0f, this->get_input("Strength").get_single_value_default(1.0f));
}
float get_size()
{
return math::clamp(this->get_input("Size").get_single_value_default(0.5f), 0.0f, 1.0f);
}
int get_number_of_iterations()
{
return node_storage(bnode()).iter;
return math::clamp(this->get_input("Iterations").get_single_value_default(3), 2, 5);
}
float get_color_modulation_factor()
float get_fade()
{
return node_storage(bnode()).colmod;
return math::clamp(this->get_input("Fade").get_single_value_default(0.9f), 0.75f, 1.0f);
}
float get_color_modulation()
{
return math::clamp(
this->get_input("Color Modulation").get_single_value_default(0.25f), 0.0f, 1.0f);
}
/* The glare node can compute the glare on a fraction of the input image size to improve
@@ -1985,6 +2176,7 @@ void register_node_type_cmp_glare()
cmp_node_type_base(&ntype, CMP_NODE_GLARE, "Glare", NODE_CLASS_OP_FILTER);
ntype.enum_name_legacy = "GLARE";
ntype.declare = file_ns::cmp_node_glare_declare;
ntype.updatefunc = file_ns::node_update;
ntype.draw_buttons = file_ns::node_composit_buts_glare;
ntype.initfunc = file_ns::node_composit_init_glare;
blender::bke::node_type_storage(