Files
dotfiles/ghostty/.config/ghostty/starfield.glsl
Matt Anderson 605c99bc01 first commit
2026-03-28 10:40:48 -05:00

157 lines
3.9 KiB
GLSL

// flip this: false = white stars on dark bg, true = dark stars on light bg
const bool invertStars = false;
// how dark the inverted stars are (0.0 = pure black, 1.0 = invisible/white)
const float invertedStarBrightness = 0.7;
// transparent background
const bool transparent = true;
// terminal contents luminance threshold to be considered background (0.0 to 1.0)
const float threshold = 0.15;
// divisions of grid
const float repeats = 6.;
// number of layers
const float layers = 3.;
float luminance(vec3 color) {
return dot(color, vec3(0.2126, 0.7152, 0.0722));
}
float N21(vec2 p) {
p = fract(p * vec2(233.34, 851.73));
p += dot(p, p + 23.45);
return fract(p.x * p.y);
}
vec2 N22(vec2 p) {
float n = N21(p);
return vec2(n, N21(p + n));
}
mat2 scale(vec2 _scale) {
return mat2(_scale.x, 0.0,
0.0, _scale.y);
}
// 2D Noise based on Morgan McGuire
float noise(in vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
// Four corners in 2D of a tile
float a = N21(i);
float b = N21(i + vec2(1.0, 0.0));
float c = N21(i + vec2(0.0, 1.0));
float d = N21(i + vec2(1.0, 1.0));
// Smooth Interpolation
vec2 u = f * f * (3.0 - 2.0 * f); // Cubic Hermite Curve
// Mix 4 corners percentages
return mix(a, b, u.x) +
(c - a) * u.y * (1.0 - u.x) +
(d - b) * u.x * u.y;
}
float perlin2(vec2 uv, int octaves, float pscale) {
float col = 1.;
float initScale = 4.;
for (int l = 0; l < octaves; l++) {
float val = noise(uv * initScale);
if (col <= 0.01) {
col = 0.;
break;
}
val -= 0.01;
val *= 0.5;
col *= val;
initScale *= pscale;
}
return col;
}
vec3 stars(vec2 uv, float offset) {
float timeScale = -(iTime * 0.01 + offset) / layers;
float trans = fract(timeScale);
float newRnd = floor(timeScale);
vec3 col = vec3(0.);
// Translate uv then scale for center
uv -= vec2(0.5);
uv = scale(vec2(trans)) * uv;
uv += vec2(0.5);
// Create square aspect ratio
uv.x *= iResolution.x / iResolution.y;
// Create boxes
uv *= repeats;
// Get position
vec2 ipos = floor(uv);
// Return uv as 0 to 1
uv = fract(uv);
// Calculate random xy and size
vec2 rndXY = N22(newRnd + ipos * (offset + 1.)) * 0.9 + 0.05;
float rndSize = N21(ipos) * 200. + 500.;
vec2 j = (rndXY - uv) * rndSize;
float sparkle = 1. / dot(j, j);
col += vec3(1.0) * sparkle;
col *= smoothstep(1., 0.8, trans);
return col;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
// Normalized pixel coordinates (from 0 to 1)
vec2 uv = fragCoord / iResolution.xy;
vec3 col = vec3(0.);
for (float i = 0.; i < layers; i++) {
col += stars(uv, i);
}
// Clamp sparkle intensity
col = clamp(col, 0.0, 1.0);
// Invert: white sparkle on black bg becomes dark sparkle on white bg
if (invertStars) {
col = vec3(1.0) - col;
col = mix(col, vec3(1.0), invertedStarBrightness);
}
// Sample the terminal screen texture including alpha channel
vec4 terminalColor = texture(iChannel0, uv);
if (transparent) {
col = invertStars ? col * terminalColor.rgb : col + terminalColor.rgb;
}
// Mask: detect background pixels of terminal content
// For light bg, background has HIGH luminance; for dark bg, background has LOW luminance
float termLum = luminance(terminalColor.rgb);
float mask;
if (invertStars) {
// Light bg: background is bright, text is dark — show stars where luminance is HIGH
mask = step(1.0 - threshold, termLum);
} else {
// Dark bg: background is dark, text is bright — show stars where luminance is LOW
mask = 1.0 - step(threshold, termLum);
}
vec3 blendedColor = mix(terminalColor.rgb, col, mask);
// Apply terminal's alpha to control overall opacity
fragColor = vec4(blendedColor, terminalColor.a);
}