Kelsidavis-WoWee/assets/shaders/fsr_easu.frag.glsl

103 lines
3.6 KiB
Text
Raw Normal View History

#version 450
// FSR 1.0 EASU (Edge Adaptive Spatial Upsampling) — Fragment Shader
// Based on AMD FidelityFX Super Resolution 1.0
// Implements edge-adaptive bilinear upsampling with directional filtering
layout(set = 0, binding = 0) uniform sampler2D uInput;
layout(push_constant) uniform FSRConstants {
vec4 con0; // inputSize.xy, 1/inputSize.xy
vec4 con1; // inputSize.xy / outputSize.xy, 0.5 * inputSize.xy / outputSize.xy
vec4 con2; // outputSize.xy, 1/outputSize.xy
vec4 con3; // sharpness, 0, 0, 0
} fsr;
layout(location = 0) in vec2 TexCoord;
layout(location = 0) out vec4 outColor;
// Fetch a texel with offset (in input pixels)
vec3 fsrFetch(vec2 p, vec2 off) {
return textureLod(uInput, (p + off + 0.5) * fsr.con0.zw, 0.0).rgb;
}
void main() {
// Undo the vertex shader Y flip (postprocess.vert flips for Vulkan overlay,
// but we need standard UV coords for texture sampling)
vec2 tc = vec2(TexCoord.x, 1.0 - TexCoord.y);
// Map output pixel to input space
vec2 pp = tc * fsr.con2.xy; // output pixel position
vec2 ip = pp * fsr.con1.xy - 0.5; // input pixel position (centered)
vec2 fp = floor(ip);
vec2 ff = ip - fp;
// 12-tap filter: 4x3 grid around the pixel
// b c
// e f g h
// i j k l
// n o
vec3 b = fsrFetch(fp, vec2( 0, -1));
vec3 c = fsrFetch(fp, vec2( 1, -1));
vec3 e = fsrFetch(fp, vec2(-1, 0));
vec3 f = fsrFetch(fp, vec2( 0, 0));
vec3 g = fsrFetch(fp, vec2( 1, 0));
vec3 h = fsrFetch(fp, vec2( 2, 0));
vec3 i = fsrFetch(fp, vec2(-1, 1));
vec3 j = fsrFetch(fp, vec2( 0, 1));
vec3 k = fsrFetch(fp, vec2( 1, 1));
vec3 l = fsrFetch(fp, vec2( 2, 1));
vec3 n = fsrFetch(fp, vec2( 0, 2));
vec3 o = fsrFetch(fp, vec2( 1, 2));
// Luma (use green channel as good perceptual approximation)
float bL = b.g, cL = c.g, eL = e.g, fL = f.g;
float gL = g.g, hL = h.g, iL = i.g, jL = j.g;
float kL = k.g, lL = l.g, nL = n.g, oL = o.g;
// Directional edge detection
// Compute gradients in 4 directions (N-S, E-W, NE-SW, NW-SE)
float dc = cL - jL;
float db = bL - kL;
float de = eL - hL;
float di = iL - lL;
// Length of the edge in each direction
float lenH = abs(eL - fL) + abs(fL - gL) + abs(iL - jL) + abs(jL - kL);
float lenV = abs(bL - fL) + abs(fL - jL) + abs(cL - gL) + abs(gL - kL);
// Determine dominant edge direction
float dirH = lenV / (lenH + lenV + 1e-7);
float dirV = lenH / (lenH + lenV + 1e-7);
// Bilinear weights
float w1 = (1.0 - ff.x) * (1.0 - ff.y);
float w2 = ff.x * (1.0 - ff.y);
float w3 = (1.0 - ff.x) * ff.y;
float w4 = ff.x * ff.y;
// Edge-aware sharpening: boost weights along edges
float sharpness = fsr.con3.x;
float edgeStr = max(abs(lenH - lenV) / (lenH + lenV + 1e-7), 0.0);
float sharp = mix(0.0, sharpness, edgeStr);
// Sharpen bilinear by pulling toward nearest texel
float maxW = max(max(w1, w2), max(w3, w4));
w1 = mix(w1, float(w1 == maxW), sharp * 0.25);
w2 = mix(w2, float(w2 == maxW), sharp * 0.25);
w3 = mix(w3, float(w3 == maxW), sharp * 0.25);
w4 = mix(w4, float(w4 == maxW), sharp * 0.25);
// Normalize
float wSum = w1 + w2 + w3 + w4;
w1 /= wSum; w2 /= wSum; w3 /= wSum; w4 /= wSum;
// Final color: weighted blend of the 4 nearest texels with edge awareness
vec3 color = f * w1 + g * w2 + j * w3 + k * w4;
// Optional: blend in some of the surrounding texels for anti-aliasing
float aa = 0.125 * edgeStr;
color = mix(color, (b + c + e + h + i + l + n + o) / 8.0, aa * 0.15);
outColor = vec4(clamp(color, 0.0, 1.0), 1.0);
}