Blender V4.5
node_composite_defocus.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"
11
12#include "DNA_camera_types.h"
13#include "DNA_object_types.h"
14#include "DNA_scene_types.h"
15
16#include "BKE_camera.h"
17
18#include "RNA_access.hh"
19
20#include "UI_interface.hh"
21#include "UI_resources.hh"
22
24#include "COM_bokeh_kernel.hh"
25#include "COM_node_operation.hh"
26#include "COM_utilities.hh"
27
29
30/* ************ Defocus Node ****************** */
31
33
35
37{
38 b.add_input<decl::Color>("Image")
39 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
40 .compositor_domain_priority(0);
41 b.add_input<decl::Float>("Z").default_value(1.0f).min(0.0f).max(1.0f).compositor_domain_priority(
42 1);
43 b.add_output<decl::Color>("Image");
44}
45
46static void node_composit_init_defocus(bNodeTree * /*ntree*/, bNode *node)
47{
48 /* defocus node */
50 nbd->bktype = 0;
51 nbd->rotation = 0.0f;
52 nbd->gamco = 0;
53 nbd->samples = 16;
54 nbd->fstop = 128.0f;
55 nbd->maxblur = 16;
56 nbd->scale = 1.0f;
57 nbd->no_zbuf = 1;
58 node->storage = nbd;
59}
60
62{
63 uiLayout *sub, *col;
64
65 col = &layout->column(false);
66 col->label(IFACE_("Bokeh Type:"), ICON_NONE);
67 col->prop(ptr, "bokeh", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
68 col->prop(ptr, "angle", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
69
70 col = &layout->column(false);
71 uiLayoutSetActive(col, RNA_boolean_get(ptr, "use_zbuffer") == true);
72 col->prop(ptr, "f_stop", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
73
74 layout->prop(ptr, "blur_max", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
75
76 uiTemplateID(layout, C, ptr, "scene", nullptr, nullptr, nullptr);
77
78 col = &layout->column(false);
79 col->prop(ptr, "use_zbuffer", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
80 sub = &col->column(false);
81 uiLayoutSetActive(sub, RNA_boolean_get(ptr, "use_zbuffer") == false);
82 sub->prop(ptr, "z_scale", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
83}
84
85using namespace blender::compositor;
86
88 public:
90
91 void execute() override
92 {
93 const Result &input = this->get_input("Image");
94 Result &output = this->get_result("Image");
95 if (input.is_single_value() || node_storage(bnode()).maxblur < 1.0f) {
96 output.share_data(input);
97 return;
98 }
99
101
102 const int maximum_defocus_radius = math::ceil(compute_maximum_defocus_radius());
103
104 /* The special zero value indicate a circle, in which case, the roundness should be set to
105 * 1, and the number of sides can be anything and is arbitrarily set to 3. */
106 const bool is_circle = node_storage(bnode()).bktype == 0;
107 const int2 kernel_size = int2(maximum_defocus_radius * 2 + 1);
108 const int sides = is_circle ? 3 : node_storage(bnode()).bktype;
109 const float rotation = node_storage(bnode()).rotation;
110 const float roundness = is_circle ? 1.0f : 0.0f;
111 const Result &bokeh_kernel = context().cache_manager().bokeh_kernels.get(
112 context(), kernel_size, sides, rotation, roundness, 0.0f, 0.0f);
113
114 if (this->context().use_gpu()) {
115 this->execute_gpu(input, radius, bokeh_kernel, output, maximum_defocus_radius);
116 }
117 else {
118 this->execute_cpu(input, radius, bokeh_kernel, output, maximum_defocus_radius);
119 }
120
121 radius.release();
122 }
123
125 const Result &radius,
126 const Result &bokeh_kernel,
127 Result &output,
128 const int search_radius)
129 {
130 GPUShader *shader = context().get_shader("compositor_defocus_blur");
131 GPU_shader_bind(shader);
132
133 GPU_shader_uniform_1i(shader, "search_radius", search_radius);
134
135 input.bind_as_texture(shader, "input_tx");
136
137 radius.bind_as_texture(shader, "radius_tx");
138
139 GPU_texture_filter_mode(bokeh_kernel, true);
140 bokeh_kernel.bind_as_texture(shader, "weights_tx");
141
142 const Domain domain = compute_domain();
143 output.allocate_texture(domain);
144 output.bind_as_image(shader, "output_img");
145
147
149 input.unbind_as_texture();
150 radius.unbind_as_texture();
151 bokeh_kernel.unbind_as_texture();
152 output.unbind_as_image();
153 }
154
156 const Result &radius,
157 const Result &bokeh_kernel,
158 Result &output,
159 const int search_radius)
160 {
161 const Domain domain = compute_domain();
162 output.allocate_texture(domain);
163
164 /* Given the texel in the range [-radius, radius] in both axis, load the appropriate weight
165 * from the weights image, where the given texel (0, 0) corresponds the center of weights
166 * image. Note that we load the weights image inverted along both directions to maintain
167 * the shape of the weights if it was not symmetrical. To understand why inversion makes sense,
168 * consider a 1D weights image whose right half is all ones and whose left half is all zeros.
169 * Further, consider that we are blurring a single white pixel on a black background. When
170 * computing the value of a pixel that is to the right of the white pixel, the white pixel will
171 * be in the left region of the search window, and consequently, without inversion, a zero will
172 * be sampled from the left side of the weights image and result will be zero. However, what
173 * we expect is that pixels to the right of the white pixel will be white, that is, they should
174 * sample a weight of 1 from the right side of the weights image, hence the need for
175 * inversion. */
176 auto load_weight = [&](const int2 texel, const float radius) {
177 /* Add the radius to transform the texel into the range [0, radius * 2], with an additional
178 * 0.5 to sample at the center of the pixels, then divide by the upper bound plus one to
179 * transform the texel into the normalized range [0, 1] needed to sample the weights sampler.
180 * Finally, invert the textures coordinates by subtracting from 1 to maintain the shape of
181 * the weights as mentioned in the function description. */
182 return bokeh_kernel.sample_bilinear_extended(
183 1.0f - ((float2(texel) + float2(radius + 0.5f)) / (radius * 2.0f + 1.0f)));
184 };
185
186 parallel_for(domain.size, [&](const int2 texel) {
187 float center_radius = math::max(0.0f, radius.load_pixel<float, true>(texel));
188
189 /* Go over the window of the given search radius and accumulate the colors multiplied by
190 * their respective weights as well as the weights themselves, but only if both the radius of
191 * the center pixel and the radius of the candidate pixel are less than both the x and y
192 * distances of the candidate pixel. */
193 float4 accumulated_color = float4(0.0);
194 float4 accumulated_weight = float4(0.0);
195 for (int y = -search_radius; y <= search_radius; y++) {
196 for (int x = -search_radius; x <= search_radius; x++) {
197 float candidate_radius = math::max(
198 0.0f, radius.load_pixel_extended<float, true>(texel + int2(x, y)));
199
200 /* Skip accumulation if either the x or y distances of the candidate pixel are larger
201 * than either the center or candidate pixel radius. Note that the max and min functions
202 * here denote "either" in the aforementioned description. */
203 float radius = math::min(center_radius, candidate_radius);
204 if (math::max(math::abs(x), math::abs(y)) > radius) {
205 continue;
206 }
207
208 float4 weight = load_weight(int2(x, y), radius);
209 float4 input_color = input.load_pixel_extended<float4>(texel + int2(x, y));
210
211 accumulated_color += input_color * weight;
212 accumulated_weight += weight;
213 }
214 }
215
216 accumulated_color = math::safe_divide(accumulated_color, accumulated_weight);
217
218 output.store_pixel(texel, accumulated_color);
219 });
220 }
221
223 {
224 if (node_storage(bnode()).no_zbuf) {
226 }
228 }
229
231 {
232 Result &input_depth = get_input("Z");
233 if (this->context().use_gpu() && !input_depth.is_single_value()) {
235 }
237 }
238
240 {
241 GPUShader *shader = context().get_shader("compositor_defocus_radius_from_scale");
242 GPU_shader_bind(shader);
243
244 GPU_shader_uniform_1f(shader, "scale", node_storage(bnode()).scale);
245 GPU_shader_uniform_1f(shader, "max_radius", node_storage(bnode()).maxblur);
246
247 Result &input_depth = get_input("Z");
248 input_depth.bind_as_texture(shader, "radius_tx");
249
250 Result output_radius = context().create_result(ResultType::Float);
251 const Domain domain = input_depth.domain();
252 output_radius.allocate_texture(domain);
253 output_radius.bind_as_image(shader, "radius_img");
254
256
258 input_depth.unbind_as_texture();
259 output_radius.unbind_as_image();
260
261 return output_radius;
262 }
263
265 {
266 const float scale = node_storage(bnode()).scale;
267 const float max_radius = node_storage(bnode()).maxblur;
268
269 Result &input_depth = get_input("Z");
270
271 Result output_radius = context().create_result(ResultType::Float);
272
273 auto compute_radius = [&](const float depth) {
274 return math::clamp(depth * scale, 0.0f, max_radius);
275 };
276
277 if (input_depth.is_single_value()) {
278 output_radius.allocate_single_value();
279 output_radius.set_single_value(compute_radius(input_depth.get_single_value<float>()));
280 return output_radius;
281 }
282
283 const Domain domain = input_depth.domain();
284 output_radius.allocate_texture(domain);
285
286 parallel_for(domain.size, [&](const int2 texel) {
287 float depth = input_depth.load_pixel<float>(texel);
288 output_radius.store_pixel(texel, compute_radius(depth));
289 });
290
291 return output_radius;
292 }
293
295 {
296 Result &input_depth = get_input("Z");
297 Result output_radius = context().create_result(ResultType::Float);
298 if (this->context().use_gpu() && !input_depth.is_single_value()) {
300 }
301 else {
303 }
304
305 if (output_radius.is_single_value()) {
306 return output_radius;
307 }
308
309 /* We apply a dilate morphological operator on the radius computed from depth, the operator
310 * radius is the maximum possible defocus radius. This is done such that objects in
311 * focus---that is, objects whose defocus radius is small---are not affected by nearby out of
312 * focus objects, hence the use of erosion. */
313 const float morphological_radius = compute_maximum_defocus_radius();
314 Result eroded_radius = context().create_result(ResultType::Float);
315 morphological_blur(context(), output_radius, eroded_radius, float2(morphological_radius));
316 output_radius.release();
317
318 return eroded_radius;
319 }
320
322 {
323 GPUShader *shader = context().get_shader("compositor_defocus_radius_from_depth");
324 GPU_shader_bind(shader);
325
326 const float distance_to_image_of_focus = compute_distance_to_image_of_focus();
327 GPU_shader_uniform_1f(shader, "f_stop", get_f_stop());
328 GPU_shader_uniform_1f(shader, "focal_length", get_focal_length());
329 GPU_shader_uniform_1f(shader, "max_radius", node_storage(bnode()).maxblur);
330 GPU_shader_uniform_1f(shader, "pixels_per_meter", compute_pixels_per_meter());
331 GPU_shader_uniform_1f(shader, "distance_to_image_of_focus", distance_to_image_of_focus);
332
333 Result &input_depth = get_input("Z");
334 input_depth.bind_as_texture(shader, "depth_tx");
335
336 const Domain domain = input_depth.domain();
337 output_radius.allocate_texture(domain);
338 output_radius.bind_as_image(shader, "radius_img");
339
341
343 input_depth.unbind_as_texture();
344 output_radius.unbind_as_image();
345 }
346
348 {
349 const float f_stop = this->get_f_stop();
350 const float focal_length = this->get_focal_length();
351 const float max_radius = node_storage(this->bnode()).maxblur;
352 const float pixels_per_meter = this->compute_pixels_per_meter();
353 const float distance_to_image_of_focus = this->compute_distance_to_image_of_focus();
354
355 Result &input_depth = get_input("Z");
356
357 /* Given a depth value, compute the radius of the circle of confusion in pixels based on
358 * equation (8) of the paper:
359 *
360 * Potmesil, Michael, and Indranil Chakravarty. "A lens and aperture camera model for
361 * synthetic image generation." ACM SIGGRAPH Computer Graphics 15.3 (1981): 297-305. */
362 auto compute_radius = [&](const float depth) {
363 /* Compute `Vu` in equation (7). */
364 const float distance_to_image_of_object = (focal_length * depth) / (depth - focal_length);
365
366 /* Compute C in equation (8). Notice that the last multiplier was included in the absolute
367 * since it is negative when the object distance is less than the focal length, as noted in
368 * equation (7). */
369 float diameter = math::abs((distance_to_image_of_object - distance_to_image_of_focus) *
370 (focal_length / (f_stop * distance_to_image_of_object)));
371
372 /* The diameter is in meters, so multiply by the pixels per meter. */
373 float radius = (diameter / 2.0f) * pixels_per_meter;
374
375 return math::min(max_radius, radius);
376 };
377
378 if (input_depth.is_single_value()) {
379 output_radius.allocate_single_value();
380 output_radius.set_single_value(compute_radius(input_depth.get_single_value<float>()));
381 return;
382 }
383
384 const Domain domain = input_depth.domain();
385 output_radius.allocate_texture(domain);
386
387 parallel_for(domain.size, [&](const int2 texel) {
388 float depth = input_depth.load_pixel<float>(texel);
389 output_radius.store_pixel(texel, compute_radius(depth));
390 });
391 }
392
393 /* Computes the maximum possible defocus radius in pixels. */
395 {
396 if (node_storage(bnode()).no_zbuf) {
397 return node_storage(bnode()).maxblur;
398 }
399
400 const float maximum_diameter = compute_maximum_diameter_of_circle_of_confusion();
401 const float pixels_per_meter = compute_pixels_per_meter();
402 const float radius = (maximum_diameter / 2.0f) * pixels_per_meter;
403 return math::min(radius, node_storage(bnode()).maxblur);
404 }
405
406 /* Computes the diameter of the circle of confusion at infinity. This computes the limit in
407 * figure (5) of the paper:
408 *
409 * Potmesil, Michael, and Indranil Chakravarty. "A lens and aperture camera model for synthetic
410 * image generation." ACM SIGGRAPH Computer Graphics 15.3 (1981): 297-305.
411 *
412 * Notice that the diameter is asymmetric around the focus point, and we are computing the
413 * limiting diameter at infinity, while another limiting diameter exist at zero distance from the
414 * lens. This is a limitation of the implementation, as it assumes far defocusing only. */
416 {
417 const float f_stop = get_f_stop();
418 const float focal_length = get_focal_length();
419 const float distance_to_image_of_focus = compute_distance_to_image_of_focus();
420 return math::abs((distance_to_image_of_focus / (f_stop * focal_length)) -
421 (focal_length / f_stop));
422 }
423
424 /* Computes the distance in meters to the image of the focus point across a lens of the specified
425 * focal length. This computes `Vp` in equation (7) of the paper:
426 *
427 * Potmesil, Michael, and Indranil Chakravarty. "A lens and aperture camera model for synthetic
428 * image generation." ACM SIGGRAPH Computer Graphics 15.3 (1981): 297-305. */
430 {
431 const float focal_length = get_focal_length();
432 const float focus_distance = compute_focus_distance();
433 return (focal_length * focus_distance) / (focus_distance - focal_length);
434 }
435
436 /* Returns the focal length in meters. Fall back to 50 mm in case of an invalid camera. Ensure a
437 * minimum of 1e-6. */
439 {
440 const Camera *camera = get_camera();
441 return camera ? math::max(1e-6f, camera->lens / 1000.0f) : 50.0f / 1000.0f;
442 }
443
444 /* Computes the distance to the point that is completely in focus. Default to 10 meters for null
445 * camera. */
447 {
448 const Object *camera_object = get_camera_object();
449 if (!camera_object) {
450 return 10.0f;
451 }
452 return BKE_camera_object_dof_distance(camera_object);
453 }
454
455 /* Computes the number of pixels per meter of the sensor size. This is essentially the resolution
456 * over the sensor size, using the sensor fit axis. Fall back to DEFAULT_SENSOR_WIDTH in case of
457 * an invalid camera. Note that the stored sensor size is in millimeter, so convert to meters. */
459 {
460 const int2 size = compute_domain().size;
461 const Camera *camera = get_camera();
462 const float default_value = size.x / (DEFAULT_SENSOR_WIDTH / 1000.0f);
463 if (!camera) {
464 return default_value;
465 }
466
467 switch (camera->sensor_fit) {
469 return size.x / (camera->sensor_x / 1000.0f);
471 return size.y / (camera->sensor_y / 1000.0f);
473 return size.x > size.y ? size.x / (camera->sensor_x / 1000.0f) :
474 size.y / (camera->sensor_y / 1000.0f);
475 }
476 default:
477 break;
478 }
479
480 return default_value;
481 }
482
483 /* Returns the f-stop number. Fall back to 1e-3 for zero f-stop. */
485 {
486 return math::max(1e-3f, node_storage(bnode()).fstop);
487 }
488
490 {
491 const Object *camera_object = get_camera_object();
492 if (!camera_object || camera_object->type != OB_CAMERA) {
493 return nullptr;
494 }
495
496 return reinterpret_cast<Camera *>(camera_object->data);
497 }
498
500 {
501 return get_scene()->camera;
502 }
503
505 {
506 return bnode().id ? reinterpret_cast<Scene *>(bnode().id) : &context().get_scene();
507 }
508};
509
511{
512 return new DefocusOperation(context, node);
513}
514
515} // namespace blender::nodes::node_composite_defocus_cc
516
518{
520
521 static blender::bke::bNodeType ntype;
522
523 cmp_node_type_base(&ntype, "CompositorNodeDefocus", CMP_NODE_DEFOCUS);
524 ntype.ui_name = "Defocus";
525 ntype.ui_description = "Apply depth of field in 2D, using a Z depth map or mask";
526 ntype.enum_name_legacy = "DEFOCUS";
528 ntype.declare = file_ns::cmp_node_defocus_declare;
529 ntype.draw_buttons = file_ns::node_composit_buts_defocus;
530 ntype.initfunc = file_ns::node_composit_init_defocus;
533 ntype.get_compositor_operation = file_ns::get_compositor_operation;
534
536}
Camera data-block and utility functions.
float BKE_camera_object_dof_distance(const struct Object *ob)
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1215
#define NODE_CLASS_OP_FILTER
Definition BKE_node.hh:437
#define CMP_NODE_DEFOCUS
#define IFACE_(msgid)
@ CAMERA_SENSOR_FIT_HOR
@ CAMERA_SENSOR_FIT_AUTO
@ CAMERA_SENSOR_FIT_VERT
#define DEFAULT_SENSOR_WIDTH
Object is a sort of wrapper for general info.
@ OB_CAMERA
void GPU_shader_uniform_1i(GPUShader *sh, const char *name, int value)
void GPU_shader_uniform_1f(GPUShader *sh, const char *name, float value)
void GPU_shader_bind(GPUShader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
void GPU_shader_unbind()
void GPU_texture_filter_mode(GPUTexture *texture, bool use_filter)
#define NOD_REGISTER_NODE(REGISTER_FUNC)
#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)
@ UI_ITEM_R_SPLIT_EMPTY_NAME
void uiLayoutSetActive(uiLayout *layout, bool active)
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
Result & get(Context &context, int2 size, int sides, float rotation, float roundness, float catadioptric, float lens_shift)
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 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)
const Domain & domain() 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_bilinear_extended(const float2 &coordinates) const
bool is_single_value() const
Definition result.cc:622
const T & get_single_value() const
void execute_gpu(const Result &input, const Result &radius, const Result &bokeh_kernel, Result &output, const int search_radius)
void execute_cpu(const Result &input, const Result &radius, const Result &bokeh_kernel, Result &output, const int search_radius)
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:2748
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:5603
void morphological_blur(Context &context, const Result &input, Result &output, const float2 &radius, const MorphologicalBlurOperation operation=MorphologicalBlurOperation::Erode, const int filter_type=R_FILTER_GAUSS)
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)
T clamp(const T &a, const T &min, const T &max)
T safe_divide(const T &a, const T &b)
T min(const T &a, const T &b)
T ceil(const T &a)
T max(const T &a, const T &b)
T abs(const T &a)
static void node_composit_buts_defocus(uiLayout *layout, bContext *C, PointerRNA *ptr)
static void cmp_node_defocus_declare(NodeDeclarationBuilder &b)
static void node_composit_init_defocus(bNodeTree *, bNode *node)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
static void register_node_type_cmp_defocus()
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
bool RNA_boolean_get(PointerRNA *ptr, const char *name)
#define min(a, b)
Definition sort.cc:36
char sensor_fit
float sensor_y
float sensor_x
void * storage
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
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:277
const char * enum_name_legacy
Definition BKE_node.hh:235
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:355
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)
max
Definition text_draw.cc:251
static pxr::UsdShadeInput get_input(const pxr::UsdShadeShader &usd_shader, const pxr::TfToken &input_name)
PointerRNA * ptr
Definition wm_files.cc:4226