Blender V5.0
node_composite_depth_combine.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_base.hh"
10#include "BLI_math_vector.hh"
12
13#include "UI_resources.hh"
14
15#include "COM_algorithm_smaa.hh"
16#include "COM_node_operation.hh"
17#include "COM_utilities.hh"
18
19#include "GPU_shader.hh"
20
22
23/* **************** DEPTH COMBINE ******************** */
24
26
28{
29 b.add_input<decl::Color>("A")
30 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
31 .structure_type(StructureType::Dynamic);
32 b.add_input<decl::Float>("Depth A").default_value(1.0f).min(0.0f).max(10000.0f).structure_type(
33 StructureType::Dynamic);
34 b.add_input<decl::Color>("B")
35 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
36 .structure_type(StructureType::Dynamic);
37 b.add_input<decl::Float>("Depth B").default_value(1.0f).min(0.0f).max(10000.0f).structure_type(
38 StructureType::Dynamic);
39 b.add_input<decl::Bool>("Use Alpha")
40 .default_value(false)
42 "Use the alpha of the first input as mixing factor and return the more opaque alpha of "
43 "the two inputs");
44 b.add_input<decl::Bool>("Anti-Alias")
45 .default_value(true)
47 "Anti-alias the generated mask before combining for smoother boundaries at the cost of "
48 "more expensive processing");
49
50 b.add_output<decl::Color>("Result").structure_type(StructureType::Dynamic);
51 b.add_output<decl::Float>("Depth").structure_type(StructureType::Dynamic);
52}
53
54using namespace blender::compositor;
55
57 public:
59
60 void execute() override
61 {
62 if (this->get_input("A").is_single_value() && this->get_input("B").is_single_value() &&
63 this->get_input("Depth A").is_single_value() &&
64 this->get_input("Depth B").is_single_value())
65 {
67 }
68 else if (use_anti_aliasing()) {
70 }
71 else {
73 }
74 }
75
77 {
78 const float4 first_color = get_input("A").get_single_value<float4>();
79 const float4 second_color = get_input("B").get_single_value<float4>();
80 const float first_z_value = get_input("Depth A").get_single_value<float>();
81 const float second_z_value = get_input("Depth B").get_single_value<float>();
82
83 /* Mix between the first and second images using a mask such that the image with the object
84 * closer to the camera is returned. The mask value is then 1, and thus returns the first image
85 * if its Depth value is less than that of the second image. Otherwise, its value is 0, and
86 * thus returns the second image. Furthermore, if the object in the first image is closer but
87 * has a non-opaque alpha, then the alpha is used as a mask, but only if Use Alpha is enabled.
88 */
89 const float z_combine_factor = float(first_z_value < second_z_value);
90 const float alpha_factor = use_alpha() ? first_color.w : 1.0f;
91 const float mix_factor = z_combine_factor * alpha_factor;
92
93 Result &combined = get_result("Result");
94 if (combined.should_compute()) {
95 float4 combined_color = math::interpolate(second_color, first_color, mix_factor);
96 /* Use the more opaque alpha from the two images. */
97 combined_color.w = use_alpha() ? math::max(second_color.w, first_color.w) : combined_color.w;
98
99 combined.allocate_single_value();
100 combined.set_single_value(combined_color);
101 }
102
103 Result &combined_z = get_result("Depth");
104 if (combined_z.should_compute()) {
105 const float combined_z_value = math::interpolate(second_z_value, first_z_value, mix_factor);
106 combined_z.allocate_single_value();
107 combined_z.set_single_value(combined_z_value);
108 }
109 }
110
112 {
113 if (this->context().use_gpu()) {
114 this->execute_simple_gpu();
115 }
116 else {
117 this->execute_simple_cpu();
118 }
119 }
120
122 {
123 if (this->get_result("Result").should_compute()) {
125 }
126
127 if (this->get_result("Depth").should_compute()) {
129 }
130 }
131
133 {
134 gpu::Shader *shader = this->context().get_shader("compositor_z_combine_simple_image");
135 GPU_shader_bind(shader);
136
137 GPU_shader_uniform_1b(shader, "use_alpha", this->use_alpha());
138
139 const Result &first = this->get_input("A");
140 first.bind_as_texture(shader, "first_tx");
141 const Result &first_z = this->get_input("Depth A");
142 first_z.bind_as_texture(shader, "first_z_tx");
143 const Result &second = this->get_input("B");
144 second.bind_as_texture(shader, "second_tx");
145 const Result &second_z = this->get_input("Depth B");
146 second_z.bind_as_texture(shader, "second_z_tx");
147
148 Result &combined = this->get_result("Result");
149 const Domain domain = this->compute_domain();
150 combined.allocate_texture(domain);
151 combined.bind_as_image(shader, "combined_img");
152
154
155 first.unbind_as_texture();
156 first_z.unbind_as_texture();
157 second.unbind_as_texture();
158 second_z.unbind_as_texture();
159 combined.unbind_as_image();
161 }
162
164 {
165 gpu::Shader *shader = this->context().get_shader("compositor_z_combine_simple_depth");
166 GPU_shader_bind(shader);
167
168 const Result &first_z = this->get_input("Depth A");
169 first_z.bind_as_texture(shader, "first_z_tx");
170 const Result &second_z = this->get_input("Depth B");
171 second_z.bind_as_texture(shader, "second_z_tx");
172
173 Result &combined_z = this->get_result("Depth");
174 const Domain domain = this->compute_domain();
175 combined_z.allocate_texture(domain);
176 combined_z.bind_as_image(shader, "combined_z_img");
177
179
180 first_z.unbind_as_texture();
181 second_z.unbind_as_texture();
182 combined_z.unbind_as_image();
184 }
185
187 {
188 const bool use_alpha = this->use_alpha();
189
190 const Result &first = this->get_input("A");
191 const Result &first_z = this->get_input("Depth A");
192 const Result &second = this->get_input("B");
193 const Result &second_z = this->get_input("Depth B");
194
195 const Domain domain = this->compute_domain();
196 Result &combined = this->get_result("Result");
197 if (combined.should_compute()) {
198 combined.allocate_texture(domain);
199 parallel_for(domain.size, [&](const int2 texel) {
200 float4 first_color = first.load_pixel<float4, true>(texel);
201 float4 second_color = second.load_pixel<float4, true>(texel);
202 float first_z_value = first_z.load_pixel<float, true>(texel);
203 float second_z_value = second_z.load_pixel<float, true>(texel);
204
205 /* Choose the closer pixel as the foreground, that is, the pixel with the lower z value. If
206 * Use Alpha is disabled, return the foreground, otherwise, mix between the foreground and
207 * background using the alpha of the foreground. */
208 float4 foreground_color = first_z_value < second_z_value ? first_color : second_color;
209 float4 background_color = first_z_value < second_z_value ? second_color : first_color;
210 float mix_factor = use_alpha ? foreground_color.w : 1.0f;
211 float4 combined_color = math::interpolate(background_color, foreground_color, mix_factor);
212
213 /* Use the more opaque alpha from the two images. */
214 combined_color.w = use_alpha ? math::max(second_color.w, first_color.w) : combined_color.w;
215 combined.store_pixel(texel, combined_color);
216 });
217 }
218
219 Result &combined_z_output = this->get_result("Depth");
220 if (combined_z_output.should_compute()) {
221 combined_z_output.allocate_texture(domain);
222 parallel_for(domain.size, [&](const int2 texel) {
223 float first_z_value = first_z.load_pixel<float, true>(texel);
224 float second_z_value = second_z.load_pixel<float, true>(texel);
225 float combined_z = math::min(first_z_value, second_z_value);
226 combined_z_output.store_pixel(texel, combined_z);
227 });
228 }
229 }
230
232 {
234
235 Result anti_aliased_mask = this->context().create_result(ResultType::Float);
236 smaa(this->context(), mask, anti_aliased_mask);
237 mask.release();
238
239 if (this->context().use_gpu()) {
240 this->execute_anti_aliased_gpu(anti_aliased_mask);
241 }
242 else {
243 this->execute_anti_aliased_cpu(anti_aliased_mask);
244 }
245
246 anti_aliased_mask.release();
247 }
248
250 {
251 if (this->get_result("Result").should_compute()) {
253 }
254
255 if (this->get_result("Depth").should_compute()) {
257 }
258 }
259
261 {
262 gpu::Shader *shader = this->context().get_shader("compositor_z_combine_from_mask_image");
263 GPU_shader_bind(shader);
264
265 GPU_shader_uniform_1b(shader, "use_alpha", this->use_alpha());
266
267 const Result &first = this->get_input("A");
268 first.bind_as_texture(shader, "first_tx");
269 const Result &second = this->get_input("B");
270 second.bind_as_texture(shader, "second_tx");
271 mask.bind_as_texture(shader, "mask_tx");
272
273 Result &combined = this->get_result("Result");
274 const Domain domain = this->compute_domain();
275 combined.allocate_texture(domain);
276 combined.bind_as_image(shader, "combined_img");
277
279
280 first.unbind_as_texture();
281 second.unbind_as_texture();
282 mask.unbind_as_texture();
283 combined.unbind_as_image();
285 }
286
288 {
289 gpu::Shader *shader = this->context().get_shader("compositor_z_combine_from_mask_depth");
290 GPU_shader_bind(shader);
291
292 const Result &first_z = this->get_input("Depth A");
293 first_z.bind_as_texture(shader, "first_z_tx");
294 const Result &second_z = this->get_input("Depth B");
295 second_z.bind_as_texture(shader, "second_z_tx");
296
297 Result &combined_z = this->get_result("Depth");
298 const Domain domain = this->compute_domain();
299 combined_z.allocate_texture(domain);
300 combined_z.bind_as_image(shader, "combined_z_img");
301
303
304 first_z.unbind_as_texture();
305 second_z.unbind_as_texture();
306 combined_z.unbind_as_image();
308 }
309
311 {
312 const bool use_alpha = this->use_alpha();
313
314 const Result &first = this->get_input("A");
315 const Result &first_z = this->get_input("Depth A");
316 const Result &second = this->get_input("B");
317 const Result &second_z = this->get_input("Depth B");
318
319 const Domain domain = this->compute_domain();
320 Result &combined = this->get_result("Result");
321 if (combined.should_compute()) {
322 combined.allocate_texture(domain);
323 parallel_for(domain.size, [&](const int2 texel) {
324 float4 first_color = first.load_pixel<float4, true>(texel);
325 float4 second_color = second.load_pixel<float4, true>(texel);
326 float mask_value = mask.load_pixel<float>(texel);
327
328 /* Choose the closer pixel as the foreground, that is, the masked pixel with the lower z
329 * value. If Use Alpha is disabled, return the foreground, otherwise, mix between the
330 * foreground and background using the alpha of the foreground. */
331 float4 foreground_color = math::interpolate(second_color, first_color, mask_value);
332 float4 background_color = math::interpolate(first_color, second_color, mask_value);
333 float mix_factor = use_alpha ? foreground_color.w : 1.0f;
334 float4 combined_color = math::interpolate(background_color, foreground_color, mix_factor);
335
336 /* Use the more opaque alpha from the two images. */
337 combined_color.w = use_alpha ? math::max(second_color.w, first_color.w) : combined_color.w;
338 combined.store_pixel(texel, combined_color);
339 });
340 }
341
342 Result &combined_z_output = this->get_result("Depth");
343 if (combined_z_output.should_compute()) {
344 combined_z_output.allocate_texture(domain);
345 parallel_for(domain.size, [&](const int2 texel) {
346 float first_z_value = first_z.load_pixel<float, true>(texel);
347 float second_z_value = second_z.load_pixel<float, true>(texel);
348 float combined_z = math::min(first_z_value, second_z_value);
349 combined_z_output.store_pixel(texel, combined_z);
350 });
351 }
352 }
353
355 {
356 if (this->context().use_gpu()) {
357 return this->compute_mask_gpu();
358 }
359
360 return this->compute_mask_cpu();
361 }
362
364 {
365 gpu::Shader *shader = context().get_shader("compositor_z_combine_compute_mask");
366 GPU_shader_bind(shader);
367
368 const Result &first_z = get_input("Depth A");
369 first_z.bind_as_texture(shader, "first_z_tx");
370 const Result &second_z = get_input("Depth B");
371 second_z.bind_as_texture(shader, "second_z_tx");
372
373 const Domain domain = compute_domain();
375 mask.allocate_texture(domain);
376 mask.bind_as_image(shader, "mask_img");
377
379
380 first_z.unbind_as_texture();
381 second_z.unbind_as_texture();
382 mask.unbind_as_image();
384
385 return mask;
386 }
387
389 {
390 const Result &first_z = this->get_input("Depth A");
391 const Result &second_z = this->get_input("Depth B");
392
393 const Domain domain = this->compute_domain();
395 mask.allocate_texture(domain);
396
397 parallel_for(domain.size, [&](const int2 texel) {
398 float first_z_value = first_z.load_pixel<float, true>(texel);
399 float second_z_value = second_z.load_pixel<float, true>(texel);
400 float z_combine_factor = float(first_z_value < second_z_value);
401 mask.store_pixel(texel, z_combine_factor);
402 });
403
404 return mask;
405 }
406
408 {
409 return this->get_input("Use Alpha").get_single_value_default(false);
410 }
411
413 {
414 return this->get_input("Anti-Alias").get_single_value_default(true);
415 }
416};
417
419{
420 return new ZCombineOperation(context, node);
421}
422
423} // namespace blender::nodes::node_composite_zcombine_cc
424
426{
428
429 static blender::bke::bNodeType ntype;
430
431 cmp_node_type_base(&ntype, "CompositorNodeZcombine", CMP_NODE_ZCOMBINE);
432 ntype.ui_name = "Depth Combine";
433 ntype.ui_description = "Combine two images using depth maps";
434 ntype.enum_name_legacy = "ZCOMBINE";
436 ntype.declare = file_ns::cmp_node_zcombine_declare;
437 ntype.get_compositor_operation = file_ns::get_compositor_operation;
438
440}
#define NODE_CLASS_OP_COLOR
Definition BKE_node.hh:449
#define CMP_NODE_ZCOMBINE
void GPU_shader_uniform_1b(blender::gpu::Shader *sh, const char *name, bool value)
void GPU_shader_bind(blender::gpu::Shader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
void GPU_shader_unbind()
#define NOD_REGISTER_NODE(REGISTER_FUNC)
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
virtual Domain compute_domain()
Definition operation.cc:56
T get_single_value_default(const T &default_value) const
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
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
const T & get_single_value() const
nullptr float
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
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 interpolate(const T &a, const T &b, const FactorT &t)
T max(const T &a, const T &b)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
static void cmp_node_zcombine_declare(NodeDeclarationBuilder &b)
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
static void register_node_type_cmp_zcombine()
void cmp_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
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