diff --git a/ghostty/bettercrt.glsl b/ghostty/bettercrt.glsl new file mode 100644 index 0000000..8f58b89 --- /dev/null +++ b/ghostty/bettercrt.glsl @@ -0,0 +1,33 @@ +// Original shader collected from: https://www.shadertoy.com/view/WsVSzV +// Licensed under Shadertoy's default since the original creator didn't provide any license. (CC BY NC SA 3.0) +// Slight modifications were made to give a green-ish effect. + +// This shader was modified by April Hall (arithefirst) +// Sourced from https://github.com/m-ahdal/ghostty-shaders/blob/main/retro-terminal.glsl +// Changes made: +// - Removed tint +// - Made the boundaries match ghostty's background color + +float warp = 0.25; // simulate curvature of CRT monitor +float scan = 0.50; // simulate darkness between scanlines + +void mainImage(out vec4 fragColor, in vec2 fragCoord) +{ + // squared distance from center + vec2 uv = fragCoord / iResolution.xy; + vec2 dc = abs(0.5 - uv); + dc *= dc; + + // warp the fragment coordinates + uv.x -= 0.5; uv.x *= 1.0 + (dc.y * (0.3 * warp)); uv.x += 0.5; + uv.y -= 0.5; uv.y *= 1.0 + (dc.x * (0.4 * warp)); uv.y += 0.5; + + // determine if we are drawing in a scanline + float apply = abs(sin(fragCoord.y) * 0.25 * scan); + + // sample the texture + vec3 color = texture(iChannel0, uv).rgb; + + // mix the sampled color with the scanline intensity + fragColor = vec4(mix(color, vec3(0.0), apply), 1.0); +} diff --git a/ghostty/bloom.glsl b/ghostty/bloom.glsl new file mode 100644 index 0000000..51b771c --- /dev/null +++ b/ghostty/bloom.glsl @@ -0,0 +1,50 @@ +// Golden spiral samples, [x, y, weight] weight is inverse of distance. +const vec3[24] samples = { + vec3(0.1693761725038636, 0.9855514761735895, 1), + vec3(-1.333070830962943, 0.4721463328627773, 0.7071067811865475), + vec3(-0.8464394909806497, -1.51113870578065, 0.5773502691896258), + vec3(1.554155680728463, -1.2588090085709776, 0.5), + vec3(1.681364377589461, 1.4741145918052656, 0.4472135954999579), + vec3(-1.2795157692199817, 2.088741103228784, 0.4082482904638631), + vec3(-2.4575847530631187, -0.9799373355024756, 0.3779644730092272), + vec3(0.5874641440200847, -2.7667464429345077, 0.35355339059327373), + vec3(2.997715703369726, 0.11704939884745152, 0.3333333333333333), + vec3(0.41360842451688395, 3.1351121305574803, 0.31622776601683794), + vec3(-3.167149933769243, 0.9844599011770256, 0.30151134457776363), + vec3(-1.5736713846521535, -3.0860263079123245, 0.2886751345948129), + vec3(2.888202648340422, -2.1583061557896213, 0.2773500981126146), + vec3(2.7150778983300325, 2.5745586041105715, 0.2672612419124244), + vec3(-2.1504069972377464, 3.2211410627650165, 0.2581988897471611), + vec3(-3.6548858794907493, -1.6253643308191343, 0.25), + vec3(1.0130775986052671, -3.9967078676335834, 0.24253562503633297), + vec3(4.229723673607257, 0.33081361055181563, 0.23570226039551587), + vec3(0.40107790291173834, 4.340407413572593, 0.22941573387056174), + vec3(-4.319124570236028, 1.159811599693438, 0.22360679774997896), + vec3(-1.9209044802827355, -4.160543952132907, 0.2182178902359924), + vec3(3.8639122286635708, -2.6589814382925123, 0.21320071635561041), + vec3(3.3486228404946234, 3.4331800232609, 0.20851441405707477), + vec3(-2.8769733643574344, 3.9652268864187157, 0.20412414523193154) + }; + +float lum(vec4 c) { + return 0.299 * c.r + 0.587 * c.g + 0.114 * c.b; +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord.xy / iResolution.xy; + + vec4 color = texture(iChannel0, uv); + + vec2 step = vec2(1.414) / iResolution.xy; + + for (int i = 0; i < 24; i++) { + vec3 s = samples[i]; + vec4 c = texture(iChannel0, uv + s.xy * step); + float l = lum(c); + if (l > 0.2) { + color += l * s.z * c * 0.2; + } + } + + fragColor = color; +} \ No newline at end of file diff --git a/ghostty/config b/ghostty/config new file mode 100644 index 0000000..e3694f8 --- /dev/null +++ b/ghostty/config @@ -0,0 +1,311 @@ +# keybind = global:cmd+grave_accent=toggle_quick_terminal + +# font settings +font-size = 11 +bold-is-bright = true +font-style-italic = Regular Italic + +# font options +font-family = "SFMono Nerd Font" +#font-family = "Berkeley Mono" +# font-family = "Fragment Mono" +# font-family = "MonaspiceNe NFM" + +# theme +background = #000000 +# foreground = #c7c7c7 +unfocused-split-opacity = 1 + +# shaders +# custom-shader = ./starfield-colors.glsl +# custom-shader-animation = always + +# window +window-padding-x = 10 +window-padding-y = 10,3 +window-padding-color = extend +window-inherit-working-directory = false +background-opacity = 0.7 +background-blur = 20 + +# size +window-height = 45 +window-width = 111 + +# shell +link-url = true +mouse-hide-while-typing = false +cursor-style = underline +shell-integration = detect +shell-integration-features = no-cursor,no-sudo +macos-titlebar-style = hidden +macos-option-as-alt = true +keybind = shift+enter=text:\r + +# colors +cursor-color = #c7c7c7 +selection-background = #c1ddff +cursor-invert-fg-bg = false +selection-invert-fg-bg = false + +# palette + +# custom +palette = 0=#1d1f21 +palette = 1=#c91b00 +palette = 2=#00c200 +palette = 3=#d29922 +palette = 4=#59a6ff +palette = 5=#c930c7 +palette = 6=#00c5c7 +palette = 7=#c7c7c7 +palette = 8=#676767 +palette = 9=#ff6d67 +palette = 10=#5ff967 +palette = 11=#fefb67 +palette = 12=#6871ff +palette = 13=#ff76ff +palette = 14=#5ffdff +palette = 15=#fffeff + +# default +palette = 16=#000000 +palette = 17=#00005f +palette = 18=#000087 +palette = 19=#0000af +palette = 20=#0000d7 +palette = 21=#0000ff +palette = 22=#005f00 +palette = 23=#005f5f +palette = 24=#005f87 +palette = 25=#005faf +palette = 26=#005fd7 +palette = 27=#005fff +palette = 28=#008700 +palette = 29=#00875f +palette = 30=#008787 +palette = 31=#0087af +palette = 32=#0087d7 +palette = 33=#0087ff +palette = 34=#00af00 +palette = 35=#00af5f +palette = 36=#00af87 +palette = 37=#00afaf +palette = 38=#00afd7 +palette = 39=#00afff +palette = 40=#00d700 +palette = 41=#00d75f +palette = 42=#00d787 +palette = 43=#00d7af +palette = 44=#00d7d7 +palette = 45=#00d7ff +palette = 46=#00ff00 +palette = 47=#00ff5f +palette = 48=#00ff87 +palette = 49=#00ffaf +palette = 50=#00ffd7 +palette = 51=#00ffff +palette = 52=#5f0000 +palette = 53=#5f005f +palette = 54=#5f0087 +palette = 55=#5f00af +palette = 56=#5f00d7 +palette = 57=#5f00ff +palette = 58=#5f5f00 +palette = 59=#5f5f5f +palette = 60=#5f5f87 +palette = 61=#5f5faf +palette = 62=#5f5fd7 +palette = 63=#5f5fff +palette = 64=#5f8700 +palette = 65=#5f875f +palette = 66=#5f8787 +palette = 67=#5f87af +palette = 68=#5f87d7 +palette = 69=#5f87ff +palette = 70=#5faf00 +palette = 71=#5faf5f +palette = 72=#5faf87 +palette = 73=#5fafaf +palette = 74=#5fafd7 +palette = 75=#5fafff +palette = 76=#5fd700 +palette = 77=#5fd75f +palette = 78=#5fd787 +palette = 79=#5fd7af +palette = 80=#5fd7d7 +palette = 81=#5fd7ff +palette = 82=#5fff00 +palette = 83=#5fff5f +palette = 84=#5fff87 +palette = 85=#5fffaf +palette = 86=#5fffd7 +palette = 87=#5fffff +palette = 88=#870000 +palette = 89=#87005f +palette = 90=#870087 +palette = 91=#8700af +palette = 92=#8700d7 +palette = 93=#8700ff +palette = 94=#875f00 +palette = 95=#875f5f +palette = 96=#875f87 +palette = 97=#875faf +palette = 98=#875fd7 +palette = 99=#875fff +palette = 100=#878700 +palette = 101=#87875f +palette = 102=#878787 +palette = 103=#8787af +palette = 104=#8787d7 +palette = 105=#8787ff +palette = 106=#87af00 +palette = 107=#87af5f +palette = 108=#87af87 +palette = 109=#87afaf +palette = 110=#87afd7 +palette = 111=#87afff +palette = 112=#87d700 +palette = 113=#87d75f +palette = 114=#87d787 +palette = 115=#87d7af +palette = 116=#87d7d7 +palette = 117=#87d7ff +palette = 118=#87ff00 +palette = 119=#87ff5f +palette = 120=#87ff87 +palette = 121=#87ffaf +palette = 122=#87ffd7 +palette = 123=#87ffff +palette = 124=#af0000 +palette = 125=#af005f +palette = 126=#af0087 +palette = 127=#af00af +palette = 128=#af00d7 +palette = 129=#af00ff +palette = 130=#af5f00 +palette = 131=#af5f5f +palette = 132=#af5f87 +palette = 133=#af5faf +palette = 134=#af5fd7 +palette = 135=#af5fff +palette = 136=#af8700 +palette = 137=#af875f +palette = 138=#af8787 +palette = 139=#af87af +palette = 140=#af87d7 +palette = 141=#af87ff +palette = 142=#afaf00 +palette = 143=#afaf5f +palette = 144=#afaf87 +palette = 145=#afafaf +palette = 146=#afafd7 +palette = 147=#afafff +palette = 148=#afd700 +palette = 149=#afd75f +palette = 150=#afd787 +palette = 151=#afd7af +palette = 152=#afd7d7 +palette = 153=#afd7ff +palette = 154=#afff00 +palette = 155=#afff5f +palette = 156=#afff87 +palette = 157=#afffaf +palette = 158=#afffd7 +palette = 159=#afffff +palette = 160=#d70000 +palette = 161=#d7005f +palette = 162=#d70087 +palette = 163=#d700af +palette = 164=#d700d7 +palette = 165=#d700ff +palette = 166=#d75f00 +palette = 167=#d75f5f +palette = 168=#d75f87 +palette = 169=#d75faf +palette = 170=#d75fd7 +palette = 171=#d75fff +palette = 172=#d78700 +palette = 173=#d7875f +palette = 174=#d78787 +palette = 175=#d787af +palette = 176=#d787d7 +palette = 177=#d787ff +palette = 178=#d7af00 +palette = 179=#d7af5f +palette = 180=#d7af87 +palette = 181=#d7afaf +palette = 182=#d7afd7 +palette = 183=#d7afff +palette = 184=#d7d700 +palette = 185=#d7d75f +palette = 186=#d7d787 +palette = 187=#d7d7af +palette = 188=#d7d7d7 +palette = 189=#d7d7ff +palette = 190=#d7ff00 +palette = 191=#d7ff5f +palette = 192=#d7ff87 +palette = 193=#d7ffaf +palette = 194=#d7ffd7 +palette = 195=#d7ffff +palette = 196=#ff0000 +palette = 197=#ff005f +palette = 198=#ff0087 +palette = 199=#ff00af +palette = 200=#ff00d7 +palette = 201=#ff00ff +palette = 202=#ff5f00 +palette = 203=#ff5f5f +palette = 204=#ff5f87 +palette = 205=#ff5faf +palette = 206=#ff5fd7 +palette = 207=#ff5fff +palette = 208=#ff8700 +palette = 209=#ff875f +palette = 210=#ff8787 +palette = 211=#ff87af +palette = 212=#ff87d7 +palette = 213=#ff87ff +palette = 214=#ffaf00 +palette = 215=#ffaf5f +palette = 216=#ffaf87 +palette = 217=#ffafaf +palette = 218=#ffafd7 +palette = 219=#ffafff +palette = 220=#ffd700 +palette = 221=#ffd75f +palette = 222=#ffd787 +palette = 223=#ffd7af +palette = 224=#ffd7d7 +palette = 225=#ffd7ff +palette = 226=#ffff00 +palette = 227=#ffff5f +palette = 228=#ffff87 +palette = 229=#ffffaf +palette = 230=#ffffd7 +palette = 231=#ffffff +palette = 232=#080808 +palette = 233=#121212 +palette = 234=#1c1c1c +palette = 235=#262626 +palette = 236=#303030 +palette = 237=#3a3a3a +palette = 238=#444444 +palette = 239=#4e4e4e +palette = 240=#585858 +palette = 241=#626262 +palette = 242=#6c6c6c +palette = 243=#767676 +palette = 244=#808080 +palette = 245=#8a8a8a +palette = 246=#949494 +palette = 247=#9e9e9e +palette = 248=#a8a8a8 +palette = 249=#b2b2b2 +palette = 250=#bcbcbc +palette = 251=#c6c6c6 +palette = 252=#d0d0d0 +palette = 253=#dadada +palette = 254=#e4e4e4 +palette = 255=#eeeeee diff --git a/ghostty/crt.glsl b/ghostty/crt.glsl new file mode 100644 index 0000000..31d1bec --- /dev/null +++ b/ghostty/crt.glsl @@ -0,0 +1,310 @@ +// source: https://gist.github.com/qwerasd205/c3da6c610c8ffe17d6d2d3cc7068f17f +// credits: https://github.com/qwerasd205 +//============================================================== +// +// [CRTS] PUBLIC DOMAIN CRT-STYLED SCALAR by Timothy Lottes +// +// [+] Adapted with alterations for use in Ghostty by Qwerasd. +// For more information on changes, see comment below license. +// +//============================================================== +// +// LICENSE = UNLICENSE (aka PUBLIC DOMAIN) +// +//-------------------------------------------------------------- +// This is free and unencumbered software released into the +// public domain. +//-------------------------------------------------------------- +// Anyone is free to copy, modify, publish, use, compile, sell, +// or distribute this software, either in source code form or as +// a compiled binary, for any purpose, commercial or +// non-commercial, and by any means. +//-------------------------------------------------------------- +// In jurisdictions that recognize copyright laws, the author or +// authors of this software dedicate any and all copyright +// interest in the software to the public domain. We make this +// dedication for the benefit of the public at large and to the +// detriment of our heirs and successors. We intend this +// dedication to be an overt act of relinquishment in perpetuity +// of all present and future rights to this software under +// copyright law. +//-------------------------------------------------------------- +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +// KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +// AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT +// OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +//-------------------------------------------------------------- +// For more information, please refer to +// +//============================================================== + +// This shader is a modified version of the excellent +// FixingPixelArtFast by Timothy Lottes on Shadertoy. +// +// The original shader can be found at: +// https://www.shadertoy.com/view/MtSfRK +// +// Modifications have been made to reduce the verbosity, +// and many of the comments have been removed / reworded. +// Additionally, the license has been moved to the top of +// the file, and can be read above. I (Qwerasd) choose to +// release the modified version under the same license. + +// The appearance of this shader can be altered +// by adjusting the parameters defined below. + +// "Scanlines" per real screen pixel. +// e.g. SCALE 0.5 means each scanline is 2 pixels. +// Recommended values: +// o High DPI displays: 0.33333333 +// - Low DPI displays: 0.66666666 +#define SCALE 0.33333333 + +// "Tube" warp +#define CRTS_WARP 1 + +// Darkness of vignette in corners after warping +// 0.0 = completely black +// 1.0 = no vignetting +#define MIN_VIN 0.5 + +// Try different masks +// #define CRTS_MASK_GRILLE 1 +// #define CRTS_MASK_GRILLE_LITE 1 +// #define CRTS_MASK_NONE 1 +#define CRTS_MASK_SHADOW 1 + +// Scanline thinness +// 0.50 = fused scanlines +// 0.70 = recommended default +// 1.00 = thinner scanlines (too thin) +#define INPUT_THIN 0.75 + +// Horizonal scan blur +// -3.0 = pixely +// -2.5 = default +// -2.0 = smooth +// -1.0 = too blurry +#define INPUT_BLUR -2.75 + +// Shadow mask effect, ranges from, +// 0.25 = large amount of mask (not recommended, too dark) +// 0.50 = recommended default +// 1.00 = no shadow mask +#define INPUT_MASK 0.65 + +float FromSrgb1(float c) { + return (c <= 0.04045) ? c * (1.0 / 12.92) : + pow(c * (1.0 / 1.055) + (0.055 / 1.055), 2.4); +} +vec3 FromSrgb(vec3 c) { + return vec3( + FromSrgb1(c.r), FromSrgb1(c.g), FromSrgb1(c.b)); +} + +vec3 CrtsFetch(vec2 uv) { + return FromSrgb(texture(iChannel0, uv.xy).rgb); +} + +#define CrtsRcpF1(x) (1.0/(x)) +#define CrtsSatF1(x) clamp((x),0.0,1.0) + +float CrtsMax3F1(float a, float b, float c) { + return max(a, max(b, c)); +} + +vec2 CrtsTone( + float thin, + float mask) { + #ifdef CRTS_MASK_NONE + mask = 1.0; + #endif + + #ifdef CRTS_MASK_GRILLE_LITE + // Normal R mask is {1.0,mask,mask} + // LITE R mask is {mask,1.0,1.0} + mask = 0.5 + mask * 0.5; + #endif + + vec2 ret; + float midOut = 0.18 / ((1.5 - thin) * (0.5 * mask + 0.5)); + float pMidIn = 0.18; + ret.x = ((-pMidIn) + midOut) / ((1.0 - pMidIn) * midOut); + ret.y = ((-pMidIn) * midOut + pMidIn) / (midOut * (-pMidIn) + midOut); + + return ret; +} + +vec3 CrtsMask(vec2 pos, float dark) { + #ifdef CRTS_MASK_GRILLE + vec3 m = vec3(dark, dark, dark); + float x = fract(pos.x * (1.0 / 3.0)); + if (x < (1.0 / 3.0)) m.r = 1.0; + else if (x < (2.0 / 3.0)) m.g = 1.0; + else m.b = 1.0; + return m; + #endif + + #ifdef CRTS_MASK_GRILLE_LITE + vec3 m = vec3(1.0, 1.0, 1.0); + float x = fract(pos.x * (1.0 / 3.0)); + if (x < (1.0 / 3.0)) m.r = dark; + else if (x < (2.0 / 3.0)) m.g = dark; + else m.b = dark; + return m; + #endif + + #ifdef CRTS_MASK_NONE + return vec3(1.0, 1.0, 1.0); + #endif + + #ifdef CRTS_MASK_SHADOW + pos.x += pos.y * 3.0; + vec3 m = vec3(dark, dark, dark); + float x = fract(pos.x * (1.0 / 6.0)); + if (x < (1.0 / 3.0)) m.r = 1.0; + else if (x < (2.0 / 3.0)) m.g = 1.0; + else m.b = 1.0; + return m; + #endif +} + +vec3 CrtsFilter( + vec2 ipos, + vec2 inputSizeDivOutputSize, + vec2 halfInputSize, + vec2 rcpInputSize, + vec2 rcpOutputSize, + vec2 twoDivOutputSize, + float inputHeight, + vec2 warp, + float thin, + float blur, + float mask, + vec2 tone +) { + // Optional apply warp + vec2 pos; + #ifdef CRTS_WARP + // Convert to {-1 to 1} range + pos = ipos * twoDivOutputSize - vec2(1.0, 1.0); + + // Distort pushes image outside {-1 to 1} range + pos *= vec2( + 1.0 + (pos.y * pos.y) * warp.x, + 1.0 + (pos.x * pos.x) * warp.y); + + // TODO: Vignette needs optimization + float vin = 1.0 - ( + (1.0 - CrtsSatF1(pos.x * pos.x)) * (1.0 - CrtsSatF1(pos.y * pos.y))); + vin = CrtsSatF1((-vin) * inputHeight + inputHeight); + + // Leave in {0 to inputSize} + pos = pos * halfInputSize + halfInputSize; + #else + pos = ipos * inputSizeDivOutputSize; + #endif + + // Snap to center of first scanline + float y0 = floor(pos.y - 0.5) + 0.5; + // Snap to center of one of four pixels + float x0 = floor(pos.x - 1.5) + 0.5; + + // Inital UV position + vec2 p = vec2(x0 * rcpInputSize.x, y0 * rcpInputSize.y); + // Fetch 4 nearest texels from 2 nearest scanlines + vec3 colA0 = CrtsFetch(p); + p.x += rcpInputSize.x; + vec3 colA1 = CrtsFetch(p); + p.x += rcpInputSize.x; + vec3 colA2 = CrtsFetch(p); + p.x += rcpInputSize.x; + vec3 colA3 = CrtsFetch(p); + p.y += rcpInputSize.y; + vec3 colB3 = CrtsFetch(p); + p.x -= rcpInputSize.x; + vec3 colB2 = CrtsFetch(p); + p.x -= rcpInputSize.x; + vec3 colB1 = CrtsFetch(p); + p.x -= rcpInputSize.x; + vec3 colB0 = CrtsFetch(p); + + // Vertical filter + // Scanline intensity is using sine wave + // Easy filter window and integral used later in exposure + float off = pos.y - y0; + float pi2 = 6.28318530717958; + float hlf = 0.5; + float scanA = cos(min(0.5, off * thin) * pi2) * hlf + hlf; + float scanB = cos(min(0.5, (-off) * thin + thin) * pi2) * hlf + hlf; + + // Horizontal kernel is simple gaussian filter + float off0 = pos.x - x0; + float off1 = off0 - 1.0; + float off2 = off0 - 2.0; + float off3 = off0 - 3.0; + float pix0 = exp2(blur * off0 * off0); + float pix1 = exp2(blur * off1 * off1); + float pix2 = exp2(blur * off2 * off2); + float pix3 = exp2(blur * off3 * off3); + float pixT = CrtsRcpF1(pix0 + pix1 + pix2 + pix3); + + #ifdef CRTS_WARP + // Get rid of wrong pixels on edge + pixT *= max(MIN_VIN, vin); + #endif + + scanA *= pixT; + scanB *= pixT; + + // Apply horizontal and vertical filters + vec3 color = + (colA0 * pix0 + colA1 * pix1 + colA2 * pix2 + colA3 * pix3) * scanA + + (colB0 * pix0 + colB1 * pix1 + colB2 * pix2 + colB3 * pix3) * scanB; + + // Apply phosphor mask + color *= CrtsMask(ipos, mask); + + // Tonal control, start by protecting from /0 + float peak = max(1.0 / (256.0 * 65536.0), + CrtsMax3F1(color.r, color.g, color.b)); + // Compute the ratios of {R,G,B} + vec3 ratio = color * CrtsRcpF1(peak); + // Apply tonal curve to peak value + peak = peak * CrtsRcpF1(peak * tone.x + tone.y); + // Reconstruct color + return ratio * peak; +} + +float ToSrgb1(float c) { + return (c < 0.0031308 ? c * 12.92 : 1.055 * pow(c, 0.41666) - 0.055); +} +vec3 ToSrgb(vec3 c) { + return vec3( + ToSrgb1(c.r), ToSrgb1(c.g), ToSrgb1(c.b)); +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + float aspect = iResolution.x / iResolution.y; + fragColor.rgb = CrtsFilter( + fragCoord.xy, + vec2(1.0), + iResolution.xy * SCALE * 0.5, + 1.0 / (iResolution.xy * SCALE), + 1.0 / iResolution.xy, + 2.0 / iResolution.xy, + iResolution.y, + vec2(1.0 / (50.0 * aspect), 1.0 / 50.0), + INPUT_THIN, + INPUT_BLUR, + INPUT_MASK, + CrtsTone(INPUT_THIN, INPUT_MASK) + ); + + // Linear to SRGB for output. + fragColor.rgb = ToSrgb(fragColor.rgb); +} \ No newline at end of file diff --git a/ghostty/crt2.glsl b/ghostty/crt2.glsl new file mode 100644 index 0000000..c085e0b --- /dev/null +++ b/ghostty/crt2.glsl @@ -0,0 +1,308 @@ +//============================================================== +// +// [CRTS] PUBLIC DOMAIN CRT-STYLED SCALAR by Timothy Lottes +// +// [+] Adapted with alterations for use in Ghostty by Qwerasd. +// For more information on changes, see comment below license. +// +//============================================================== +// +// LICENSE = UNLICENSE (aka PUBLIC DOMAIN) +// +//-------------------------------------------------------------- +// This is free and unencumbered software released into the +// public domain. +//-------------------------------------------------------------- +// Anyone is free to copy, modify, publish, use, compile, sell, +// or distribute this software, either in source code form or as +// a compiled binary, for any purpose, commercial or +// non-commercial, and by any means. +//-------------------------------------------------------------- +// In jurisdictions that recognize copyright laws, the author or +// authors of this software dedicate any and all copyright +// interest in the software to the public domain. We make this +// dedication for the benefit of the public at large and to the +// detriment of our heirs and successors. We intend this +// dedication to be an overt act of relinquishment in perpetuity +// of all present and future rights to this software under +// copyright law. +//-------------------------------------------------------------- +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +// KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +// AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT +// OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +//-------------------------------------------------------------- +// For more information, please refer to +// +//============================================================== + +// This shader is a modified version of the excellent +// FixingPixelArtFast by Timothy Lottes on Shadertoy. +// +// The original shader can be found at: +// https://www.shadertoy.com/view/MtSfRK +// +// Modifications have been made to reduce the verbosity, +// and many of the comments have been removed / reworded. +// Additionally, the license has been moved to the top of +// the file, and can be read above. I (Qwerasd) choose to +// release the modified version under the same license. + +// The appearance of this shader can be altered +// by adjusting the parameters defined below. + +// "Scanlines" per real screen pixel. +// e.g. SCALE 0.5 means each scanline is 2 pixels. +// Recommended values: +// o High DPI displays: 0.33333333 +// - Low DPI displays: 0.66666666 +#define SCALE 0.33333333 + +// "Tube" warp +#define CRTS_WARP 1 + +// Darkness of vignette in corners after warping +// 0.0 = completely black +// 1.0 = no vignetting +#define MIN_VIN 0.5 + +// Try different masks +// #define CRTS_MASK_GRILLE 1 +// #define CRTS_MASK_GRILLE_LITE 1 +// #define CRTS_MASK_NONE 1 +#define CRTS_MASK_SHADOW 1 + +// Scanline thinness +// 0.50 = fused scanlines +// 0.70 = recommended default +// 1.00 = thinner scanlines (too thin) +#define INPUT_THIN 0.75 + +// Horizonal scan blur +// -3.0 = pixely +// -2.5 = default +// -2.0 = smooth +// -1.0 = too blurry +#define INPUT_BLUR -2.75 + +// Shadow mask effect, ranges from, +// 0.25 = large amount of mask (not recommended, too dark) +// 0.50 = recommended default +// 1.00 = no shadow mask +#define INPUT_MASK 0.65 + +float FromSrgb1(float c) { + return (c <= 0.04045) ? c * (1.0 / 12.92) : + pow(c * (1.0 / 1.055) + (0.055 / 1.055), 2.4); +} +vec3 FromSrgb(vec3 c) { + return vec3( + FromSrgb1(c.r), FromSrgb1(c.g), FromSrgb1(c.b)); +} + +vec3 CrtsFetch(vec2 uv) { + return FromSrgb(texture(iChannel0, uv.xy).rgb); +} + +#define CrtsRcpF1(x) (1.0/(x)) +#define CrtsSatF1(x) clamp((x),0.0,1.0) + +float CrtsMax3F1(float a, float b, float c) { + return max(a, max(b, c)); +} + +vec2 CrtsTone( + float thin, + float mask) { + #ifdef CRTS_MASK_NONE + mask = 1.0; + #endif + + #ifdef CRTS_MASK_GRILLE_LITE + // Normal R mask is {1.0,mask,mask} + // LITE R mask is {mask,1.0,1.0} + mask = 0.5 + mask * 0.5; + #endif + + vec2 ret; + float midOut = 0.18 / ((1.5 - thin) * (0.5 * mask + 0.5)); + float pMidIn = 0.18; + ret.x = ((-pMidIn) + midOut) / ((1.0 - pMidIn) * midOut); + ret.y = ((-pMidIn) * midOut + pMidIn) / (midOut * (-pMidIn) + midOut); + + return ret; +} + +vec3 CrtsMask(vec2 pos, float dark) { + #ifdef CRTS_MASK_GRILLE + vec3 m = vec3(dark, dark, dark); + float x = fract(pos.x * (1.0 / 3.0)); + if (x < (1.0 / 3.0)) m.r = 1.0; + else if (x < (2.0 / 3.0)) m.g = 1.0; + else m.b = 1.0; + return m; + #endif + + #ifdef CRTS_MASK_GRILLE_LITE + vec3 m = vec3(1.0, 1.0, 1.0); + float x = fract(pos.x * (1.0 / 3.0)); + if (x < (1.0 / 3.0)) m.r = dark; + else if (x < (2.0 / 3.0)) m.g = dark; + else m.b = dark; + return m; + #endif + + #ifdef CRTS_MASK_NONE + return vec3(1.0, 1.0, 1.0); + #endif + + #ifdef CRTS_MASK_SHADOW + pos.x += pos.y * 3.0; + vec3 m = vec3(dark, dark, dark); + float x = fract(pos.x * (1.0 / 6.0)); + if (x < (1.0 / 3.0)) m.r = 1.0; + else if (x < (2.0 / 3.0)) m.g = 1.0; + else m.b = 1.0; + return m; + #endif +} + +vec3 CrtsFilter( + vec2 ipos, + vec2 inputSizeDivOutputSize, + vec2 halfInputSize, + vec2 rcpInputSize, + vec2 rcpOutputSize, + vec2 twoDivOutputSize, + float inputHeight, + vec2 warp, + float thin, + float blur, + float mask, + vec2 tone +) { + // Optional apply warp + vec2 pos; + #ifdef CRTS_WARP + // Convert to {-1 to 1} range + pos = ipos * twoDivOutputSize - vec2(1.0, 1.0); + + // Distort pushes image outside {-1 to 1} range + pos *= vec2( + 1.0 + (pos.y * pos.y) * warp.x, + 1.0 + (pos.x * pos.x) * warp.y); + + // TODO: Vignette needs optimization + float vin = 1.0 - ( + (1.0 - CrtsSatF1(pos.x * pos.x)) * (1.0 - CrtsSatF1(pos.y * pos.y))); + vin = CrtsSatF1((-vin) * inputHeight + inputHeight); + + // Leave in {0 to inputSize} + pos = pos * halfInputSize + halfInputSize; + #else + pos = ipos * inputSizeDivOutputSize; + #endif + + // Snap to center of first scanline + float y0 = floor(pos.y - 0.5) + 0.5; + // Snap to center of one of four pixels + float x0 = floor(pos.x - 1.5) + 0.5; + + // Inital UV position + vec2 p = vec2(x0 * rcpInputSize.x, y0 * rcpInputSize.y); + // Fetch 4 nearest texels from 2 nearest scanlines + vec3 colA0 = CrtsFetch(p); + p.x += rcpInputSize.x; + vec3 colA1 = CrtsFetch(p); + p.x += rcpInputSize.x; + vec3 colA2 = CrtsFetch(p); + p.x += rcpInputSize.x; + vec3 colA3 = CrtsFetch(p); + p.y += rcpInputSize.y; + vec3 colB3 = CrtsFetch(p); + p.x -= rcpInputSize.x; + vec3 colB2 = CrtsFetch(p); + p.x -= rcpInputSize.x; + vec3 colB1 = CrtsFetch(p); + p.x -= rcpInputSize.x; + vec3 colB0 = CrtsFetch(p); + + // Vertical filter + // Scanline intensity is using sine wave + // Easy filter window and integral used later in exposure + float off = pos.y - y0; + float pi2 = 6.28318530717958; + float hlf = 0.5; + float scanA = cos(min(0.5, off * thin) * pi2) * hlf + hlf; + float scanB = cos(min(0.5, (-off) * thin + thin) * pi2) * hlf + hlf; + + // Horizontal kernel is simple gaussian filter + float off0 = pos.x - x0; + float off1 = off0 - 1.0; + float off2 = off0 - 2.0; + float off3 = off0 - 3.0; + float pix0 = exp2(blur * off0 * off0); + float pix1 = exp2(blur * off1 * off1); + float pix2 = exp2(blur * off2 * off2); + float pix3 = exp2(blur * off3 * off3); + float pixT = CrtsRcpF1(pix0 + pix1 + pix2 + pix3); + + #ifdef CRTS_WARP + // Get rid of wrong pixels on edge + pixT *= max(MIN_VIN, vin); + #endif + + scanA *= pixT; + scanB *= pixT; + + // Apply horizontal and vertical filters + vec3 color = + (colA0 * pix0 + colA1 * pix1 + colA2 * pix2 + colA3 * pix3) * scanA + + (colB0 * pix0 + colB1 * pix1 + colB2 * pix2 + colB3 * pix3) * scanB; + + // Apply phosphor mask + color *= CrtsMask(ipos, mask); + + // Tonal control, start by protecting from /0 + float peak = max(1.0 / (256.0 * 65536.0), + CrtsMax3F1(color.r, color.g, color.b)); + // Compute the ratios of {R,G,B} + vec3 ratio = color * CrtsRcpF1(peak); + // Apply tonal curve to peak value + peak = peak * CrtsRcpF1(peak * tone.x + tone.y); + // Reconstruct color + return ratio * peak; +} + +float ToSrgb1(float c) { + return (c < 0.0031308 ? c * 12.92 : 1.055 * pow(c, 0.41666) - 0.055); +} +vec3 ToSrgb(vec3 c) { + return vec3( + ToSrgb1(c.r), ToSrgb1(c.g), ToSrgb1(c.b)); +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + float aspect = iResolution.x / iResolution.y; + fragColor.rgb = CrtsFilter( + fragCoord.xy, + vec2(1.0), + iResolution.xy * SCALE * 0.5, + 1.0 / (iResolution.xy * SCALE), + 1.0 / iResolution.xy, + 2.0 / iResolution.xy, + iResolution.y, + vec2(1.0 / (50.0 * aspect), 1.0 / 50.0), + INPUT_THIN, + INPUT_BLUR, + INPUT_MASK, + CrtsTone(INPUT_THIN, INPUT_MASK) + ); + + // Linear to SRGB for output. + fragColor.rgb = ToSrgb(fragColor.rgb); +} \ No newline at end of file diff --git a/ghostty/glitchy.glsl b/ghostty/glitchy.glsl new file mode 100644 index 0000000..603e3ec --- /dev/null +++ b/ghostty/glitchy.glsl @@ -0,0 +1,117 @@ +// modified version of https://www.shadertoy.com/view/wld3WN +// amount of seconds for which the glitch loop occurs +#define DURATION 10. +// percentage of the duration for which the glitch is triggered +#define AMT .1 + +#define SS(a, b, x) (smoothstep(a, b, x) * smoothstep(b, a, x)) + +#define UI0 1597334673U +#define UI1 3812015801U +#define UI2 uvec2(UI0, UI1) +#define UI3 uvec3(UI0, UI1, 2798796415U) +#define UIF (1. / float(0xffffffffU)) + +// Hash by David_Hoskins +vec3 hash33(vec3 p) +{ + uvec3 q = uvec3(ivec3(p)) * UI3; + q = (q.x ^ q.y ^ q.z)*UI3; + return -1. + 2. * vec3(q) * UIF; +} + +// Gradient noise by iq +float gnoise(vec3 x) +{ + // grid + vec3 p = floor(x); + vec3 w = fract(x); + + // quintic interpolant + vec3 u = w * w * w * (w * (w * 6. - 15.) + 10.); + + // gradients + vec3 ga = hash33(p + vec3(0., 0., 0.)); + vec3 gb = hash33(p + vec3(1., 0., 0.)); + vec3 gc = hash33(p + vec3(0., 1., 0.)); + vec3 gd = hash33(p + vec3(1., 1., 0.)); + vec3 ge = hash33(p + vec3(0., 0., 1.)); + vec3 gf = hash33(p + vec3(1., 0., 1.)); + vec3 gg = hash33(p + vec3(0., 1., 1.)); + vec3 gh = hash33(p + vec3(1., 1., 1.)); + + // projections + float va = dot(ga, w - vec3(0., 0., 0.)); + float vb = dot(gb, w - vec3(1., 0., 0.)); + float vc = dot(gc, w - vec3(0., 1., 0.)); + float vd = dot(gd, w - vec3(1., 1., 0.)); + float ve = dot(ge, w - vec3(0., 0., 1.)); + float vf = dot(gf, w - vec3(1., 0., 1.)); + float vg = dot(gg, w - vec3(0., 1., 1.)); + float vh = dot(gh, w - vec3(1., 1., 1.)); + + // interpolation + float gNoise = va + u.x * (vb - va) + + u.y * (vc - va) + + u.z * (ve - va) + + u.x * u.y * (va - vb - vc + vd) + + u.y * u.z * (va - vc - ve + vg) + + u.z * u.x * (va - vb - ve + vf) + + u.x * u.y * u.z * (-va + vb + vc - vd + ve - vf - vg + vh); + + return 2. * gNoise; +} + +// gradient noise in range [0, 1] +float gnoise01(vec3 x) +{ + return .5 + .5 * gnoise(x); +} + +// warp uvs for the crt effect +vec2 crt(vec2 uv) +{ + float tht = atan(uv.y, uv.x); + float r = length(uv); + // curve without distorting the center + r /= (1. - .1 * r * r); + uv.x = r * cos(tht); + uv.y = r * sin(tht); + return .5 * (uv + 1.); +} + + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) +{ + vec2 uv = fragCoord / iResolution.xy; + float t = iTime; + + // smoothed interval for which the glitch gets triggered + float glitchAmount = SS(DURATION * .001, DURATION * AMT, mod(t, DURATION)); + float displayNoise = 0.; + vec3 col = vec3(0.); + vec2 eps = vec2(5. / iResolution.x, 0.); + vec2 st = vec2(0.); + + // analog distortion + float y = uv.y * iResolution.y; + float distortion = gnoise(vec3(0., y * .01, t * 500.)) * (glitchAmount * 4. + .1); + distortion *= gnoise(vec3(0., y * .02, t * 250.)) * (glitchAmount * 2. + .025); + + ++displayNoise; + distortion += smoothstep(.999, 1., sin((uv.y + t * 1.6) * 2.)) * .02; + distortion -= smoothstep(.999, 1., sin((uv.y + t) * 2.)) * .02; + st = uv + vec2(distortion, 0.); + // chromatic aberration + col.r += textureLod(iChannel0, st + eps + distortion, 0.).r; + col.g += textureLod(iChannel0, st, 0.).g; + col.b += textureLod(iChannel0, st - eps - distortion, 0.).b; + + // white noise + scanlines + displayNoise = 0.2 * clamp(displayNoise, 0., 1.); + col += (.15 + .65 * glitchAmount) * (hash33(vec3(fragCoord, mod(float(iFrame), + 1000.))).r) * displayNoise; + col -= (.25 + .75 * glitchAmount) * (sin(4. * t + uv.y * iResolution.y * 1.75)) + * displayNoise; + fragColor = vec4(col, 1.0); +} diff --git a/ghostty/glow-rgbsplit-twitchy.glsl b/ghostty/glow-rgbsplit-twitchy.glsl new file mode 100644 index 0000000..9411e4e --- /dev/null +++ b/ghostty/glow-rgbsplit-twitchy.glsl @@ -0,0 +1,144 @@ +// First it does a "chromatic aberration" by splitting the rgb signals by a product of sin functions +// over time, then it does a glow effect in a perceptual color space +// Based on kalgynirae's Ghostty passable glow shader and NickWest's Chromatic Aberration shader demo +// Passable glow: https://github.com/kalgynirae/dotfiles/blob/main/ghostty/glow.glsl +// "Chromatic Aberration": https://www.shadertoy.com/view/Mds3zn + +// sRGB linear -> nonlinear transform from https://bottosson.github.io/posts/colorwrong/ +float f(float x) { + if (x >= 0.0031308) { + return 1.055 * pow(x, 1.0 / 2.4) - 0.055; + } else { + return 12.92 * x; + } +} + +float f_inv(float x) { + if (x >= 0.04045) { + return pow((x + 0.055) / 1.055, 2.4); + } else { + return x / 12.92; + } +} + +// Oklab <-> linear sRGB conversions from https://bottosson.github.io/posts/oklab/ +vec4 toOklab(vec4 rgb) { + vec3 c = vec3(f_inv(rgb.r), f_inv(rgb.g), f_inv(rgb.b)); + float l = 0.4122214708 * c.r + 0.5363325363 * c.g + 0.0514459929 * c.b; + float m = 0.2119034982 * c.r + 0.6806995451 * c.g + 0.1073969566 * c.b; + float s = 0.0883024619 * c.r + 0.2817188376 * c.g + 0.6299787005 * c.b; + float l_ = pow(l, 1.0 / 3.0); + float m_ = pow(m, 1.0 / 3.0); + float s_ = pow(s, 1.0 / 3.0); + return vec4( + 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_, + 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_, + 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_, + rgb.a + ); +} + +vec4 toRgb(vec4 oklab) { + vec3 c = oklab.rgb; + float l_ = c.r + 0.3963377774 * c.g + 0.2158037573 * c.b; + float m_ = c.r - 0.1055613458 * c.g - 0.0638541728 * c.b; + float s_ = c.r - 0.0894841775 * c.g - 1.2914855480 * c.b; + float l = l_ * l_ * l_; + float m = m_ * m_ * m_; + float s = s_ * s_ * s_; + vec3 linear_srgb = vec3( + 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s, + -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s, + -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s + ); + return vec4( + clamp(f(linear_srgb.r), 0.0, 1.0), + clamp(f(linear_srgb.g), 0.0, 1.0), + clamp(f(linear_srgb.b), 0.0, 1.0), + oklab.a + ); +} + +// Bloom samples from https://gist.github.com/qwerasd205/c3da6c610c8ffe17d6d2d3cc7068f17f +const vec3[24] samples = { + vec3(0.1693761725038636, 0.9855514761735895, 1), + vec3(-1.333070830962943, 0.4721463328627773, 0.7071067811865475), + vec3(-0.8464394909806497, -1.51113870578065, 0.5773502691896258), + vec3(1.554155680728463, -1.2588090085709776, 0.5), + vec3(1.681364377589461, 1.4741145918052656, 0.4472135954999579), + vec3(-1.2795157692199817, 2.088741103228784, 0.4082482904638631), + vec3(-2.4575847530631187, -0.9799373355024756, 0.3779644730092272), + vec3(0.5874641440200847, -2.7667464429345077, 0.35355339059327373), + vec3(2.997715703369726, 0.11704939884745152, 0.3333333333333333), + vec3(0.41360842451688395, 3.1351121305574803, 0.31622776601683794), + vec3(-3.167149933769243, 0.9844599011770256, 0.30151134457776363), + vec3(-1.5736713846521535, -3.0860263079123245, 0.2886751345948129), + vec3(2.888202648340422, -2.1583061557896213, 0.2773500981126146), + vec3(2.7150778983300325, 2.5745586041105715, 0.2672612419124244), + vec3(-2.1504069972377464, 3.2211410627650165, 0.2581988897471611), + vec3(-3.6548858794907493, -1.6253643308191343, 0.25), + vec3(1.0130775986052671, -3.9967078676335834, 0.24253562503633297), + vec3(4.229723673607257, 0.33081361055181563, 0.23570226039551587), + vec3(0.40107790291173834, 4.340407413572593, 0.22941573387056174), + vec3(-4.319124570236028, 1.159811599693438, 0.22360679774997896), + vec3(-1.9209044802827355, -4.160543952132907, 0.2182178902359924), + vec3(3.8639122286635708, -2.6589814382925123, 0.21320071635561041), + vec3(3.3486228404946234, 3.4331800232609, 0.20851441405707477), + vec3(-2.8769733643574344, 3.9652268864187157, 0.20412414523193154) +}; + +float offsetFunction(float iTime) { + float amount = 1.0; + const float periods[4] = {6.0, 16.0, 19.0, 27.0}; + for (int i = 0; i < 4; i++) { + amount *= 1.0 + 0.5 * sin(iTime*periods[i]); + } + //return amount; + return amount * periods[3]; +} + +const float DIM_CUTOFF = 0.35; +const float BRIGHT_CUTOFF = 0.65; +const float ABBERATION_FACTOR = 0.05; + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord.xy / iResolution.xy; + + float amount = offsetFunction(iTime); + + vec3 col; + col.r = texture( iChannel0, vec2(uv.x-ABBERATION_FACTOR*amount / iResolution.x, uv.y) ).r; + col.g = texture( iChannel0, uv ).g; + col.b = texture( iChannel0, vec2(uv.x+ABBERATION_FACTOR*amount / iResolution.x, uv.y) ).b; + + vec4 splittedColor = vec4(col, 1.0); + vec4 source = toOklab(splittedColor); + vec4 dest = source; + + if (source.x > DIM_CUTOFF) { + dest.x *= 1.2; + // dest.x = 1.2; + } else { + vec2 step = vec2(1.414) / iResolution.xy; + vec3 glow = vec3(0.0); + for (int i = 0; i < 24; i++) { + vec3 s = samples[i]; + float weight = s.z; + vec4 c = toOklab(texture(iChannel0, uv + s.xy * step)); + if (c.x > DIM_CUTOFF) { + glow.yz += c.yz * weight * 0.3; + if (c.x <= BRIGHT_CUTOFF) { + glow.x += c.x * weight * 0.05; + } else { + glow.x += c.x * weight * 0.10; + } + } + } + // float lightness_diff = clamp(glow.x - dest.x, 0.0, 1.0); + // dest.x = lightness_diff; + // dest.yz = dest.yz * (1.0 - lightness_diff) + glow.yz * lightness_diff; + dest.xyz += glow.xyz; + } + + fragColor = toRgb(dest); +} diff --git a/ghostty/in-game-crt.glsl b/ghostty/in-game-crt.glsl new file mode 100644 index 0000000..34cfc62 --- /dev/null +++ b/ghostty/in-game-crt.glsl @@ -0,0 +1,304 @@ +// In-game CRT shader +// Author: sarphiv +// License: CC BY-NC-SA 4.0 +// Description: +// Shader for ghostty that is focussed on being usable while looking like a stylized CRT terminal in a modern video game. +// I know a tiny bit about shaders, and nothing about GLSL, +// so this is a Frakenstein's monster combination of other shaders together with a lot of surgery. +// On the bright side, i've cleaned up the body parts and surgery a lot. + +// Based on: +// 1. https://gist.github.com/mitchellh/39d62186910dcc27cad097fed16eb882 (forces the choice of license) +// 2. https://gist.github.com/qwerasd205/c3da6c610c8ffe17d6d2d3cc7068f17f +// 3. https://gist.github.com/seanwcom/0fbe6b270aaa5f28823e053d3dbb14ca + + +// Settings: +// How straight the terminal is in each axis +// (x, y) \in R^2 : x, y > 0 +#define CURVE 13.0, 11.0 + +// How far apart the different colors are from each other +// x \in R +#define COLOR_FRINGING_SPREAD 1.0 + +// How much the ghost images are spread out +// x \in R : x >= 0 +#define GHOSTING_SPREAD 0.75 +// How visible ghost images are +// x \in R : x >= 0 +#define GHOSTING_STRENGTH 1.0 + +// How much of the non-linearly darkened colors are mixed in +// [0, 1] +#define DARKEN_MIX 0.4 + +// How far in the vignette spreads +// x \in R : x >= 0 +#define VIGNETTE_SPREAD 0.3 +// How bright the vignette is +// x \in R : x >= 0 +#define VIGNETTE_BRIGHTNESS 6.4 + +// Tint all colors +// [0, 1]^3 +#define TINT 0.93, 1.00, 0.96 + +// How visible the scan line effect is +// NOTE: Technically these are not scan lines, but rather the lack of them +// [0, 1] +#define SCAN_LINES_STRENGTH 0.15 +// How bright the spaces between the lines are +// [0, 1] +#define SCAN_LINES_VARIANCE 0.35 +// Pixels per scan line effect +// x \in R : x > 0 +#define SCAN_LINES_PERIOD 4.0 + +// How visible the aperture grille is +// x \in R : x >= 0 +#define APERTURE_GRILLE_STRENGTH 0.2 +// Pixels per aperture grille +// x \in R : x > 0 +#define APERTURE_GRILLE_PERIOD 2.0 + +// How much the screen flickers +// x \in R : x >= 0 +#define FLICKER_STRENGTH 0.05 +// How fast the screen flickers +// x \in R : x > 0 +#define FLICKER_FREQUENCY 15.0 + +// How much noise is added to filled areas +// [0, 1] +#define NOISE_CONTENT_STRENGTH 0.15 +// How much noise is added everywhere +// [0, 1] +#define NOISE_UNIFORM_STRENGTH 0.03 + +// How big the bloom is +// x \in R : x >= 0 +#define BLOOM_SPREAD 8.0 +// How visible the bloom is +// [0, 1] +#define BLOOM_STRENGTH 0.04 + +// How fast colors fade in and out +// [0, 1] +#define FADE_FACTOR 0.55 + + + +// Disabled values for when the settings are not defined +#ifndef COLOR_FRINGING_SPREAD +#define COLOR_FRINGING_SPREAD 0.0 +#endif + +#if !defined(GHOSTING_SPREAD) || !defined(GHOSTING_STRENGTH) +#undef GHOSTING_SPREAD +#undef GHOSTING_STRENGTH +#define GHOSTING_SPREAD 0.0 +#define GHOSTING_STRENGTH 0.0 +#endif + +#ifndef DARKEN_MIX +#define DARKEN_MIX 0.0 +#endif + +#if !defined(VIGNETTE_SPREAD) || !defined(VIGNETTE_BRIGHTNESS) +#undef VIGNETTE_SPREAD +#undef VIGNETTE_BRIGHTNESS +#define VIGNETTE_SPREAD 0.0 +#define VIGNETTE_BRIGHTNESS 1.0 +#endif + +#ifndef TINT +#define TINT 1.00, 1.00, 1.00 +#endif + +#if !defined(SCAN_LINES_STRENGTH) || !defined(SCAN_LINES_VARIANCE) || !defined(SCAN_LINES_PERIOD) +#undef SCAN_LINES_STRENGTH +#undef SCAN_LINES_VARIANCE +#undef SCAN_LINES_PERIOD +#define SCAN_LINES_STRENGTH 0.0 +#define SCAN_LINES_VARIANCE 1.0 +#define SCAN_LINES_PERIOD 1.0 +#endif + +#if !defined(APERTURE_GRILLE_STRENGTH) || !defined(APERTURE_GRILLE_PERIOD) +#undef APERTURE_GRILLE_STRENGTH +#undef APERTURE_GRILLE_PERIOD +#define APERTURE_GRILLE_STRENGTH 0.0 +#define APERTURE_GRILLE_PERIOD 1.0 +#endif + +#if !defined(FLICKER_STRENGTH) || !defined(FLICKER_FREQUENCY) +#undef FLICKER_STRENGTH +#undef FLICKER_FREQUENCY +#define FLICKER_STRENGTH 0.0 +#define FLICKER_FREQUENCY 1.0 +#endif + +#if !defined(NOISE_CONTENT_STRENGTH) || !defined(NOISE_UNIFORM_STRENGTH) +#undef NOISE_CONTENT_STRENGTH +#undef NOISE_UNIFORM_STRENGTH +#define NOISE_CONTENT_STRENGTH 0.0 +#define NOISE_UNIFORM_STRENGTH 0.0 +#endif + +#if !defined(BLOOM_SPREAD) || !defined(BLOOM_STRENGTH) +#undef BLOOM_SPREAD +#undef BLOOM_STRENGTH +#define BLOOM_SPREAD 0.0 +#define BLOOM_STRENGTH 0.0 +#endif + +#ifndef FADE_FACTOR +#define FADE_FACTOR 1.00 +#endif + + + +// Constants +#define PI 3.1415926535897932384626433832795 + +#ifdef BLOOM_SPREAD +// Golden spiral samples used for bloom. +// [x, y, weight] weight is inverse of distance. +const vec3[24] bloom_samples = { + vec3( 0.1693761725038636, 0.9855514761735895, 1), + vec3(-1.333070830962943, 0.4721463328627773, 0.7071067811865475), + vec3(-0.8464394909806497, -1.51113870578065, 0.5773502691896258), + vec3( 1.554155680728463, -1.2588090085709776, 0.5), + vec3( 1.681364377589461, 1.4741145918052656, 0.4472135954999579), + vec3(-1.2795157692199817, 2.088741103228784, 0.4082482904638631), + vec3(-2.4575847530631187, -0.9799373355024756, 0.3779644730092272), + vec3( 0.5874641440200847, -2.7667464429345077, 0.35355339059327373), + vec3( 2.997715703369726, 0.11704939884745152, 0.3333333333333333), + vec3( 0.41360842451688395, 3.1351121305574803, 0.31622776601683794), + vec3(-3.167149933769243, 0.9844599011770256, 0.30151134457776363), + vec3(-1.5736713846521535, -3.0860263079123245, 0.2886751345948129), + vec3( 2.888202648340422, -2.1583061557896213, 0.2773500981126146), + vec3( 2.7150778983300325, 2.5745586041105715, 0.2672612419124244), + vec3(-2.1504069972377464, 3.2211410627650165, 0.2581988897471611), + vec3(-3.6548858794907493, -1.6253643308191343, 0.25), + vec3( 1.0130775986052671, -3.9967078676335834, 0.24253562503633297), + vec3( 4.229723673607257, 0.33081361055181563, 0.23570226039551587), + vec3( 0.40107790291173834, 4.340407413572593, 0.22941573387056174), + vec3(-4.319124570236028, 1.159811599693438, 0.22360679774997896), + vec3(-1.9209044802827355, -4.160543952132907, 0.2182178902359924), + vec3( 3.8639122286635708, -2.6589814382925123, 0.21320071635561041), + vec3( 3.3486228404946234, 3.4331800232609, 0.20851441405707477), + vec3(-2.8769733643574344, 3.9652268864187157, 0.20412414523193154) +}; +#endif + + + + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + // Get texture coordinates + vec2 uv = fragCoord.xy / iResolution.xy; + +#ifdef CURVE + // Curve texture coordinates to mimic non-flat CRT monior + uv = (uv - 0.5) * 2.0; + uv.xy *= 1.0 + pow((abs(vec2(uv.y, uv.x)) / vec2(CURVE)), vec2(2.0)); + uv = (uv / 2.0) + 0.5; +#endif + + + // Retrieve colors from appropriate locations + fragColor.r = texture(iChannel0, vec2(uv.x + 0.0003 * COLOR_FRINGING_SPREAD, uv.y + 0.0003 * COLOR_FRINGING_SPREAD)).x; + fragColor.g = texture(iChannel0, vec2(uv.x + 0.0000 * COLOR_FRINGING_SPREAD, uv.y - 0.0006 * COLOR_FRINGING_SPREAD)).y; + fragColor.b = texture(iChannel0, vec2(uv.x - 0.0006 * COLOR_FRINGING_SPREAD, uv.y + 0.0000 * COLOR_FRINGING_SPREAD)).z; + fragColor.a = texture(iChannel0, uv).a; + + + // Add faint ghost images + fragColor.r += 0.04 * GHOSTING_STRENGTH * texture(iChannel0, GHOSTING_SPREAD * vec2(+0.025, -0.027) + uv.xy).x; + fragColor.g += 0.02 * GHOSTING_STRENGTH * texture(iChannel0, GHOSTING_SPREAD * vec2(-0.022, -0.020) + uv.xy).y; + fragColor.b += 0.04 * GHOSTING_STRENGTH * texture(iChannel0, GHOSTING_SPREAD * vec2(-0.020, -0.018) + uv.xy).z; + + + // Quadratically darken everything + fragColor.rgb = mix(fragColor.rgb, fragColor.rgb*fragColor.rgb, DARKEN_MIX); + + + // Vignette effect + fragColor.rgb *= VIGNETTE_BRIGHTNESS * pow(uv.x * uv.y * (1.0-uv.x) * (1.0-uv.y), VIGNETTE_SPREAD); + + + // Tint all colors + fragColor.rgb *= vec3(TINT); + + + // NOTE: At this point, RGB values may be above 1 + + + // Add scan lines effect + fragColor.rgb *= mix( + 1.0, + SCAN_LINES_VARIANCE/2.0*(1.0 + sin(2*PI* uv.y * iResolution.y/SCAN_LINES_PERIOD)), + SCAN_LINES_STRENGTH + ); + + + // Add aperture grille + int aperture_grille_step = int(8 * mod(fragCoord.x, APERTURE_GRILLE_PERIOD) / APERTURE_GRILLE_PERIOD); + float aperture_grille_mask; + + if (aperture_grille_step < 3) + aperture_grille_mask = 0.0; + else if (aperture_grille_step < 4) + aperture_grille_mask = mod(8*fragCoord.x, APERTURE_GRILLE_PERIOD) / APERTURE_GRILLE_PERIOD; + else if (aperture_grille_step < 7) + aperture_grille_mask = 1.0; + else if (aperture_grille_step < 8) + aperture_grille_mask = mod(-8*fragCoord.x, APERTURE_GRILLE_PERIOD) / APERTURE_GRILLE_PERIOD; + + fragColor.rgb *= 1.0 - APERTURE_GRILLE_STRENGTH*aperture_grille_mask; + + + // Add flicker + fragColor *= 1.0 - FLICKER_STRENGTH/2.0*(1.0 + sin(2*PI*FLICKER_FREQUENCY*iTime)); + + + // Add noise + // NOTE: Hard-coded noise distributions + float noiseContent = smoothstep(0.4, 0.6, fract(sin(uv.x * uv.y * (1.0-uv.x) * (1.0-uv.y) * iTime * 4096.0) * 65536.0)); + float noiseUniform = smoothstep(0.4, 0.6, fract(sin(uv.x * uv.y * (1.0-uv.x) * (1.0-uv.y) * iTime * 8192.0) * 65536.0)); + fragColor.rgb *= clamp(noiseContent + 1.0 - NOISE_CONTENT_STRENGTH, 0.0, 1.0); + fragColor.rgb = clamp(fragColor.rgb + noiseUniform * NOISE_UNIFORM_STRENGTH, 0.0, 1.0); + + + // NOTE: At this point, RGB values are again within [0, 1] + + + // Remove output outside of screen bounds + if (uv.x < 0.0 || uv.x > 1.0) + fragColor.rgb *= 0.0; + if (uv.y < 0.0 || uv.y > 1.0) + fragColor.rgb *= 0.0; + + +#ifdef BLOOM_SPREAD + // Add bloom + vec2 step = BLOOM_SPREAD * vec2(1.414) / iResolution.xy; + + for (int i = 0; i < 24; i++) { + vec3 bloom_sample = bloom_samples[i]; + vec4 neighbor = texture(iChannel0, uv + bloom_sample.xy * step); + float luminance = 0.299 * neighbor.r + 0.587 * neighbor.g + 0.114 * neighbor.b; + + fragColor += luminance * bloom_sample.z * neighbor * BLOOM_STRENGTH; + } + + fragColor = clamp(fragColor, 0.0, 1.0); +#endif + + + // Add fade effect to smoothen out color transitions + // NOTE: May need to be iTime/iTimeDelta dependent + fragColor = vec4(FADE_FACTOR*fragColor.rgb, FADE_FACTOR); +} diff --git a/ghostty/mnoise.glsl b/ghostty/mnoise.glsl new file mode 100644 index 0000000..a414a46 --- /dev/null +++ b/ghostty/mnoise.glsl @@ -0,0 +1,119 @@ +vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } +vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } +vec4 permute(vec4 x) { return mod289(((x * 34.0) + 10.0) * x); } +vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; } +float snoise(vec3 v) { + const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0); + const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); + + // First corner + vec3 i = floor(v + dot(v, C.yyy)); + vec3 x0 = v - i + dot(i, C.xxx); + + // Other corners + vec3 g = step(x0.yzx, x0.xyz); + vec3 l = 1.0 - g; + vec3 i1 = min(g.xyz, l.zxy); + vec3 i2 = max(g.xyz, l.zxy); + + // x0 = x0 - 0.0 + 0.0 * C.xxx; + // x1 = x0 - i1 + 1.0 * C.xxx; + // x2 = x0 - i2 + 2.0 * C.xxx; + // x3 = x0 - 1.0 + 3.0 * C.xxx; + vec3 x1 = x0 - i1 + C.xxx; + vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y + vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y + + // Permutations + i = mod289(i); + vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + + vec4(0.0, i1.y, i2.y, 1.0)) + + i.x + vec4(0.0, i1.x, i2.x, 1.0)); + + // Gradients: 7x7 points over a square, mapped onto an octahedron. + // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) + float n_ = 0.142857142857; // 1.0/7.0 + vec3 ns = n_ * D.wyz - D.xzx; + + vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7) + + vec4 x_ = floor(j * ns.z); + vec4 y_ = floor(j - 7.0 * x_); // mod(j,N) + + vec4 x = x_ * ns.x + ns.yyyy; + vec4 y = y_ * ns.x + ns.yyyy; + vec4 h = 1.0 - abs(x) - abs(y); + + vec4 b0 = vec4(x.xy, y.xy); + vec4 b1 = vec4(x.zw, y.zw); + + // vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; + // vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; + vec4 s0 = floor(b0) * 2.0 + 1.0; + vec4 s1 = floor(b1) * 2.0 + 1.0; + vec4 sh = -step(h, vec4(0.0)); + + vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; + vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww; + + vec3 p0 = vec3(a0.xy, h.x); + vec3 p1 = vec3(a0.zw, h.y); + vec3 p2 = vec3(a1.xy, h.z); + vec3 p3 = vec3(a1.zw, h.w); + + // Normalise gradients + vec4 norm = + taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); + p0 *= norm.x; + p1 *= norm.y; + p2 *= norm.z; + p3 *= norm.w; + + // Mix final noise value + vec4 m = + max(0.5 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0); + m = m * m; + return 105.0 * + dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3))); +} + +float noise2D(vec2 uv) { + uvec2 pos = uvec2(floor(uv * 1000.)); + return float((pos.x * 68657387u ^ pos.y * 361524851u + pos.x) % 890129u) * + (1.0 / 890128.0); +} + +float roundRectSDF(vec2 center, vec2 size, float radius) { + return length(max(abs(center) - size + radius, 0.)) - radius; +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy, sd = vec2(2.), sdh = vec2(1.); + vec4 ghosttyCol = texture(iChannel0, uv); + float ratio = iResolution.y / iResolution.x, + fw = max(fwidth(uv.x), fwidth(uv.y)); + + vec2 puv = floor(uv * vec2(60., 60. * ratio)) / 60.; + puv += + (smoothstep(0., 0.7, noise2D(puv)) - 0.5) * 0.05 - vec2(0., iTime * 0.08); + + uv = fract(vec2(uv.x, uv.y * ratio) * 10.); + float d = roundRectSDF((sd + 0.01) * (uv - .5), sdh, 0.075), + d2 = roundRectSDF((sd + 0.065) * (fract(uv * 6.) - .5), sdh, 0.2), + noiseTime = iTime * 0.03, noise = snoise(vec3(puv, noiseTime)); + + noise += snoise(vec3(puv * 1.1, noiseTime + 0.5)) + .1; + noise += snoise(vec3(puv * 2., noiseTime + 0.8)); + noise = pow(noise, 2.); + + vec3 col1 = vec3(0.), col2 = vec3(0.), col3 = vec3(0.07898), + col4 = vec3(0.089184), + fcol = mix(mix(mix(col1, col3, smoothstep(0.0, 0.3, noise)), col2, + smoothstep(0.0, 0.5, noise)), + col4, smoothstep(0.0, 1.0, noise)); + + fragColor = vec4( + ghosttyCol.rgb + + mix(col4, fcol, smoothstep(fw, -fw, d) * smoothstep(fw, -fw, d2)), + ghosttyCol.a); +} diff --git a/ghostty/retro-terminal.glsl b/ghostty/retro-terminal.glsl new file mode 100644 index 0000000..c5f315a --- /dev/null +++ b/ghostty/retro-terminal.glsl @@ -0,0 +1,34 @@ +// Original shader collected from: https://www.shadertoy.com/view/WsVSzV +// Licensed under Shadertoy's default since the original creator didn't provide any license. (CC BY NC SA 3.0) +// Slight modifications were made to give a green-ish effect. + +float warp = 0.25; // simulate curvature of CRT monitor +float scan = 0.50; // simulate darkness between scanlines + +void mainImage(out vec4 fragColor, in vec2 fragCoord) +{ + // squared distance from center + vec2 uv = fragCoord / iResolution.xy; + vec2 dc = abs(0.5 - uv); + dc *= dc; + + // warp the fragment coordinates + uv.x -= 0.5; uv.x *= 1.0 + (dc.y * (0.3 * warp)); uv.x += 0.5; + uv.y -= 0.5; uv.y *= 1.0 + (dc.x * (0.4 * warp)); uv.y += 0.5; + + // sample inside boundaries, otherwise set to black + if (uv.y > 1.0 || uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0) + fragColor = vec4(0.0, 0.0, 0.0, 1.0); + else + { + // determine if we are drawing in a scanline + float apply = abs(sin(fragCoord.y) * 0.5 * scan); + + // sample the texture and apply a teal tint + vec3 color = texture(iChannel0, uv).rgb; + vec3 tealTint = vec3(0.0, 0.8, 0.6); // teal color (slightly more green than blue) + + // mix the sampled color with the teal tint based on scanline intensity + fragColor = vec4(mix(color * tealTint, vec3(0.0), apply), 1.0); + } +} diff --git a/ghostty/starfield-colors.glsl b/ghostty/starfield-colors.glsl new file mode 100644 index 0000000..cf125a9 --- /dev/null +++ b/ghostty/starfield-colors.glsl @@ -0,0 +1,145 @@ +// divisions of grid +const float repeats = 30.; + +// number of layers +const float layers = 21.; + +// star colours +const vec3 blue = vec3(51.,64.,195.)/255.; +const vec3 cyan = vec3(117.,250.,254.)/255.; +const vec3 white = vec3(255.,255.,255.)/255.; +const vec3 yellow = vec3(251.,245.,44.)/255.; +const vec3 red = vec3(247,2.,20.)/255.; + +// spectrum function +vec3 spectrum(vec2 pos){ + pos.x *= 4.; + vec3 outCol = vec3(0); + if( pos.x > 0.){ + outCol = mix(blue, cyan, fract(pos.x)); + } + if( pos.x > 1.){ + outCol = mix(cyan, white, fract(pos.x)); + } + if( pos.x > 2.){ + outCol = mix(white, yellow, fract(pos.x)); + } + if( pos.x > 3.){ + outCol = mix(yellow, red, fract(pos.x)); + } + + return 1.-(pos.y * (1.-outCol)); +} + +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; 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 + 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) * 100. + 200.; + + vec2 j = (rndXY - uv) * rndSize; + float sparkle = 1. / dot(j, j); + + // Set stars to be pure white + col += spectrum(fract(rndXY*newRnd*ipos)) * vec3(sparkle); + + col *= smoothstep(1., 0.8, trans); + return col; // Return pure white stars only +} + +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); + } + + // Sample the terminal screen texture including alpha channel + vec4 terminalColor = texture(iChannel0, uv); + + // Make a mask that is 1.0 where the terminal content is not black + float mask = 1 - step(0.5, dot(terminalColor.rgb, vec3(1.0))); + vec3 blendedColor = mix(terminalColor.rgb, col, mask); + + // Apply terminal's alpha to control overall opacity + fragColor = vec4(blendedColor, terminalColor.a); + +}