Skip to content

Instantly share code, notes, and snippets.

@maidopi-usagi
Created January 1, 2025 17:37
Show Gist options
  • Select an option

  • Save maidopi-usagi/66aaaf63ff0814ddb8b0c44dac6696df to your computer and use it in GitHub Desktop.

Select an option

Save maidopi-usagi/66aaaf63ff0814ddb8b0c44dac6696df to your computer and use it in GitHub Desktop.
Godot AtlasTexture Outline Shader
shader_type canvas_item;
const vec2 VERTICES[4] = vec2[](vec2(0.0),vec2(0.0,1.0),vec2(1.0),vec2(1.0,0.0));
varying vec2 originalUV;
varying flat vec4 regionInfo;
uniform int outline_size:hint_range(0,10) = 1;
uniform vec4 outline_color:source_color = vec4(1.0);
void vertex() {
originalUV= VERTICES[VERTEX_ID];
regionInfo.xy = VERTEX.xy/(VERTICES[0] - 0.5);
regionInfo.zw = UV;
VERTEX.xy += vec2(float(outline_size)*2.0) * (originalUV - vec2(0.5));
}
vec2 region_from_texture(vec2 uv, vec2 size, vec4 region) {
return region.zw + uv / size * region.xy;
}
vec2 texture_from_region(vec2 uv, vec2 size, vec4 region) {
return uv * size / region.xy - region.zw;
}
vec4 sample_tex(sampler2D t, vec2 offset, vec2 size, vec2 uv_to_transform)
{
vec2 uv = uv_to_transform + offset;
bool inside = uv.x <= 1.0 && uv.x >= 0.0 && uv.y >= 0.0 && uv.y <= 1.0;
if (!inside) return vec4(0.0);
vec2 restoredUV = region_from_texture(uv, size, regionInfo);
return texture(t, restoredUV);
}
void fragment() {
vec2 size = vec2(textureSize(TEXTURE, 0));
vec2 atlas_size = regionInfo.xy;
vec2 scaled_ratio = (atlas_size + vec2(float(outline_size) * 2.0)) / atlas_size;
vec2 uv = (originalUV - 0.5) * scaled_ratio + 0.5;
float outline = 0.0;
vec2 offset_ratio = float(outline_size) / atlas_size;
outline = max(sample_tex(TEXTURE, vec2(0.0, -1.0) * offset_ratio, size, uv).a, outline);
outline = max(sample_tex(TEXTURE, vec2(1.0, -0.0) * offset_ratio, size, uv).a, outline);
outline = max(sample_tex(TEXTURE, vec2(-1.0, 0.0) * offset_ratio, size, uv).a, outline);
outline = max(sample_tex(TEXTURE, vec2(0.0, 1.0) * offset_ratio, size, uv).a, outline);
vec4 tex = sample_tex(TEXTURE, vec2(0.0), size, uv);
COLOR = mix(vec4(outline_color.rgb, outline * outline_color.a), tex, tex.a);
}
@Delsin-Yu
Copy link

An updated version that accounts for corners.

shader_type canvas_item;

const vec2 vertices_offsets[4] = vec2[](vec2(0., 0.), vec2(0., 1.), vec2(1., 1.), vec2(1., 0.));
varying vec2 original_uv;
varying flat vec4 region_info;

uniform bool side = true;
uniform bool corner = false;
uniform float outline_size = 1;
uniform vec4 outline_color : source_color = vec4(1.0);

void vertex() {
	original_uv = vertices_offsets[VERTEX_ID];
	region_info.xy = VERTEX.xy / (vertices_offsets[0] - .5);
	region_info.zw = UV;
	VERTEX.xy += vec2(float(outline_size) * 2.) * (original_uv - vec2(.5));
}

vec2 region_from_texture(vec2 uv, vec2 size, vec4 region) {
	return region.zw + uv / size * region.xy;
}

vec2 texture_from_region(vec2 uv, vec2 size, vec4 region) {
	return uv * size / region.xy - region.zw;
}

vec4 sample_tex(sampler2D t, vec2 offset, vec2 size, vec2 uv_to_transform) {
	vec2 uv = uv_to_transform + offset;
	bool inside = uv.x <= 1.0 && uv.x >= 0.0 && uv.y >= 0.0 && uv.y <= 1.0;
	if (!inside) return vec4(0.0);
	vec2 restoredUV = region_from_texture(uv, size, region_info);
	return texture(t, restoredUV);
}

