Blender V5.0
bokeh_kernel.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <cstdint>
6#include <memory>
7
8#include "BLI_hash.hh"
9#include "BLI_math_base.hh"
10#include "BLI_math_numbers.hh"
11#include "BLI_math_vector.hh"
13
14#include "GPU_shader.hh"
15
16#include "COM_bokeh_kernel.hh"
17#include "COM_context.hh"
18#include "COM_result.hh"
19#include "COM_utilities.hh"
20
21namespace blender::compositor {
22
23/* --------------------------------------------------------------------
24 * Bokeh Kernel Key.
25 */
26
37
43
45{
46 return a.size == b.size && a.sides == b.sides && a.rotation == b.rotation &&
47 a.roundness == b.roundness && a.catadioptric == b.catadioptric &&
48 a.lens_shift == b.lens_shift;
49}
50
51/* --------------------------------------------------------------------
52 * Bokeh Kernel.
53 */
54
56 int2 size,
57 int sides,
58 float rotation,
59 float roundness,
60 float catadioptric,
61 float lens_shift)
62 : result(context.create_result(ResultType::Color))
63{
64 this->result.allocate_texture(Domain(size), false);
65
66 if (context.use_gpu()) {
67 this->compute_gpu(context, sides, rotation, roundness, catadioptric, lens_shift);
68 }
69 else {
70 this->compute_cpu(sides, rotation, roundness, catadioptric, lens_shift);
71 }
72}
73
75{
76 this->result.release();
77}
78
79/* The exterior angle is the angle between each two consecutive vertices of the regular polygon
80 * from its center. */
81static float compute_exterior_angle(int sides)
82{
83 return (math::numbers::pi * 2.0f) / sides;
84}
85
86static float compute_rotation(float angle, int sides)
87{
88 /* Offset the rotation such that the second vertex of the regular polygon lies on the positive
89 * y axis, which is 90 degrees minus the angle that it makes with the positive x axis assuming
90 * the first vertex lies on the positive x axis. */
91 const float offset = (math::numbers::pi / 2.0f) - compute_exterior_angle(sides);
92 return angle - offset;
93}
94
95void BokehKernel::compute_gpu(Context &context,
96 const int sides,
97 const float rotation,
98 const float roundness,
99 const float catadioptric,
100 const float lens_shift)
101{
102 gpu::Shader *shader = context.get_shader("compositor_bokeh_image");
103 GPU_shader_bind(shader);
104
105 GPU_shader_uniform_1f(shader, "exterior_angle", compute_exterior_angle(sides));
106 GPU_shader_uniform_1f(shader, "rotation", compute_rotation(rotation, sides));
107 GPU_shader_uniform_1f(shader, "roundness", roundness);
108 GPU_shader_uniform_1f(shader, "catadioptric", catadioptric);
109 GPU_shader_uniform_1f(shader, "lens_shift", lens_shift);
110
111 this->result.bind_as_image(shader, "output_img");
112
113 compute_dispatch_threads_at_least(shader, this->result.domain().size);
114
115 this->result.unbind_as_image();
117}
118
119/* Get the 2D vertex position of the vertex with the given index in the regular polygon
120 * representing this bokeh. The polygon is rotated by the rotation amount and have a unit
121 * circumradius. The regular polygon is one whose vertices' exterior angles are given by
122 * exterior_angle. See the bokeh function for more information. */
123static float2 get_regular_polygon_vertex_position(const int vertex_index,
124 const float exterior_angle,
125 const float rotation)
126{
127 float angle = exterior_angle * vertex_index - rotation;
129}
130
131/* Find the closest point to the given point on the given line. This assumes the length of the
132 * given line is not zero. */
134 const float2 line_start,
135 const float2 line_end)
136{
137 float2 line_vector = line_end - line_start;
138 float2 point_vector = point - line_start;
139 float line_length_squared = math::dot(line_vector, line_vector);
140 float parameter = math::dot(point_vector, line_vector) / line_length_squared;
141 return line_start + line_vector * parameter;
142}
143
144/* Compute the value of the bokeh at the given point. The computed bokeh is essentially a regular
145 * polygon centered in space having the given circumradius. The regular polygon is one whose
146 * vertices' exterior angles are given by "exterior_angle", which relates to the number of vertices
147 * n through the equation "exterior angle = 2 pi / n". The regular polygon may additionally morph
148 * into a shape with the given properties:
149 *
150 * - The regular polygon may have a circular hole in its center whose radius is controlled by the
151 * "catadioptric" value.
152 * - The regular polygon is rotated by the "rotation" value.
153 * - The regular polygon can morph into a circle controlled by the "roundness" value, such that it
154 * becomes a full circle at unit roundness.
155 *
156 * The function returns 0 when the point lies inside the regular polygon and 1 otherwise. However,
157 * at the edges, it returns a narrow band gradient as a form of anti-aliasing. */
158static float bokeh(const float2 point,
159 const float circumradius,
160 const float exterior_angle,
161 const float rotation,
162 const float roundness,
163 const float catadioptric)
164{
165 if (circumradius == 0.0f) {
166 return 0.0f;
167 }
168
169 /* Get the index of the vertex of the regular polygon whose polar angle is maximum but less than
170 * the polar angle of the given point, taking rotation into account. This essentially finds the
171 * vertex closest to the given point in the clock-wise direction. */
172 float angle = math::mod_periodic(math::atan2(point.y, point.x) + rotation,
174 int vertex_index = int(angle / exterior_angle);
175
176 /* Compute the shortest distance between the origin and the polygon edge composed from the
177 * previously selected vertex and the one following it. */
179 vertex_index, exterior_angle, rotation) *
180 circumradius;
182 vertex_index + 1, exterior_angle, rotation) *
183 circumradius;
184 float2 closest_point = closest_point_on_line(point, first_vertex, second_vertex);
185 float distance_to_edge = math::length(closest_point);
186
187 /* Mix the distance to the edge with the circumradius, making it tend to the distance to a
188 * circle when roundness tends to 1. */
189 float distance_to_edge_round = math::interpolate(distance_to_edge, circumradius, roundness);
190
191 /* The point is outside of the bokeh, so we return 0. */
192 float distance = math::length(point);
193 if (distance > distance_to_edge_round) {
194 return 0.0f;
195 }
196
197 /* The point is inside the catadioptric hole and is not part of the bokeh, so we return 0. */
198 float catadioptric_distance = distance_to_edge_round * catadioptric;
199 if (distance < catadioptric_distance) {
200 return 0.0f;
201 }
202
203 /* The point is very close to the edge of the bokeh, so we return the difference between the
204 * distance to the edge and the distance as a form of anti-aliasing. */
205 if (distance_to_edge_round - distance < 1.0f) {
206 return distance_to_edge_round - distance;
207 }
208
209 /* The point is very close to the edge of the catadioptric hole, so we return the difference
210 * between the distance to the hole and the distance as a form of anti-aliasing. */
211 if (catadioptric != 0.0f && distance - catadioptric_distance < 1.0f) {
212 return distance - catadioptric_distance;
213 }
214
215 /* Otherwise, the point is part of the bokeh and we return 1. */
216 return 1.0f;
217}
218
219static float4 spectral_bokeh(const int2 texel,
220 const int2 size,
221 const float exterior_angle,
222 const float rotation,
223 const float roundness,
224 const float catadioptric,
225 const float lens_shift)
226{
227 /* Since we need the regular polygon to occupy the entirety of the output image, the circumradius
228 * of the regular polygon is half the width of the output image. */
229 float circumradius = float(size.x) / 2.0f;
230
231 /* Move the texel coordinates such that the regular polygon is centered. */
232 float2 point = float2(texel) + float2(0.5f) - circumradius;
233
234 /* Each of the color channels of the output image contains a bokeh with a different circumradius.
235 * The largest one occupies the whole image as stated above, while the other two have circumradii
236 * that are shifted by an amount that is proportional to the "lens_shift" value. The alpha
237 * channel of the output is the average of all three values. */
238 float min_shift = math::abs(lens_shift * circumradius);
239 float min = bokeh(
240 point, circumradius - min_shift, exterior_angle, rotation, roundness, catadioptric);
241
242 float median_shift = min_shift / 2.0f;
243 float median = bokeh(
244 point, circumradius - median_shift, exterior_angle, rotation, roundness, catadioptric);
245
246 float max = bokeh(point, circumradius, exterior_angle, rotation, roundness, catadioptric);
247 float4 bokeh = float4(min, median, max, (max + median + min) / 3.0f);
248
249 /* If the lens shift is negative, swap the min and max bokeh values, which are stored in the red
250 * and blue channels respectively. Note that we take the absolute value of the lens shift above,
251 * so the sign of the lens shift only controls this swap. */
252 if (lens_shift < 0.0f) {
253 bokeh = float4(bokeh.z, bokeh.y, bokeh.x, bokeh.w);
254 }
255
256 return bokeh;
257}
258
259void BokehKernel::compute_cpu(const int sides,
260 const float rotation,
261 const float roundness,
262 const float catadioptric,
263 const float lens_shift)
264{
265 const int2 size = this->result.domain().size;
266 const float exterior_angle = compute_exterior_angle(sides);
267 const float corrected_rotation = compute_rotation(rotation, sides);
268
269 parallel_for(size, [&](const int2 texel) {
270 const float4 bokeh_value = spectral_bokeh(
271 texel, size, exterior_angle, corrected_rotation, roundness, catadioptric, lens_shift);
272 this->result.store_pixel(texel, bokeh_value);
273 });
274}
275
276/* --------------------------------------------------------------------
277 * Bokeh Kernel Container.
278 */
279
281{
282 /* First, delete all resources that are no longer needed. */
283 map_.remove_if([](auto item) { return !item.value->needed; });
284
285 /* Second, reset the needed status of the remaining resources to false to ready them to track
286 * their needed status for the next evaluation. */
287 for (auto &value : map_.values()) {
288 value->needed = false;
289 }
290}
291
293 int2 size,
294 int sides,
295 float rotation,
296 float roundness,
297 float catadioptric,
298 float lens_shift)
299{
300 const BokehKernelKey key(size, sides, rotation, roundness, catadioptric, lens_shift);
301
302 auto &bokeh_kernel = *map_.lookup_or_add_cb(key, [&]() {
303 return std::make_unique<BokehKernel>(
304 context, size, sides, rotation, roundness, catadioptric, lens_shift);
305 });
306
307 bokeh_kernel.needed = true;
308 return bokeh_kernel.result;
309}
310
311} // namespace blender::compositor
void GPU_shader_uniform_1f(blender::gpu::Shader *sh, const char *name, float value)
void GPU_shader_bind(blender::gpu::Shader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
void GPU_shader_unbind()
static double angle(const Eigen::Vector3d &v1, const Eigen::Vector3d &v2)
Definition IK_Math.h:117
unsigned long long int uint64_t
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)
BokehKernelKey(int2 size, int sides, float rotation, float roundness, float catadioptric, float lens_shift)
BokehKernel(Context &context, int2 size, int sides, float rotation, float roundness, float catadioptric, float lens_shift)
nullptr float
float distance(VecOp< float, D >, VecOp< float, D >) RET
void compute_dispatch_threads_at_least(gpu::Shader *shader, int2 threads_range, int2 local_size=int2(16))
Definition utilities.cc:196
static float4 spectral_bokeh(const int2 texel, const int2 size, const float exterior_angle, const float rotation, const float roundness, const float catadioptric, const float lens_shift)
bool operator==(const BokehKernelKey &a, const BokehKernelKey &b)
static float compute_exterior_angle(int sides)
static float2 get_regular_polygon_vertex_position(const int vertex_index, const float exterior_angle, const float rotation)
static float compute_rotation(float angle, int sides)
static float2 closest_point_on_line(const float2 point, const float2 line_start, const float2 line_end)
static float bokeh(const float2 point, const float circumradius, const float exterior_angle, const float rotation, const float roundness, const float catadioptric)
void parallel_for(const int2 range, const Function &function)
T cos(const AngleRadianBase< T > &a)
T length(const VecBase< T, Size > &a)
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
T interpolate(const T &a, const T &b, const FactorT &t)
T atan2(const T &y, const T &x)
T mod_periodic(const T &a, const T &b)
T sin(const AngleRadianBase< T > &a)
T abs(const T &a)
VecBase< float, 4 > float4
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
#define min(a, b)
Definition sort.cc:36
max
Definition text_draw.cc:251