Blender V5.0
fog_glow_kernel.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <complex>
6#include <cstdint>
7#include <memory>
8#include <numeric>
9
10#if defined(WITH_FFTW3)
11# include <fftw3.h>
12#endif
13
15#include "BLI_hash.hh"
16#include "BLI_index_range.hh"
17#include "BLI_math_base.h"
18#include "BLI_math_base.hh"
19#include "BLI_math_vector.hh"
21#include "BLI_task.hh"
22
24
25namespace blender::compositor {
26
27/* --------------------------------------------------------------------
28 * Fog Glow Kernel Key.
29 */
30
37
42
44{
45 return a.kernel_size == b.kernel_size && a.spatial_size == b.spatial_size &&
46 a.field_of_view == b.field_of_view;
47}
48
49/* --------------------------------------------------------------------
50 * Fog Glow Kernel.
51 */
52
53/* Given the texel coordinates and the constant field-of-view-per-pixel value, under the assumption
54 * of a relatively small field of view as discussed in Section 3.2, this function computes the
55 * fog glow kernel value. The kernel value is derived from Equation (5) of the following paper:
56 *
57 * Spencer, Greg, et al. "Physically-Based Glare Effects for Digital Images."
58 * Proceedings of the 22nd Annual Conference on Computer Graphics and Interactive Techniques,
59 * 1995.
60 */
61
62[[maybe_unused]] static float compute_fog_glow_kernel_value(
63 int2 texel, math::AngleRadian field_of_view_per_pixel)
64{
65 const float theta_degree = math::length(float2(texel)) * field_of_view_per_pixel.degree();
66 const float f0 = 2.61f * 1e6f * math::exp(-math::square(theta_degree / 0.02f));
67 const float f1 = 20.91f / math::cube(theta_degree + 0.02f);
68 const float f2 = 72.37f / math::square(theta_degree + 0.02f);
69 const float kernel_value = 0.384f * f0 + 0.478f * f1 + 0.138f * f2;
70
71 return kernel_value;
72}
73
74FogGlowKernel::FogGlowKernel(int kernel_size, int2 spatial_size, math::AngleRadian field_of_view)
75{
76#if defined(WITH_FFTW3)
77
78 /* The FFTW real to complex transforms utilizes the hermitian symmetry of real transforms and
79 * stores only half the output since the other half is redundant, so we only allocate half of
80 * the first dimension. See Section 4.3.4 Real-data DFT Array Format in the FFTW manual for
81 * more information. */
82 const int2 frequency_size = int2(spatial_size.x / 2 + 1, spatial_size.y);
83
84 float *kernel_spatial_domain = fftwf_alloc_real(spatial_size.x * spatial_size.y);
85 frequencies_ = reinterpret_cast<std::complex<float> *>(
86 fftwf_alloc_complex(frequency_size.x * frequency_size.y));
87
88 /* Create a real to complex plan to transform the kernel to the frequency domain. */
89 fftwf_plan forward_plan = fftwf_plan_dft_r2c_2d(spatial_size.y,
90 spatial_size.x,
91 kernel_spatial_domain,
92 reinterpret_cast<fftwf_complex *>(frequencies_),
93 FFTW_ESTIMATE);
94
95 /* Use a double to sum the kernel since floats are not stable with threaded summation. */
96 threading::EnumerableThreadSpecific<double> sum_by_thread([]() { return 0.0; });
97
98 /* Compute the entire kernel's spatial space using compute_fog_glow_kernel_value. */
99 threading::parallel_for(IndexRange(spatial_size.y), 1, [&](const IndexRange sub_y_range) {
100 double &sum = sum_by_thread.local();
101 for (const int64_t y : sub_y_range) {
102 for (const int64_t x : IndexRange(spatial_size.x)) {
103 const int2 texel = int2(x, y);
104 const int2 center_texel = spatial_size / 2;
105 const int2 kernel_texel = texel - center_texel;
106 const math::AngleRadian field_of_view_per_pixel = field_of_view / kernel_size;
107
108 const float kernel_value = compute_fog_glow_kernel_value(kernel_texel,
109 field_of_view_per_pixel);
110 sum += kernel_value;
111
112 /* We offset the computed kernel with wrap around such that it is centered at the zero
113 * point, which is the expected format for doing circular convolutions in the frequency
114 * domain. */
115 int64_t output_x = mod_i(kernel_texel.x, spatial_size.x);
116 int64_t output_y = mod_i(kernel_texel.y, spatial_size.y);
117 kernel_spatial_domain[output_x + output_y * spatial_size.x] = kernel_value;
118 }
119 }
120 });
121
122 fftwf_execute_dft_r2c(
123 forward_plan, kernel_spatial_domain, reinterpret_cast<fftwf_complex *>(frequencies_));
124 fftwf_destroy_plan(forward_plan);
125 fftwf_free(kernel_spatial_domain);
126
127 /* The computed kernel is not normalized and should be normalized, but instead of normalizing the
128 * kernel during computation, we normalize it in the frequency domain when convolving the kernel
129 * to the image since we will be doing sample normalization anyways. This is okay since the
130 * Fourier transform is linear. */
131 normalization_factor_ = float(std::accumulate(sum_by_thread.begin(), sum_by_thread.end(), 0.0));
132#else
133 UNUSED_VARS(kernel_size, spatial_size, field_of_view);
134#endif
135}
136
138{
139#if defined(WITH_FFTW3)
140 fftwf_free(frequencies_);
141#endif
142}
143
144std::complex<float> *FogGlowKernel::frequencies() const
145{
146 return frequencies_;
147}
148
150{
151 return normalization_factor_;
152}
153
154/* --------------------------------------------------------------------
155 * Fog Glow Kernel Container.
156 */
157
159{
160 /* First, delete all resources that are no longer needed. */
161 map_.remove_if([](auto item) { return !item.value->needed; });
162
163 /* Second, reset the needed status of the remaining resources to false to ready them to track
164 * their needed status for the next evaluation. */
165 for (auto &value : map_.values()) {
166 value->needed = false;
167 }
168}
169
171 int2 spatial_size,
172 math::AngleRadian field_of_view)
173{
174 const FogGlowKernelKey key(kernel_size, spatial_size, field_of_view);
175
176 auto &kernel = *map_.lookup_or_add_cb(key, [&]() {
177 return std::make_unique<FogGlowKernel>(kernel_size, spatial_size, field_of_view);
178 });
179
180 kernel.needed = true;
181 return kernel;
182}
183
184} // namespace blender::compositor
#define UNUSED_VARS(...)
unsigned long long int uint64_t
FogGlowKernel & get(int kernel_size, int2 spatial_size, math::AngleRadian field_of_view)
FogGlowKernelKey(int kernel_size, int2 spatial_size, math::AngleRadian field_of_view)
std::complex< float > * frequencies() const
FogGlowKernel(int kernel_size, int2 spatial_size, math::AngleRadian field_of_view)
nullptr float
static float compute_fog_glow_kernel_value(int2 texel, math::AngleRadian field_of_view_per_pixel)
bool operator==(const BokehKernelKey &a, const BokehKernelKey &b)
AngleRadianBase< float > AngleRadian
T length(const VecBase< T, Size > &a)
T exp(const T &x)
T square(const T &a)
T cube(const T &a)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
uint64_t get_default_hash(const T &v, const Args &...args)
Definition BLI_hash.hh:233
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2