Shader not working on all sprites in web export

Godot Version

4.5

Question

Hi,
First of all i don’t know much about shaders, reason why i have no clue why this is happening.

I have this outline shader that is a slightly modified version of something i found in the asset store.
The shader draws a pulsing outline around a sprite.

The shader works well when run from the editor on windows and on Android.
In the web export it works as expected but only on a subset of sprites, let me share screenshots for clarity:

Correct behaviour, all cards have an outline

Web behaviour, only the left card has the outline

The card scene is like this:
image

The sprite has this material:

The shader is this one (with credits to the main author :slight_smile: )
Note that i use instance parameters in case this matters.

//slightly customized
//2D outline shader 1.0.0
//Julian-Vos; MIT; 2024-04-01
//https://godotengine.org/asset-library/asset/2829

shader_type canvas_item;

uniform vec4 color : source_color = vec4(1.0);
uniform float width : hint_range(0, 100) = 1.0;
uniform int pattern : hint_range(0, 2) = 0; // diamond, circle, square
uniform bool inside = false;
uniform bool add_margins = true; // only useful when inside is false
uniform vec2 number_of_images = vec2(1.0); // number of horizontal and vertical images in the sprite sheet

varying flat vec4 modulate;

// CUSTOM
instance uniform bool enabled = false;
instance uniform float pulse_frequency : hint_range(0, 100) = 10.0;
// CUSTOM

void vertex() {
	// CUSTOM
	if(enabled) {
	// CUSTOM
		modulate = COLOR;
		if (add_margins) {
			if ((UV.x <= 0.0 || UV.x >= 1.0) && (UV.y <= 0.0 || UV.y >= 1.0)) {
				VERTEX += (UV * 2.0 - 1.0) * width;
			} else {
				VERTEX += sign(VERTEX) * width; // replace sign(VERTEX) by (sign(VERTEX) * 2.0 - 1.0) if your AnimatedSprite2D isn't Centered
			}
		}
	}	
}

bool hasContraryNeighbour(vec2 uv, vec2 texture_pixel_size, vec2 image_top_left, vec2 image_bottom_right, sampler2D texture) {
	for (float i = -ceil(width); i <= ceil(width); i++) {
		float x = abs(i) > width ? width * sign(i) : i;
		float offset;

		if (pattern == 0) {
			offset = width - abs(x);
		} else if (pattern == 1) {
			offset = floor(sqrt(pow(width + 0.5, 2) - x * x));
		} else if (pattern == 2) {
			offset = width;
		}

		for (float j = -ceil(offset); j <= ceil(offset); j++) {
			float y = abs(j) > offset ? offset * sign(j) : j;
			vec2 xy = uv + texture_pixel_size * vec2(x, y);

			if ((xy != clamp(xy, image_top_left, image_bottom_right) || texture(texture, xy).a <= 0.0) == inside) {
				return true;
			}
		}
	}

	return false;
}

void fragment() {
	// CUSTOM
	if(enabled) {
	// CUSTOM
		vec2 uv = UV;
		vec2 image_top_left = floor(uv * number_of_images) / number_of_images;
		vec2 image_bottom_right = image_top_left + vec2(1.0) / number_of_images;

		if (add_margins) {
			vec2 texture_pixel_size = vec2(1.0) / (vec2(1.0) / TEXTURE_PIXEL_SIZE + vec2(width * 2.0) * number_of_images);

			uv = (uv - texture_pixel_size * width - image_top_left) * TEXTURE_PIXEL_SIZE / texture_pixel_size + image_top_left;

			if (uv != clamp(uv, image_top_left, image_bottom_right)) {
				COLOR.a = 0.0;
			} else {
				COLOR = texture(TEXTURE, uv) * modulate;
			}
		} else {
			COLOR = texture(TEXTURE, uv) * modulate;
		}

		if ((COLOR.a > 0.0) == inside && hasContraryNeighbour(uv, TEXTURE_PIXEL_SIZE, image_top_left, image_bottom_right, TEXTURE)) {
			COLOR.rgb = inside ? mix(COLOR.rgb, color.rgb * modulate.rgb, color.a * modulate.a) : color.rgb * modulate.rgb;

			//COLOR.a += (1.0 - COLOR.a) * color.a * modulate.a;
			COLOR.a = clamp(sin(TIME * pulse_frequency)/2.0 + 1.0, 0.0, 1.0);
		}
	}
}

Ok after going back and forth with WhateverGPT giving me solutions to false problems but also interesting investigation ideas, I (or we if you prefer) found the issue:

the instance uniforms in the shader

I needed those to be able to selectively activate the shader on specific sprites but sadly that doesn’t work well with the web export. Why? I have no idea.

WebGL limitations says WhateverGPT.
“WebGL has a known limitation with instance uniform and multiple objects sharing a ShaderMaterial.” :person_shrugging:

The solution was to make the material “local to scene” and use normal uniforms instead.

Thank you for your attention to this matter! (cit.)

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.