Blender V4.3
gabor.h
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Foundation
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
5/* Implements Gabor noise based on the paper:
6 *
7 * Lagae, Ares, et al. "Procedural noise using sparse Gabor convolution." ACM Transactions on
8 * Graphics (TOG) 28.3 (2009): 1-10.
9 *
10 * But with the improvements from the paper:
11 *
12 * Tavernier, Vincent, et al. "Making gabor noise fast and normalized." Eurographics 2019-40th
13 * Annual Conference of the European Association for Computer Graphics. 2019.
14 *
15 * And compute the Phase and Intensity of the Gabor based on the paper:
16 *
17 * Tricard, Thibault, et al. "Procedural phasor noise." ACM Transactions on Graphics (TOG) 38.4
18 * (2019): 1-13.
19 */
20
21#pragma once
22
24
25/* The original Gabor noise paper specifies that the impulses count for each cell should be
26 * computed by sampling a Poisson distribution whose mean is the impulse density. However,
27 * Tavernier's paper showed that stratified Poisson point sampling is better assuming the weights
28 * are sampled using a Bernoulli distribution, as shown in Figure (3). By stratified sampling, they
29 * mean a constant number of impulses per cell, so the stratification is the grid itself in that
30 * sense, as described in the supplementary material of the paper. */
31#define IMPULSES_COUNT 8
32
33/* Computes a 2D Gabor kernel based on Equation (6) in the original Gabor noise paper. Where the
34 * frequency argument is the F_0 parameter and the orientation argument is the w_0 parameter. We
35 * assume the Gaussian envelope has a unit magnitude, that is, K = 1. That is because we will
36 * eventually normalize the final noise value to the unit range, so the multiplication by the
37 * magnitude will be canceled by the normalization. Further, we also assume a unit Gaussian width,
38 * that is, a = 1. That is because it does not provide much artistic control. It follows that the
39 * Gaussian will be truncated at pi.
40 *
41 * To avoid the discontinuities caused by the aforementioned truncation, the Gaussian is windowed
42 * using a Hann window, that is because contrary to the claim made in the original Gabor paper,
43 * truncating the Gaussian produces significant artifacts especially when differentiated for bump
44 * mapping. The Hann window is C1 continuous and has limited effect on the shape of the Gaussian,
45 * so it felt like an appropriate choice.
46 *
47 * Finally, instead of computing the Gabor value directly, we instead use the complex phasor
48 * formulation described in section 3.1.1 in Tricard's paper. That's done to be able to compute the
49 * phase and intensity of the Gabor noise after summation based on equations (8) and (9). The
50 * return value of the Gabor kernel function is then a complex number whose real value is the
51 * value computed in the original Gabor noise paper, and whose imaginary part is the sine
52 * counterpart of the real part, which is the only extra computation in the new formulation.
53 *
54 * Note that while the original Gabor noise paper uses the cosine part of the phasor, that is, the
55 * real part of the phasor, we use the sine part instead, that is, the imaginary part of the
56 * phasor, as suggested by Tavernier's paper in "Section 3.3. Instance stationarity and
57 * normalization", to ensure a zero mean, which should help with normalization. */
58ccl_device float2 compute_2d_gabor_kernel(float2 position, float frequency, float orientation)
59{
60 float distance_squared = dot(position, position);
61 float hann_window = 0.5f + 0.5f * cosf(M_PI_F * distance_squared);
62 float gaussian_envelop = expf(-M_PI_F * distance_squared);
63 float windowed_gaussian_envelope = gaussian_envelop * hann_window;
64
65 float2 frequency_vector = frequency * make_float2(cosf(orientation), sinf(orientation));
66 float angle = 2.0f * M_PI_F * dot(position, frequency_vector);
67 float2 phasor = make_float2(cosf(angle), sinf(angle));
68
69 return windowed_gaussian_envelope * phasor;
70}
71
72/* Computes the approximate standard deviation of the zero mean normal distribution representing
73 * the amplitude distribution of the noise based on Equation (9) in the original Gabor noise paper.
74 * For simplicity, the Hann window is ignored and the orientation is fixed since the variance is
75 * orientation invariant. We start integrating the squared Gabor kernel with respect to x:
76 *
77 * \int_{-\infty}^{-\infty} (e^{- \pi (x^2 + y^2)} cos(2 \pi f_0 x))^2 dx
78 *
79 * Which gives:
80 *
81 * \frac{(e^{2 \pi f_0^2}-1) e^{-2 \pi y^2 - 2 pi f_0^2}}{2^\frac{3}{2}}
82 *
83 * Then we similarly integrate with respect to y to get:
84 *
85 * \frac{1 - e^{-2 \pi f_0^2}}{4}
86 *
87 * Secondly, we note that the second moment of the weights distribution is 0.5 since it is a
88 * fair Bernoulli distribution. So the final standard deviation expression is square root the
89 * integral multiplied by the impulse density multiplied by the second moment.
90 *
91 * Note however that the integral is almost constant for all frequencies larger than one, and
92 * converges to an upper limit as the frequency approaches infinity, so we replace the expression
93 * with the following limit:
94 *
95 * \lim_{x \to \infty} \frac{1 - e^{-2 \pi f_0^2}}{4}
96 *
97 * To get an approximation of 0.25. */
99{
100 float integral_of_gabor_squared = 0.25f;
101 float second_moment = 0.5f;
102 return sqrtf(IMPULSES_COUNT * second_moment * integral_of_gabor_squared);
103}
104
105/* Computes the Gabor noise value at the given position for the given cell. This is essentially the
106 * sum in Equation (8) in the original Gabor noise paper, where we sum Gabor kernels sampled at a
107 * random position with a random weight. The orientation of the kernel is constant for anisotropic
108 * noise while it is random for isotropic noise. The original Gabor noise paper mentions that the
109 * weights should be uniformly distributed in the [-1, 1] range, however, Tavernier's paper showed
110 * that using a Bernoulli distribution yields better results, so that is what we do. */
112 float2 cell, float2 position, float frequency, float isotropy, float base_orientation)
113
114{
115 float2 noise = make_float2(0.0f, 0.0f);
116 for (int i = 0; i < IMPULSES_COUNT; ++i) {
117 /* Compute unique seeds for each of the needed random variables. */
118 float3 seed_for_orientation = make_float3(cell.x, cell.y, i * 3);
119 float3 seed_for_kernel_center = make_float3(cell.x, cell.y, i * 3 + 1);
120 float3 seed_for_weight = make_float3(cell.x, cell.y, i * 3 + 2);
121
122 /* For isotropic noise, add a random orientation amount, while for anisotropic noise, use the
123 * base orientation. Linearly interpolate between the two cases using the isotropy factor. Note
124 * that the random orientation range spans pi as opposed to two pi, that's because the Gabor
125 * kernel is symmetric around pi. */
126 float random_orientation = (hash_float3_to_float(seed_for_orientation) - 0.5f) * M_PI_F;
127 float orientation = base_orientation + random_orientation * isotropy;
128
129 float2 kernel_center = hash_float3_to_float2(seed_for_kernel_center);
130 float2 position_in_kernel_space = position - kernel_center;
131
132 /* The kernel is windowed beyond the unit distance, so early exit with a zero for points that
133 * are further than a unit radius. */
134 if (dot(position_in_kernel_space, position_in_kernel_space) >= 1.0f) {
135 continue;
136 }
137
138 /* We either add or subtract the Gabor kernel based on a Bernoulli distribution of equal
139 * probability. */
140 float weight = hash_float3_to_float(seed_for_weight) < 0.5f ? -1.0f : 1.0f;
141
142 noise += weight * compute_2d_gabor_kernel(position_in_kernel_space, frequency, orientation);
143 }
144 return noise;
145}
146
147/* Computes the Gabor noise value by dividing the space into a grid and evaluating the Gabor noise
148 * in the space of each cell of the 3x3 cell neighborhood. */
150 float frequency,
151 float isotropy,
152 float base_orientation)
153{
154 float2 cell_position = floor(coordinates);
155 float2 local_position = coordinates - cell_position;
156
157 float2 sum = make_float2(0.0f, 0.0f);
158 for (int j = -1; j <= 1; j++) {
159 for (int i = -1; i <= 1; i++) {
160 float2 cell_offset = make_float2(i, j);
161 float2 current_cell_position = cell_position + cell_offset;
162 float2 position_in_cell_space = local_position - cell_offset;
164 current_cell_position, position_in_cell_space, frequency, isotropy, base_orientation);
165 }
166 }
167
168 return sum;
169}
170
171/* Identical to compute_2d_gabor_kernel, except it is evaluated in 3D space. Notice that Equation
172 * (6) in the original Gabor noise paper computes the frequency vector using (cos(w_0), sin(w_0)),
173 * which we also do in the 2D variant, however, for 3D, the orientation is already a unit frequency
174 * vector, so we just need to scale it by the frequency value. */
175ccl_device float2 compute_3d_gabor_kernel(float3 position, float frequency, float3 orientation)
176{
177 float distance_squared = dot(position, position);
178 float hann_window = 0.5f + 0.5f * cosf(M_PI_F * distance_squared);
179 float gaussian_envelop = expf(-M_PI_F * distance_squared);
180 float windowed_gaussian_envelope = gaussian_envelop * hann_window;
181
182 float3 frequency_vector = frequency * orientation;
183 float angle = 2.0f * M_PI_F * dot(position, frequency_vector);
184 float2 phasor = make_float2(cosf(angle), sinf(angle));
185
186 return windowed_gaussian_envelope * phasor;
187}
188
189/* Identical to compute_2d_gabor_standard_deviation except we do triple integration in 3D. The only
190 * difference is the denominator in the integral expression, which is `2^{5 / 2}` for the 3D case
191 * instead of 4 for the 2D case. Similarly, the limit evaluates to `1 / (4 * sqrt(2))`. */
193{
194 float integral_of_gabor_squared = 1.0f / (4.0f * M_SQRT2_F);
195 float second_moment = 0.5f;
196 return sqrtf(IMPULSES_COUNT * second_moment * integral_of_gabor_squared);
197}
198
199/* Computes the orientation of the Gabor kernel such that it is constant for anisotropic
200 * noise while it is random for isotropic noise. We randomize in spherical coordinates for a
201 * uniform distribution. */
202ccl_device float3 compute_3d_orientation(float3 orientation, float isotropy, float4 seed)
203{
204 /* Return the base orientation in case we are completely anisotropic. */
205 if (isotropy == 0.0f) {
206 return orientation;
207 }
208
209 /* Compute the orientation in spherical coordinates. */
210 float inclination = acos(orientation.z);
211 float azimuth = (orientation.y < 0.0f ? -1.0f : 1.0f) *
212 acos(orientation.x / len(make_float2(orientation.x, orientation.y)));
213
214 /* For isotropic noise, add a random orientation amount, while for anisotropic noise, use the
215 * base orientation. Linearly interpolate between the two cases using the isotropy factor. Note
216 * that the random orientation range is to pi as opposed to two pi, that's because the Gabor
217 * kernel is symmetric around pi. */
218 float2 random_angles = hash_float4_to_float2(seed) * M_PI_F;
219 inclination += random_angles.x * isotropy;
220 azimuth += random_angles.y * isotropy;
221
222 /* Convert back to Cartesian coordinates, */
223 return make_float3(
224 sinf(inclination) * cosf(azimuth), sinf(inclination) * sinf(azimuth), cosf(inclination));
225}
226
228 float3 cell, float3 position, float frequency, float isotropy, float3 base_orientation)
229
230{
231 float2 noise = make_float2(0.0f, 0.0f);
232 for (int i = 0; i < IMPULSES_COUNT; ++i) {
233 /* Compute unique seeds for each of the needed random variables. */
234 float4 seed_for_orientation = make_float4(cell.x, cell.y, cell.z, i * 3);
235 float4 seed_for_kernel_center = make_float4(cell.x, cell.y, cell.z, i * 3 + 1);
236 float4 seed_for_weight = make_float4(cell.x, cell.y, cell.z, i * 3 + 2);
237
238 float3 orientation = compute_3d_orientation(base_orientation, isotropy, seed_for_orientation);
239
240 float3 kernel_center = hash_float4_to_float3(seed_for_kernel_center);
241 float3 position_in_kernel_space = position - kernel_center;
242
243 /* The kernel is windowed beyond the unit distance, so early exit with a zero for points that
244 * are further than a unit radius. */
245 if (dot(position_in_kernel_space, position_in_kernel_space) >= 1.0f) {
246 continue;
247 }
248
249 /* We either add or subtract the Gabor kernel based on a Bernoulli distribution of equal
250 * probability. */
251 float weight = hash_float4_to_float(seed_for_weight) < 0.5f ? -1.0f : 1.0f;
252
253 noise += weight * compute_3d_gabor_kernel(position_in_kernel_space, frequency, orientation);
254 }
255 return noise;
256}
257
258/* Identical to compute_2d_gabor_noise but works in the 3D neighborhood of the noise. */
260 float frequency,
261 float isotropy,
262 float3 base_orientation)
263{
264 float3 cell_position = floor(coordinates);
265 float3 local_position = coordinates - cell_position;
266
267 float2 sum = make_float2(0.0f, 0.0f);
268 for (int k = -1; k <= 1; k++) {
269 for (int j = -1; j <= 1; j++) {
270 for (int i = -1; i <= 1; i++) {
271 float3 cell_offset = make_float3(i, j, k);
272 float3 current_cell_position = cell_position + cell_offset;
273 float3 position_in_cell_space = local_position - cell_offset;
275 current_cell_position, position_in_cell_space, frequency, isotropy, base_orientation);
276 }
277 }
278 }
279
280 return sum;
281}
282
285 ccl_private float *stack,
286 uint type,
287 uint stack_offsets_1,
288 uint stack_offsets_2,
289 int offset)
290{
291 uint coordinates_stack_offset;
292 uint scale_stack_offset;
293 uint frequency_stack_offset;
294 uint anisotropy_stack_offset;
295 uint orientation_2d_stack_offset;
296 uint orientation_3d_stack_offset;
297
298 svm_unpack_node_uchar4(stack_offsets_1,
299 &coordinates_stack_offset,
300 &scale_stack_offset,
301 &frequency_stack_offset,
302 &anisotropy_stack_offset);
304 stack_offsets_2, &orientation_2d_stack_offset, &orientation_3d_stack_offset);
305
306 float3 coordinates = stack_load_float3(stack, coordinates_stack_offset);
307
308 uint value_stack_offset;
309 uint phase_stack_offset;
310 uint intensity_stack_offset;
311
312 uint4 node_1 = read_node(kg, &offset);
314 node_1.x, &value_stack_offset, &phase_stack_offset, &intensity_stack_offset);
315 float scale = stack_load_float_default(stack, scale_stack_offset, node_1.y);
316 float frequency = stack_load_float_default(stack, frequency_stack_offset, node_1.z);
317 float anisotropy = stack_load_float_default(stack, anisotropy_stack_offset, node_1.w);
318
319 uint4 node_2 = read_node(kg, &offset);
320 float orientation_2d = stack_load_float_default(stack, orientation_2d_stack_offset, node_2.x);
321 float3 orientation_3d = stack_load_float3(stack, orientation_3d_stack_offset);
322
323 float3 scaled_coordinates = coordinates * scale;
324 float isotropy = 1.0f - clamp(anisotropy, 0.0f, 1.0f);
325 frequency = max(0.001f, frequency);
326
327 float2 phasor = make_float2(0.0f, 0.0f);
328 float standard_deviation = 1.0f;
329 switch ((NodeGaborType)type) {
330 case NODE_GABOR_TYPE_2D: {
331 phasor = compute_2d_gabor_noise(make_float2(scaled_coordinates.x, scaled_coordinates.y),
332 frequency,
333 isotropy,
334 orientation_2d);
335 standard_deviation = compute_2d_gabor_standard_deviation();
336 break;
337 }
338 case NODE_GABOR_TYPE_3D: {
339 float3 orientation = normalize(orientation_3d);
340 phasor = compute_3d_gabor_noise(scaled_coordinates, frequency, isotropy, orientation);
341 standard_deviation = compute_3d_gabor_standard_deviation();
342 break;
343 }
344 }
345
346 /* Normalize the noise by dividing by six times the standard deviation, which was determined
347 * empirically. */
348 float normalization_factor = 6.0f * standard_deviation;
349
350 /* As discussed in compute_2d_gabor_kernel, we use the imaginary part of the phasor as the Gabor
351 * value. But remap to [0, 1] from [-1, 1]. */
352 if (stack_valid(value_stack_offset)) {
353 stack_store_float(stack, value_stack_offset, (phasor.y / normalization_factor) * 0.5f + 0.5f);
354 }
355
356 /* Compute the phase based on equation (9) in Tricard's paper. But remap the phase into the
357 * [0, 1] range. */
358 if (stack_valid(phase_stack_offset)) {
359 float phase = (atan2(phasor.y, phasor.x) + M_PI_F) / (2.0f * M_PI_F);
360 stack_store_float(stack, phase_stack_offset, phase);
361 }
362
363 /* Compute the intensity based on equation (8) in Tricard's paper. */
364 if (stack_valid(intensity_stack_offset)) {
365 stack_store_float(stack, intensity_stack_offset, len(phasor) / normalization_factor);
366 }
367
368 return offset;
369}
370
unsigned int uint
NodeGaborType
static T sum(const btAlignedObjectArray< T > &items)
static unsigned long seed
Definition btSoftBody.h:39
SIMD_FORCE_INLINE btVector3 & normalize()
Normalize this vector x^2 + y^2 + z^2 = 1.
Definition btVector3.h:303
additional_info("compositor_sum_squared_difference_float_shared") .push_constant(Type output_img float dot(value.rgb, luminance_coefficients)") .define("LOAD(value)"
const KernelGlobalsCPU *ccl_restrict KernelGlobals
#define sinf(x)
#define cosf(x)
#define ccl_device
#define expf(x)
#define ccl_private
#define ccl_device_noinline
#define CCL_NAMESPACE_END
ccl_device_forceinline float4 make_float4(const float x, const float y, const float z, const float w)
ccl_device_forceinline float3 make_float3(const float x, const float y, const float z)
ccl_device_forceinline float2 make_float2(const float x, const float y)
#define sqrtf(x)
int len
ccl_device float2 compute_2d_gabor_noise(float2 coordinates, float frequency, float isotropy, float base_orientation)
Definition gabor.h:149
ccl_device float compute_3d_gabor_standard_deviation()
Definition gabor.h:192
ccl_device float2 compute_3d_gabor_noise_cell(float3 cell, float3 position, float frequency, float isotropy, float3 base_orientation)
Definition gabor.h:227
ccl_device float2 compute_2d_gabor_kernel(float2 position, float frequency, float orientation)
Definition gabor.h:58
ccl_device float3 compute_3d_orientation(float3 orientation, float isotropy, float4 seed)
Definition gabor.h:202
ccl_device float compute_2d_gabor_standard_deviation()
Definition gabor.h:98
ccl_device float2 compute_2d_gabor_noise_cell(float2 cell, float2 position, float frequency, float isotropy, float base_orientation)
Definition gabor.h:111
#define IMPULSES_COUNT
Definition gabor.h:31
ccl_device float2 compute_3d_gabor_kernel(float3 position, float frequency, float3 orientation)
Definition gabor.h:175
ccl_device_noinline int svm_node_tex_gabor(KernelGlobals kg, ccl_private ShaderData *sd, ccl_private float *stack, uint type, uint stack_offsets_1, uint stack_offsets_2, int offset)
Definition gabor.h:283
ccl_device float2 compute_3d_gabor_noise(float3 coordinates, float frequency, float isotropy, float3 base_orientation)
Definition gabor.h:259
ccl_device_inline float3 hash_float4_to_float3(float4 k)
Definition hash.h:217
ccl_device_inline float hash_float4_to_float(float4 k)
Definition hash.h:173
ccl_device_inline float2 hash_float3_to_float2(float3 k)
Definition hash.h:231
ccl_device_inline float hash_float3_to_float(float3 k)
Definition hash.h:168
ccl_device_inline float2 hash_float4_to_float2(float4 k)
Definition hash.h:237
CCL_NAMESPACE_BEGIN ccl_device_inline float3 stack_load_float3(ccl_private float *stack, uint a)
ccl_device_inline uint4 read_node(KernelGlobals kg, ccl_private int *offset)
ccl_device_forceinline void svm_unpack_node_uchar3(uint i, ccl_private uint *x, ccl_private uint *y, ccl_private uint *z)
ccl_device_forceinline void svm_unpack_node_uchar2(uint i, ccl_private uint *x, ccl_private uint *y)
ccl_device_inline float stack_load_float_default(ccl_private float *stack, uint a, uint value)
ccl_device_inline void stack_store_float(ccl_private float *stack, uint a, float f)
ccl_device_forceinline void svm_unpack_node_uchar4(uint i, ccl_private uint *x, ccl_private uint *y, ccl_private uint *z, ccl_private uint *w)
ccl_device_inline bool stack_valid(uint a)
@ NODE_GABOR_TYPE_2D
@ NODE_GABOR_TYPE_3D
ShaderData
ccl_device_inline float2 floor(const float2 a)
#define M_PI_F
Definition mikk_util.hh:15
float x
float y
float z
Definition sky_float3.h:27
float y
Definition sky_float3.h:27
float x
Definition sky_float3.h:27
uint x
Definition types_uint4.h:15
uint y
Definition types_uint4.h:15
uint z
Definition types_uint4.h:15
uint w
Definition types_uint4.h:15
float max
ccl_device_inline int clamp(int a, int mn, int mx)
Definition util/math.h:379