git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commit773316aafa780848d2891c270bc01a68b049ec26
parent62e51e0264
authorLucas Galante <[email protected]>
date2026-08-24 14:50
droplet: core density (DropletSpec::core)

The body opacity now ramps from clarity at the rim up to 1 + core (×
the color's own alpha, clamped opaque) at the deep interior — the
water reads thickest in the middle, which is where a module's text
sits, so glyphs get a calmer field without losing the watery rim.
Rides p_spec_tint.x: a droplet's glint is always white, so the tint
RGB slots were free and now carry droplet params. Default 0.35.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/backend/window_runner.rs |  4 +++-
 src/scene/paint.rs           |  8 ++++++++
 src/vk/renderer.rs           |  6 ++++--
 src/vk/shader2d.wgsl         | 12 +++++++-----
 4 files changed, 22 insertions(+), 8 deletions(-)

diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 17fed56..2a33655 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -2024,7 +2024,9 @@ pub fn tessellate_display_list(
                     // DE material's (a drop is wetter than the DE's plates).
                     material: [plate_mat[0], spec.gleam, spec.shine, spec.rim],
                     host: [sr * scale, spec.clarity.clamp(0.0, 1.0), spec.dome, ar * scale],
-                    specular_tint: [1.0, 1.0, 1.0, bow * scale],
+                    // Droplet glints are always white, so the tint RGB slots
+                    // carry droplet params instead: x = core density.
+                    specular_tint: [spec.core.clamp(0.0, 2.0), 1.0, 1.0, bow * scale],
                     mode: 10.0,
                     shape: spec.curve.clamp(2.0, 6.0),
                 });
diff --git a/src/scene/paint.rs b/src/scene/paint.rs
index ad2a554..4b662af 100644
--- a/src/scene/paint.rs
+++ b/src/scene/paint.rs
@@ -113,6 +113,13 @@ pub struct DropletSpec {
     /// instead of two arcs meeting, and the contact eases out of the flat
     /// top like a meniscus. Clamped to [2, 6].
     pub curve: f32,
+    /// Extra tint density at the drop's deep interior: the body opacity ramps
+    /// from `clarity` at the rim up to `1 + core` (× the color's own alpha,
+    /// clamped to opaque) inside — the water reads thickest in the middle,
+    /// which is also where a module's text sits, so glyphs get a calmer
+    /// field without giving up the watery rim. 0 = the original flat
+    /// interior falloff.
+    pub core: f32,
 }
 
 impl Default for DropletSpec {
@@ -138,6 +145,7 @@ impl Default for DropletSpec {
             rim: 0.5,
             bow: 0.12,
             curve: 2.6,
+            core: 0.35,
         }
     }
 }
diff --git a/src/vk/renderer.rs b/src/vk/renderer.rs
index 6b480e5..8a61db2 100644
--- a/src/vk/renderer.rs
+++ b/src/vk/renderer.rs
@@ -97,8 +97,10 @@ pub struct PlatePush {
     /// (slab carve about a line: `rect` = [cx, cy, half-width, _], `radii.xy` =
     /// the line's unit normal, `host` = the surface it is engraved into),
     /// 9.0 = trough, 10.0 = droplet (`radii` = [sag, belly r, belly half-w,
-    /// blend k] px, `host` = [sheet corner r px, clarity, dome amplitude, _],
-    /// `material.w` = fresnel rim — see shader2d's MODE_DROPLET).
+    /// blend k] px, `host` = [sheet corner r px, clarity, dome amplitude,
+    /// attach r px], `material.w` = fresnel rim, `specular_tint` = [core
+    /// density, _, _, bottom-bow rise px] — droplet glints are always white,
+    /// so the tint RGB is repurposed; see shader2d's MODE_DROPLET).
     pub mode: f32,
     /// Corner shape exponent: 2.0 = circular arcs, > 2 = superellipse
     /// (continuous-curvature) corners — see shader2d's `plate_sdf_grad`.
diff --git a/src/vk/shader2d.wgsl b/src/vk/shader2d.wgsl
index 7da9948..d016ff5 100644
--- a/src/vk/shader2d.wgsl
+++ b/src/vk/shader2d.wgsl
@@ -354,7 +354,8 @@ fn plate_shade(frag: vec2f, vcol: vec4f) -> vec4f {
     //   p_radii = [sag, belly radius, belly half-width, blend k] px;
     //   p_host  = [sheet bottom-corner radius px, edge clarity 0-1, dome
     //              amplitude, attach (top-corner) radius px];
-    //   p_spec_tint.w = bottom-bow edge rise px (0 = flat bottom run);
+    //   p_spec_tint = [core density, _, _, bottom-bow edge rise px] — a
+    //     droplet's glint is always white, so the tint RGB slots are free;
     //   p_mat.w = fresnel rim crest amplitude (droplets carve nothing, so the
     //             AO slot is free); p_light.w = shaded band width px.
     // The silhouette is the smooth union of a film SHEET attached to the top
@@ -434,11 +435,12 @@ fn plate_shade(frag: vec2f, vcol: vec4f) -> vec4f {
         let extra = rrect_clip.p_mat.w * f * f * f;
         let shade = 1.0 + (diff / flat_shade - 1.0 + extra) * strength;
         let spec = roll_spec(sv);
-        // Thin edges are clearer water: the tint opacity falls toward the rim.
-        let body = mix(clarity, 1.0, u);
+        // Thin edges are clearer water; the deep interior densifies by the
+        // core term (thickest water in the middle — the text's field).
+        let body = mix(clarity, 1.0 + rrect_clip.p_spec_tint.x, u);
         return vec4f(
-            base.rgb * shade + rrect_clip.p_spec_tint.rgb * (spec * strength),
-            abs(base.a) * body * aa2,
+            base.rgb * shade + vec3f(spec * strength),
+            min(abs(base.a) * body, 1.0) * aa2,
         );
     }