Pixelate shader only displays once

godot version 4.3 forward+

Can’t view more then 1 instance of a scene at a time with pixelate shader, Help me please!

I have this pixelate shader, when disabled in the node tree, both card scenes become visible

shader_type canvas_item;
render_mode unshaded;

uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_nearest;
uniform float pixel_size : hint_range (0.5,16.0,0.1) = 0.5;

void fragment(){
	float x = FRAGCOORD.x - mod (FRAGCOORD.x, pixel_size);
	float y = FRAGCOORD.y - mod (FRAGCOORD.y, pixel_size);
	float center = floor(pixel_size / 2.0);
	COLOR = texture(SCREEN_TEXTURE, vec2 (x+center,y+center) * SCREEN_PIXEL_SIZE);
}

Not sure what I should do to fix it. If I figure it out, I’ll post it here for anyone else who might have the same issue.

What exactly are you trying to accomplish?
You are working with hint_screen_texture, ergo you are post-processing the whole screen/viewport, not just a single card. Try to use TEXTURE and TEXTURE_PIXEL_SIZE.

Hey fencer, Thats your plasma shader btw :wink:

I’m trying to apply a pixelate shader to each of the cards,
Each card has a shader at the root of the scene with several labels as children.

The goal is to limit the players ability to read the cards based on their characters intelligence But thats not important yet :sweat_smile:

I was hoping I could apply it to each individual card, but it seems I will have to do something a bit more complicated like sending a mask from each card to the pixelate screen function.

unless there is a way to buffer the card to an image and send that over to the pixelate shader.

If you know I would be grateful

2 Likes

Not sure how it turns out, but try something like this:

shader_type canvas_item;

uniform float pixels: hint_range(1.0, 256.0, 1.0) = 32.0;

void fragment() {
    vec2 uv = round(UV * pixels) / pixels;
    COLOR = textureLod(TEXTURE, uv, 0);
}

No dice with LODtexture, its just changing level of detail so makes sense there is no change, I’m gonna try an intermediate buffering technique, if it works I’ll post here, wish me luck.

Edit: I realized after that rendering text to an image and combining with the shader would be very computationally heavy. I think It will be more efficient to just pass the masks to the shader on a viewport

The solution to my problem was to pass the uv position and size of each card to a pixelate shader on a viewport that follows the camera. I will build on this for what I specifically need, but maybe this will be a good example for anyone else trying the same thing.

Auto-load Global script with the signal:

signal card_moved(position, size)

Then in process function of each card:

Global.emit_signal("card_moved",position,size*scale)

The viewport container will manage the positions and convert them to uv positions then send them to the shader

extends SubViewportContainer

const position_amount = 32
var vp_resolution : Vector2
var card_positions : Array[Vector2] = []
var card_sizes : Array[Vector2] = []
var card_count = 0      # Number of cards processed

func _ready():
	Global.connect("card_moved", _on_card_data)
	set_process_priority(-1) # process last
	card_positions.resize(position_amount)
	card_positions.fill(Vector2.ZERO)
	card_sizes.resize(position_amount)
	card_sizes.fill(Vector2.ZERO)
	card_count = 0
	vp_resolution = get_viewport().size

func _on_card_data(flag_position: Vector2, flag_size: Vector2):
	var pos_uv = (flag_position + (flag_size*0.5)) / vp_resolution #Convert position to UV
	var size_uv = flag_size / vp_resolution # Convert size to UV
	card_positions[card_count] = pos_uv
	card_sizes[card_count] = size_uv
	card_count += 1
	if (card_count >= position_amount) :
		print("ohh no!")

func clear_data():
	card_positions.fill(Vector2.ZERO)
	card_sizes.fill(Vector2.ZERO)
	card_count = 0

func _process(_delta):
	# Now process the data in the ViewportManager
	material.set_shader_parameter("card_positions", card_positions)	
	material.set_shader_parameter("card_sizes", card_sizes)
	material.set_shader_parameter("card_count", card_count)
	clear_data()

Then the shaders masks those areas and applies the pixelation effect

shader_type canvas_item;
render_mode unshaded;

const int buf_size = 32;
uniform vec2[buf_size] card_positions;  // Now in UV space (0-1)
uniform vec2[buf_size] card_sizes;  // Now in UV space (0-1)
uniform int card_count = 0;
uniform vec4 test_color = vec4 (0.0);

uniform float border_radius : hint_range(0.0, 0.1, 0.001) = 0.02; // Small rounded corners
uniform sampler2D SCREEN_TEXTURE : hint_screen_texture;

uniform float pixel_size : hint_range (0.5,16.0,0.1) = 0.5;

// Function to draw a rounded rectangle in UV space
float draw_rounded_rectangle(vec2 uv, vec2 size, float radius, float edge) {
    edge = max(edge, 1.0e-8); // Prevent zero or negative edge
    radius = max(radius, 0.0); // Prevent negative radius

    vec2 half_size = size * 0.5; // Half-size for centering
    vec2 dist = abs(uv) - (half_size - radius); // Distance from pixel to edges
    float corner_dist_squared = dot(max(dist, 0.0), max(dist, 0.0)); // Squared distance to corner

    // Return smoothed edge value using squared distance
    return clamp((1.0 - (sqrt(corner_dist_squared) - radius) / edge), 0.0, 1.0);
}

void fragment() {
    vec2 uv = SCREEN_UV; // UV coordinates
	vec2 card_uv = SCREEN_UV;
    vec4 color = texture(SCREEN_TEXTURE, uv); // Default background color
	float x = FRAGCOORD.x - mod (FRAGCOORD.x, pixel_size);
	float y = FRAGCOORD.y - mod (FRAGCOORD.y, pixel_size);
	float center = floor(pixel_size / 2.0);
	vec4 colorhold = texture(SCREEN_TEXTURE, vec2 (x+center,y+center) * SCREEN_PIXEL_SIZE);
    float mask = 0.0; // Accumulate rectangle masks
	for (int i = 0; i < card_count; i++){
		vec2 card_size = card_sizes[i];
		vec2 card_pos = card_positions[i];
		float test = draw_rounded_rectangle(card_uv - vec2 (card_pos.x,card_pos.y), card_size, 0.01, 0.00);
    	color *= (1.0 - test);
		color += vec4(colorhold.rgb * (test),test);
	}
    COLOR = color;
}

Image showing 3 cards sending their position and size and 1 that is not.