Blender V5.0
node_composite_lensdist.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.h"
10#include "BLI_math_base.hh"
11#include "BLI_math_vector.hh"
13#include "BLI_noise.hh"
14
15#include "RNA_types.hh"
16
17#include "GPU_shader.hh"
18#include "GPU_texture.hh"
19
20#include "COM_node_operation.hh"
21#include "COM_utilities.hh"
22
24
25/* Distortion can't be exactly -1.0 as it will cause infinite pincushion distortion. */
26#define MINIMUM_DISTORTION -0.999f
27/* Arbitrary scaling factor for the dispersion input in horizontal distortion mode. */
28#define HORIZONTAL_DISPERSION_SCALE 5.0f
29/* Arbitrary scaling factor for the dispersion input in radial distortion mode. */
30#define RADIAL_DISPERSION_SCALE 4.0f
31/* Arbitrary scaling factor for the distortion input. */
32#define DISTORTION_SCALE 4.0f
33
35
36static const EnumPropertyItem type_items[] = {
38 "RADIAL",
39 0,
40 N_("Radial"),
41 N_("Radially distorts the image to create a barrel or a Pincushion distortion")},
43 "HORIZONTAL",
44 0,
45 N_("Horizontal"),
46 N_("Horizontally distorts the image to create a channel/color shifting effect")},
47 {0, nullptr, 0, nullptr, nullptr},
48};
49
51{
52 b.use_custom_socket_order();
53 b.allow_any_socket_order();
54 b.add_input<decl::Color>("Image")
55 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
56 .hide_value()
57 .structure_type(StructureType::Dynamic);
58 b.add_output<decl::Color>("Image").structure_type(StructureType::Dynamic).align_with_previous();
59
60 b.add_input<decl::Menu>("Type")
62 .static_items(type_items)
64 b.add_input<decl::Float>("Distortion")
65 .default_value(0.0f)
68 .max(1.0f)
69 .usage_by_single_menu(CMP_NODE_LENS_DISTORTION_RADIAL)
70 .description(
71 "The amount of distortion. 0 means no distortion, -1 means full Pincushion distortion, "
72 "and 1 means full Barrel distortion");
73 b.add_input<decl::Float>("Dispersion")
74 .default_value(0.0f)
76 .min(0.0f)
77 .max(1.0f)
78 .description("The amount of chromatic aberration to add to the distortion");
79 b.add_input<decl::Bool>("Jitter")
80 .default_value(false)
81 .usage_by_single_menu(CMP_NODE_LENS_DISTORTION_RADIAL)
83 "Introduces jitter while doing distortion, which can be faster but can produce grainy "
84 "or noisy results");
85 b.add_input<decl::Bool>("Fit")
86 .default_value(false)
87 .usage_by_single_menu(CMP_NODE_LENS_DISTORTION_RADIAL)
89 "Scales the image such that it fits entirely in the frame, leaving no empty spaces at "
90 "the corners");
91}
92
93static void node_composit_init_lensdist(bNodeTree * /*ntree*/, bNode *node)
94{
95 /* Unused, kept for forward compatibility. */
97 node->storage = data;
98}
99
100using namespace blender::compositor;
101
102/* --------------------------------------------------------------------
103 * Screen Lens Distortion
104 */
105
106/* A model that approximates lens distortion parameterized by a distortion parameter and dependent
107 * on the squared distance to the center of the image. The distorted pixel is then computed as the
108 * scalar multiplication of the pixel coordinates with the value returned by this model. See the
109 * compute_distorted_uv function for more details. */
110static float compute_distortion_scale(const float distortion, const float distance_squared)
111{
112 return 1.0f / (1.0f + math::sqrt(math::max(0.0f, 1.0f - distortion * distance_squared)));
113}
114
115/* A vectorized version of compute_distortion_scale that is applied on the chromatic distortion
116 * parameters passed to the shader. */
117static float3 compute_chromatic_distortion_scale(const float3 &chromatic_distortion,
118 const float distance_squared)
119{
120 return 1.0f / (1.0f + math::sqrt(math::max(float3(0.0f),
121 1.0f - chromatic_distortion * distance_squared)));
122}
123
124/* Compute the image coordinates after distortion by the given distortion scale computed by the
125 * compute_distortion_scale function. Note that the function expects centered normalized UV
126 * coordinates but outputs non-centered image coordinates. */
127static float2 compute_distorted_uv(const float2 &uv, const float uv_scale, const int2 &size)
128{
129 return (uv * uv_scale + 0.5f) * float2(size);
130}
131
132/* Compute the number of integration steps that should be used to approximate the distorted pixel
133 * using a heuristic, see the compute_number_of_steps function for more details. The numbers of
134 * steps is proportional to the number of pixels spanned by the distortion amount. For jitter
135 * distortion, the square root of the distortion amount plus 1 is used with a minimum of 2 steps.
136 * For non-jitter distortion, the distortion amount plus 1 is used as the number of steps */
137static int compute_number_of_integration_steps_heuristic(const float distortion,
138 const bool use_jitter)
139{
140 if (use_jitter) {
141 return distortion < 4.0f ? 2 : int(math::sqrt(distortion + 1.0f));
142 }
143 return int(distortion + 1.0f);
144}
145
146/* Compute the number of integration steps that should be used to compute each channel of the
147 * distorted pixel. Each of the channels are distorted by their respective chromatic distortion
148 * amount, then the amount of distortion between each two consecutive channels is computed, this
149 * amount is then used to heuristically infer the number of needed integration steps, see the
150 * integrate_distortion function for more information. */
151static int4 compute_number_of_integration_steps(const float3 &chromatic_distortion,
152 const int2 &size,
153 const float2 &uv,
154 const float distance_squared,
155 const bool use_jitter)
156{
157 /* Distort each channel by its respective chromatic distortion amount. */
158 float3 distortion_scale = compute_chromatic_distortion_scale(chromatic_distortion,
159 distance_squared);
160 float2 distorted_uv_red = compute_distorted_uv(uv, distortion_scale.x, size);
161 float2 distorted_uv_green = compute_distorted_uv(uv, distortion_scale.y, size);
162 float2 distorted_uv_blue = compute_distorted_uv(uv, distortion_scale.z, size);
163
164 /* Infer the number of needed integration steps to compute the distorted red channel starting
165 * from the green channel. */
166 float distortion_red = math::distance(distorted_uv_red, distorted_uv_green);
167 int steps_red = compute_number_of_integration_steps_heuristic(distortion_red, use_jitter);
168
169 /* Infer the number of needed integration steps to compute the distorted blue channel starting
170 * from the green channel. */
171 float distortion_blue = math::distance(distorted_uv_green, distorted_uv_blue);
172 int steps_blue = compute_number_of_integration_steps_heuristic(distortion_blue, use_jitter);
173
174 /* The number of integration steps used to compute the green and the alpha channels is the sum
175 * of both the red and the blue channels steps because they are computed once with each of them.
176 */
177 return int4(steps_red, steps_red + steps_blue, steps_blue, steps_red + steps_blue);
178}
179
180/* Returns a random jitter amount, which is essentially a random value in the [0, 1] range. If
181 * jitter is not enabled, return a constant 0.5 value instead. */
182static float get_jitter(const int2 &texel, const int seed, const bool use_jitter)
183{
184 if (use_jitter) {
185 return noise::hash_to_float(texel.x, texel.y, seed);
186 }
187 return 0.5f;
188}
189
190/* Each color channel may have a different distortion with the guarantee that the red will have the
191 * lowest distortion while the blue will have the highest one. If each channel is distorted
192 * independently, the image will look disintegrated, with each channel seemingly merely shifted.
193 * Consequently, the distorted pixels needs to be computed by integrating along the path of change
194 * of distortion starting from one channel to another. For instance, to compute the distorted red
195 * from the distorted green, we accumulate the color of the distorted pixel starting from the
196 * distortion of the red, taking small steps until we reach the distortion of the green. The pixel
197 * color is weighted such that it is maximum at the start distortion and zero at the end distortion
198 * in an arithmetic progression. The integration steps can be augmented with random values to
199 * simulate lens jitter. Finally, it should be noted that this function integrates both the start
200 * and end channels in reverse directions for more efficient computation. */
201static float4 integrate_distortion(const int2 &texel,
202 const Result &input,
203 const int2 &size,
204 const float3 &chromatic_distortion,
205 const int start,
206 const int end,
207 const float distance_squared,
208 const float2 &uv,
209 const int steps,
210 const bool use_jitter)
211{
212 float4 accumulated_color = float4(0.0f);
213 float distortion_amount = chromatic_distortion[end] - chromatic_distortion[start];
214 for (int i = 0; i < steps; i++) {
215 /* The increment will be in the [0, 1) range across iterations. Include the start channel in
216 * the jitter seed to make sure each channel gets a different jitter. */
217 float increment = (i + get_jitter(texel, start * steps + i, use_jitter)) / steps;
218 float distortion = chromatic_distortion[start] + increment * distortion_amount;
219 float distortion_scale = compute_distortion_scale(distortion, distance_squared);
220
221 /* Sample the color at the distorted coordinates and accumulate it weighted by the increment
222 * value for both the start and end channels. */
223 float2 distorted_uv = compute_distorted_uv(uv, distortion_scale, size);
224 float4 color = input.sample_bilinear_zero(distorted_uv / float2(size));
225 accumulated_color[start] += (1.0f - increment) * color[start];
226 accumulated_color[end] += increment * color[end];
227 accumulated_color.w += color.w;
228 }
229 return accumulated_color;
230}
231
232static void radial_lens_distortion(const int2 texel,
233 const Result &input,
234 Result &output,
235 const int2 &size,
236 const float3 &chromatic_distortion,
237 const float scale,
238 const bool use_jitter)
239{
240 /* Compute the UV image coordinates in the range [-1, 1] as well as the squared distance to the
241 * center of the image, which is at (0, 0) in the UV coordinates. */
242 float2 center = float2(size) / 2.0f;
243 float2 uv = scale * (float2(texel) + float2(0.5f) - center) / center;
244 float distance_squared = math::dot(uv, uv);
245
246 /* If any of the color channels will get distorted outside of the screen beyond what is possible,
247 * write a zero transparent color and return. */
248 float3 distortion_bounds = chromatic_distortion * distance_squared;
249 if (distortion_bounds.x > 1.0f || distortion_bounds.y > 1.0f || distortion_bounds.z > 1.0f) {
250 output.store_pixel(texel, float4(0.0f));
251 return;
252 }
253
254 /* Compute the number of integration steps that should be used to compute each channel of the
255 * distorted pixel. */
257 chromatic_distortion, size, uv, distance_squared, use_jitter);
258
259 /* Integrate the distortion of the red and green, then the green and blue channels. That means
260 * the green will be integrated twice, but this is accounted for in the number of steps which the
261 * color will later be divided by. See the compute_number_of_integration_steps function for more
262 * details. */
263 float4 color = float4(0.0f);
265 input,
266 size,
267 chromatic_distortion,
268 0,
269 1,
270 distance_squared,
271 uv,
272 number_of_steps.x,
273 use_jitter);
275 input,
276 size,
277 chromatic_distortion,
278 1,
279 2,
280 distance_squared,
281 uv,
282 number_of_steps.z,
283 use_jitter);
284
285 /* The integration above performed weighted accumulation, and thus the color needs to be divided
286 * by the sum of the weights. Assuming no jitter, the weights are generated as an arithmetic
287 * progression starting from (0.5 / n) to ((n - 0.5) / n) for n terms. The sum of an arithmetic
288 * progression can be computed as (n * (start + end) / 2), which when subsisting the start and
289 * end reduces to (n / 2). So the color should be multiplied by 2 / n. On the other hand alpha
290 * is not weighted by the arithmetic progression, so it is multiplied by (1.0) and it is
291 * normalized by averaging only (i.e. division by (n)). The jitter sequence approximately sums to
292 * the same value because it is a uniform random value whose mean value is 0.5, so the expression
293 * doesn't change regardless of jitter. */
294 color *= float4(float3(2.0f), 1.0f) / float4(number_of_steps);
295
296 output.store_pixel(texel, color);
297}
298
300 public:
302
303 void execute() override
304 {
305 const Result &input = this->get_input("Image");
306 Result &output = this->get_result("Image");
307
308 if (this->is_identity()) {
309 output.share_data(input);
310 return;
311 }
312
313 switch (this->get_type()) {
316 return;
319 return;
320 }
321
322 output.share_data(input);
323 }
324
326 {
327 if (this->context().use_gpu()) {
329 }
330 else {
332 }
333 }
334
336 {
337 gpu::Shader *shader = context().get_shader("compositor_horizontal_lens_distortion");
338 GPU_shader_bind(shader);
339
340 const Result &input_image = get_input("Image");
341 GPU_texture_filter_mode(input_image, true);
343 input_image.bind_as_texture(shader, "input_tx");
344
345 const Domain domain = compute_domain();
346
347 const float dispersion = (get_dispersion() * HORIZONTAL_DISPERSION_SCALE) / domain.size.x;
348 GPU_shader_uniform_1f(shader, "dispersion", dispersion);
349
350 Result &output_image = get_result("Image");
351 output_image.allocate_texture(domain);
352 output_image.bind_as_image(shader, "output_img");
353
355
356 input_image.unbind_as_texture();
357 output_image.unbind_as_image();
359 }
360
362 {
363 const Domain domain = compute_domain();
364 const float dispersion = (get_dispersion() * HORIZONTAL_DISPERSION_SCALE) / domain.size.x;
365
366 const Result &input = get_input("Image");
367
368 Result &output = get_result("Image");
369 output.allocate_texture(domain);
370
371 const int2 size = domain.size;
372 parallel_for(size, [&](const int2 texel) {
373 /* Get the normalized coordinates of the pixel centers. */
374 float2 normalized_texel = (float2(texel) + float2(0.5f)) / float2(size);
375
376 /* Sample the red and blue channels shifted by the dispersion amount. */
377 const float4 red = input.sample_bilinear_zero(normalized_texel + float2(dispersion, 0.0f));
378 const float4 green = input.load_pixel<float4>(texel);
379 const float4 blue = input.sample_bilinear_zero(normalized_texel - float2(dispersion, 0.0f));
380
381 const float alpha = blender::math::dot(float3(red.w, green.w, blue.w), float3(1.0f)) / 3.0f;
382
383 output.store_pixel(texel, float4(red.x, green.y, blue.z, alpha));
384 });
385 }
386
388 {
389 if (this->context().use_gpu()) {
391 }
392 else {
394 }
395 }
396
398 {
400 GPU_shader_bind(shader);
401
402 const Result &input_image = get_input("Image");
403 GPU_texture_filter_mode(input_image, true);
405 input_image.bind_as_texture(shader, "input_tx");
406
407 const Domain domain = compute_domain();
408
409 const float3 chromatic_distortion = compute_chromatic_distortion();
410 GPU_shader_uniform_3fv(shader, "chromatic_distortion", chromatic_distortion);
411
412 GPU_shader_uniform_1f(shader, "scale", compute_scale());
413
414 Result &output_image = get_result("Image");
415 output_image.allocate_texture(domain);
416 output_image.bind_as_image(shader, "output_img");
417
419
420 input_image.unbind_as_texture();
421 output_image.unbind_as_image();
423 }
424
426 {
427 if (get_use_jitter()) {
428 return "compositor_radial_lens_distortion_jitter";
429 }
430 return "compositor_radial_lens_distortion";
431 }
432
434 {
435 const float scale = this->compute_scale();
436 const bool use_jitter = this->get_use_jitter();
437 const float3 chromatic_distortion = this->compute_chromatic_distortion();
438
439 const Result &input = get_input("Image");
440
441 const Domain domain = compute_domain();
442 Result &output = get_result("Image");
443 output.allocate_texture(domain);
444
445 const int2 size = domain.size;
446 parallel_for(size, [&](const int2 texel) {
447 radial_lens_distortion(texel, input, output, size, chromatic_distortion, scale, use_jitter);
448 });
449 }
450
452 {
453 const Result &input = get_input("Distortion");
454 return clamp_f(input.get_single_value_default(0.0f), MINIMUM_DISTORTION, 1.0f);
455 }
456
458 {
459 const Result &input = get_input("Dispersion");
460 return clamp_f(input.get_single_value_default(0.0f), 0.0f, 1.0f);
461 }
462
463 /* Get the distortion amount for each channel. The green channel has a distortion amount that
464 * matches that specified in the node inputs, while the red and blue channels have higher and
465 * lower distortion amounts respectively based on the dispersion value. */
467 {
468 const float green_distortion = get_distortion();
469 const float dispersion = get_dispersion() / RADIAL_DISPERSION_SCALE;
470 const float red_distortion = clamp_f(green_distortion + dispersion, MINIMUM_DISTORTION, 1.0f);
471 const float blue_distortion = clamp_f(green_distortion - dispersion, MINIMUM_DISTORTION, 1.0f);
472 return float3(red_distortion, green_distortion, blue_distortion) * DISTORTION_SCALE;
473 }
474
475 /* The distortion model will distort the image in such a way that the result will no longer
476 * fit the domain of the original image, so we scale the image to account for that. If get_is_fit
477 * is false, then the scaling factor will be such that the furthest pixels horizontally and
478 * vertically are at the boundary of the image. Otherwise, if get_is_fit is true, the scaling
479 * factor will be such that the furthest pixels diagonally are at the corner of the image. */
481 {
483 const float maximum_distortion = max_fff(distortion[0], distortion[1], distortion[2]);
484
485 if (get_is_fit() && (maximum_distortion > 0.0f)) {
486 return 1.0f / (1.0f + 2.0f * maximum_distortion);
487 }
488 return 1.0f / (1.0f + maximum_distortion);
489 }
490
492 {
493 const Result &input = this->get_input("Type");
494 const MenuValue default_menu_value = MenuValue(CMP_NODE_LENS_DISTORTION_RADIAL);
495 const MenuValue menu_value = input.get_single_value_default(default_menu_value);
496 return CMPNodeLensDistortionType(menu_value.value);
497 }
498
500 {
501 return this->get_input("Jitter").get_single_value_default(false);
502 }
503
505 {
506 return this->get_input("Fit").get_single_value_default(false);
507 }
508
509 /* Returns true if the operation does nothing and the input can be passed through. */
511 {
512 /* The input is a single value and the operation does nothing. */
513 if (this->get_input("Image").is_single_value()) {
514 return true;
515 }
516
517 /* Projector have zero dispersion and does nothing. */
519 return this->get_dispersion() == 0.0f;
520 }
521
522 /* Both distortion and dispersion are zero and the operation does nothing. Jittering has an
523 * effect regardless, so its gets an exemption. */
524 if (!this->get_use_jitter() && this->get_distortion() == 0.0f &&
525 this->get_dispersion() == 0.0f)
526 {
527 return true;
528 }
529
530 return false;
531 }
532};
533
535{
536 return new LensDistortionOperation(context, node);
537}
538
539} // namespace blender::nodes::node_composite_lensdist_cc
540
542{
544
545 static blender::bke::bNodeType ntype;
546
547 cmp_node_type_base(&ntype, "CompositorNodeLensdist", CMP_NODE_LENSDIST);
548 ntype.ui_name = "Lens Distortion";
549 ntype.ui_description = "Simulate distortion and dispersion from camera lenses";
550 ntype.enum_name_legacy = "LENSDIST";
552 ntype.declare = file_ns::cmp_node_lensdist_declare;
553 ntype.initfunc = file_ns::node_composit_init_lensdist;
556 ntype.get_compositor_operation = file_ns::get_compositor_operation;
557
559}
#define NODE_CLASS_DISTORT
Definition BKE_node.hh:455
#define CMP_NODE_LENSDIST
MINLINE float max_fff(float a, float b, float c)
MINLINE float clamp_f(float value, float min, float max)
CMPNodeLensDistortionType
@ CMP_NODE_LENS_DISTORTION_RADIAL
@ CMP_NODE_LENS_DISTORTION_HORIZONTAL
void GPU_shader_uniform_1f(blender::gpu::Shader *sh, const char *name, float value)
void GPU_shader_uniform_3fv(blender::gpu::Shader *sh, const char *name, const float data[3])
void GPU_shader_bind(blender::gpu::Shader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
void GPU_shader_unbind()
void GPU_texture_extend_mode(blender::gpu::Texture *texture, GPUSamplerExtendMode extend_mode)
@ GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER
void GPU_texture_filter_mode(blender::gpu::Texture *texture, bool use_filter)
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ PROP_FACTOR
Definition RNA_types.hh:251
BMesh const char void * data
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
static unsigned long seed
Definition btSoftBody.h:39
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
void share_data(const Result &source)
Definition result.cc:523
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 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
#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 parallel_for(const int2 range, const Function &function)
T sqrt(const T &a)
T distance(const T &a, const T &b)
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
T max(const T &a, const T &b)
static int4 compute_number_of_integration_steps(const float3 &chromatic_distortion, const int2 &size, const float2 &uv, const float distance_squared, const bool use_jitter)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
static float4 integrate_distortion(const int2 &texel, const Result &input, const int2 &size, const float3 &chromatic_distortion, const int start, const int end, const float distance_squared, const float2 &uv, const int steps, const bool use_jitter)
static float2 compute_distorted_uv(const float2 &uv, const float uv_scale, const int2 &size)
static float get_jitter(const int2 &texel, const int seed, const bool use_jitter)
static int compute_number_of_integration_steps_heuristic(const float distortion, const bool use_jitter)
static void cmp_node_lensdist_declare(NodeDeclarationBuilder &b)
static float compute_distortion_scale(const float distortion, const float distance_squared)
static void node_composit_init_lensdist(bNodeTree *, bNode *node)
static float3 compute_chromatic_distortion_scale(const float3 &chromatic_distortion, const float distance_squared)
static void radial_lens_distortion(const int2 texel, const Result &input, Result &output, const int2 &size, const float3 &chromatic_distortion, const float scale, const bool use_jitter)
float hash_to_float(uint32_t kx)
Definition noise.cc:233
VecBase< int32_t, 4 > int4
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
VecBase< float, 3 > float3
#define DISTORTION_SCALE
#define HORIZONTAL_DISPERSION_SCALE
#define MINIMUM_DISTORTION
#define RADIAL_DISPERSION_SCALE
static void register_node_type_cmp_lensdist()
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
void * storage
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
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:289
const char * enum_name_legacy
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:362
i
Definition text_draw.cc:230
#define N_(msgid)