// a surface in one pass: its drop shadow, its gradient fill and a second gradient just // inside the rim (frosted's sheen, liquid's lit edge), shaped by the rounded outline // worked out here rather than by a path, so amane lays it straight onto the window // without a layer of its own. surface.rs fills the values: // 0: shape width, height, corner radius, margin (room around the shape for the shadow) // 1: shadow colour, its alpha already scaled by the shadow's strength // 2: shadow deviation, its x and y offset, the inset of the inner gradient // 3: fill angle, inner angle, fill stops (up to 3), inner stops (0 to 5) // 4-6: fill colours, 7: their offsets // 8-12: inner colours, 13: offsets of the first four, 14.x: the fifth's // colours are plain (not premultiplied), 0 to 1. the shadow follows vello's own // blurred rounded rectangle, the gradients amane's linear ones, so it looks the same // vello's error function approximation (raphlinus.github.io, blurred rounded rects) fn erf7(x: f32) -> f32 { let y = clamp(x * 1.1283791671, -100.0, 100.0); let yy = y * y; let z = y + (0.24295 + (0.03395 + 0.0104 * yy) * yy) * (y * yy); return z / sqrt(1.0 + z * z); } // vello's blurred rounded rectangle at p, from its centre fn shadow(p: vec2, w: f32, h: f32, radius: f32, deviation: f32) -> f32 { let s = max(deviation, 1e-5); let inv = 1.0 / s; let min_edge = min(w, h); let radius_max = 0.5 * min_edge; let r0 = min(length(vec2(radius, s * 1.15)), radius_max); let r1 = min(length(vec2(radius, s * 2.0)), radius_max); let exponent = 2.0 * r1 / r0; let delta = 1.25 * s * (exp(-pow(0.5 * inv * w, 2.0)) - exp(-pow(0.5 * inv * h, 2.0))); let width = w + min(delta, 0.0); let height = h - max(delta, 0.0); let scale = 0.5 * erf7(inv * 0.5 * (max(width, height) - 0.5 * radius)); let y0 = abs(p.y) - (height * 0.5 - r1); let x0 = abs(p.x) - (width * 0.5 - r1); let d_pos = pow(pow(max(x0, 0.0), exponent) + pow(max(y0, 0.0), exponent), 1.0 / exponent); let d = d_pos + min(max(x0, y0), 0.0) - r1; return scale * (erf7(inv * (min_edge + d)) - erf7(inv * d)); } // signed distance to a rounded rectangle centred on 0, negative inside fn rounded(p: vec2, half: vec2, radius: f32) -> f32 { let r = min(radius, min(half.x, half.y)); let q = abs(p) - half + vec2(r); return length(max(q, vec2(0.0))) + min(max(q.x, q.y), 0.0) - r; } // how much of a pixel `pixel` wide the shape covers at distance d fn cover(d: f32, pixel: f32) -> f32 { return clamp(0.5 - d / pixel, 0.0, 1.0); } // where p falls along amane's linear gradient over a box at `origin`, 0 to 1: // a line through the centre, 0 degrees pointing up, long enough to reach the corners fn along(p: vec2, origin: vec2, extent: vec2, angle: f32) -> f32 { let a = radians(angle); let direction = vec2(sin(a), -cos(a)); let half_length = (extent.x * abs(direction.x) + extent.y * abs(direction.y)) / 2.0; return clamp(0.5 + dot(p - origin - extent / 2.0, direction) / max(2.0 * half_length, 1e-5), 0.0, 1.0); } fn premultiplied(c: vec4) -> vec4 { return vec4(c.rgb * c.a, c.a); } // the fill's colour at t, premultiplied; stops past the last repeat it fn fill_color(t: f32) -> vec4 { let count = u32(values[3].z); var color = premultiplied(values[4]); for (var i = 1u; i < count; i++) { let lo = values[7][i - 1u]; let hi = values[7][i]; let k = clamp((t - lo) / max(hi - lo, 1e-5), 0.0, 1.0); color = select(color, mix(premultiplied(values[3u + i]), premultiplied(values[4u + i]), k), t > lo); } return color; } fn inner_offset(i: u32) -> f32 { return select(values[13][min(i, 3u)], values[14].x, i == 4u); } fn inner_color(t: f32) -> vec4 { let count = u32(values[3].w); var color = premultiplied(values[8]); for (var i = 1u; i < count; i++) { let lo = inner_offset(i - 1u); let hi = inner_offset(i); let k = clamp((t - lo) / max(hi - lo, 1e-5), 0.0, 1.0); color = select(color, mix(premultiplied(values[7u + i]), premultiplied(values[8u + i]), k), t > lo); } return color; } @fragment fn main(@location(0) uv: vec2) -> @location(0) vec4 { let shape = values[0]; let margin = shape.w; let extent = shape.xy; // this pixel, from the shape's top left corner let p = uv * size - vec2(margin); let pixel = max(fwidth(p.x), 1e-4); let centre = extent / 2.0; // the shadow, under everything let offset = values[2].yz; let shade = shadow(p - centre - offset, extent.x, extent.y, shape.z, values[2].x); var color = premultiplied(vec4(values[1].rgb, values[1].a * shade)); // the fill over it let inside = cover(rounded(p - centre, centre, shape.z), pixel); let fill = fill_color(along(p, vec2(0.0), extent, values[3].x)) * inside; color = fill + color * (1.0 - fill.a); // the inner gradient, inset from the rim by values[2].w if values[3].w > 0.0 { let inset = values[2].w; let inner = extent - vec2(2.0 * inset); let within = cover(rounded(p - centre, inner / 2.0, max(shape.z - inset, 0.0)), pixel); let light = inner_color(along(p, vec2(inset), inner, values[3].y)) * within; color = light + color * (1.0 - light.a); } // amane blends a plain colour over the window if color.a <= 0.0 { return vec4(0.0); } return vec4(color.rgb / color.a, color.a); }