Animated Dotted Outline

I mixed an inner stroke shader I made with dotted-circle.

The result is a new shader that you can tweak to have both an inner stroke, but also mix it to have a dotted line as your inner stroke. It can be animated by changing the phase speed.

It doesn’t make a perfectly regular dotted line, but I think is good enough.

Got a bit of inspiration from Animal Well to make this one.

It has several options as shown in the screenshot. It allows to create several effects.

 

P.S. I’m pretty sure you can make it work in Godot 3.0 by changing source_color to hint_color, but let me know if that doesn’t work. I am not knowdlegeable enough to be sure, but I believe this would work on any renderer, maybe not GLES2.

Shader code
shader_type canvas_item;

uniform vec4 color : source_color = vec4(1.0);
uniform float inner_stroke_thickness = 1.0; // Inner stroke thickness, adjust according to sprite size
uniform float inner_stroke_opacity = 1.0; // Inner stroke opacity, ranges from 0.0 to 1.0
uniform float inside_opacity = 0.0;
uniform float frequency = 8.0; // Controls the number of dotted lines
uniform float phase_speed = 1.0; // Controls the rotation of the border

void fragment() {
	// Final outputs
	vec4 inner_stroke;
	vec4 circle_outline;
	
	// INNER STROKE
	float radius = inner_stroke_thickness / float(textureSize(TEXTURE, 0).x);
	// Initialize alpha to maximum
	float minAlpha = 1.0;
	
	// Sample a grid around the pixel based on the defined radius to find the minimum alpha
    for (int x = -1; x <= 1; x++) {
        for (int y = -1; y <= 1; y++) {
            vec2 offset = vec2(float(x), float(y)) * radius;
            float sampleAlpha = texture(TEXTURE, UV + offset).a;
            minAlpha = min(minAlpha, sampleAlpha);
        }
	}
	
	// Get the original alpha value at the fragment
    float originalAlpha = texture(TEXTURE, UV).a;

    // Compare and apply the inner stroke color if in the inner stroke region
    if (originalAlpha > minAlpha) {
        float innerStrokeAlpha = originalAlpha * (originalAlpha - minAlpha) * inner_stroke_opacity; // Blend the inner stroke alpha based on the difference in alpha and opacity setting
        inner_stroke = vec4(1.0, 1.0, 1.0, innerStrokeAlpha);
    } else {
		float insideAlpha = originalAlpha * inside_opacity; // Blend the inner stroke alpha based on the difference in alpha and opacity setting
        inner_stroke = vec4(1.0, 1.0, 1.0, insideAlpha);
    }
	// INNER STROKE END
	
	// CIRCLE OUTLINE
	vec2 pos = UV - vec2(0.5);
	float outer_radius = inner_stroke_thickness / 2.0;
	float inner_radius = outer_radius - inner_stroke_thickness;
	float outer_circle = step(length(pos), outer_radius);
	float inner_circle = step(length(pos), inner_radius);
	
	float angle = atan(pos.y, pos.x);
	if (angle < 0.0) {
		angle += 2.0 * PI;
	}
	
	float wave = 0.5 * sin(frequency * angle + TIME * phase_speed) + 0.5;
	float ring = outer_circle - inner_circle;
	ring *= step(0.5, wave);
	
	circle_outline = vec4(color.rgb, ring * color.a);
	// CIRCLE OUTLINE END
	
	COLOR = inner_stroke * circle_outline;
}
Tags
animated, border, circle, dotted, inner, line, outline, stroke
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

Related shaders

Animated Dotted Rectangle Outline

Dotted Circle

Dotted grid 2d [Improved]

Subscribe
Notify of
guest

8 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
choco
choco
5 months ago

Can’t seem to get it to work, instead of an outline I get a cut-up circle. Are there extra steps that need to be done to get the outline?
https://imgur.com/a/SNDb2aS

TalkingFox
TalkingFox
5 months ago

– I think this shader assumes that you have a parent mask applied. I got it to work like this:

Root -Texture Rect of a solid white square with transparent borders.Visibility – Clip Children is set to “Clip Only”Size: Arbitrary, I used 100×100.
Child – Color Rect with this shader applied.Size: Same as parent.Shader paramsColor – WhiteInner Stroke Thickness – 1.5Inner Stroke Opacity – 1Inside Opacity – 1Frequency – 8Phase Speed – 6

