Blender V4.3
node_composite_tonemap.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
9#include <cmath>
10
11#include "BLI_assert.h"
12#include "BLI_math_base.hh"
13#include "BLI_math_vector.hh"
15
16#include "RNA_access.hh"
17
18#include "UI_interface.hh"
19#include "UI_resources.hh"
20
22
24#include "COM_node_operation.hh"
25#include "COM_utilities.hh"
26
28
30
32
34{
35 b.add_input<decl::Color>("Image")
36 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
37 .compositor_domain_priority(0);
38 b.add_output<decl::Color>("Image");
39}
40
41static void node_composit_init_tonemap(bNodeTree * /*ntree*/, bNode *node)
42{
43 NodeTonemap *ntm = MEM_cnew<NodeTonemap>(__func__);
44 ntm->type = 1;
45 ntm->key = 0.18;
46 ntm->offset = 1;
47 ntm->gamma = 1;
48 ntm->f = 0;
49 ntm->m = 0; /* Actual value is set according to input. */
50 /* Default a of 1 works well with natural HDR images, but not always so for CGI.
51 * Maybe should use 0 or at least lower initial value instead. */
52 ntm->a = 1;
53 ntm->c = 0;
54 node->storage = ntm;
55}
56
58{
60
61 col = uiLayoutColumn(layout, false);
62 uiItemR(col, ptr, "tonemap_type", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
63 if (RNA_enum_get(ptr, "tonemap_type") == 0) {
64 uiItemR(col, ptr, "key", UI_ITEM_R_SPLIT_EMPTY_NAME | UI_ITEM_R_SLIDER, nullptr, ICON_NONE);
65 uiItemR(col, ptr, "offset", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE);
66 uiItemR(col, ptr, "gamma", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE);
67 }
68 else {
69 uiItemR(col, ptr, "intensity", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE);
70 uiItemR(
71 col, ptr, "contrast", UI_ITEM_R_SPLIT_EMPTY_NAME | UI_ITEM_R_SLIDER, nullptr, ICON_NONE);
72 uiItemR(
73 col, ptr, "adaptation", UI_ITEM_R_SPLIT_EMPTY_NAME | UI_ITEM_R_SLIDER, nullptr, ICON_NONE);
74 uiItemR(
75 col, ptr, "correction", UI_ITEM_R_SPLIT_EMPTY_NAME | UI_ITEM_R_SLIDER, nullptr, ICON_NONE);
76 }
77}
78
79using namespace blender::realtime_compositor;
80
82 public:
84
85 void execute() override
86 {
87 Result &input_image = get_input("Image");
88 Result &output_image = get_result("Image");
89 if (input_image.is_single_value()) {
90 input_image.pass_through(output_image);
91 return;
92 }
93
94 switch (get_type()) {
97 return;
100 return;
101 default:
103 return;
104 }
105 }
106
107 /* Tone mapping based on equation (3) from Reinhard, Erik, et al. "Photographic tone reproduction
108 * for digital images." Proceedings of the 29th annual conference on Computer graphics and
109 * interactive techniques. 2002. */
111 {
112 const float luminance_scale = compute_luminance_scale();
113 const float luminance_scale_blend_factor = compute_luminance_scale_blend_factor();
114 const float gamma = node_storage(bnode()).gamma;
115 const float inverse_gamma = gamma != 0.0f ? 1.0f / gamma : 0.0f;
116
117 GPUShader *shader = context().get_shader("compositor_tone_map_simple");
118 GPU_shader_bind(shader);
119
120 GPU_shader_uniform_1f(shader, "luminance_scale", luminance_scale);
121 GPU_shader_uniform_1f(shader, "luminance_scale_blend_factor", luminance_scale_blend_factor);
122 GPU_shader_uniform_1f(shader, "inverse_gamma", inverse_gamma);
123
124 const Result &input_image = get_input("Image");
125 input_image.bind_as_texture(shader, "input_tx");
126
127 const Domain domain = compute_domain();
128 Result &output_image = get_result("Image");
129 output_image.allocate_texture(domain);
130 output_image.bind_as_image(shader, "output_img");
131
132 compute_dispatch_threads_at_least(shader, domain.size);
133
135 output_image.unbind_as_image();
136 input_image.unbind_as_texture();
137 }
138
139 /* Computes the scaling factor in equation (2) from Reinhard's 2002 paper. */
141 {
142 const float geometric_mean = compute_geometric_mean_of_luminance();
143 return geometric_mean != 0.0 ? node_storage(bnode()).key / geometric_mean : 0.0f;
144 }
145
146 /* Computes equation (1) from Reinhard's 2002 paper. However, note that the equation in the paper
147 * is most likely wrong, and the intention is actually to compute the geometric mean through a
148 * logscale arithmetic mean, that is, the division should happen inside the exponential function,
149 * not outside of it. That's because the sum of the log luminance will be a very large negative
150 * number, whose exponential will almost always be zero, which is unexpected and useless. */
152 {
153 return std::exp(compute_average_log_luminance());
154 }
155
156 /* Equation (3) from Reinhard's 2002 paper blends between high luminance scaling for high
157 * luminance values and low luminance scaling for low luminance values. This is done by adding 1
158 * to the denominator, since for low luminance values, the denominator will be close to 1 and for
159 * high luminance values, the 1 in the denominator will be relatively insignificant. But the
160 * response of such function is not always ideal, so in this implementation, the 1 was exposed as
161 * a parameter to the user for more flexibility. */
163 {
164 return node_storage(bnode()).offset;
165 }
166
167 /* Tone mapping based on equation (1) and the trilinear interpolation between equations (6) and
168 * (7) from Reinhard, Erik, and Kate Devlin. "Dynamic range reduction inspired by photoreceptor
169 * physiology." IEEE transactions on visualization and computer graphics 11.1 (2005): 13-24. */
171 {
172 const float4 global_adaptation_level = compute_global_adaptation_level();
173 const float contrast = compute_contrast();
174 const float intensity = compute_intensity();
175 const float chromatic_adaptation = get_chromatic_adaptation();
176 const float light_adaptation = get_light_adaptation();
177
178 GPUShader *shader = context().get_shader("compositor_tone_map_photoreceptor");
179 GPU_shader_bind(shader);
180
181 GPU_shader_uniform_4fv(shader, "global_adaptation_level", global_adaptation_level);
182 GPU_shader_uniform_1f(shader, "contrast", contrast);
183 GPU_shader_uniform_1f(shader, "intensity", intensity);
184 GPU_shader_uniform_1f(shader, "chromatic_adaptation", chromatic_adaptation);
185 GPU_shader_uniform_1f(shader, "light_adaptation", light_adaptation);
186
187 float luminance_coefficients[3];
189 GPU_shader_uniform_3fv(shader, "luminance_coefficients", luminance_coefficients);
190
191 const Result &input_image = get_input("Image");
192 input_image.bind_as_texture(shader, "input_tx");
193
194 const Domain domain = compute_domain();
195 Result &output_image = get_result("Image");
196 output_image.allocate_texture(domain);
197 output_image.bind_as_image(shader, "output_img");
198
199 compute_dispatch_threads_at_least(shader, domain.size);
200
202 output_image.unbind_as_image();
203 input_image.unbind_as_texture();
204 }
205
206 /* Computes the global adaptation level from the trilinear interpolation equations constructed
207 * from equations (6) and (7) in Reinhard's 2005 paper. */
209 {
210 const float4 average_color = compute_average_color();
211 const float average_luminance = compute_average_luminance();
212 const float chromatic_adaptation = get_chromatic_adaptation();
213 return math::interpolate(float4(average_luminance), average_color, chromatic_adaptation);
214 }
215
217 {
218 /* The average color will reduce to zero if chromatic adaptation is zero, so just return zero
219 * in this case to avoid needlessly computing the average. See the trilinear interpolation
220 * equations constructed from equations (6) and (7) in Reinhard's 2005 paper. */
221 if (get_chromatic_adaptation() == 0.0f) {
222 return float4(0.0f);
223 }
224
225 const Result &input = get_input("Image");
226 return sum_color(context(), input) / (input.domain().size.x * input.domain().size.y);
227 }
228
230 {
231 /* The average luminance will reduce to zero if chromatic adaptation is one, so just return
232 * zero in this case to avoid needlessly computing the average. See the trilinear interpolation
233 * equations constructed from equations (6) and (7) in Reinhard's 2005 paper. */
234 if (get_chromatic_adaptation() == 1.0f) {
235 return 0.0f;
236 }
237
238 float luminance_coefficients[3];
240 const Result &input = get_input("Image");
241 float sum = sum_luminance(context(), input, luminance_coefficients);
242 return sum / (input.domain().size.x * input.domain().size.y);
243 }
244
245 /* Computes equation (5) from Reinhard's 2005 paper. */
247 {
248 return std::exp(-node_storage(bnode()).f);
249 }
250
251 /* If the contrast is not zero, return it, otherwise, a zero contrast denote automatic derivation
252 * of the contrast value based on equations (2) and (4) from Reinhard's 2005 paper. */
254 {
255 if (node_storage(bnode()).m != 0.0f) {
256 return node_storage(bnode()).m;
257 }
258
259 const float log_maximum_luminance = compute_log_maximum_luminance();
260 const float log_minimum_luminance = compute_log_minimum_luminance();
261
262 /* This is merely to guard against zero division later. */
263 if (log_maximum_luminance == log_minimum_luminance) {
264 return 1.0f;
265 }
266
267 const float average_log_luminance = compute_average_log_luminance();
268 const float dynamic_range = log_maximum_luminance - log_minimum_luminance;
269 const float luminance_key = (log_maximum_luminance - average_log_luminance) / (dynamic_range);
270
271 return 0.3f + 0.7f * std::pow(luminance_key, 1.4f);
272 }
273
275 {
276 const Result &input_image = get_input("Image");
277
278 float luminance_coefficients[3];
280 const float sum_of_log_luminance = sum_log_luminance(
281 context(), input_image, luminance_coefficients);
282
283 return sum_of_log_luminance / (input_image.domain().size.x * input_image.domain().size.y);
284 }
285
287 {
288 float luminance_coefficients[3];
290 const float maximum = maximum_luminance(context(), get_input("Image"), luminance_coefficients);
291 return std::log(math::max(maximum, 1e-5f));
292 }
293
295 {
296 float luminance_coefficients[3];
298 const float minimum = minimum_luminance(context(), get_input("Image"), luminance_coefficients);
299 return std::log(math::max(minimum, 1e-5f));
300 }
301
303 {
304 return node_storage(bnode()).c;
305 }
306
308 {
309 return node_storage(bnode()).a;
310 }
311
313 {
314 return static_cast<CMPNodeToneMapType>(node_storage(bnode()).type);
315 }
316};
317
319{
320 return new ToneMapOperation(context, node);
321}
322
323} // namespace blender::nodes::node_composite_tonemap_cc
324
326{
328
329 static blender::bke::bNodeType ntype;
330
331 cmp_node_type_base(&ntype, CMP_NODE_TONEMAP, "Tonemap", NODE_CLASS_OP_COLOR);
332 ntype.declare = file_ns::cmp_node_tonemap_declare;
333 ntype.draw_buttons = file_ns::node_composit_buts_tonemap;
334 ntype.initfunc = file_ns::node_composit_init_tonemap;
337 ntype.get_compositor_operation = file_ns::get_compositor_operation;
338
340}
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1799
#define NODE_CLASS_OP_COLOR
Definition BKE_node.hh:406
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
CMPNodeToneMapType
@ CMP_NODE_TONE_MAP_PHOTORECEPTOR
@ CMP_NODE_TONE_MAP_SIMPLE
void GPU_shader_uniform_1f(GPUShader *sh, const char *name, float value)
void GPU_shader_uniform_3fv(GPUShader *sh, const char *name, const float data[3])
void GPU_shader_bind(GPUShader *shader)
void GPU_shader_uniform_4fv(GPUShader *sh, const char *name, const float data[4])
void GPU_shader_unbind()
BLI_INLINE void IMB_colormanagement_get_luminance_coefficients(float r_rgb[3])
uiLayout * uiLayoutColumn(uiLayout *layout, bool align)
void uiItemR(uiLayout *layout, PointerRNA *ptr, const char *propname, eUI_Item_Flag flag, const char *name, int icon)
@ UI_ITEM_R_SPLIT_EMPTY_NAME
@ UI_ITEM_R_SLIDER
struct GPUShader GPUShader
static T sum(const btAlignedObjectArray< T > &items)
GPUShader * get_shader(const char *info_name, ResultPrecision precision)
NodeOperation(Context &context, DNode node)
Result & get_input(StringRef identifier) const
Definition operation.cc:144
Result & get_result(StringRef identifier)
Definition operation.cc:46
void bind_as_image(GPUShader *shader, const char *image_name, bool read=false) const
Definition result.cc:264
void pass_through(Result &target)
Definition result.cc:289
const Domain & domain() const
Definition result.cc:712
void allocate_texture(Domain domain, bool from_pool=true)
Definition result.cc:204
void bind_as_texture(GPUShader *shader, const char *texture_name) const
Definition result.cc:253
local_group_size(16, 16) .push_constant(Type b
uint col
void node_type_storage(bNodeType *ntype, const char *storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:4632
void node_register_type(bNodeType *ntype)
Definition node.cc:1708
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 node_composit_init_tonemap(bNodeTree *, bNode *node)
static void node_composit_buts_tonemap(uiLayout *layout, bContext *, PointerRNA *ptr)
static void cmp_node_tonemap_declare(NodeDeclarationBuilder &b)
float sum_log_luminance(Context &context, GPUTexture *texture, float3 luminance_coefficients)
float4 sum_color(Context &context, GPUTexture *texture)
float sum_luminance(Context &context, GPUTexture *texture, float3 luminance_coefficients)
float maximum_luminance(Context &context, GPUTexture *texture, float3 luminance_coefficients)
float minimum_luminance(Context &context, GPUTexture *texture, float3 luminance_coefficients)
void compute_dispatch_threads_at_least(GPUShader *shader, int2 threads_range, int2 local_size=int2(16))
Definition utilities.cc:131
VecBase< float, 4 > float4
void register_node_type_cmp_tonemap()
void cmp_node_type_base(blender::bke::bNodeType *ntype, int type, const char *name, short nclass)
void node_free_standard_storage(bNode *node)
Definition node_util.cc:46
void node_copy_standard_storage(bNodeTree *, bNode *dest_node, const bNode *src_node)
Definition node_util.cc:58
int RNA_enum_get(PointerRNA *ptr, const char *name)
Defines a node type.
Definition BKE_node.hh:218
NodeGetCompositorOperationFunction get_compositor_operation
Definition BKE_node.hh:324
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:267
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:238
NodeDeclareFunction declare
Definition BKE_node.hh:347
PointerRNA * ptr
Definition wm_files.cc:4126