Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
scenefx/render/fx_renderer/shaders/droplet.frag (5.7K)
1 // Droplet backdrop refraction: the region behind a status-bar water drop,
2 // re-rendered through the drop's lens.
3 //
4 // The silhouette is the same shape the cce-ui client draws — a superellipse-
5 // rounded box (attach radius on the top corners, sheet radius on the bottom)
6 // smooth-intersected with a huge disc whose lowest point touches the box
7 // bottom (the continuous bottom arc) — so the refracting region and the
8 // drawn drop can never disagree. Inside it, the backdrop texture (scenefx's
9 // unblurred optimized-blur snapshot of everything beneath the bar layer) is
10 // sampled with two warps:
11 //
12 // - a rim-weighted offset along the SDF gradient: the image bends around
13 // the drop edge the way glass pulls the background with it;
14 // - a faint inverted, minified sample about the drop center: the upside-
15 // down image of the scene a real hanging drop shows in its belly.
16 //
17 // The client's translucent tint, gleam and text composite on top of this.
18
19 #ifdef GL_FRAGMENT_PRECISION_HIGH
20 precision highp float;
21 #else
22 precision mediump float;
23 #endif
24
25 varying vec4 v_color;
26 varying vec2 v_texcoord;
27
28 uniform sampler2D tex;
29 uniform vec2 tex_size;
30 uniform vec2 position; // box origin (same space corner shaders use)
31 uniform vec2 size; // box size, px
32 uniform float attach_r; // top-corner radius px
33 uniform float sheet_r; // bottom-corner radius px
34 uniform float bow_rise; // bottom-arc edge rise px (0 = flat bottom run)
35 uniform float blend_k; // smooth-intersection blend px
36 uniform float curve; // corner-shape exponent (2 = circular)
37 uniform float band_px; // dome skirt width px (the refraction falloff scale)
38 uniform float refr; // rim refraction strength px (sign flips direction)
39 uniform float ghost; // inverted-lens ghost strength 0-1
40 uniform float compress_ceil; // backdrop compression ceiling, linear luma (0 = off)
41 uniform float compress_knee; // where compression starts, linear luma
42 uniform bool compress_invert; // dark text: lift shadows instead
43
44 // The same backdrop compression the blurred backdrop gets (tex.frag's
45 // compress_backdrop — keep the two in step): the refracted image is the
46 // segment's backdrop when this node is live, so it is what the text has to
47 // read against.
48 vec3 compress_backdrop(vec3 c) {
49 vec3 lin = pow(max(c, vec3(0.0)), vec3(2.2));
50 if (compress_invert) {
51 lin = vec3(1.0) - lin;
52 }
53 float y = dot(lin, vec3(0.2126, 0.7152, 0.0722));
54 if (y > compress_knee) {
55 float span = max(compress_ceil - compress_knee, 1e-4);
56 float y2 = compress_knee + span * (1.0 - exp(-(y - compress_knee) / span));
57 lin *= y2 / y;
58 }
59 if (compress_invert) {
60 lin = vec3(1.0) - lin;
61 }
62 return pow(clamp(lin, 0.0, 1.0), vec3(1.0 / 2.2));
63 }
64
65 // Signed distance to the drop silhouette at box-local p (y DOWN from the box
66 // top). The rounded-box part is the same construction as the client's
67 // rr_sdf_grad: quadrant-selected corner radius, Lp corners with a first-order
68 // |grad| correction above exponent 2.
69 float drop_dist(vec2 p) {
70 vec2 half_ext = size * 0.5;
71 vec2 c = p - half_ext;
72 float r = (c.y > 0.0) ? sheet_r : attach_r;
73 vec2 q = abs(c) - half_ext + vec2(r, r);
74 float d;
75 if (q.x > 0.0 && q.y > 0.0) {
76 if (curve > 2.001 && r > 0.0) {
77 vec2 u = q / r;
78 float lp = max(pow(pow(u.x, curve) + pow(u.y, curve), 1.0 / curve), 1e-4);
79 vec2 g = vec2(pow(u.x / lp, curve - 1.0), pow(u.y / lp, curve - 1.0));
80 d = r * (lp - 1.0) / max(length(g), 1e-4);
81 } else {
82 d = length(q) - r;
83 }
84 } else {
85 d = max(q.x, q.y) - r;
86 }
87 if (bow_rise > 0.25) {
88 // Smooth-intersect with the bottom-arc disc (smax = -smin(-a,-b)).
89 float hx = half_ext.x;
90 float R = hx * hx / (2.0 * bow_rise);
91 vec2 cc = vec2(hx, size.y - R);
92 float dc = length(p - cc) - R;
93 float k = max(blend_k, 1.0);
94 float hm = clamp(0.5 + 0.5 * (d - dc) / k, 0.0, 1.0);
95 d = mix(dc, d, hm) + k * hm * (1.0 - hm);
96 }
97 return d;
98 }
99
100 void main() {
101 // Box-local coords with y down. NOTE: deliberately NOT the corner
102 // shaders' extra y flip — that transform is unverifiable from the
103 // symmetric shapes that use it (equal radii look the same flipped), and
104 // with this drop's asymmetric silhouette it rendered the attach taper at
105 // the BOTTOM (live bug: black flipped boxes behind the modules).
106 // gl_FragCoord minus the box position is already top-down box-local
107 // here.
108 vec2 rel = gl_FragCoord.xy - position;
109
110 float d = drop_dist(rel);
111 float aa = clamp(-d + 0.5, 0.0, 1.0);
112 if (aa <= 0.0) {
113 discard;
114 }
115
116 // SDF gradient by central differences, in the same y-down local space.
117 vec2 grad = normalize(vec2(
118 drop_dist(rel + vec2(1.0, 0.0)) - drop_dist(rel - vec2(1.0, 0.0)),
119 drop_dist(rel + vec2(0.0, 1.0)) - drop_dist(rel - vec2(0.0, 1.0))
120 ) + vec2(1e-6, 0.0));
121
122 // Rim-weighted lens profile: 1 at the silhouette, 0 deep inside.
123 float t = clamp(-d / max(band_px, 1.0), 0.0, 1.0);
124 float lens = (1.0 - t) * (1.0 - t);
125
126 // Refraction: offset the sample along the gradient. gl_FragCoord shares
127 // the box-local frame's orientation (see `rel` above), so the offset
128 // applies directly. Positive refr samples outward — the background bends
129 // around the drop edge.
130 vec2 off = grad * (refr * lens);
131 vec2 uv = (gl_FragCoord.xy + off) / tex_size;
132 vec4 col = texture2D(tex, clamp(uv, vec2(0.0), vec2(1.0)));
133
134 // Inverted lens ghost: the belly shows a faint upside-down, minified
135 // image of the scene about the drop center.
136 if (ghost > 0.001) {
137 vec2 center_fc = position + size * 0.5;
138 vec2 ghost_uv = (center_fc + (center_fc - gl_FragCoord.xy) * 0.35) / tex_size;
139 vec4 gcol = texture2D(tex, clamp(ghost_uv, vec2(0.0), vec2(1.0)));
140 col.rgb = mix(col.rgb, gcol.rgb, ghost * t * t);
141 }
142
143 if (compress_ceil > 0.0) {
144 col.rgb = compress_backdrop(col.rgb);
145 }
146
147 gl_FragColor = vec4(col.rgb, 1.0) * aa;
148 }