Blender V4.5
node_composite_displace.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2006 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BLI_math_vector.hh"
11
12#include "GPU_shader.hh"
13#include "GPU_texture.hh"
14
15#include "COM_node_operation.hh"
16#include "COM_utilities.hh"
17
19
20/* **************** Displace ******************** */
21
23
25{
26 b.add_input<decl::Color>("Image")
27 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
28 .compositor_domain_priority(0);
29 b.add_input<decl::Vector>("Vector")
30 .dimensions(2)
31 .default_value({1.0f, 1.0f})
32 .min(0.0f)
33 .max(1.0f)
34 .subtype(PROP_TRANSLATION)
35 .compositor_domain_priority(1);
36 b.add_input<decl::Float>("X Scale")
37 .default_value(0.0f)
38 .min(-1000.0f)
39 .max(1000.0f)
41 b.add_input<decl::Float>("Y Scale")
42 .default_value(0.0f)
43 .min(-1000.0f)
44 .max(1000.0f)
46 b.add_output<decl::Color>("Image");
47}
48
49using namespace blender::compositor;
50
52 public:
54
55 void execute() override
56 {
57 if (this->is_identity()) {
58 const Result &input = this->get_input("Image");
59 Result &output = this->get_result("Image");
60 output.share_data(input);
61 return;
62 }
63
64 if (this->context().use_gpu()) {
65 this->execute_gpu();
66 }
67 else {
68 this->execute_cpu();
69 }
70 }
71
73 {
74 GPUShader *shader = context().get_shader("compositor_displace");
75 GPU_shader_bind(shader);
76
77 const Result &input_image = get_input("Image");
78 GPU_texture_mipmap_mode(input_image, true, true);
79 GPU_texture_anisotropic_filter(input_image, true);
81 input_image.bind_as_texture(shader, "input_tx");
82
83 const Result &input_displacement = get_input("Vector");
84 input_displacement.bind_as_texture(shader, "displacement_tx");
85 const Result &input_x_scale = get_input("X Scale");
86 input_x_scale.bind_as_texture(shader, "x_scale_tx");
87 const Result &input_y_scale = get_input("Y Scale");
88 input_y_scale.bind_as_texture(shader, "y_scale_tx");
89
90 const Domain domain = compute_domain();
91 Result &output_image = get_result("Image");
92 output_image.allocate_texture(domain);
93 output_image.bind_as_image(shader, "output_img");
94
96
97 input_image.unbind_as_texture();
98 input_displacement.unbind_as_texture();
99 input_x_scale.unbind_as_texture();
100 input_y_scale.unbind_as_texture();
101 output_image.unbind_as_image();
103 }
104
106 {
107 const Result &image = get_input("Image");
108 const Result &input_displacement = get_input("Vector");
109 const Result &x_scale = get_input("X Scale");
110 const Result &y_scale = get_input("Y Scale");
111
112 const Domain domain = compute_domain();
113 Result &output = get_result("Image");
114 output.allocate_texture(domain);
115
116 /* In order to perform EWA sampling, we need to compute the partial derivative of the displaced
117 * coordinates along the x and y directions using a finite difference approximation. But in
118 * order to avoid loading multiple neighboring displacement values for each pixel, we operate
119 * on the image in 2x2 blocks of pixels, where the derivatives are computed horizontally and
120 * vertically across the 2x2 block such that odd texels use a forward finite difference
121 * equation while even invocations use a backward finite difference equation. */
122 const int2 size = domain.size;
123 parallel_for(math::divide_ceil(size, int2(2)), [&](const int2 base_texel) {
124 const int x = base_texel.x * 2;
125 const int y = base_texel.y * 2;
126
127 const int2 lower_left_texel = int2(x, y);
128 const int2 lower_right_texel = int2(x + 1, y);
129 const int2 upper_left_texel = int2(x, y + 1);
130 const int2 upper_right_texel = int2(x + 1, y + 1);
131
132 auto compute_coordinates = [&](const int2 &texel) -> float2 {
133 /* Add 0.5 to evaluate the sampler at the center of the pixel and divide by the image size
134 * to get the coordinates into the sampler's expected [0, 1] range. */
135 float2 coordinates = (float2(texel) + float2(0.5f)) / float2(size);
136
137 /* Note that the input displacement is in pixel space, so divide by the input size to
138 * transform it into the normalized sampler space. */
139 float2 scale = float2(x_scale.load_pixel_extended<float, true>(texel),
140 y_scale.load_pixel_extended<float, true>(texel));
141 float2 displacement = input_displacement.load_pixel_extended<float3, true>(texel).xy() *
142 scale / float2(size);
143 return coordinates - displacement;
144 };
145
146 const float2 lower_left_coordinates = compute_coordinates(lower_left_texel);
147 const float2 lower_right_coordinates = compute_coordinates(lower_right_texel);
148 const float2 upper_left_coordinates = compute_coordinates(upper_left_texel);
149 const float2 upper_right_coordinates = compute_coordinates(upper_right_texel);
150
151 /* Compute the partial derivatives using finite difference. Divide by the input size since
152 * sample_ewa_zero assumes derivatives with respect to texel coordinates. */
153 const float2 lower_x_gradient = (lower_right_coordinates - lower_left_coordinates) / size.x;
154 const float2 left_y_gradient = (upper_left_coordinates - lower_left_coordinates) / size.y;
155 const float2 right_y_gradient = (upper_right_coordinates - lower_right_coordinates) / size.y;
156 const float2 upper_x_gradient = (upper_right_coordinates - upper_left_coordinates) / size.x;
157
158 /* Computes one of the 2x2 pixels given its texel location, coordinates, and gradients. */
159 auto compute_pixel = [&](const int2 &texel,
160 const float2 &coordinates,
161 const float2 &x_gradient,
162 const float2 &y_gradient) {
163 /* Sample the input using the displaced coordinates passing in the computed gradients in
164 * order to utilize the anisotropic filtering capabilities of the sampler. */
165 float4 displaced_color = image.sample_ewa_zero(coordinates, x_gradient, y_gradient);
166 output.store_pixel(texel, displaced_color);
167 };
168
169 /* Compute each of the pixels in the 2x2 block, making sure to exempt out of bounds right
170 * and upper pixels. */
171 compute_pixel(lower_left_texel, lower_left_coordinates, lower_x_gradient, left_y_gradient);
172 if (lower_right_texel.x != size.x) {
173 compute_pixel(
174 lower_right_texel, lower_right_coordinates, lower_x_gradient, right_y_gradient);
175 }
176 if (upper_left_texel.y != size.y) {
177 compute_pixel(upper_left_texel, upper_left_coordinates, upper_x_gradient, left_y_gradient);
178 }
179 if (upper_right_texel.x != size.x && upper_right_texel.y != size.y) {
180 compute_pixel(
181 upper_right_texel, upper_right_coordinates, upper_x_gradient, right_y_gradient);
182 }
183 });
184 }
185
187 {
188 const Result &input_image = get_input("Image");
189 if (input_image.is_single_value()) {
190 return true;
191 }
192
193 const Result &input_displacement = get_input("Vector");
194 if (input_displacement.is_single_value() &&
195 math::is_zero(input_displacement.get_single_value<float3>().xy()))
196 {
197 return true;
198 }
199
200 const Result &input_x_scale = get_input("X Scale");
201 const Result &input_y_scale = get_input("Y Scale");
202 if (input_x_scale.is_single_value() && input_x_scale.get_single_value<float>() == 0.0f &&
203 input_y_scale.is_single_value() && input_y_scale.get_single_value<float>() == 0.0f)
204 {
205 return true;
206 }
207
208 return false;
209 }
210};
211
213{
214 return new DisplaceOperation(context, node);
215}
216
217} // namespace blender::nodes::node_composite_displace_cc
218
220{
222
223 static blender::bke::bNodeType ntype;
224
225 cmp_node_type_base(&ntype, "CompositorNodeDisplace", CMP_NODE_DISPLACE);
226 ntype.ui_name = "Displace";
227 ntype.ui_description = "Displace pixel position using an offset vector";
228 ntype.enum_name_legacy = "DISPLACE";
230 ntype.declare = file_ns::cmp_node_displace_declare;
231 ntype.get_compositor_operation = file_ns::get_compositor_operation;
232
234}
#define NODE_CLASS_DISTORT
Definition BKE_node.hh:441
#define CMP_NODE_DISPLACE
void GPU_shader_bind(GPUShader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
void GPU_shader_unbind()
void GPU_texture_anisotropic_filter(GPUTexture *texture, bool use_aniso)
void GPU_texture_extend_mode(GPUTexture *texture, GPUSamplerExtendMode extend_mode)
@ GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER
void GPU_texture_mipmap_mode(GPUTexture *texture, bool use_mipmap, bool use_filter)
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ PROP_TRANSLATION
Definition RNA_types.hh:249
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
GPUShader * get_shader(const char *info_name, ResultPrecision precision)
NodeOperation(Context &context, DNode node)
Result & get_result(StringRef identifier)
Definition operation.cc:39
Result & get_input(StringRef identifier) const
Definition operation.cc:138
virtual Domain compute_domain()
Definition operation.cc:56
void share_data(const Result &source)
Definition result.cc:401
void allocate_texture(Domain domain, bool from_pool=true)
Definition result.cc:309
void unbind_as_texture() const
Definition result.cc:389
void bind_as_texture(GPUShader *shader, const char *texture_name) const
Definition result.cc:365
T load_pixel_extended(const int2 &texel) const
void bind_as_image(GPUShader *shader, const char *image_name, bool read=false) const
Definition result.cc:376
void unbind_as_image() const
Definition result.cc:395
float4 sample_ewa_zero(const float2 &coordinates, const float2 &x_gradient, const float2 &y_gradient) const
bool is_single_value() const
Definition result.cc:625
const T & get_single_value() const
#define input
#define output
void node_register_type(bNodeType &ntype)
Definition node.cc:2748
void compute_dispatch_threads_at_least(GPUShader *shader, int2 threads_range, int2 local_size=int2(16))
Definition utilities.cc:170
void parallel_for(const int2 range, const Function &function)
VecBase< T, Size > divide_ceil(const VecBase< T, Size > &a, const VecBase< T, Size > &b)
bool is_zero(const T &a)
static void cmp_node_displace_declare(NodeDeclarationBuilder &b)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
VecBase< float, 3 > float3
static void register_node_type_cmp_displace()
void cmp_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
#define min(a, b)
Definition sort.cc:36
Defines a node type.
Definition BKE_node.hh:226
std::string ui_description
Definition BKE_node.hh:232
NodeGetCompositorOperationFunction get_compositor_operation
Definition BKE_node.hh:336
const char * enum_name_legacy
Definition BKE_node.hh:235
NodeDeclareFunction declare
Definition BKE_node.hh:355
int xy[2]
Definition wm_draw.cc:174