Realtime Compositor: Implement normalize node

This patch implements the normalize node for the realtime compositor.

Differential Revision: https://developer.blender.org/D16279

Reviewed By: Clement Foucault
This commit is contained in:
Omar Emara 2022-10-20 16:31:35 +02:00
parent 9d710374bd
commit 58e25f11ae
7 changed files with 153 additions and 2 deletions

View File

@ -70,6 +70,17 @@ float sum_luminance_squared_difference(Context &context,
* coefficients to compute the luminance. */
float maximum_luminance(Context &context, GPUTexture *texture, float3 luminance_coefficients);
/* Computes the maximum float of all pixels in the given float texture, limited to the given range.
* Values outside of the given range are ignored. If non of the pixel values are in the range, the
* lower bound of the range is returned. For instance, if the given range is [-10, 10] and the
* image contains the values {2, 5, 11}, the maximum will be 5, since 11 is outside of the range.
* This is particularly useful for Z Depth normalization, since Z Depth can contain near infinite
* values, so enforcing an upper bound is beneficial. */
float maximum_float_in_range(Context &context,
GPUTexture *texture,
float lower_bound,
float upper_bound);
/* --------------------------------------------------------------------
* Minimum Reductions.
*/
@ -78,4 +89,15 @@ float maximum_luminance(Context &context, GPUTexture *texture, float3 luminance_
* coefficients to compute the luminance. */
float minimum_luminance(Context &context, GPUTexture *texture, float3 luminance_coefficients);
/* Computes the minimum float of all pixels in the given float texture, limited to the given range.
* Values outside of the given range are ignored. If non of the pixel values are in the range, the
* upper bound of the range is returned. For instance, if the given range is [-10, 10] and the
* image contains the values {-11, 2, 5}, the minimum will be 2, since -11 is outside of the range.
* This is particularly useful for Z Depth normalization, since Z Depth can contain near infinite
* values, so enforcing a lower bound is beneficial. */
float minimum_float_in_range(Context &context,
GPUTexture *texture,
float lower_bound,
float upper_bound);
} // namespace blender::realtime_compositor

View File

@ -249,6 +249,25 @@ float maximum_luminance(Context &context, GPUTexture *texture, float3 luminance_
return maximum;
}
float maximum_float_in_range(Context &context,
GPUTexture *texture,
float lower_bound,
float upper_bound)
{
GPUShader *shader = context.shader_manager().get("compositor_maximum_float_in_range");
GPU_shader_bind(shader);
GPU_shader_uniform_1f(shader, "lower_bound", lower_bound);
GPU_shader_uniform_1f(shader, "upper_bound", upper_bound);
float *reduced_value = parallel_reduction_dispatch(context, texture, shader, GPU_R32F);
const float maximum = *reduced_value;
MEM_freeN(reduced_value);
GPU_shader_unbind();
return maximum;
}
/* --------------------------------------------------------------------
* Minimum Reductions.
*/
@ -268,4 +287,23 @@ float minimum_luminance(Context &context, GPUTexture *texture, float3 luminance_
return minimum;
}
float minimum_float_in_range(Context &context,
GPUTexture *texture,
float lower_bound,
float upper_bound)
{
GPUShader *shader = context.shader_manager().get("compositor_minimum_float_in_range");
GPU_shader_bind(shader);
GPU_shader_uniform_1f(shader, "lower_bound", lower_bound);
GPU_shader_uniform_1f(shader, "upper_bound", upper_bound);
float *reduced_value = parallel_reduction_dispatch(context, texture, shader, GPU_R32F);
const float minimum = *reduced_value;
MEM_freeN(reduced_value);
GPU_shader_unbind();
return minimum;
}
} // namespace blender::realtime_compositor

View File

