|
2 | 2 | // Use of this source code is governed by a BSD-style license that can be
|
3 | 3 | // found in the LICENSE file.
|
4 | 4 |
|
| 5 | +// The math for this shader was based on the work done in Raph Levien's blog |
| 6 | +// post "Blurred rounded rectangles": |
| 7 | +// https://web.archive.org/web/20231103044404/https://raphlinus.github.io/graphics/2020/04/21/blurred-rounded-rects.html |
| 8 | + |
5 | 9 | precision highp float;
|
6 | 10 |
|
7 | 11 | #include <impeller/gaussian.glsl>
|
8 | 12 | #include <impeller/types.glsl>
|
9 | 13 |
|
10 | 14 | uniform FragInfo {
|
11 | 15 | f16vec4 color;
|
12 |
| - vec2 rect_size; |
13 |
| - float blur_sigma; |
14 |
| - vec2 corner_radii; |
| 16 | + vec2 center; |
| 17 | + vec2 adjust; |
| 18 | + float minEdge; |
| 19 | + float r1; |
| 20 | + float exponent; |
| 21 | + float sInv; |
| 22 | + float exponentInv; |
| 23 | + float scale; |
15 | 24 | }
|
16 | 25 | frag_info;
|
17 | 26 |
|
18 | 27 | in vec2 v_position;
|
19 | 28 |
|
20 | 29 | out f16vec4 frag_color;
|
21 | 30 |
|
22 |
| -const int kSampleCount = 4; |
23 |
| - |
24 |
| -/// Closed form unidirectional rounded rect blur mask solution using the |
25 |
| -/// analytical Gaussian integral (with approximated erf). |
26 |
| -vec4 RRectBlurX(float sample_position_x, |
27 |
| - vec4 sample_position_y, |
28 |
| - vec2 half_size) { |
29 |
| - // The vertical edge of the rrect consists of a flat portion and a curved |
30 |
| - // portion, the two of which vary in size depending on the size of the |
31 |
| - // corner radii, both adding up to half_size.y. |
32 |
| - // half_size.y - corner_radii.y is the size of the vertical flat |
33 |
| - // portion of the rrect. |
34 |
| - // subtracting the absolute value of the Y sample_position will be |
35 |
| - // negative (and then clamped to 0) for positions that are located |
36 |
| - // vertically in the flat part of the rrect, and will be the relative |
37 |
| - // distance from the center of curvature otherwise. |
38 |
| - vec4 space_y = min(vec4(0.0), half_size.y - frag_info.corner_radii.y - |
39 |
| - abs(sample_position_y)); |
40 |
| - // space is now in the range [0.0, corner_radii.y]. If the y sample was |
41 |
| - // in the flat portion of the rrect, it will be 0.0 |
42 |
| - |
43 |
| - // We will now calculate rrect_distance as the distance from the centerline |
44 |
| - // of the rrect towards the near side of the rrect. |
45 |
| - // half_size.x - frag_info.corner_radii.x is the size of the horizontal |
46 |
| - // flat portion of the rrect. |
47 |
| - // We add to that the X size (space_x) of the curved corner measured at |
48 |
| - // the indicated Y coordinate we calculated as space_y, such that: |
49 |
| - // (space_y / corner_radii.y)^2 + (space_x / corner_radii.x)^2 == 1.0 |
50 |
| - // Since we want the space_x, we rearrange the equation as: |
51 |
| - // space_x = corner_radii.x * sqrt(1.0 - (space_y / corner_radii.y)^2) |
52 |
| - // We need to prevent negative values inside the sqrt which can occur |
53 |
| - // when the Y sample was beyond the vertical size of the rrect and thus |
54 |
| - // space_y was larger than corner_radii.y. |
55 |
| - // The calling function RRectBlur will never provide a Y sample outside |
56 |
| - // of that range, though, so the max(0.0) is mostly a precaution. |
57 |
| - vec4 unit_space_y = space_y / frag_info.corner_radii.y; |
58 |
| - vec4 unit_space_x = sqrt(max(vec4(0.0), 1.0 - unit_space_y * unit_space_y)); |
59 |
| - vec4 rrect_distance = |
60 |
| - half_size.x - frag_info.corner_radii.x * (1.0 - unit_space_x); |
| 31 | +const float kTwoOverSqrtPi = 2.0 / sqrt(3.1415926); |
61 | 32 |
|
62 |
| - vec4 result; |
63 |
| - // Now we integrate the Gaussian over the range of the relative positions |
64 |
| - // of the left and right sides of the rrect relative to the sampling |
65 |
| - // X coordinate. |
66 |
| - vec4 integral = IPVec4FastGaussianIntegral( |
67 |
| - float(sample_position_x) + vec4(-rrect_distance[0], rrect_distance[0], |
68 |
| - -rrect_distance[1], rrect_distance[1]), |
69 |
| - float(frag_info.blur_sigma)); |
70 |
| - // integral.y contains the evaluation of the indefinite gaussian integral |
71 |
| - // function at (X + rrect_distance) and integral.x contains the evaluation |
72 |
| - // of it at (X - rrect_distance). Subtracting the two produces the |
73 |
| - // integral result over the range from one to the other. |
74 |
| - result.xy = integral.yw - integral.xz; |
75 |
| - integral = IPVec4FastGaussianIntegral( |
76 |
| - float(sample_position_x) + vec4(-rrect_distance[2], rrect_distance[2], |
77 |
| - -rrect_distance[3], rrect_distance[3]), |
78 |
| - float(frag_info.blur_sigma)); |
79 |
| - result.zw = integral.yw - integral.xz; |
80 |
| - |
81 |
| - return result; |
| 33 | +float maxXY(vec2 v) { |
| 34 | + return max(v.x, v.y); |
82 | 35 | }
|
83 | 36 |
|
84 |
| -float RRectBlur(vec2 sample_position, vec2 half_size) { |
85 |
| - // Limit the sampling range to 3 standard deviations in the Y direction from |
86 |
| - // the kernel center to incorporate 99.7% of the color contribution. |
87 |
| - float half_sampling_range = frag_info.blur_sigma * 3.0; |
88 |
| - |
89 |
| - // We want to cover the range [Y - half_range, Y + half_range], but we |
90 |
| - // don't want to sample beyond the edge of the rrect (where the RRectBlurX |
91 |
| - // function produces bad information and where the real answer at those |
92 |
| - // locations will be 0.0 anyway). |
93 |
| - float begin_y = max(-half_sampling_range, sample_position.y - half_size.y); |
94 |
| - float end_y = min(half_sampling_range, sample_position.y + half_size.y); |
95 |
| - float interval = (end_y - begin_y) / kSampleCount; |
| 37 | +// use crate::math::compute_erf7; |
| 38 | +float computeErf7(float x) { |
| 39 | + x *= kTwoOverSqrtPi; |
| 40 | + float xx = x * x; |
| 41 | + x = x + (0.24295 + (0.03395 + 0.0104 * xx) * xx) * (x * xx); |
| 42 | + return x / sqrt(1.0 + x * x); |
| 43 | +} |
96 | 44 |
|
97 |
| - // Sample the X blur kSampleCount times, weighted by the Gaussian function. |
98 |
| - vec4 ys = vec4(0.5, 1.5, 2.5, 3.5) * interval + begin_y; |
99 |
| - vec4 sample_ys = sample_position.y - ys; |
100 |
| - vec4 blurx = RRectBlurX(sample_position.x, sample_ys, half_size); |
101 |
| - vec4 gaussian_y = IPGaussian(ys, float(frag_info.blur_sigma)); |
102 |
| - return dot(blurx, gaussian_y * interval); |
| 45 | +// The length formula, but with an exponent other than 2 |
| 46 | +float powerDistance(vec2 p) { |
| 47 | + float xp = pow(p.x, frag_info.exponent); |
| 48 | + float yp = pow(p.y, frag_info.exponent); |
| 49 | + return pow(xp + yp, frag_info.exponentInv); |
103 | 50 | }
|
104 | 51 |
|
105 | 52 | void main() {
|
106 |
| - frag_color = frag_info.color; |
| 53 | + vec2 adjusted = abs(v_position - frag_info.center) - frag_info.adjust; |
107 | 54 |
|
108 |
| - vec2 half_size = frag_info.rect_size * 0.5; |
109 |
| - vec2 sample_position = v_position - half_size; |
| 55 | + float dPos = powerDistance(max(adjusted, 0.0)); |
| 56 | + float dNeg = min(maxXY(adjusted), 0.0); |
| 57 | + float d = dPos + dNeg - frag_info.r1; |
| 58 | + float z = |
| 59 | + frag_info.scale * (computeErf7(frag_info.sInv * (frag_info.minEdge + d)) - |
| 60 | + computeErf7(frag_info.sInv * d)); |
110 | 61 |
|
111 |
| - frag_color *= float16_t(RRectBlur(sample_position, half_size)); |
| 62 | + frag_color = frag_info.color * float16_t(z); |
112 | 63 | }
|
0 commit comments