pub const FXAA_FRAG: &str = r#"
#version 330 core
in vec2 f_uv;
out vec4 frag_color;
uniform sampler2D u_scene;
uniform vec2 u_texel_size;
uniform float u_edge_threshold;
uniform float u_edge_threshold_min;
uniform float u_subpixel;
uniform int u_search_steps;
const vec3 LUMA = vec3(0.299, 0.587, 0.114);
float luma(vec3 c) {
return dot(c, LUMA);
}
void main() {
// ── Sample center and neighbors ─────────────────────────────────────
vec3 rgbM = texture(u_scene, f_uv).rgb;
float lumM = luma(rgbM);
float lumNW = luma(texture(u_scene, f_uv + vec2(-1.0, -1.0) * u_texel_size).rgb);
float lumNE = luma(texture(u_scene, f_uv + vec2( 1.0, -1.0) * u_texel_size).rgb);
float lumSW = luma(texture(u_scene, f_uv + vec2(-1.0, 1.0) * u_texel_size).rgb);
float lumSE = luma(texture(u_scene, f_uv + vec2( 1.0, 1.0) * u_texel_size).rgb);
float lumN = luma(texture(u_scene, f_uv + vec2( 0.0, -1.0) * u_texel_size).rgb);
float lumS = luma(texture(u_scene, f_uv + vec2( 0.0, 1.0) * u_texel_size).rgb);
float lumW = luma(texture(u_scene, f_uv + vec2(-1.0, 0.0) * u_texel_size).rgb);
float lumE = luma(texture(u_scene, f_uv + vec2( 1.0, 0.0) * u_texel_size).rgb);
// ── Edge detection ──────────────────────────────────────────────────
float lumMin = min(lumM, min(min(lumN, lumS), min(lumW, lumE)));
float lumMax = max(lumM, max(max(lumN, lumS), max(lumW, lumE)));
float lumRange = lumMax - lumMin;
// Skip if contrast is too low (not an edge)
if (lumRange < max(u_edge_threshold_min, lumMax * u_edge_threshold)) {
frag_color = vec4(rgbM, 1.0);
return;
}
// ── Subpixel aliasing test ──────────────────────────────────────────
float lumL = (lumN + lumS + lumW + lumE) * 0.25;
float rangeL = abs(lumL - lumM);
float blendL = max(0.0, (rangeL / lumRange) - 0.25) * (1.0 / 0.75);
blendL = min(blendL * blendL, 1.0) * u_subpixel;
// ── Determine edge direction ────────────────────────────────────────
float edgeH = abs(lumNW + lumNE - 2.0 * lumN)
+ abs(lumW + lumE - 2.0 * lumM) * 2.0
+ abs(lumSW + lumSE - 2.0 * lumS);
float edgeV = abs(lumNW + lumSW - 2.0 * lumW)
+ abs(lumN + lumS - 2.0 * lumM) * 2.0
+ abs(lumNE + lumSE - 2.0 * lumE);
bool isHorizontal = edgeH >= edgeV;
// ── Choose edge endpoints ───────────────────────────────────────────
float stepLength = isHorizontal ? u_texel_size.y : u_texel_size.x;
float lum1 = isHorizontal ? lumN : lumW;
float lum2 = isHorizontal ? lumS : lumE;
float grad1 = lum1 - lumM;
float grad2 = lum2 - lumM;
bool steeper1 = abs(grad1) >= abs(grad2);
float gradScaled = 0.25 * max(abs(grad1), abs(grad2));
if (!steeper1) stepLength = -stepLength;
// ── Edge search along perpendicular direction ───────────────────────
vec2 posN = f_uv;
vec2 posP = f_uv;
vec2 dir = isHorizontal ? vec2(u_texel_size.x, 0.0) : vec2(0.0, u_texel_size.y);
float halfStep = isHorizontal ? u_texel_size.y * 0.5 : u_texel_size.x * 0.5;
if (isHorizontal) {
posN.y += stepLength * 0.5;
posP.y += stepLength * 0.5;
} else {
posN.x += stepLength * 0.5;
posP.x += stepLength * 0.5;
}
float lumEnd1 = lumM;
float lumEnd2 = lumM;
bool reached1 = false;
bool reached2 = false;
for (int i = 0; i < u_search_steps; ++i) {
if (!reached1) {
posN -= dir;
lumEnd1 = luma(texture(u_scene, posN).rgb) - lumM;
reached1 = abs(lumEnd1) >= gradScaled;
}
if (!reached2) {
posP += dir;
lumEnd2 = luma(texture(u_scene, posP).rgb) - lumM;
reached2 = abs(lumEnd2) >= gradScaled;
}
if (reached1 && reached2) break;
}
// ── Compute final blend ─────────────────────────────────────────────
float distN = isHorizontal ? (f_uv.x - posN.x) : (f_uv.y - posN.y);
float distP = isHorizontal ? (posP.x - f_uv.x) : (posP.y - f_uv.y);
float dist = min(distN, distP);
float spanLength = distN + distP;
bool goodSpan = (distN < distP) ? (lumEnd1 < 0.0) : (lumEnd2 < 0.0);
float pixelOffset = goodSpan ? (0.5 - dist / spanLength) : 0.0;
float finalBlend = max(pixelOffset, blendL);
// ── Apply ───────────────────────────────────────────────────────────
vec2 finalUv = f_uv;
if (isHorizontal) {
finalUv.y += finalBlend * stepLength;
} else {
finalUv.x += finalBlend * stepLength;
}
frag_color = vec4(texture(u_scene, finalUv).rgb, 1.0);
}
"#;Expand description
FXAA fragment shader implementing Timothy Lottes’ FXAA 3.11 algorithm.
Uniforms:
u_scene: sampler2D — input scene textureu_texel_size: vec2 — 1.0 / resolutionu_edge_threshold: floatu_edge_threshold_min: floatu_subpixel: floatu_search_steps: int