Earlier today (20240620221253) I ran into the problem that ViewportTexture does not have mipmaps.

I realized that to solve this problem without generating mipmaps, we would need to:

  • determine the LOD to use
  • linearly interpolate between the current mipmap and the next mipmap (using mix)
  • average the colors from each of the pixels that would be in the mipmap at that LOD

When I was streaming earlier today, I got a little tripped up on determining what LOD to use, but apparently there’s a function to do just that called textureQueryLod. With that, we can skip figuring out what LOD to use and just let Godot do that for us:

shader_type spatial;
 
uniform sampler2D albedo_texture: filter_nearest;
 
vec4 sample(vec2 uv, int lod) {
	ivec2 size = textureSize(albedo_texture, 0);
	vec2 uv_step = 1.0 / vec2(size);
 
	int block = lod * lod;
	int count = 0;
	vec4 color = vec4(0.0);
	for (int i = -block/2; i <= block/2; i++) {
		for (int j = -block/2; j <= block/2; j++) {
			vec2 delta = vec2(uv_step.x * float(i), uv_step.y * float(j));
			color += texture(albedo_texture, uv + delta);
			count += 1;
		}
	}
	return color / float(count);
}
 
vec4 samplef(vec2 uv, float lod) {
	int low_lod = int(lod);
	int high_lod = int(lod + 1.0);
	return mix(sample(uv, low_lod), sample(uv, high_lod), mod(lod, 1.0));
}
 
void fragment() {
	float lod = textureQueryLod(albedo_texture, UV).y;
	ALBEDO = samplef(UV, lod).xyz;
}

This fixes the pixel shimmering and improves the image quality when zoomed out!