How can I get my shader to change resolution dynamically?

Godot Version

v4.2.stable

Question

I have the following shader:

shader_type canvas_item;

uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap;

uniform float SCREEN_CURVE_RADIUS = 5;
uniform float SCREEN_CORNER_RADIUS = 0.1;
uniform float BRIGHTNESS = 1.5;
uniform float PIXEL_SHARPNESS = 2.0;
uniform float LINE_SHARPNESS = 6.0;
uniform float MASK_STRENGTH = 0.15;

uniform bool CURVE_SCREEN = true;
uniform bool SCANLINES = true;
uniform bool SHADOW_MASK = true;
uniform bool LIGHT_EFFECTS = true;

uniform float TILES = 2.0;
//const vec2 IRES = vec2(16, 9) * 60.0;
uniform float RESOLUTION = 50;
const vec2 IRES = vec2(16, 9);

#define TARGET_RES (IRES * RESOLUTION * TILES)

vec3 ACESFilm(vec3 x) {
    return clamp((x*(2.51*x + 0.03)) / (x*(2.43*x + 0.59) + 0.14), 0.0, 1.0);
}

vec2 curveScreen(vec2 uv) {
  float r = 3.14159265 * 0.5 / SCREEN_CURVE_RADIUS;
  float d = 1.0 - cos(uv.x * r) * cos(uv.y * r); // distance to screen
  float s = cos(r); // scale factor to re-fit window
  return uv / (1.0 - d) * s;
}

float discardCorners(vec2 pos) {
  pos = abs(pos);
  pos.x = pos.x * 1.333 - 0.333; // 4:3 aspect ratio correction
  if (min(pos.x, pos.y) < 1.0 - SCREEN_CORNER_RADIUS) return 1.0; // not near corner -- break early
  float d = distance(pos, vec2(1.0 - SCREEN_CORNER_RADIUS));
  return float(d < SCREEN_CORNER_RADIUS);
}

vec3 getSample(vec2 pos, vec2 off) {
  //get nearest emulated sample
  pos = floor(pos * TARGET_RES) + vec2(0.5) + off;
  vec3 col = vec3(0.0);
  if (pos.x >= 0.0 && pos.x <= TARGET_RES.x * 2.0 && pos.y >= 0.0 && pos.y <= TARGET_RES.y) {
    col = texelFetch(SCREEN_TEXTURE, ivec2(pos), 0).rgb;
    col = pow(((col + 0.055) / 1.055), vec3(2.4)); // SRGB => linear
  }
  return col;
}

vec3 getScanline(vec2 pos, float off) {
  // 3-tap gaussian filter to get colour at arbitrary point along scanline
  float d = 0.5 - fract(pos.x * TARGET_RES.x);
  vec3 ca = getSample(pos, vec2(-1.0, off));
  vec3 cb = getSample(pos, vec2(0.0, off));
  vec3 cc = getSample(pos, vec2(1.0, off));
  float wa = gaussian(d - 1.0, PIXEL_SHARPNESS);
  float wb = gaussian(d, PIXEL_SHARPNESS);
  float wc = gaussian(d + 1.0, PIXEL_SHARPNESS);
  return (ca * wa + cb * wb + cc * wc) / (wa + wb + wc);
}

vec3 getScreenColour(vec2 pos) {
  // Get influence of 3 nearest scanlines
  float d = 0.5 - fract(pos.y * TARGET_RES.y);
  vec3 ca = getScanline(pos, -1.0);
  vec3 cb = getScanline(pos, 0.0);
  vec3 cc = getScanline(pos, 1.0);
  float wa = gaussian(d - 1.0, LINE_SHARPNESS);
  float wb = gaussian(d, LINE_SHARPNESS);
  float wc = gaussian(d + 1.0, LINE_SHARPNESS);
  return (ca * wa + cb * wb + cc * wc);
}

vec3 SlotMask_PixelPerfect(vec2 pos, float resolution_y) {
  //pos /= 1.0 + floor( resolution_y / 1440.0 );
  pos /= 1.0 + floor(resolution_y / (IRES.y * RESOLUTION * 15.0));
  float glow = 0.5;
  float f = mod(pos.x, 3.0);
  vec3 col = vec3(float(f <= 1.0), float(f > 1.0 && f <= 2.0), float(f > 2.0));
  col += vec3(float(f < 1.5 || f >= 2.5), float(f > 0.5 && f <= 2.5), float(f > 1.5 || f <= 0.5)) * glow;
  col *= (mod(pos.y + (fract(pos.x / 6.0) > 0.5 ? 1.5 : 0.0), 3.0) < 1.0) ? glow : 1.0;
  col /= 1.0 + glow;
  return col;
}

void fragment() {
  //vec2 uv =  FRAGCOORD.xy / (1.0 / SCREEN_PIXEL_SIZE).xy;
  vec2 uv = FRAGCOORD.xy / TARGET_RES;
  vec2 pos = uv;
  pos = pos * 2.0 - 1.0;
  //pos.x *= (1.0 / SCREEN_PIXEL_SIZE).x/(1.0 / SCREEN_PIXEL_SIZE).y*(IRES.y/IRES.x);
  pos.x *= SCREEN_PIXEL_SIZE.y / SCREEN_PIXEL_SIZE.x * (IRES.y / IRES.x);

  if (CURVE_SCREEN) pos = curveScreen(pos);

  if (max(abs(pos.x), abs(pos.y)) < 1.0) {
    // skip everything if we're beyond the screen edge
    vec3 col = vec3(1.0);

    if (CURVE_SCREEN) col *= discardCorners(pos);

    if (LIGHT_EFFECTS) col *= 1.0 - sqrt(length(pos) * 0.25); // vignette

    pos = pos * 0.5 + 0.5;

    if (SCANLINES) col *= getScreenColour(pos);
    else col *= getSample(pos, vec2(0.0));

    if (SHADOW_MASK) {
      vec3 shadowmask = SlotMask_PixelPerfect(FRAGCOORD.xy, (1.0 / SCREEN_PIXEL_SIZE).y);
      col *= mix(vec3(1.0 - MASK_STRENGTH), vec3(1.0 + MASK_STRENGTH), shadowmask);
    }

    if (LIGHT_EFFECTS) {
      col *= BRIGHTNESS;
      col = ACESFilm(col);
    }

    col = pow(col, vec3(1.0 / 2.4)) * 1.055 - 0.055; // linear => SRGB

    COLOR = vec4(col, 1.0);
  }
}

What I’m hoping is that by changing the resolution, as the name implies, I will change how clear the “virtual monitor” looks. But what happens instead is that the shader will zoom in or out of the upper right corner of the viewport, instead of showing whatever is behind the ColorRect but just with a different resolution.

Has anyone got any idea as to why?

I don’t see the gaussian function …
… do you try to start with a fixed UV ?

vec2 uv = UV;
1 Like