Skip to content

Commit f7a4517

Browse files
authored
[Impeller] Improved rounded rect shadow approximation (flutter#37075)
1 parent e81abd2 commit f7a4517

File tree

3 files changed

+78
-15
lines changed

3 files changed

+78
-15
lines changed

impeller/compiler/shader_lib/impeller/gaussian.glsl

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <impeller/constants.glsl>
99

10+
/// Gaussian distribution function.
1011
float IPGaussian(float x, float sigma) {
1112
float variance = sigma * sigma;
1213
return exp(-0.5 * x * x / variance) / (kSqrtTwoPi * sigma);
@@ -20,8 +21,12 @@ float IPErf(float x) {
2021
return sign(x) * (1 - 1 / (b * b * b * b));
2122
}
2223

24+
/// Vec2 variation for the Abramowitz and Stegun erf approximation.
2325
vec2 IPVec2Erf(vec2 x) {
24-
return vec2(IPErf(x.x), IPErf(x.y));
26+
vec2 a = abs(x);
27+
// 0.278393*x + 0.230389*x^2 + 0.078108*x^4 + 1
28+
vec2 b = (0.278393 + (0.230389 + 0.078108 * a * a) * a) * a + 1.0;
29+
return sign(x) * (1 - 1 / (b * b * b * b));
2530
}
2631

2732
/// Indefinite integral of the Gaussian function (with constant range 0->1).
@@ -32,4 +37,18 @@ float IPGaussianIntegral(float x, float sigma) {
3237
return 0.535 * IPErf(x * (kHalfSqrtTwo / sigma)) + 0.465;
3338
}
3439

40+
/// Vec2 variation for the indefinite integral of the Gaussian function (with
41+
/// constant range 0->1).
42+
vec2 IPVec2GaussianIntegral(vec2 x, float sigma) {
43+
// ( 1 + erf( x * (sqrt(2) / (2 * sigma) ) ) / 2
44+
// Because this sigmoid is always > 1, we remap it (n * 1.07 - 0.07)
45+
// so that it always fades to zero before it reaches the blur radius.
46+
return 0.535 * IPVec2Erf(x * (kHalfSqrtTwo / sigma)) + 0.465;
47+
}
48+
49+
/// Simple logistic sigmoid with a domain of [-1, 1] and range of [0, 1].
50+
float IPSigmoid(float x) {
51+
return 1.03731472073 / (1 + exp(-4 * x)) - 0.0186573603638;
52+
}
53+
3554
#endif

impeller/entity/contents/rrect_shadow_contents.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ bool RRectShadowContents::Render(const ContentContext& renderer,
9494

9595
FS::FragInfo frag_info;
9696
frag_info.color = color_;
97-
frag_info.blur_radius = blur_radius;
97+
frag_info.blur_sigma = sigma_.sigma;
9898
frag_info.rect_size = Point(positive_rect.size);
9999
frag_info.corner_radius =
100100
std::min(corner_radius_, std::min(positive_rect.size.width / 2.0f,

impeller/entity/shaders/rrect_blur.frag

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
#include <impeller/gaussian.glsl>
6+
57
uniform FragInfo {
68
vec4 color;
7-
float blur_radius;
9+
float blur_sigma;
810
vec2 rect_size;
911
float corner_radius;
1012
}
@@ -14,21 +16,63 @@ in vec2 v_position;
1416

1517
out vec4 frag_color;
1618

17-
// Simple logistic sigmoid with a domain of [-1, 1] and range of [0, 1].
18-
float Sigmoid(float x) {
19-
return 1.03731472073 / (1 + exp(-4 * x)) - 0.0186573603638;
20-
}
19+
const int kSampleCount = 5;
2120

22-
float RRectDistance(vec2 sample_position, vec2 rect_size, float corner_radius) {
23-
vec2 space = abs(sample_position) - rect_size + corner_radius;
21+
float RRectDistance(vec2 sample_position, vec2 half_size) {
22+
vec2 space = abs(sample_position) - half_size + frag_info.corner_radius;
2423
return length(max(space, 0.0)) + min(max(space.x, space.y), 0.0) -
25-
corner_radius;
24+
frag_info.corner_radius;
25+
}
26+
27+
/// Closed form unidirectional rounded rect blur mask solution using the
28+
/// analytical Gaussian integral (with approximated erf).
29+
float RRectShadowX(vec2 sample_position, vec2 half_size) {
30+
// Compute the X direction distance field (not incorporating the Y distance)
31+
// for the rounded rect.
32+
float space =
33+
min(0, half_size.y - frag_info.corner_radius - abs(sample_position.y));
34+
float rrect_distance =
35+
half_size.x - frag_info.corner_radius +
36+
sqrt(max(0, frag_info.corner_radius * frag_info.corner_radius -
37+
space * space));
38+
39+
// Map the linear distance field to the analytical Gaussian integral.
40+
vec2 integral = IPVec2GaussianIntegral(
41+
sample_position.x + vec2(-rrect_distance, rrect_distance),
42+
frag_info.blur_sigma);
43+
return integral.y - integral.x;
44+
}
45+
46+
float RRectShadow(vec2 sample_position, vec2 half_size) {
47+
// Limit the sampling range to 3 standard deviations in the Y direction from
48+
// the kernel center to incorporate 99.7% of the color contribution.
49+
float half_sampling_range = frag_info.blur_sigma * 3;
50+
51+
float begin_y = max(-half_sampling_range, sample_position.y - half_size.y);
52+
float end_y = min(half_sampling_range, sample_position.y + half_size.y);
53+
float interval = (end_y - begin_y) / kSampleCount;
54+
55+
// Sample the X blur kSampleCount times, weighted by the Gaussian function.
56+
float result = 0;
57+
for (int sample_i = 0; sample_i < kSampleCount; sample_i++) {
58+
float y = begin_y + interval * (sample_i + 0.5);
59+
result += RRectShadowX(vec2(sample_position.x, sample_position.y - y),
60+
half_size) *
61+
IPGaussian(y, frag_info.blur_sigma) * interval;
62+
}
63+
64+
return result;
2665
}
2766

2867
void main() {
29-
vec2 center = v_position - frag_info.rect_size / 2.0;
30-
float dist =
31-
RRectDistance(center, frag_info.rect_size / 2.0, frag_info.corner_radius);
32-
float shadow_mask = Sigmoid(-dist / frag_info.blur_radius);
33-
frag_color = frag_info.color * shadow_mask;
68+
frag_color = frag_info.color;
69+
70+
vec2 half_size = frag_info.rect_size * 0.5;
71+
vec2 sample_position = v_position - half_size;
72+
73+
if (frag_info.blur_sigma > 0) {
74+
frag_color *= RRectShadow(sample_position, half_size);
75+
} else {
76+
frag_color *= -RRectDistance(sample_position, half_size);
77+
}
3478
}

0 commit comments

Comments
 (0)