Blender V5.0
node_composite_planetrackdeform.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2013 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BLI_array.hh"
12#include "BLI_string_utf8.h"
13
14#include "DNA_defaults.h"
15#include "DNA_movieclip_types.h"
16#include "DNA_tracking_types.h"
17
18#include "BKE_context.hh"
19#include "BKE_lib_id.hh"
20#include "BKE_movieclip.h"
21#include "BKE_tracking.h"
22
23#include "RNA_access.hh"
24#include "RNA_prototypes.hh"
25
26#include "UI_interface.hh"
28#include "UI_resources.hh"
29
30#include "GPU_shader.hh"
31#include "GPU_texture.hh"
32#include "GPU_uniform_buffer.hh"
33
34#include "COM_algorithm_smaa.hh"
35#include "COM_node_operation.hh"
36#include "COM_utilities.hh"
37
39
41
43
45{
46 b.use_custom_socket_order();
47 b.allow_any_socket_order();
48
49 b.add_input<decl::Color>("Image")
50 .hide_value()
51 .compositor_realization_mode(CompositorInputRealizationMode::Transforms)
52 .structure_type(StructureType::Dynamic);
53 b.add_output<decl::Color>("Image").structure_type(StructureType::Dynamic).align_with_previous();
54 b.add_output<decl::Float>("Plane").structure_type(StructureType::Dynamic);
55
56 b.add_layout([](uiLayout *layout, bContext *C, PointerRNA *ptr) {
57 bNode *node = ptr->data_as<bNode>();
58
59 uiTemplateID(layout, C, ptr, "clip", nullptr, "CLIP_OT_open", nullptr);
60
61 if (node->id) {
62 MovieClip *clip = reinterpret_cast<MovieClip *>(node->id);
63 MovieTracking *tracking = &clip->tracking;
64 MovieTrackingObject *tracking_object;
66 &clip->id, &RNA_MovieTracking, tracking);
67
68 uiLayout *col = &layout->column(false);
69 col->prop_search(ptr, "tracking_object", &tracking_ptr, "objects", "", ICON_OBJECT_DATA);
70
71 tracking_object = BKE_tracking_object_get_named(tracking,
72 node_storage(*node).tracking_object);
73 if (tracking_object) {
75 &clip->id, &RNA_MovieTrackingObject, tracking_object);
76
77 col->prop_search(ptr, "plane_track_name", &object_ptr, "plane_tracks", "", ICON_ANIM_DATA);
78 }
79 else {
80 layout->prop(ptr, "plane_track_name", UI_ITEM_NONE, "", ICON_ANIM_DATA);
81 }
82 }
83 });
84
85 PanelDeclarationBuilder &motion_blur_panel = b.add_panel("Motion Blur").default_closed(true);
86 motion_blur_panel.add_input<decl::Bool>("Motion Blur")
87 .default_value(false)
88 .panel_toggle()
89 .description("Use multi-sampled motion blur of the plane");
90 motion_blur_panel.add_input<decl::Int>("Samples", "Motion Blur Samples")
91 .default_value(16)
92 .min(1)
93 .max(64)
94 .description("Number of motion blur samples");
95 motion_blur_panel.add_input<decl::Float>("Shutter", "Motion Blur Shutter")
96 .default_value(0.5f)
97 .subtype(PROP_FACTOR)
98 .min(0.0f)
99 .max(1.0f)
100 .description("Exposure for motion blur as a factor of FPS");
101}
102
103static void init(const bContext *C, PointerRNA *ptr)
104{
105 bNode *node = (bNode *)ptr->data;
106
108 node->storage = data;
109
110 const Scene *scene = CTX_data_scene(C);
111 if (scene->clip) {
112 MovieClip *clip = scene->clip;
113 MovieTracking *tracking = &clip->tracking;
114
115 node->id = &clip->id;
116 id_us_plus(&clip->id);
117
118 const MovieTrackingObject *tracking_object = BKE_tracking_object_get_active(tracking);
119 STRNCPY_UTF8(data->tracking_object, tracking_object->name);
120
121 if (tracking_object->active_plane_track) {
122 STRNCPY_UTF8(data->plane_track_name, tracking_object->active_plane_track->name);
123 }
124 }
125}
126
127using namespace blender::compositor;
128
130 public:
132
133 void execute() override
134 {
136
137 const Result &input_image = get_input("Image");
138 if (input_image.is_single_value() || !plane_track) {
139 Result &output_image = get_result("Image");
140 if (output_image.should_compute()) {
141 output_image.share_data(input_image);
142 }
143
144 Result &output_mask = get_result("Plane");
145 if (output_mask.should_compute()) {
146 output_mask.allocate_single_value();
147 output_mask.set_single_value(1.0f);
148 }
149 return;
150 }
151
152 const Array<float4x4> homography_matrices = compute_homography_matrices(plane_track);
153
154 if (this->context().use_gpu()) {
155 this->execute_gpu(homography_matrices);
156 }
157 else {
158 this->execute_cpu(homography_matrices);
159 }
160 }
161
162 void execute_gpu(const Array<float4x4> homography_matrices)
163 {
164 gpu::UniformBuf *homography_matrices_buffer = GPU_uniformbuf_create_ex(
165 homography_matrices.size() * sizeof(float4x4),
166 homography_matrices.data(),
167 "Plane Track Deform Homography Matrices");
168
169 Result plane_mask = this->compute_plane_mask_gpu(homography_matrices,
170 homography_matrices_buffer);
171 Result anti_aliased_plane_mask = this->context().create_result(ResultType::Float);
172 smaa(context(), plane_mask, anti_aliased_plane_mask);
173 plane_mask.release();
174
175 Result &output_image = get_result("Image");
176 if (output_image.should_compute()) {
177 this->compute_plane_gpu(
178 homography_matrices, homography_matrices_buffer, anti_aliased_plane_mask);
179 }
180
181 GPU_uniformbuf_free(homography_matrices_buffer);
182
183 Result &output_mask = get_result("Plane");
184 if (output_mask.should_compute()) {
185 output_mask.steal_data(anti_aliased_plane_mask);
186 }
187 else {
188 anti_aliased_plane_mask.release();
189 }
190 }
191
192 void compute_plane_gpu(const Array<float4x4> &homography_matrices,
193 gpu::UniformBuf *homography_matrices_buffer,
194 Result &plane_mask)
195 {
196 gpu::Shader *shader = context().get_shader("compositor_plane_deform_motion_blur");
197 GPU_shader_bind(shader);
198
199 GPU_shader_uniform_1i(shader, "number_of_motion_blur_samples", homography_matrices.size());
200
201 const int ubo_location = GPU_shader_get_ubo_binding(shader, "homography_matrices");
202 GPU_uniformbuf_bind(homography_matrices_buffer, ubo_location);
203
204 Result &input_image = get_input("Image");
205 GPU_texture_mipmap_mode(input_image, true, true);
206 GPU_texture_anisotropic_filter(input_image, true);
207 /* We actually need zero boundary conditions, but we sampled using extended boundaries then
208 * multiply by the anti-aliased plane mask to get better quality anti-aliased planes. */
210 input_image.bind_as_texture(shader, "input_tx");
211
212 plane_mask.bind_as_texture(shader, "mask_tx");
213
214 const Domain domain = compute_domain();
215 Result &output_image = get_result("Image");
216 output_image.allocate_texture(domain);
217 output_image.bind_as_image(shader, "output_img");
218
220
221 input_image.unbind_as_texture();
222 plane_mask.unbind_as_texture();
223 output_image.unbind_as_image();
224 GPU_uniformbuf_unbind(homography_matrices_buffer);
226 }
227
229 gpu::UniformBuf *homography_matrices_buffer)
230 {
231 gpu::Shader *shader = context().get_shader("compositor_plane_deform_motion_blur_mask");
232 GPU_shader_bind(shader);
233
234 GPU_shader_uniform_1i(shader, "number_of_motion_blur_samples", homography_matrices.size());
235
236 const int ubo_location = GPU_shader_get_ubo_binding(shader, "homography_matrices");
237 GPU_uniformbuf_bind(homography_matrices_buffer, ubo_location);
238
239 const Domain domain = compute_domain();
241 plane_mask.allocate_texture(domain);
242 plane_mask.bind_as_image(shader, "mask_img");
243
245
246 plane_mask.unbind_as_image();
247 GPU_uniformbuf_unbind(homography_matrices_buffer);
249
250 return plane_mask;
251 }
252
253 void execute_cpu(const Array<float4x4> homography_matrices)
254 {
255 Result plane_mask = this->compute_plane_mask_cpu(homography_matrices);
256 Result anti_aliased_plane_mask = this->context().create_result(ResultType::Float);
257 smaa(context(), plane_mask, anti_aliased_plane_mask);
258 plane_mask.release();
259
260 Result &output_image = get_result("Image");
261 if (output_image.should_compute()) {
262 this->compute_plane_cpu(homography_matrices, anti_aliased_plane_mask);
263 }
264
265 Result &output_mask = get_result("Plane");
266 if (output_mask.should_compute()) {
267 output_mask.steal_data(anti_aliased_plane_mask);
268 }
269 else {
270 anti_aliased_plane_mask.release();
271 }
272 }
273
274 void compute_plane_cpu(const Array<float4x4> &homography_matrices, Result &plane_mask)
275 {
276 Result &input = get_input("Image");
277
278 const Domain domain = compute_domain();
279 Result &output = get_result("Image");
280 output.allocate_texture(domain);
281
282 const int2 size = domain.size;
283 parallel_for(size, [&](const int2 texel) {
284 float2 coordinates = (float2(texel) + float2(0.5f)) / float2(size);
285
286 float4 accumulated_color = float4(0.0f);
287 for (const float4x4 &homography_matrix : homography_matrices) {
288 float3 transformed_coordinates = float3x3(homography_matrix) * float3(coordinates, 1.0f);
289 /* Point is at infinity and will be zero when sampled, so early exit. */
290 if (transformed_coordinates.z == 0.0f) {
291 continue;
292 }
293 float2 projected_coordinates = transformed_coordinates.xy() / transformed_coordinates.z;
294
295 /* The derivatives of the projected coordinates with respect to x and y are the first and
296 * second columns respectively, divided by the z projection factor as can be shown by
297 * differentiating the above matrix multiplication with respect to x and y. Divide by the
298 * output size since sample_ewa assumes derivatives with respect to texel coordinates. */
299 float2 x_gradient = (homography_matrix[0].xy() / transformed_coordinates.z) / size.x;
300 float2 y_gradient = (homography_matrix[1].xy() / transformed_coordinates.z) / size.y;
301
302 float4 sampled_color = input.sample_ewa_extended(
303 projected_coordinates, x_gradient, y_gradient);
304 accumulated_color += sampled_color;
305 }
306
307 accumulated_color /= homography_matrices.size();
308
309 /* Premultiply the mask value as an alpha. */
310 float4 plane_color = accumulated_color * plane_mask.load_pixel<float>(texel);
311
312 output.store_pixel(texel, plane_color);
313 });
314 }
315
317 {
318 const Domain domain = compute_domain();
320 plane_mask.allocate_texture(domain);
321
322 const int2 size = domain.size;
323 parallel_for(size, [&](const int2 texel) {
324 float2 coordinates = (float2(texel) + float2(0.5f)) / float2(size);
325
326 float accumulated_mask = 0.0f;
327 for (const float4x4 &homography_matrix : homography_matrices) {
328 float3 transformed_coordinates = float3x3(homography_matrix) * float3(coordinates, 1.0f);
329 /* Point is at infinity and will be zero when sampled, so early exit. */
330 if (transformed_coordinates.z == 0.0f) {
331 continue;
332 }
333 float2 projected_coordinates = transformed_coordinates.xy() / transformed_coordinates.z;
334
335 bool is_inside_plane = projected_coordinates.x >= 0.0f &&
336 projected_coordinates.y >= 0.0f &&
337 projected_coordinates.x <= 1.0f && projected_coordinates.y <= 1.0f;
338 accumulated_mask += is_inside_plane ? 1.0f : 0.0f;
339 }
340
341 accumulated_mask /= homography_matrices.size();
342
343 plane_mask.store_pixel(texel, accumulated_mask);
344 });
345
346 return plane_mask;
347 }
348
350 {
352
353 Result &input_image = get_input("Image");
354 if (input_image.is_single_value() || !plane_track) {
355 return input_image.domain();
356 }
357
358 return Domain(get_movie_clip_size());
359 }
360
362 {
363 /* We evaluate at the frames in the range [frame - shutter, frame + shutter], if no motion blur
364 * is enabled or the motion blur samples is set to 1, we just evaluate at the current frame. */
365 const int samples = this->get_motion_blur_samples();
366 const float shutter = samples != 1 ? this->get_motion_blur_shutter() : 0.0f;
367 const float start_frame = context().get_frame_number() - shutter;
368 const float frame_step = (shutter * 2.0f) / samples;
369
370 Array<float4x4> matrices(samples);
371 for (int i = 0; i < samples; i++) {
372 const float frame = start_frame + frame_step * i;
373 const float clip_frame = BKE_movieclip_remap_scene_to_clip_frame(get_movie_clip(), frame);
374
375 float corners[4][2];
376 BKE_tracking_plane_marker_get_subframe_corners(plane_track, clip_frame, corners);
377
378 /* Compute a 2D projection matrix that projects from the corners of the image in normalized
379 * coordinates into the corners of the tracking plane. */
380 float3x3 homography_matrix;
381 float identity_corners[4][2] = {{0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f}};
383 corners, identity_corners, homography_matrix.ptr());
384
385 /* Store in a 4x4 matrix due to the alignment requirements of GPU uniform buffers. */
386 matrices[i] = float4x4(homography_matrix);
387 }
388
389 return matrices;
390 }
391
393 {
394 MovieClip *movie_clip = get_movie_clip();
395
396 if (!movie_clip) {
397 return nullptr;
398 }
399
401 &movie_clip->tracking, node_storage(bnode()).tracking_object);
402
403 if (!tracking_object) {
404 return nullptr;
405 }
406
408 node_storage(bnode()).plane_track_name);
409 }
410
412 {
414 BKE_movieclip_user_set_frame(&user, context().get_frame_number());
415
416 int2 size;
418 return size;
419 }
420
422 {
423 const int samples = math::clamp(
424 this->get_input("Motion Blur Samples").get_single_value_default(16), 1, 64);
425 return this->use_motion_blur() ? samples : 1;
426 }
427
429 {
430 return math::clamp(
431 this->get_input("Motion Blur Shutter").get_single_value_default(0.5f), 0.0f, 1.0f);
432 }
433
435 {
436 return this->get_input("Motion Blur").get_single_value_default(false);
437 }
438
440 {
441 return reinterpret_cast<MovieClip *>(bnode().id);
442 }
443};
444
446{
447 return new PlaneTrackDeformOperation(context, node);
448}
449
450} // namespace blender::nodes::node_composite_planetrackdeform_cc
451
453{
455
456 static blender::bke::bNodeType ntype;
457
458 cmp_node_type_base(&ntype, "CompositorNodePlaneTrackDeform", CMP_NODE_PLANETRACKDEFORM);
459 ntype.ui_name = "Plane Track Deform";
460 ntype.ui_description =
461 "Replace flat planes in footage by another image, detected by plane tracks from motion "
462 "tracking";
463 ntype.enum_name_legacy = "PLANETRACKDEFORM";
465 ntype.declare = file_ns::cmp_node_planetrackdeform_declare;
466 ntype.initfunc_api = file_ns::init;
468 ntype, "NodePlaneTrackDeformData", node_free_standard_storage, node_copy_standard_storage);
469 ntype.get_compositor_operation = file_ns::get_compositor_operation;
470
472}
Scene * CTX_data_scene(const bContext *C)
void id_us_plus(ID *id)
Definition lib_id.cc:358
float BKE_movieclip_remap_scene_to_clip_frame(const struct MovieClip *clip, float framenr)
void BKE_movieclip_user_set_frame(struct MovieClipUser *user, int framenr)
void BKE_movieclip_get_size(struct MovieClip *clip, const struct MovieClipUser *user, int *r_width, int *r_height)
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1240
#define NODE_CLASS_DISTORT
Definition BKE_node.hh:455
#define CMP_NODE_PLANETRACKDEFORM
struct MovieTrackingPlaneTrack * BKE_tracking_object_find_plane_track_with_name(struct MovieTrackingObject *tracking_object, const char *name)
Definition tracking.cc:2005
struct MovieTrackingObject * BKE_tracking_object_get_named(struct MovieTracking *tracking, const char *name)
Definition tracking.cc:1966
struct MovieTrackingObject * BKE_tracking_object_get_active(const struct MovieTracking *tracking)
void BKE_tracking_plane_marker_get_subframe_corners(struct MovieTrackingPlaneTrack *plane_track, float framenr, float corners[4][2])
Definition tracking.cc:1866
void BKE_tracking_homography_between_two_quads(float reference_corners[4][2], float corners[4][2], float H[3][3])
#define STRNCPY_UTF8(dst, src)
#define DNA_struct_default_get(struct_name)
int GPU_shader_get_ubo_binding(blender::gpu::Shader *shader, const char *name)
void GPU_shader_bind(blender::gpu::Shader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
void GPU_shader_uniform_1i(blender::gpu::Shader *sh, const char *name, int value)
void GPU_shader_unbind()
void GPU_texture_anisotropic_filter(blender::gpu::Texture *texture, bool use_aniso)
void GPU_texture_extend_mode(blender::gpu::Texture *texture, GPUSamplerExtendMode extend_mode)
void GPU_texture_mipmap_mode(blender::gpu::Texture *texture, bool use_mipmap, bool use_filter)
@ GPU_SAMPLER_EXTEND_MODE_EXTEND
void GPU_uniformbuf_free(blender::gpu::UniformBuf *ubo)
void GPU_uniformbuf_bind(blender::gpu::UniformBuf *ubo, int slot)
void GPU_uniformbuf_unbind(blender::gpu::UniformBuf *ubo)
blender::gpu::UniformBuf * GPU_uniformbuf_create_ex(size_t size, const void *data, const char *name)
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ PROP_FACTOR
Definition RNA_types.hh:251
#define C
Definition RandGen.cpp:29
void uiTemplateID(uiLayout *layout, const bContext *C, PointerRNA *ptr, blender::StringRefNull propname, const char *newop, const char *openop, const char *unlinkop, int filter=UI_TEMPLATE_ID_FILTER_ALL, bool live_icon=false, std::optional< blender::StringRef > text=std::nullopt)
#define UI_ITEM_NONE
BMesh const char void * data
void init()
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
int64_t size() const
Definition BLI_array.hh:256
const T * data() const
Definition BLI_array.hh:312
Result create_result(ResultType type, ResultPrecision precision)
gpu::Shader * 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
void share_data(const Result &source)
Definition result.cc:523
T get_single_value_default(const T &default_value) const
void store_pixel(const int2 &texel, const T &pixel_value)
void allocate_texture(const Domain domain, const bool from_pool=true, const std::optional< ResultStorageType > storage_type=std::nullopt)
Definition result.cc:389
void unbind_as_texture() const
Definition result.cc:511
void set_single_value(const T &value)
void bind_as_texture(gpu::Shader *shader, const char *texture_name) const
Definition result.cc:487
const Domain & domain() const
T load_pixel(const int2 &texel) const
void unbind_as_image() const
Definition result.cc:517
void bind_as_image(gpu::Shader *shader, const char *image_name, bool read=false) const
Definition result.cc:498
bool is_single_value() const
Definition result.cc:758
void steal_data(Result &source)
Definition result.cc:540
DeclType::Builder & add_input(StringRef name, StringRef identifier="")
void compute_plane_gpu(const Array< float4x4 > &homography_matrices, gpu::UniformBuf *homography_matrices_buffer, Result &plane_mask)
Result compute_plane_mask_gpu(const Array< float4x4 > &homography_matrices, gpu::UniformBuf *homography_matrices_buffer)
void compute_plane_cpu(const Array< float4x4 > &homography_matrices, Result &plane_mask)
uint col
#define input
#define output
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
void node_type_storage(bNodeType &ntype, std::optional< StringRefNull > storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:5414
void compute_dispatch_threads_at_least(gpu::Shader *shader, int2 threads_range, int2 local_size=int2(16))
Definition utilities.cc:196
void smaa(Context &context, const Result &input, Result &output, const float threshold=0.1f, const float local_contrast_adaptation_factor=2.0f, const int corner_rounding=25)
Definition smaa.cc:1646
void parallel_for(const int2 range, const Function &function)
T clamp(const T &a, const T &min, const T &max)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
static void cmp_node_planetrackdeform_declare(NodeDeclarationBuilder &b)
MatBase< float, 4, 4 > float4x4
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
MatBase< float, 3, 3 > float3x3
VecBase< float, 3 > float3
static void register_node_type_cmp_planetrackdeform()
void cmp_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
void node_free_standard_storage(bNode *node)
Definition node_util.cc:42
void node_copy_standard_storage(bNodeTree *, bNode *dest_node, const bNode *src_node)
Definition node_util.cc:54
PointerRNA RNA_pointer_create_discrete(ID *id, StructRNA *type, void *data)
#define min(a, b)
Definition sort.cc:36
struct MovieTracking tracking
MovieTrackingPlaneTrack * active_plane_track
struct MovieClip * clip
struct ID * id
void * storage
const c_style_mat & ptr() const
VecBase< T, 2 > xy() const
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
NodeGetCompositorOperationFunction get_compositor_operation
Definition BKE_node.hh:348
const char * enum_name_legacy
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:362
void(* initfunc_api)(const bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:302
uiLayout & column(bool align)
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
i
Definition text_draw.cc:230
max
Definition text_draw.cc:251
PointerRNA * ptr
Definition wm_files.cc:4238