Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
scenefx/render/fx_renderer/shaders/box_shadow.frag (2.9K)
1 // Writeup: https://madebyevan.com/shaders/fast-rounded-rectangle-shadows/
2
3 #ifdef GL_FRAGMENT_PRECISION_HIGH
4 precision highp float;
5 #else
6 precision mediump float;
7 #endif
8
9 varying vec4 v_color;
10 varying vec2 v_texcoord;
11
12 uniform vec2 position;
13 uniform vec2 size;
14 uniform float blur_sigma;
15 uniform float corner_radius;
16 uniform vec2 clip_position;
17 uniform vec2 clip_size;
18 uniform float clip_radius_top_left;
19 uniform float clip_radius_top_right;
20 uniform float clip_radius_bottom_left;
21 uniform float clip_radius_bottom_right;
22
23 float gaussian(float x, float sigma) {
24 const float pi = 3.141592653589793;
25 return exp(-(x * x) / (2.0 * sigma * sigma)) / (sqrt(2.0 * pi) * sigma);
26 }
27
28 // approximates the error function, needed for the gaussian integral
29 vec2 erf(vec2 x) {
30 vec2 s = sign(x), a = abs(x);
31 x = 1.0 + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
32 x *= x;
33 return s - s / (x * x);
34 }
35
36 // return the blurred mask along the x dimension
37 float roundedBoxShadowX(float x, float y, float sigma, float corner, vec2 halfSize) {
38 float delta = min(halfSize.y - corner - abs(y), 0.0);
39 float curved = halfSize.x - corner + sqrt(max(0.0, corner * corner - delta * delta));
40 vec2 integral = 0.5 + 0.5 * erf((x + vec2(-curved, curved)) * (sqrt(0.5) / sigma));
41 return integral.y - integral.x;
42 }
43
44 // return the mask for the shadow of a box from lower to upper
45 float roundedBoxShadow(vec2 lower, vec2 upper, vec2 point, float sigma, float corner_radius) {
46 // Center everything to make the math easier
47 vec2 center = (lower + upper) * 0.5;
48 vec2 halfSize = (upper - lower) * 0.5;
49 point -= center;
50
51 // The signal is only non-zero in a limited range, so don't waste samples
52 float low = point.y - halfSize.y;
53 float high = point.y + halfSize.y;
54 float start = clamp(-3.0 * sigma, low, high);
55 float end = clamp(3.0 * sigma, low, high);
56
57 // Accumulate samples (we can get away with surprisingly few samples)
58 float step = (end - start) / 4.0;
59 float y = start + step * 0.5;
60 float value = 0.0;
61 for (int i = 0; i < 4; i++) {
62 value += roundedBoxShadowX(point.x, point.y - y, sigma, corner_radius, halfSize) * gaussian(y, sigma) * step;
63 y += step;
64 }
65
66 return value;
67 }
68
69 float corner_alpha(vec2 size, vec2 position, bool is_cutout,
70 float radius_tl, float radius_tr, float radius_bl, float radius_br);
71
72 void main() {
73 float shadow_alpha = v_color.a * roundedBoxShadow(
74 position + blur_sigma,
75 position + size - blur_sigma,
76 gl_FragCoord.xy, blur_sigma * 0.5,
77 corner_radius);
78
79 // Clipping
80 float clip_corner_alpha = corner_alpha(
81 clip_size - 1.5,
82 clip_position + 0.75,
83 true,
84 clip_radius_top_left,
85 clip_radius_top_right,
86 clip_radius_bottom_left,
87 clip_radius_bottom_right
88 );
89
90 gl_FragColor = vec4(v_color.rgb, shadow_alpha) * clip_corner_alpha;
91 }