@ -349,6 +349,7 @@ set(GLSL_SRC
shaders/compositor/compositor_morphological_distance_feather.glsl
shaders/compositor/compositor_morphological_distance_threshold.glsl
shaders/compositor/compositor_morphological_step.glsl
shaders/compositor/compositor_normalize.glsl
shaders/compositor/compositor_parallel_reduction.glsl
shaders/compositor/compositor_projector_lens_distortion.glsl
shaders/compositor/compositor_realize_on_domain.glsl
@ -633,6 +634,7 @@ set(SRC_SHADER_CREATE_INFOS
shaders/compositor/infos/compositor_morphological_distance_info.hh
shaders/compositor/infos/compositor_morphological_distance_threshold_info.hh
shaders/compositor/infos/compositor_morphological_step_info.hh
shaders/compositor/infos/compositor_normalize_info.hh
shaders/compositor/infos/compositor_parallel_reduction_info.hh
shaders/compositor/infos/compositor_projector_lens_distortion_info.hh
shaders/compositor/infos/compositor_realize_on_domain_info.hh

View File

@ -0,0 +1,10 @@
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
float value = texture_load(input_tx, texel).x;
float normalized_value = (value - minimum) * scale;
float clamped_value = clamp(normalized_value, 0.0, 1.0);
imageStore(output_img, texel, vec4(clamped_value));
}

View File

@ -0,0 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(compositor_normalize)
.local_group_size(16, 16)
.push_constant(Type::FLOAT, "minimum")
.push_constant(Type::FLOAT, "scale")
.sampler(0, ImageType::FLOAT_2D, "input_tx")
.image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img")
.compute_source("compositor_normalize.glsl")
.do_static_compilation(true);

View File

@ -108,6 +108,18 @@ GPU_SHADER_CREATE_INFO(compositor_maximum_luminance)
.define("REDUCE(lhs, rhs)", "max(lhs, rhs)")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_maximum_float_in_range)
.additional_info("compositor_parallel_reduction_shared")
.image(0, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img")
.push_constant(Type::FLOAT, "lower_bound")
.push_constant(Type::FLOAT, "upper_bound")
.define("TYPE", "float")
.define("IDENTITY", "vec4(lower_bound)")
.define("INITIALIZE(v)", "((v.x <= upper_bound) && (v.x >= lower_bound)) ? v.x : lower_bound")
.define("LOAD(value)", "value.x")
.define("REDUCE(lhs, rhs)", "((rhs > lhs) && (rhs <= upper_bound)) ? rhs : lhs")
.do_static_compilation(true);
/* --------------------------------------------------------------------
* Minimum Reductions.
*/
@ -123,3 +135,15 @@ GPU_SHADER_CREATE_INFO(compositor_minimum_luminance)
.define("LOAD(value)", "value.x")
.define("REDUCE(lhs, rhs)", "min(lhs, rhs)")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(compositor_minimum_float_in_range)
.additional_info("compositor_parallel_reduction_shared")
.image(0, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img")
.push_constant(Type::FLOAT, "lower_bound")
.push_constant(Type::FLOAT, "upper_bound")
.define("TYPE", "float")
.define("IDENTITY", "vec4(upper_bound)")
.define("INITIALIZE(v)", "((v.x <= upper_bound) && (v.x >= lower_bound)) ? v.x : upper_bound")
.define("LOAD(value)", "value.x")
.define("REDUCE(lhs, rhs)", "((rhs < lhs) && (rhs >= lower_bound)) ? rhs : lhs")
.do_static_compilation(true);

View File

@ -5,7 +5,9 @@
* \ingroup cmpnodes
*/
#include "COM_algorithm_parallel_reduction.hh"
#include "COM_node_operation.hh"
#include "COM_utilities.hh"
#include "node_composite_util.hh"
@ -15,19 +17,60 @@ namespace blender::nodes::node_composite_normalize_cc {
static void cmp_node_normalize_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Float>(N_("Value")).default_value(1.0f).min(0.0f).max(1.0f);
b.add_input<decl::Float>(N_("Value"))
.default_value(1.0f)
.min(0.0f)
.max(1.0f)
.compositor_domain_priority(0);
b.add_output<decl::Float>(N_("Value"));
}
using namespace blender::realtime_compositor;
class NormalizeOperation : public NodeOperation {
private:
/* The normalize operation is specifically designed to normalize Z Depth information. But since Z
* Depth can contain near infinite values, normalization is limited to [-range_, range], meaning
* that values outside of that range will be ignored when computing the maximum and minimum for
* normalization and will eventually be 0 or 1 if they are less than or larger than the range
* respectively. */
constexpr static float range_ = 10000.0f;
public:
using NodeOperation::NodeOperation;
void execute() override
{
get_input("Value").pass_through(get_result("Value"));
Result &input_image = get_input("Value");
Result &output_image = get_result("Value");
if (input_image.is_single_value()) {
input_image.pass_through(output_image);
return;
}
const float maximum = maximum_float_in_range(
context(), input_image.texture(), -range_, range_);
const float minimum = minimum_float_in_range(
context(), input_image.texture(), -range_, range_);
const float scale = (maximum != minimum) ? (1.0f / (maximum - minimum)) : 0.0f;
GPUShader *shader = shader_manager().get("compositor_normalize");
GPU_shader_bind(shader);
GPU_shader_uniform_1f(shader, "minimum", minimum);
GPU_shader_uniform_1f(shader, "scale", scale);
input_image.bind_as_texture(shader, "input_tx");
const Domain domain = compute_domain();
output_image.allocate_texture(domain);
output_image.bind_as_image(shader, "output_img");
compute_dispatch_threads_at_least(shader, domain.size);
GPU_shader_unbind();
output_image.unbind_as_image();
input_image.unbind_as_texture();
}
};