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