GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
Plate roll: a shade line on the far edges, the glint's dark counterpart
With the crest light-facing (f2fc3c8) the down-light edges of a raised
plate shade by their diffuse fall-off alone, and that cannot answer the
glint: under the ambient floor and the truncated roll the far edge
bottoms out near 0.8 of the face, and only in its last, anti-aliased
pixel, while the glint adds ~0.17 of white over a band. On the
designer's frosted panes — a ~20-luma face over a dark viewport — that
was 1 to 5 luma: a plate read lit on one side and merely unlit on the
other, never shadowed.
roll_shade_line is roll_spec evaluated under the light's azimuth
mirrored: the same decoupled lobe, same inset, width and peak, on the
edges facing away from the light, subtracted in colour units exactly
where the glint is added, scaled by PLATE_SHADE_LINE. roll_spec and the
new function share roll_lobe. Applied on the neutral plate return and
on the fill-less window roll; the accent-tinted (focused) plate keeps
its wrap ring untouched, since a dark line under the far half of the
ring would only dim it.
PLATE_SHADE_LINE is 0.5, not the exact mirror: the subtraction is in
linear light, and at 1.0 a dark face (sRGB 119) hit black at the
silhouette. At 0.5 the same edge sits at 77 and a 196 face at 169,
against 111 and 184 with the crest change alone. Verified in a scale-2
shadow on opaque plates and on the designer, where the panes and nodes
now carry a visible dark rim on the bottom and right over the dark
viewport.
relief_shade mirrors the function and the constant, which the
shader-source constants test now pins.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
src/scene/relief_shade.rs | 14 +++++++++++++-
src/vk/shader2d.wgsl | 29 ++++++++++++++++++++++++++---
2 files changed, 39 insertions(+), 4 deletions(-)
diff --git a/src/scene/relief_shade.rs b/src/scene/relief_shade.rs
index bc6198d..4fb4411 100644
--- a/src/scene/relief_shade.rs
+++ b/src/scene/relief_shade.rs
@@ -33,6 +33,9 @@ pub const RECESS_DEPTH: f32 = 0.6;
/// Amplitude of the bright crest hugging a raised plate's silhouette.
/// Mirrors `PLATE_CREST`.
pub const PLATE_CREST: f32 = 0.25;
+/// The far-edge shade line's strength relative to the glint. Mirrors
+/// `PLATE_SHADE_LINE`.
+pub const PLATE_SHADE_LINE: f32 = 0.5;
/// The roll's descent is truncated at this fraction of the quadrant, so the
/// profile ends on a bounded slope instead of plunging vertical at the
/// silhouette. Mirrors `ROLL_CUT`.
@@ -95,6 +98,14 @@ pub fn roll_spec(sv: [f32; 2], light: [f32; 3], mat: &Finish) -> f32 {
mat.spec * (prof.powf(mat.shininess) - hv[2].powf(mat.shininess)).max(0.0) * az * az
}
+/// The glint's dark counterpart: [`roll_spec`] under the light's azimuth
+/// mirrored, so the same lobe lands on the edges facing away from the light,
+/// scaled by [`PLATE_SHADE_LINE`]. Mirrors `roll_shade_line`. Subtracted in
+/// colour units where the glint is added.
+pub fn roll_shade_line(sv: [f32; 2], light: [f32; 3], mat: &Finish) -> f32 {
+ roll_spec(sv, [-light[0], -light[1], light[2]], mat) * PLATE_SHADE_LINE
+}
+
/// The signed shading value one carve contributes at `u` across its wall —
/// the shader's `v`, before the tint branch. Positive is a white screen over
/// what is beneath, negative a black multiply; magnitude is the alpha.
@@ -208,7 +219,7 @@ pub fn plate_surface(
// The crest: the ambient-catching convex rim that makes glass read as glass.
let extra = PLATE_CREST * f * f * f * crest_weight(facing, light);
let shade = 1.0 + (diff / flat_shade(light) - 1.0 + extra) * mat.strength;
- let spec = roll_spec(sv, light, mat) * mat.strength;
+ let spec = (roll_spec(sv, light, mat) - roll_shade_line(sv, light, mat)) * mat.strength;
Some([
(base[0] * shade + spec).clamp(0.0, 1.0),
(base[1] * shade + spec).clamp(0.0, 1.0),
@@ -257,6 +268,7 @@ mod tests {
assert_eq!(wgsl_const("RECESS_DEPTH"), RECESS_DEPTH);
assert_eq!(wgsl_const("PLATE_CREST"), PLATE_CREST);
assert_eq!(wgsl_const("ROLL_CUT"), ROLL_CUT);
+ assert_eq!(wgsl_const("PLATE_SHADE_LINE"), PLATE_SHADE_LINE);
}
/// The crest is light-facing: at the silhouette the edge toward the light
diff --git a/src/vk/shader2d.wgsl b/src/vk/shader2d.wgsl
index 1d81009..e11ffb4 100644
--- a/src/vk/shader2d.wgsl
+++ b/src/vk/shader2d.wgsl
@@ -168,6 +168,9 @@ const PLATE_AMBIENT: f32 = 0.55;
// Amplitude of the bright crest line hugging a raised plate's silhouette — the
// ambient-catching convex rim that makes glass read as glass.
const PLATE_CREST: f32 = 0.25;
+// The far-edge shade line's strength relative to the glint (roll_shade_line):
+// 1 is the exact mirror; 0.5 keeps dark faces from bottoming out at black.
+const PLATE_SHADE_LINE: f32 = 0.5;
// Recess depth as a fraction of the roll width (a recess is visually shallower
// than a raised plate's full quarter-round).
const RECESS_DEPTH: f32 = 0.6;
@@ -243,11 +246,29 @@ fn rr_sdf_grad(p: vec2f, prect: vec4f, pradii: vec4f) -> vec3f {
// `sv` is the surface's slope vector — the horizontal part of the unnormalized
// normal (-∇height, 1): its magnitude is the tilt, its direction the facing.
fn roll_spec(sv: vec2f) -> f32 {
+ return roll_lobe(sv, rrect_clip.p_light.xyz);
+}
+
+// The glint's dark counterpart: the SAME decoupled lobe — same inset, width
+// and peak — on the edges facing AWAY from the light, which is roll_spec
+// evaluated under the light's azimuth mirrored. Subtracted in colour units
+// exactly as the glint is added, scaled by PLATE_SHADE_LINE. It exists
+// because the diffuse fall-off alone cannot answer the glint: with the
+// ambient floor and the truncated roll the far edge bottoms out near 0.8 of
+// the face, and only in its last pixel, while the glint adds ~0.17 of white
+// over a band — so a raised plate read lit on one side and merely unlit on
+// the other, never shadowed.
+fn roll_shade_line(sv: vec2f) -> f32 {
+ let l = rrect_clip.p_light.xyz;
+ return roll_lobe(sv, vec3f(-l.xy, l.z)) * PLATE_SHADE_LINE;
+}
+
+fn roll_lobe(sv: vec2f, light: vec3f) -> f32 {
let m = length(sv);
if (m < 1e-5) {
return 0.0;
}
- let hv = normalize(rrect_clip.p_light.xyz + vec3f(0.0, 0.0, 1.0));
+ let hv = normalize(light + vec3f(0.0, 0.0, 1.0));
let shininess = rrect_clip.p_mat.z;
let facing = sv / m;
let cos_t = inverseSqrt(1.0 + m * m);
@@ -685,7 +706,8 @@ fn plate_shade(frag: vec2f, vcol: vec4f) -> vec4f {
);
}
let spec = roll_spec(sv);
- return vec4f(base.rgb * shade + rrect_clip.p_spec_tint.rgb * (spec * strength), abs(base.a) * aa);
+ let dark = roll_shade_line(sv);
+ return vec4f(base.rgb * shade + rrect_clip.p_spec_tint.rgb * (spec * strength) - vec3f(dark * strength), abs(base.a) * aa);
}
if (mode == MODE_ROLL) {
@@ -708,7 +730,8 @@ fn plate_shade(frag: vec2f, vcol: vec4f) -> vec4f {
let n = normalize(vec3f(sv, 1.0));
let diff = PLATE_AMBIENT + (1.0 - PLATE_AMBIENT) * max(dot(n, l), 0.0);
let spec = roll_spec(sv);
- let v = (diff / flat_shade - 1.0 + extra + spec) * strength * aa;
+ let dark = roll_shade_line(sv);
+ let v = (diff / flat_shade - 1.0 + extra + spec - dark) * strength * aa;
if (v >= 0.0) {
return vec4f(1.0, 1.0, 1.0, min(v, 1.0));
}