103 lines
3.7 KiB
GLSL
103 lines
3.7 KiB
GLSL
#include <flutter/runtime_effect.glsl>
|
|
|
|
// Output surface size in pixels.
|
|
uniform vec2 uResolution;
|
|
// One source-texel step in UV space: (1/width, 1/height).
|
|
uniform vec2 uTexel;
|
|
// 1.0 enables CRT post-process effects, 0.0 keeps only base AA.
|
|
uniform float uEffectsEnabled;
|
|
// Engine time in seconds used to animate scanline travel.
|
|
uniform float uTime;
|
|
// Source frame produced by the software renderer.
|
|
uniform sampler2D uTexture;
|
|
|
|
out vec4 fragColor;
|
|
|
|
// Perceptual brightness approximation used for edge detection.
|
|
float luma(vec3 color) {
|
|
return dot(color, vec3(0.299, 0.587, 0.114));
|
|
}
|
|
|
|
void main() {
|
|
// Convert fragment coordinates to normalized UV coordinates.
|
|
vec2 uv = FlutterFragCoord().xy / uResolution;
|
|
|
|
if (uEffectsEnabled > 0.5) {
|
|
// Barrel-like warp to emulate curved CRT glass.
|
|
vec2 centered = uv * 2.0 - 1.0;
|
|
float radius2 = dot(centered, centered);
|
|
centered *= 1.0 + radius2 * 0.045;
|
|
uv = centered * 0.5 + 0.5;
|
|
|
|
// Fill outside warped bounds with a darker consumer-TV charcoal bezel.
|
|
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
|
|
vec2 clampedUv = clamp(uv, 0.0, 1.0);
|
|
vec2 edgeDelta = abs(uv - clampedUv);
|
|
float overflow = max(edgeDelta.x, edgeDelta.y);
|
|
|
|
float verticalShade = 0.90 + 0.08 * (1.0 - (FlutterFragCoord().y / uResolution.y));
|
|
float depthShade = 1.0 - smoothstep(0.0, 0.06, overflow) * 0.24;
|
|
float grain = sin(FlutterFragCoord().x * 0.21 + FlutterFragCoord().y * 0.11) * 0.007;
|
|
float moldedHighlight = smoothstep(0.08, 0.0, overflow) * 0.045;
|
|
|
|
vec3 bezelColor =
|
|
vec3(0.26, 0.26, 0.25) * verticalShade * depthShade +
|
|
vec3(moldedHighlight) +
|
|
vec3(grain);
|
|
fragColor = vec4(bezelColor, 1.0);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Read the base color from the source frame.
|
|
vec4 centerSample = texture(uTexture, uv);
|
|
|
|
// Sample 4-neighborhood (N/S/E/W) around the current pixel.
|
|
vec3 sampleN = texture(uTexture, uv + vec2(0.0, -uTexel.y)).rgb;
|
|
vec3 sampleS = texture(uTexture, uv + vec2(0.0, uTexel.y)).rgb;
|
|
vec3 sampleE = texture(uTexture, uv + vec2(uTexel.x, 0.0)).rgb;
|
|
vec3 sampleW = texture(uTexture, uv + vec2(-uTexel.x, 0.0)).rgb;
|
|
|
|
// Compute local luma range; wider range means a stronger edge.
|
|
float lumaCenter = luma(centerSample.rgb);
|
|
float lumaMin = min(
|
|
lumaCenter,
|
|
min(min(luma(sampleN), luma(sampleS)), min(luma(sampleE), luma(sampleW)))
|
|
);
|
|
float lumaMax = max(
|
|
lumaCenter,
|
|
max(max(luma(sampleN), luma(sampleS)), max(luma(sampleE), luma(sampleW)))
|
|
);
|
|
|
|
float edgeSpan = max(lumaMax - lumaMin, 0.0001);
|
|
// Convert raw edge strength into a smooth 0..1 blending amount.
|
|
float edgeAmount = smoothstep(0.03, 0.18, edgeSpan);
|
|
|
|
// Average neighbors and blend toward that average only near edges.
|
|
// This acts like a lightweight edge-aware anti-aliasing pass.
|
|
vec3 neighborhoodAvg = (sampleN + sampleS + sampleE + sampleW) * 0.25;
|
|
vec3 aaColor = mix(centerSample.rgb, neighborhoodAvg, edgeAmount * 0.45);
|
|
|
|
// Preserve source alpha and output the anti-aliased color.
|
|
vec3 outColor = aaColor;
|
|
|
|
if (uEffectsEnabled > 0.5) {
|
|
// Horizontal scanline modulation.
|
|
float scanlineBand = 0.88 + 0.12 * sin(uv.y * uResolution.y * 3.14159265);
|
|
|
|
// Slow bright line crawling down the screen.
|
|
float sweepPos = fract(uTime * 0.08);
|
|
float sweepBand = 1.0 + 0.16 * exp(-pow((uv.y - sweepPos) * 120.0, 2.0));
|
|
|
|
// Slight center brightening and edge falloff (CRT phosphor + lens feel).
|
|
vec2 centeredUv = uv * 2.0 - 1.0;
|
|
float vignette = smoothstep(1.15, 0.25, length(centeredUv));
|
|
float centerLift = 1.0 + 0.08 * (1.0 - length(centeredUv));
|
|
|
|
outColor *= scanlineBand * sweepBand * centerLift;
|
|
outColor *= mix(0.62, 1.0, vignette);
|
|
}
|
|
|
|
fragColor = vec4(outColor, centerSample.a);
|
|
}
|