Then it looks just like in the preview picture.

Last edited 5 months ago by TalkingFox
TalkingFox
TalkingFox
5 months ago
Reply to  TalkingFox

“Root -Texture Rect of a solid white square with transparent borders.”

I mistyped this. It should be the inverse rather. A texture of a solid white outline with a transparent center.

Locher
Locher
3 months ago
Reply to  TalkingFox

I have no idea what you are talking about.
Does inverse mean we have to switch textureRect and colorRect around? Which node clip its children? This is causing me such headaches.

asd
asd
3 months ago
Reply to  Locher

Parent = Texture Rect with an image of a white square border

交流困难
交流困难
4 months ago
float inner_radius = outer_radius - inner_stroke_thickness;

should be

float inner_radius = outer_radius - inner_stroke_opacity;

then ok

feiyu
feiyu
1 day ago
Reply to  交流困难

don’t work

luufery
luufery
1 month ago

shader_type canvas_item;

uniform vec4 color : source_color = vec4(1.0);
uniform float inner_stroke_thickness = 1.0; // Inner stroke thickness, adjust according to sprite size
uniform float inner_stroke_opacity = 1.0; // Inner stroke opacity, ranges from 0.0 to 1.0
uniform float inside_opacity = 0.0;
uniform float frequency = 8.0; // Controls the number of dotted lines
uniform float phase_speed = 1.0; // Controls the rotation of the border

uniform vec2 rect_size = vec2(0.3, 0.3); // 矩形大小
uniform vec2 rect_position = vec2(0.5, 0.5); // 矩形位置

void fragment() {
  // Final outputs
  vec4 inner_stroke;
  vec4 circle_outline;
   
  // INNER STROKE
  float radius = inner_stroke_thickness / float(textureSize(TEXTURE, 0).x);
  // Initialize alpha to maximum
  float minAlpha = 1.0;
   
  // Sample a grid around the pixel based on the defined radius to find the minimum alpha
  for (int x = -1; x <= 1; x++) {
    for (int y = -1; y <= 1; y++) {
      vec2 offset = vec2(float(x), float(y)) * radius;
      float sampleAlpha = texture(TEXTURE, UV + offset).a;
      minAlpha = min(minAlpha, sampleAlpha);
    }
  }
   
  // Get the original alpha value at the fragment
  float originalAlpha = texture(TEXTURE, UV).a;
  // Compare and apply the inner stroke color if in the inner stroke region
  if (originalAlpha > minAlpha) {
    float innerStrokeAlpha = originalAlpha * (originalAlpha – minAlpha) * inner_stroke_opacity; // Blend the inner stroke alpha based on the difference in alpha and opacity setting
    inner_stroke = vec4(1.0, 1.0, 1.0, innerStrokeAlpha);
  } else {
    float insideAlpha = originalAlpha * inside_opacity; // Blend the inner stroke alpha based on the difference in alpha and opacity setting
    inner_stroke = vec4(1.0, 1.0, 1.0, insideAlpha);
  }
  // INNER STROKE END
   
  // CIRCLE OUTLINE
  vec2 pos = UV – vec2(0.5);
  float outer_radius = inner_stroke_thickness / 2.0;
  float inner_radius = outer_radius – inner_stroke_thickness;
  float outer_circle = step(length(pos), outer_radius);
  float inner_circle = step(length(pos), inner_radius);
   
  float angle = atan(pos.y, pos.x);
  if (angle < 0.0) {
    angle += 2.0 * PI;
  }
   
  float wave = 0.5 * sin(frequency * angle + TIME * phase_speed) + 0.5;
  float ring = outer_circle – inner_circle;
  ring *= step(0.5, wave);
   
  circle_outline = vec4(color.rgb, ring * color.a);
  // CIRCLE OUTLINE END
   
  // 计算矩形区域
  vec2 uv_offset = UV – rect_position;
  float rect_alpha = 1.0 – step(abs(uv_offset.x), rect_size.x/2.0) * step(abs(uv_offset.y), rect_size.y/2.0);
   
  COLOR = inner_stroke * circle_outline * vec4(1.0, 1.0, 1.0, rect_alpha);
}