1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
/* The Morphological Distance Feather operation is a linear combination between the result of two
* operations. The first operation is a Gaussian blur with a radius equivalent to the dilate/erode
* distance, which is straightforward and implemented as a separable filter similar to the blur
* operation.
*
* The second operation is an approximation of a morphological inverse distance operation evaluated
* at a distance falloff function. The result of a morphological inverse distance operation is a
* narrow band distance field that starts at its maximum value at boundaries where a difference in
* values took place and linearly deceases until it reaches zero in the span of a number of pixels
* equivalent to the erode/dilate distance. Additionally, instead of linearly decreasing, the user
* may choose a different falloff which is evaluated at the computed distance. For dilation, the
* distance field decreases outwards, and for erosion, the distance field decreased inwards.
*
* The reason why the result of a Gaussian blur is mixed in with the distance field is because the
* distance field is merely approximated and not accurately computed, the defects of which is more
* apparent away from boundaries and especially at corners where the distance field should take a
* circular shape. That's why the Gaussian blur is mostly mixed only further from boundaries.
*
* The morphological inverse distance operation is approximated using a separable implementation
* and intertwined with the Gaussian blur implementation as follows. A search window of a radius
* equivalent to the dilate/erode distance is applied on the image to find either the minimum or
* maximum pixel value multiplied by its corresponding falloff value in the window. For dilation,
* we try to find the maximum, and for erosion, we try to find the minimum. Additionally, we also
* save the falloff value where the minimum or maximum was found. The found value will be that of
* the narrow band distance field and the saved falloff value will be used as the mixing factor
* with the Gaussian blur.
*
* To make sense of the aforementioned algorithm, assume we are dilating a binary image by 5 pixels
* whose half has a value of 1 and the other half has a value of zero. Consider the following:
*
* - A pixel of value 1 already has the maximum possible value, so its value will remain unchanged
* regardless of its position.
* - A pixel of value 0 that is right at the boundary of the 1's region will have a maximum value
* of around 0.8 depending on the falloff. That's because the search window intersects the 1's
* region, which when multiplied by the falloff gives the first value of the falloff, which is
* larger than the initially zero value computed at the center of the search window.
* - A pixel of value 0 that is 3 pixels away from the boundary will have a maximum value of around
* 0.4 depending on the falloff. That's because the search window intersects the 1's region,
* which when multiplied by the falloff gives the third value of the falloff, which is larger
* than the initially zero value computed at the center of the search window.
* - Finally, a pixel of value 0 that is 6 pixels away from the boundary will have a maximum value
* of 0, because the search window doesn't intersects the 1's region and only spans zero values.
*
* The previous example demonstrates how the distance field naturally arises, and the same goes for
* the erode case, except the minimum value is computed instead.
*/
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
/* A value for accumulating the blur result. */
float accumulated_value = 0.0;
/* Compute the contribution of the center pixel to the blur result. */
float center_value = texture_load(input_tx, texel).x;
accumulated_value += center_value * texture_load(weights_tx, 0).x;
/* Start with the center value as the maximum/minimum distance and reassign to the true maximum
* or minimum in the search loop below. Additionally, the center falloff is always 1.0, so start
* with that. */
float limit_distance = center_value;
float limit_distance_falloff = 1.0;
/* Compute the contributions of the pixels to the right and left, noting that the weights and
* falloffs textures only store the weights and falloffs for the positive half, but since the
* they are both symmetric, the same weights and falloffs are used for the negative half and we
* compute both of their contributions. */
for (int i = 1; i < texture_size(weights_tx); i++) {
float weight = texture_load(weights_tx, i).x;
float falloff = texture_load(falloffs_tx, i).x;
/* Loop for two iterations, where s takes the value of -1 and 1, which is used as the sign
* needed to evaluated the positive and negative sides as explain above. */
for (int s = -1; s < 2; s += 2) {
/* Compute the contribution of the pixel to the blur result. */
float value = texture_load(input_tx, texel + ivec2(s * i, 0)).x;
accumulated_value += value * weight;
/* The distance is computed such that its highest value is the pixel value itself, so
* multiply the distance falloff by the pixel value. */
float falloff_distance = value * falloff;
/* Find either the maximum or the minimum for the dilate and erode cases respectively. */
if (COMPARE(falloff_distance, limit_distance)) {
limit_distance = falloff_distance;
limit_distance_falloff = falloff;
}
}
}
/* Mix between the limit distance and the blurred accumulated value such that the limit distance
* is used for pixels closer to the boundary and the blurred value is used for pixels away from
* the boundary. */
float value = mix(accumulated_value, limit_distance, limit_distance_falloff);
/* Write the value using the transposed texel. See the execute_distance_feather_horizontal_pass
* method for more information on the rational behind this. */
imageStore(output_img, texel.yx, vec4(value));
}
|