GhosttyConfig/ghostty-shaders/smoke-and-ghost.glsl
2025-09-29 15:34:29 +02:00

193 lines
7.3 KiB
GLSL

// Settings for detection
#define TARGET_COLOR vec3(0.0, 0.0, 0.0) // RGB target pixels to transform
#define REPLACE_COLOR vec3(0.0, 0.0, 0.0) // Color to replace target pixels
#define COLOR_TOLERANCE 0.001 // Color matching tolerance
// Smoke effect settings
#define SMOKE_COLOR vec3(1., 1., 1.0) // Base color of smoke
#define SMOKE_RADIUS 0.011 // How far the smoke spreads
#define SMOKE_SPEED 0.5 // Speed of smoke movement
#define SMOKE_SCALE 25.0 // Scale of smoke detail
#define SMOKE_INTENSITY 0.2 // Intensity of the smoke effect
#define SMOKE_RISE_HEIGHT 0.14 // How high the smoke rises
#define ALPHA_MAX 0.5 // Maximum opacity for smoke
#define VERTICAL_BIAS 1.0
// Ghost face settings
#define FACE_COUNT 1 // Number of ghost faces
#define FACE_SCALE vec2(0.03, 0.05) // Size of faces, can be wider/elongated
#define FACE_DURATION 1.2 // How long faces last, can be wider/elongated
#define FACE_TRANSITION 1.5 // Face fade in/out duration
#define FACE_COLOR vec3(0.0, 0.0, 0.0)
#define GHOST_BG_COLOR vec3(1.0, 1.0, 1.0)
#define GHOST_BG_SCALE vec2(0.03, 0.06)
float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
}
float random1(float n) {
return fract(sin(n) * 43758.5453123);
}
vec2 random2(float n) {
return vec2(
random1(n),
random1(n + 1234.5678)
);
}
float noise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = random(i);
float b = random(i + vec2(1.0, 0.0));
float c = random(i + vec2(0.0, 1.0));
float d = random(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a)* u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
// Modified elongated ellipse for more cartoon-like shapes
float cartoonEllipse(vec2 uv, vec2 center, vec2 scale) {
vec2 d = (uv - center) / scale;
float len = length(d);
// Add cartoon-like falloff
return smoothstep(1.0, 0.8, len);
}
// Function to create ghost background shape
float ghostBackground(vec2 uv, vec2 center) {
vec2 d = (uv - center) / GHOST_BG_SCALE;
float baseShape = length(d * vec2(1.0, 0.8)); // Slightly oval
// Add wavy bottom
float wave = sin(d.x * 6.28 + iTime) * 0.2;
float bottomWave = smoothstep(0.0, -0.5, d.y + wave);
return smoothstep(1.0, 0.8, baseShape) + bottomWave;
}
float ghostFace(vec2 uv, vec2 center, float time, float seed) {
vec2 faceUV = (uv - center) / FACE_SCALE;
float eyeSize = 0.25 + random1(seed) * 0.05;
float eyeSpacing = 0.35;
vec2 leftEyePos = vec2(-eyeSpacing, 0.2);
vec2 rightEyePos = vec2(eyeSpacing, 0.2);
float leftEye = cartoonEllipse(faceUV, leftEyePos, vec2(eyeSize));
float rightEye = cartoonEllipse(faceUV, rightEyePos, vec2(eyeSize));
// Add simple eye highlights
float leftHighlight = cartoonEllipse(faceUV, leftEyePos + vec2(0.1, 0.1), vec2(eyeSize * 0.3));
float rightHighlight = cartoonEllipse(faceUV, rightEyePos + vec2(0.1, 0.1), vec2(eyeSize * 0.3));
vec2 mouthUV = faceUV - vec2(0.0, -0.9);
float mouthWidth = 0.5 + random1(seed + 3.0) * 0.1;
float mouthHeight = 0.8 + random1(seed + 7.0) * 0.1;
float mouth = cartoonEllipse(mouthUV, vec2(0.0), vec2(mouthWidth, mouthHeight));
// Combine features
float face = max(max(leftEye, rightEye), mouth);
face = max(face, max(leftHighlight, rightHighlight));
// Add border falloff
face *= smoothstep(1.2, 0.8, length(faceUV));
return face;
}
float calculateSmoke(vec2 uv, vec2 sourcePos) {
float verticalDisp = (uv.y - sourcePos.y) * VERTICAL_BIAS;
vec2 smokeUV = uv * SMOKE_SCALE;
smokeUV.y -= iTime * SMOKE_SPEED * (1.0 + verticalDisp);
smokeUV.x += sin(iTime * 0.5 + uv.y * 4.0) * 0.1;
float n = noise(smokeUV) * 0.5 + 0.5;
n += noise(smokeUV * 2.0 + iTime * 0.1) * 0.25;
float verticalFalloff = 1.0 - smoothstep(0.0, SMOKE_RISE_HEIGHT, verticalDisp);
return n * verticalFalloff;
}
float isTargetPixel(vec2 uv) {
vec4 color = texture(iChannel0, uv);
return float(all(lessThan(abs(color.rgb - TARGET_COLOR), vec3(COLOR_TOLERANCE))));
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord/iResolution.xy;
vec4 originalColor = texture(iChannel0, uv);
// Calculate smoke effect
float smokeAccum = 0.0;
float targetInfluence = 0.0;
float stepSize = SMOKE_RADIUS / 4.0;
for (float x = -SMOKE_RADIUS; x <= SMOKE_RADIUS; x += stepSize) {
for (float y = -SMOKE_RADIUS; y <= 0.0; y += stepSize) {
vec2 offset = vec2(x, y);
vec2 sampleUV = uv + offset;
if (sampleUV.x >= 0.0 && sampleUV.x <= 1.0 &&
sampleUV.y >= 0.0 && sampleUV.y <= 1.0) {
float isTarget = isTargetPixel(sampleUV);
if (isTarget > 0.0) {
float dist = length(offset);
float falloff = 1.0 - smoothstep(0.0, SMOKE_RADIUS, dist);
float smoke = calculateSmoke(uv, sampleUV);
smokeAccum += smoke * falloff;
targetInfluence += falloff;
}
}
}
}
smokeAccum /= max(targetInfluence, 1.0);
targetInfluence = smoothstep(0.0, 1.0, targetInfluence);
float smokePresence = smokeAccum * targetInfluence;
// Calculate ghost faces with backgrounds
float faceAccum = 0.0;
float bgAccum = 0.0;
float timeBlock = floor(iTime / FACE_DURATION);
if (smokePresence > 0.2) {
for (int i = 0; i < FACE_COUNT; i++) {
vec2 facePos = random2(timeBlock + float(i) * 1234.5);
facePos = facePos * 0.8 + 0.1;
float faceTime = mod(iTime, FACE_DURATION);
float fadeFactor = smoothstep(0.0, FACE_TRANSITION, faceTime) *
(1.0 - smoothstep(FACE_DURATION - FACE_TRANSITION, FACE_DURATION, faceTime));
// Add ghost background
float ghostBg = ghostBackground(uv, facePos) * fadeFactor;
bgAccum = max(bgAccum, ghostBg);
// Add face features
float face = ghostFace(uv, facePos, iTime, timeBlock + float(i) * 100.0) * fadeFactor;
faceAccum = max(faceAccum, face);
}
bgAccum *= smoothstep(0.2, 0.4, smokePresence);
faceAccum *= smoothstep(0.2, 0.4, smokePresence);
}
// Combine all elements
bool isTarget = all(lessThan(abs(originalColor.rgb - TARGET_COLOR), vec3(COLOR_TOLERANCE)));
vec3 baseColor = isTarget ? REPLACE_COLOR : originalColor.rgb;
// Layer the effects: base -> smoke -> ghost background -> face features
vec3 smokeEffect = mix(baseColor, SMOKE_COLOR, smokeAccum * SMOKE_INTENSITY * targetInfluence * (1.0 - faceAccum));
vec3 withBackground = mix(smokeEffect, GHOST_BG_COLOR, bgAccum * 0.7);
vec3 finalColor = mix(withBackground, FACE_COLOR, faceAccum);
float alpha = mix(originalColor.a, ALPHA_MAX, max(smokePresence, max(bgAccum, faceAccum) * smokePresence));
fragColor = vec4(finalColor, alpha);
}