mirror of
https://github.com/Pixelplanet/Pixel-VR-Player.git
synced 2026-10-25 06:01:35 +01:00
148 lines
5.7 KiB
GLSL
148 lines
5.7 KiB
GLSL
#version 450
|
|
|
|
// Procedural vector/SDF UI fragment shader.
|
|
// Supports rounded boxes with antialiased borders, and crisp vector icons.
|
|
|
|
layout(push_constant) uniform Push {
|
|
mat4 mvp;
|
|
vec4 color;
|
|
vec4 params; // x: shapeType, y: radius, z: borderWidth, w: extra
|
|
vec4 borderColor;
|
|
} pc;
|
|
|
|
layout(location = 0) in vec2 vCoord;
|
|
layout(location = 0) out vec4 outColor;
|
|
|
|
// SDF for rounded box
|
|
float sdRoundedBox(vec2 p, vec2 b, float r) {
|
|
vec2 q = abs(p) - b + vec2(r);
|
|
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
|
|
}
|
|
|
|
// Inigo Quilez 2D triangle SDF
|
|
float sdTriangle(vec2 p, vec2 p0, vec2 p1, vec2 p2) {
|
|
vec2 e0 = p1 - p0, e1 = p2 - p1, e2 = p0 - p2;
|
|
vec2 v0 = p - p0, v1 = p - p1, v2 = p - p2;
|
|
vec2 pq0 = v0 - e0 * clamp(dot(v0, e0) / dot(e0, e0), 0.0, 1.0);
|
|
vec2 pq1 = v1 - e1 * clamp(dot(v1, e1) / dot(e1, e1), 0.0, 1.0);
|
|
vec2 pq2 = v2 - e2 * clamp(dot(v2, e2) / dot(e2, e2), 0.0, 1.0);
|
|
float s = sign(e0.x * e2.y - e0.y * e2.x);
|
|
vec2 d = min(min(vec2(dot(pq0, pq0), s * (v0.x * e0.y - v0.y * e0.x)),
|
|
vec2(dot(pq1, pq1), s * (v1.x * e1.y - v1.y * e1.x))),
|
|
vec2(dot(pq2, pq2), s * (v2.x * e2.y - v2.y * e2.x)));
|
|
return -sqrt(d.x) * sign(d.y);
|
|
}
|
|
|
|
// SDF line segment
|
|
float sdSegment(vec2 p, vec2 a, vec2 b) {
|
|
vec2 pa = p - a, ba = b - a;
|
|
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
|
|
return length(pa - ba * h);
|
|
}
|
|
|
|
void main() {
|
|
int shape = int(pc.params.x + 0.5);
|
|
vec2 p = vCoord;
|
|
float d = 0.0;
|
|
bool hasBorder = false;
|
|
|
|
if (shape == 0) {
|
|
// Shape 0: Rounded rectangle (e.g. button body, panel, progress bar)
|
|
float r = clamp(pc.params.y, 0.0, 1.0);
|
|
d = sdRoundedBox(p, vec2(1.0), r);
|
|
hasBorder = (pc.params.z > 0.0);
|
|
} else if (shape == 1) {
|
|
// Shape 1: Play icon (triangle pointing right)
|
|
vec2 pPlay = p - vec2(0.04, 0.0);
|
|
d = sdTriangle(pPlay, vec2(-0.45, 0.55), vec2(0.55, 0.0), vec2(-0.45, -0.55)) - 0.06;
|
|
} else if (shape == 2) {
|
|
// Shape 2: Pause icon (two vertical bars)
|
|
vec2 pBar = vec2(abs(p.x) - 0.30, p.y);
|
|
d = sdRoundedBox(pBar, vec2(0.12, 0.52), 0.04);
|
|
} else if (shape == 3) {
|
|
// Shape 3: Rewind (<<) (two left-pointing triangles)
|
|
vec2 tri0 = vec2(0.10, 0.45);
|
|
vec2 tri1 = vec2(-0.38, 0.0);
|
|
vec2 tri2 = vec2(0.10, -0.45);
|
|
float d1 = sdTriangle(p - vec2(-0.06, 0.0), tri0, tri1, tri2) - 0.04;
|
|
float d2 = sdTriangle(p - vec2(0.42, 0.0), tri0, tri1, tri2) - 0.04;
|
|
d = min(d1, d2);
|
|
} else if (shape == 4) {
|
|
// Shape 4: Fast Forward (>>) (two right-pointing triangles)
|
|
vec2 tri0 = vec2(-0.10, 0.45);
|
|
vec2 tri1 = vec2(0.38, 0.0);
|
|
vec2 tri2 = vec2(-0.10, -0.45);
|
|
float d1 = sdTriangle(p - vec2(0.06, 0.0), tri0, tri1, tri2) - 0.04;
|
|
float d2 = sdTriangle(p - vec2(-0.42, 0.0), tri0, tri1, tri2) - 0.04;
|
|
d = min(d1, d2);
|
|
} else if (shape == 5) {
|
|
// Shape 5: Files / Folder icon
|
|
float body = sdRoundedBox(p - vec2(0.0, -0.10), vec2(0.65, 0.45), 0.08);
|
|
float tab = sdRoundedBox(p - vec2(-0.30, 0.40), vec2(0.32, 0.16), 0.06);
|
|
d = min(body, tab);
|
|
} else if (shape == 6) {
|
|
// Shape 6: Settings Gear icon
|
|
float r = length(p);
|
|
float theta = atan(p.y, p.x);
|
|
float teeth = sin(6.0 * theta);
|
|
float gearOuter = r - (0.48 + 0.14 * smoothstep(-0.4, 0.4, teeth));
|
|
float gearHole = 0.20 - r;
|
|
d = max(gearOuter, gearHole);
|
|
} else if (shape == 7) {
|
|
// Shape 7: Recenter / Target icon
|
|
float r = length(p);
|
|
float ring = abs(r - 0.52) - 0.06;
|
|
float dotCenter = r - 0.16;
|
|
float tickH = sdRoundedBox(vec2(abs(p.x) - 0.62, p.y), vec2(0.10, 0.03), 0.01);
|
|
float tickV = sdRoundedBox(vec2(p.x, abs(p.y) - 0.62), vec2(0.03, 0.10), 0.01);
|
|
d = min(min(ring, dotCenter), min(tickH, tickV));
|
|
} else if (shape == 8) {
|
|
// Shape 8: Reticle Cursor (outer ring + center dot)
|
|
float r = length(p);
|
|
float outerRing = abs(r - 0.70) - 0.08;
|
|
float centerDot = r - 0.22;
|
|
d = min(outerRing, centerDot);
|
|
} else if (shape == 9) {
|
|
// Shape 9: Close / X icon
|
|
float s1 = sdSegment(p, vec2(-0.45, -0.45), vec2(0.45, 0.45)) - 0.10;
|
|
float s2 = sdSegment(p, vec2(-0.45, 0.45), vec2(0.45, -0.45)) - 0.10;
|
|
d = min(s1, s2);
|
|
} else if (shape == 10) {
|
|
// Shape 10: Checkmark (✓)
|
|
float s1 = sdSegment(p, vec2(-0.45, -0.05), vec2(-0.12, -0.45)) - 0.09;
|
|
float s2 = sdSegment(p, vec2(-0.12, -0.45), vec2(0.48, 0.45)) - 0.09;
|
|
d = min(s1, s2);
|
|
} else if (shape == 11) {
|
|
// Shape 11: Stop icon (filled rounded square)
|
|
d = sdRoundedBox(p, vec2(0.46), 0.10);
|
|
} else if (shape == 12) {
|
|
// Shape 12: Download (down arrow onto a base line)
|
|
float shaft = sdRoundedBox(p - vec2(0.0, 0.18), vec2(0.09, 0.42), 0.04);
|
|
float head = sdTriangle(p - vec2(0.0, -0.20), vec2(-0.36, 0.30),
|
|
vec2(0.36, 0.30), vec2(0.0, -0.28)) - 0.03;
|
|
float base = sdRoundedBox(p - vec2(0.0, -0.62), vec2(0.52, 0.08), 0.03);
|
|
d = min(min(shaft, head), base);
|
|
} else {
|
|
d = sdRoundedBox(p, vec2(1.0), 0.0);
|
|
}
|
|
|
|
float aa = fwidth(d);
|
|
if (aa < 1e-4) aa = 0.015;
|
|
float fillAlpha = 1.0 - smoothstep(-aa * 0.5, aa * 0.5, d);
|
|
|
|
if (fillAlpha <= 0.0) {
|
|
discard;
|
|
}
|
|
|
|
vec4 finalColor = pc.color;
|
|
if (hasBorder) {
|
|
float borderWidth = pc.params.z;
|
|
float innerD = d + borderWidth;
|
|
float borderAlpha = 1.0 - smoothstep(-aa * 0.5, aa * 0.5, innerD);
|
|
finalColor = mix(pc.borderColor, pc.color, borderAlpha);
|
|
}
|
|
|
|
finalColor.a *= fillAlpha;
|
|
outColor = finalColor;
|
|
}
|