AMD FSR CAS (Contrast Adaptive Shapenening)

AMD FSR is a well known upscaling method. It works in two passes, the upscale pass and the sharpening pass. This is the sharpening pass only.

There’s not really a good way to implement multiple passes in gdshaders, but it’s still a decent sharpening filter. (Apply it to a viewport texture.)

Screenshots are closeups of a viewport with FXAA enabled and different levels of CAS. Lower numbers are sharper.

Adapted from https://www.shadertoy.com/view/stXSWB

Shader code
/*
* SOURCE: https://www.shadertoy.com/view/stXSWB
*
* FidelityFX Super Resolution scales up a low resolution
* image, while adding fine detail.
*
* It works in two passes. This is ONLY the second, sharpening pass
*
* MIT Open License
*
* https://gpuopen.com/fsr
*
* For sharpening with a full resolution render buffer,
* FidelityFX CAS is a better option.
* https://www.shadertoy.com/view/ftsXzM
*
* For readability and compatibility, these optimisations have been removed:
*   * Fast approximate inverse and inversesqrt
*   * textureGather fetches (not WebGL compatible)
*   * Multiplying by reciprocal instead of division
*
* Apologies to AMD for the numerous slowdowns and errors I have introduced.
*
*/
shader_type canvas_item;

/***** RCAS *****/
const float FSR_RCAS_LIMIT = (0.25-(1.0/16.0));
const bool FSR_RCAS_DENOISE = false;

void fragment()
{
    // Set up constants
    float con;
    float sharpness = 1.0;

    con = exp2(-sharpness);

    // Perform RCAS pass
    
	// Constant generated by RcasSetup().
    // Algorithm uses minimal 3x3 pixel neighborhood.
    //    b 
    //  d e f
    //    h
	vec2 off_mult = TEXTURE_PIXEL_SIZE * 1.0;
    vec2 sp = UV;//vec2(ip);
    vec3 b = texture(TEXTURE, sp + vec2( 0,-1)*off_mult).rgb;
    vec3 d = texture(TEXTURE,  + vec2(-1, 0)*off_mult).rgb;
    vec3 e = texture(TEXTURE, sp).rgb;
    vec3 f = texture(TEXTURE, sp+vec2( 1, 0)*off_mult).rgb;
    vec3 h = texture(TEXTURE, sp+vec2( 0, 1)*off_mult).rgb;
    // Luma times 2.
    float bL = b.g + .5 * (b.b + b.r);
    float dL = d.g + .5 * (d.b + d.r);
    float eL = e.g + .5 * (e.b + e.r);
    float fL = f.g + .5 * (f.b + f.r);
    float hL = h.g + .5 * (h.b + h.r);
    // Noise detection.
    float nz = .25 * (bL + dL + fL + hL) - eL;
    nz=clamp(
        abs(nz)
        /(
            max(max(bL,dL),max(eL,max(fL,hL)))
            -min(min(bL,dL),min(eL,min(fL,hL)))
        ),
        0., 1.
    );
    nz=1.-.5*nz;
    // Min and max of ring.
    vec3 mn4 = min(b, min(f, h));
    vec3 mx4 = max(b, max(f, h));
    // Immediate constants for peak range.
    vec2 peakC = vec2(1., -4.);
    // Limiters, these need to be high precision RCPs.
    vec3 hitMin = mn4 / (4. * mx4);
    vec3 hitMax = (peakC.x - mx4) / (4.* mn4 + peakC.y);
    vec3 lobeRGB = max(-hitMin, hitMax);
    float lobe = max(
        -FSR_RCAS_LIMIT,
        min(max(lobeRGB.r, max(lobeRGB.g, lobeRGB.b)), 0.)
    )*con;
    // Apply noise removal.
    if (FSR_RCAS_DENOISE) {
    	lobe *= nz;
    }
    // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes.
    vec3 col = (lobe * (b + d + h + f) + e) / (4. * lobe + 1.);
	
	COLOR.rgb = col;
}
Live Preview
This shader is a port from an existing Shadertoy project. Shadertoy shaders are by default protected under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0) license unless anything else has been stated by the author. For more info, see our License terms.

More from threethan

Related shaders

Comments 1