Circuit

This is a somewhat complicated shader that generates random moving circuit-like lines. It does this without any external textures, just using the TIME variable and a bit of fixed randomness.

Update: made some small revisions, so you can set a texture (optional), set it to only replace a given colour (optional, useful for applying to a panel but not affecting the borders) and allowing a different colour for the ‘heads’ of lines. Example animated webp: https://files.catbox.moe/iuyhrc.webp

 

Shader code
shader_type canvas_item;

group_uniforms line_characteristics;

uniform float line_width: hint_range(0.001, 0.1) = 0.01;
uniform int segment_count: hint_range(0, 10, 1) = 5;
uniform float segment_length: hint_range(0.1, 1.0, 0.01) = 0.2;
uniform float tail_lag: hint_range(0.1, 1.0, 0.01) = 0.4;
uniform vec4 line_colour: source_color = vec4(1.0, 1.0, 0, 1.0);
uniform float head_size_multiplier: hint_range(0.0, 10.0, 0.1) = 5.0;
uniform vec4 head_colour: source_color = vec4(1.0, 1.0, 0, 1.0);

group_uniforms spawn_characteristics;

uniform int line_count: hint_range(0, 200, 1) = 10;
uniform float line_lifetime: hint_range(0.5, 10.0) = 5.0;
uniform float start_direction_degrees = 0;
uniform bool random_start_direction = true;

group_uniforms apply_conditions;

uniform bool only_on_colour = false;
uniform vec4 apply_colour: source_color;
uniform bool use_albedo_texture = false;
uniform sampler2D albedo_texture;
uniform vec4 albedo_modulate: source_color = vec4(1.0);

float rand(int seed) {
    float fseed = float(seed);
    return fract(sin(dot(vec2(fseed, fseed * 0.66), vec2(12.9898, 78.233))) * 43758.5453);
}

float get_start(int seed) {
  if(!random_start_direction)
    return radians(start_direction_degrees);
  float rand_dir = floor(rand(seed) * 8.0);
  return rand_dir * radians(45);
}

float get_turn(int seed, float initial, float angle_size) {
  int rand_dir = int(floor(rand(seed) * 2.0));
  if(rand_dir == 0)
    return initial + angle_size;
  else
    return initial - angle_size;
}

float get_segment_intensity(
    vec2 start, vec2 end,
    vec2 segment_process_range, 
    float progress, 
    vec2 uv) {
  vec2 segment_vec = end - start;
  
  vec2 pixel_vec_segment = uv - start;
  float t = clamp(dot(pixel_vec_segment, segment_vec) / (segment_length * segment_length), 0.0, 1.0);
  vec2 closest_point = start + t * segment_vec;
  float distance_to_segment = distance(uv, closest_point);
  
  float point_progress = mix(segment_process_range.x, segment_process_range.y, t);
  
  if (point_progress <= min(progress, 1.0 - tail_lag) && point_progress >= progress - tail_lag) {      
      float intensity = 1.0 - smoothstep(0.0, line_width, distance_to_segment);
      float lit_progress = (point_progress - (progress - tail_lag)) / tail_lag;
      return intensity * lit_progress;
  }
  
  return 0.0;
}

float render_line(float time, vec2 uv, out float head_intensity) {
  float local_time = time / line_lifetime;
  int seed = int(floor(local_time));
  float progress = fract(local_time);
    
  float line_intensity = 0.0;
  
  vec2 last_point = vec2(rand(seed), rand(seed + 1));
  float last_angle = get_start(seed);
  vec2 last_dir = vec2(cos(last_angle), sin(last_angle));
  
  float last_progress = 0.0;
  
  for(int j = 0; j < segment_count; j++) {
    vec2 segment_bounds = vec2(last_progress, last_progress + (1.0-tail_lag)/float(segment_count));
    last_progress = segment_bounds.y;
    if(progress < segment_bounds.x)
      break;
      
    vec2 next_point = last_point + last_dir * segment_length;
    float next_angle = get_turn(seed + j, last_angle, radians(45));
    vec2 next_dir = vec2(cos(next_angle), sin(next_angle));
    
    line_intensity = max(line_intensity, get_segment_intensity(
        last_point, next_point, 
        segment_bounds,
        progress, 
        uv));
        
    if(progress >= segment_bounds.x && progress <= segment_bounds.y) {
      vec2 head_pos = last_point + last_dir * segment_length * (progress - segment_bounds.x) / (segment_bounds.y - segment_bounds.x);;
      float head_glow = 1.0 - smoothstep(0.0, line_width * head_size_multiplier, distance(uv, head_pos));
      head_intensity = head_glow;
    }
        
    last_point = next_point;
    last_angle = next_angle;
    last_dir = next_dir;
  }
  
  return line_intensity;
}

void fragment() {
    vec4 under_colour = COLOR;
    if(only_on_colour && under_colour != apply_colour)
    {
      COLOR = under_colour;
    }
    else
    {    
      if(use_albedo_texture)
      {
        under_colour = texture(albedo_texture, UV);
        under_colour *= albedo_modulate;
      }
      
      // correct for aspect ratio
      vec2 uv_dx = dFdx(UV);
      vec2 uv_dy = dFdy(UV);
      
      float stretch_x = length(uv_dx);
      float stretch_y = length(uv_dy);
      float aspect_ratio = stretch_x / stretch_y;
      
      vec2 centered = UV - vec2(0.5);
      centered.y *= aspect_ratio;
      vec2 uv = centered + vec2(0.5);
    
      // draw lines
      float line_intensity = 0.0;
      float head_intensity = 0.0;
      for(int i = 0; i < line_count; i++) {
        float offset = float(i) * 123.123;
        float current_head;
        float current_line = render_line(TIME + offset, uv, current_head);
        line_intensity = max(line_intensity, current_line);
        head_intensity = max(head_intensity, current_head);
      }
      
      if(head_intensity > 0.0)
        COLOR = mix(under_colour, head_colour, head_intensity * head_colour.a);
      else
        COLOR = mix(under_colour, line_colour, line_intensity * line_colour.a);
    }
}
Live Preview
Tags
circuit
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.

More from aquinas_nz

guest

4 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
火火兽
火火兽
4 months ago

大佬您的代码在使用上会每过几秒钟闪一下的问题,请麻烦修复一下。

feor
feor
4 months ago

I’ve fixed the flashing by setting the head_intensity argument to inout instead of out on line 71.
float render_line(float time, vec2 uv, inout float head_intensity) {

Last edited 4 months ago by feor
MAG
MAG
2 months ago
Reply to  feor

Still not working on some GPUs. (1660 Ti).
The problem is in undefined var in 145 line float current_head;
You need to replace it with float current_head = 0.0; (or any other value, -1.0, for example)
Only after that fix with inout in 71 line will work.
It probably will still contain some rare bugs , but adding if (current_head != 0.0) before setting COLOR should be enough, but it’s already too much. Like, there is already 0.000001% chance that head with id 0.0 could realy apear)))

Last edited 2 months ago by MAG
ELO ŻELO
ELO ŻELO
4 months ago

hey if you wanna modify this shader to work on a specific shape do this:
if(use_albedo_texture)
   {
    under_colour = texture(albedo_texture, UV);
    under_colour *= albedo_modulate;
float tex_alpha = under_colour.a;
if (tex_alpha < 0.01) {
    discard;
}
and add the shape to the albedo_texture in apply condition