Realtime Compositor: Implement Displace node
This patch implements the Displace node for the realtime compositor. This is different from the CPU implementation in that it relies on the approximate EWA filtering provided by the driver's implementation of anisotropic filtering to displace the texture. This is easier and faster than implementing EWA manually in the shader and has identical quality where it matters. Pull Request: https://projects.blender.org/blender/blender/pulls/106681
This commit is contained in:
@@ -101,6 +101,7 @@ set(GLSL_SRC
|
||||
shaders/compositor_convert.glsl
|
||||
shaders/compositor_despeckle.glsl
|
||||
shaders/compositor_directional_blur.glsl
|
||||
shaders/compositor_displace.glsl
|
||||
shaders/compositor_edge_filter.glsl
|
||||
shaders/compositor_ellipse_mask.glsl
|
||||
shaders/compositor_filter.glsl
|
||||
@@ -205,6 +206,7 @@ set(SRC_SHADER_CREATE_INFOS
|
||||
shaders/infos/compositor_convert_info.hh
|
||||
shaders/infos/compositor_despeckle_info.hh
|
||||
shaders/infos/compositor_directional_blur_info.hh
|
||||
shaders/infos/compositor_displace_info.hh
|
||||
shaders/infos/compositor_edge_filter_info.hh
|
||||
shaders/infos/compositor_ellipse_mask_info.hh
|
||||
shaders/infos/compositor_filter_info.hh
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
|
||||
|
||||
/* A shared table that stores the displaced coordinates of all pixels in the work group. This is
|
||||
* necessary to avoid recomputing displaced coordinates when computing the gradients necessary for
|
||||
* anisotropic filtering, see the implementation for more information. */
|
||||
shared vec2 displaced_coordinates_table[gl_WorkGroupSize.x][gl_WorkGroupSize.y];
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
|
||||
ivec2 input_size = texture_size(input_tx);
|
||||
|
||||
/* 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. */
|
||||
vec2 coordinates = (vec2(texel) + vec2(0.5)) / vec2(input_size);
|
||||
|
||||
/* Note that the input displacement is in pixel space, so divide by the input size to transform
|
||||
* it into the normalized sampler space. */
|
||||
vec2 scale = vec2(texture_load(x_scale_tx, texel).x, texture_load(y_scale_tx, texel).x);
|
||||
vec2 displacement = texture_load(displacement_tx, texel).xy * scale / vec2(input_size);
|
||||
vec2 displaced_coordinates = coordinates - displacement;
|
||||
|
||||
/* Store the displaced coordinates into the shared table and issue a barrier to later compute the
|
||||
* gradients from the table. */
|
||||
ivec2 table_index = ivec2(gl_LocalInvocationID);
|
||||
displaced_coordinates_table[table_index.x][table_index.y] = displaced_coordinates;
|
||||
barrier();
|
||||
|
||||
/* Compute the partial derivative of the displaced coordinates along the x direction using a
|
||||
* finite difference approximation. Odd invocations use a forward finite difference equation
|
||||
* while even invocations use a backward finite difference equation. This is done such that
|
||||
* invocations at the edges of the work group wouldn't need access to pixels that are outside of
|
||||
* the work group.
|
||||
*
|
||||
* The x_step value is 1 for even invocations and when added to the x table index and multiplied
|
||||
* by the result yields a standard forward finite difference equation. The x_step value is -1 for
|
||||
* odd invocations and when added to the x table index and multiplied by the result yields a
|
||||
* standard backward finite difference equation, because multiplication by -1 flips the order of
|
||||
* subtraction. */
|
||||
int x_step = (table_index.x % 2) * -2 + 1;
|
||||
vec2 x_neighbour = displaced_coordinates_table[table_index.x + x_step][table_index.y];
|
||||
vec2 x_gradient = (x_neighbour - displaced_coordinates) * x_step;
|
||||
|
||||
/* Compute the partial derivative of the displaced coordinates along the y direction using a
|
||||
* finite difference approximation. See the previous code section for more information. */
|
||||
int y_step = (table_index.x % 2) * 2 - 1;
|
||||
vec2 y_neighbour = displaced_coordinates_table[table_index.x][table_index.y + y_step];
|
||||
vec2 y_gradient = (y_neighbour - displaced_coordinates) * y_step;
|
||||
|
||||
/* Sample the input using the displaced coordinates passing in the computed gradients in order to
|
||||
* utilize the anisotropic filtering capabilities of the sampler. */
|
||||
vec4 displaced_color = textureGrad(input_tx, displaced_coordinates, x_gradient, y_gradient);
|
||||
|
||||
imageStore(output_img, texel, displaced_color);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "gpu_shader_create_info.hh"
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_displace)
|
||||
.local_group_size(16, 16)
|
||||
.sampler(0, ImageType::FLOAT_2D, "input_tx")
|
||||
.sampler(1, ImageType::FLOAT_2D, "displacement_tx")
|
||||
.sampler(2, ImageType::FLOAT_2D, "x_scale_tx")
|
||||
.sampler(3, ImageType::FLOAT_2D, "y_scale_tx")
|
||||
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img")
|
||||
.compute_source("compositor_displace.glsl")
|
||||
.do_static_compilation(true);
|
||||
@@ -5,6 +5,11 @@
|
||||
* \ingroup cmpnodes
|
||||
*/
|
||||
|
||||
#include "BLI_math_vector.hh"
|
||||
|
||||
#include "GPU_shader.h"
|
||||
#include "GPU_texture.h"
|
||||
|
||||
#include "BLT_translation.h"
|
||||
|
||||
#include "COM_node_operation.hh"
|
||||
@@ -17,14 +22,25 @@ namespace blender::nodes::node_composite_displace_cc {
|
||||
|
||||
static void cmp_node_displace_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f});
|
||||
b.add_input<decl::Color>(N_("Image"))
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.compositor_domain_priority(0);
|
||||
b.add_input<decl::Vector>(N_("Vector"))
|
||||
.default_value({1.0f, 1.0f, 1.0f})
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.subtype(PROP_TRANSLATION);
|
||||
b.add_input<decl::Float>(N_("X Scale")).default_value(0.0f).min(-1000.0f).max(1000.0f);
|
||||
b.add_input<decl::Float>(N_("Y Scale")).default_value(0.0f).min(-1000.0f).max(1000.0f);
|
||||
.subtype(PROP_TRANSLATION)
|
||||
.compositor_domain_priority(1);
|
||||
b.add_input<decl::Float>(N_("X Scale"))
|
||||
.default_value(0.0f)
|
||||
.min(-1000.0f)
|
||||
.max(1000.0f)
|
||||
.compositor_domain_priority(2);
|
||||
b.add_input<decl::Float>(N_("Y Scale"))
|
||||
.default_value(0.0f)
|
||||
.min(-1000.0f)
|
||||
.max(1000.0f)
|
||||
.compositor_domain_priority(3);
|
||||
b.add_output<decl::Color>(N_("Image"));
|
||||
}
|
||||
|
||||
@@ -36,8 +52,63 @@ class DisplaceOperation : public NodeOperation {
|
||||
|
||||
void execute() override
|
||||
{
|
||||
get_input("Image").pass_through(get_result("Image"));
|
||||
context().set_info_message("Viewport compositor setup not fully supported");
|
||||
if (is_identity()) {
|
||||
get_input("Image").pass_through(get_result("Image"));
|
||||
return;
|
||||
}
|
||||
|
||||
GPUShader *shader = shader_manager().get("compositor_displace");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
const Result &input_image = get_input("Image");
|
||||
GPU_texture_mipmap_mode(input_image.texture(), true, true);
|
||||
GPU_texture_anisotropic_filter(input_image.texture(), true);
|
||||
GPU_texture_extend_mode(input_image.texture(), GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER);
|
||||
input_image.bind_as_texture(shader, "input_tx");
|
||||
|
||||
const Result &input_displacement = get_input("Vector");
|
||||
input_displacement.bind_as_texture(shader, "displacement_tx");
|
||||
const Result &input_x_scale = get_input("X Scale");
|
||||
input_x_scale.bind_as_texture(shader, "x_scale_tx");
|
||||
const Result &input_y_scale = get_input("Y Scale");
|
||||
input_y_scale.bind_as_texture(shader, "y_scale_tx");
|
||||
|
||||
const Domain domain = compute_domain();
|
||||
Result &output_image = get_result("Image");
|
||||
output_image.allocate_texture(domain);
|
||||
output_image.bind_as_image(shader, "output_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.size);
|
||||
|
||||
input_image.unbind_as_texture();
|
||||
input_displacement.unbind_as_texture();
|
||||
input_x_scale.unbind_as_texture();
|
||||
input_y_scale.unbind_as_texture();
|
||||
output_image.unbind_as_image();
|
||||
GPU_shader_unbind();
|
||||
}
|
||||
|
||||
bool is_identity()
|
||||
{
|
||||
const Result &input_image = get_input("Image");
|
||||
if (input_image.is_single_value()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const Result &input_displacement = get_input("Vector");
|
||||
if (input_displacement.is_single_value() &&
|
||||
math::is_zero(input_displacement.get_vector_value())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const Result &input_x_scale = get_input("X Scale");
|
||||
const Result &input_y_scale = get_input("Y Scale");
|
||||
if (input_x_scale.is_single_value() && input_x_scale.get_float_value() == 0.0f &&
|
||||
input_y_scale.is_single_value() && input_y_scale.get_float_value() == 0.0f) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -57,8 +128,6 @@ void register_node_type_cmp_displace()
|
||||
cmp_node_type_base(&ntype, CMP_NODE_DISPLACE, "Displace", NODE_CLASS_DISTORT);
|
||||
ntype.declare = file_ns::cmp_node_displace_declare;
|
||||
ntype.get_compositor_operation = file_ns::get_compositor_operation;
|
||||
ntype.realtime_compositor_unsupported_message = N_(
|
||||
"Node not supported in the Viewport compositor");
|
||||
|
||||
nodeRegisterType(&ntype);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user