git.lucas.co / cce-compositor
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 }