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