void fragment() {
	vec2 size = vec2(textureSize(TEXTURE, 0));
	vec2 atlas_size = region_info.xy;
	vec2 scaled_ratio = (atlas_size + vec2(float(outline_size) * 2.0)) / atlas_size;
	vec2 uv = (original_uv - 0.5) * scaled_ratio + 0.5;
	float outline = 0.0;
	vec2 offset_ratio = float(outline_size) / atlas_size;

	if (side) {
		outline = max(sample_tex(TEXTURE, vec2(+0., -1.) * offset_ratio, size, uv).a, outline);
		outline = max(sample_tex(TEXTURE, vec2(+1., -0.) * offset_ratio, size, uv).a, outline);
		outline = max(sample_tex(TEXTURE, vec2(-1., +0.) * offset_ratio, size, uv).a, outline);
		outline = max(sample_tex(TEXTURE, vec2(+0., +1.) * offset_ratio, size, uv).a, outline);
	}

	if (corner) {
		outline = max(sample_tex(TEXTURE, vec2(-1., -1.) * offset_ratio, size, uv).a, outline);
		outline = max(sample_tex(TEXTURE, vec2(+1., +1.) * offset_ratio, size, uv).a, outline);
		outline = max(sample_tex(TEXTURE, vec2(-1., +1.) * offset_ratio, size, uv).a, outline);
		outline = max(sample_tex(TEXTURE, vec2(+1., -1.) * offset_ratio, size, uv).a, outline);
	}

	vec4 tex = sample_tex(TEXTURE, vec2(0.0), size, uv);
	COLOR = mix(vec4(outline_color.rgb, outline * outline_color.a), tex, tex.a);
}

@Delsin-Yu
Copy link

A wip implementation

shader_type canvas_item;

uniform float outline_size = 1.0;
uniform float alpha_clip_threshold = 0.01;

varying vec4 self_modulate;
varying vec2 uv_scale_amount;
varying vec2 uv_offset_amount;

const vec2 vertice_info[4] = vec2[](vec2(0, 0), vec2(0, 1), vec2(1, 1), vec2(1, 0));
const vec2 vertice_offsets[4] = vec2[](vec2(-1, -1), vec2(-1, +1), vec2(+1, +1), vec2(+1, -1));

varying vec2 original_uv;
varying flat vec4 region_info;

void vertex() {
	original_uv = vertice_info[VERTEX_ID];
	region_info.xy = VERTEX.xy / (vertice_info[0] - 0.5);
	region_info.zw = UV;
	self_modulate = COLOR;
	VERTEX += vertice_offsets[VERTEX_ID] * outline_size;
}

//vec2 region_from_texture(vec2 uv, vec2 size, vec4 region) {
	//return region.zw + uv / size * region.xy;
//}
//
//vec2 texture_from_region(vec2 uv, vec2 size, vec4 region) {
	//return uv * size / region.xy - region.zw;
//}

vec4 sample(sampler2D tex, vec2 validation_uv, vec2 sampling_uv) {
	float factor = float(step(validation_uv, vec2(0.)) + step(vec2(1.), validation_uv) == vec2(0));
	return texture(tex, sampling_uv) * factor;
	return vec4(factor, factor, factor, 1);
}

vec4 sample_offset(sampler2D tex, vec2 validation_uv, vec2 sampling_uv, vec2 offset, vec2 validation_multiplier) {
	return sample(tex, validation_uv + offset * validation_multiplier, sampling_uv + offset);
}

void fragment() {
	vec2 corrected_uv = UV;
	
	vec2 size = vec2(textureSize(TEXTURE, 0));
	vec4 color = sample(TEXTURE, original_uv, corrected_uv);
	COLOR = color;
	
	vec2 multiplier = 1. / region_info.zw;
	float outlineL = sample_offset(TEXTURE, original_uv, corrected_uv, TEXTURE_PIXEL_SIZE * vec2(outline_size, 0), multiplier).a;
	float outlineR = sample_offset(TEXTURE, original_uv, corrected_uv, TEXTURE_PIXEL_SIZE * vec2(-outline_size, 0), multiplier).a;
	float outlineU = sample_offset(TEXTURE, original_uv, corrected_uv, TEXTURE_PIXEL_SIZE * vec2(0, outline_size), multiplier).a;
	float outlineD = sample_offset(TEXTURE, original_uv, corrected_uv, TEXTURE_PIXEL_SIZE * vec2(0, -outline_size), multiplier).a;
	
	float sum = outlineL + outlineR + outlineU + outlineD;
	float clampedSum = ceil(sum);
	
	if(color.a <= alpha_clip_threshold) COLOR = vec4(self_modulate.rgb, self_modulate.a * clampedSum);
	else COLOR = color;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment