Back to shaders
Shader test bench
LaserCircle v3.fs
runnable fragment
Complete GLSL fragment shader. Stronghold runs it directly when the browser can compile it.
Code
uniform float circleSize;
uniform float thickness;
uniform vec2 position;
uniform float rotation;
uniform float moveSpeed;
uniform float moveAmount;
uniform float rotationSpeed;
uniform int reverseRotation;
uniform vec4 color1;
uniform vec4 color2;
uniform vec4 color3;
uniform vec4 color4;
uniform vec4 color5;
uniform vec4 color6;
precision mediump float;
/*{
"DESCRIPTION": "Animated circular line with customizable colors, movement, and toggleable rotation direction on a black background.",
"CREDIT": "Cornelius // ProjectileObjects",
"ISFVSN": "2.0",
"CATEGORIES": [ "Generator" ],
"INPUTS": [
{ "NAME": "circleSize", "TYPE": "float", "DEFAULT": 0.3, "MIN": 0.05, "MAX": 0.9 },
{ "NAME": "thickness", "TYPE": "float", "DEFAULT": 0.02, "MIN": 0.001, "MAX": 0.1 },
{ "NAME": "position", "TYPE": "point2D", "DEFAULT": [0.0, 0.0], "MIN": [-1.0, -1.0], "MAX": [1.0, 1.0] },
{ "NAME": "rotation", "TYPE": "float", "DEFAULT": 0.0, "MIN": 0.0, "MAX": 360.0 },
{ "NAME": "moveSpeed", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 5.0 },
{ "NAME": "moveAmount", "TYPE": "float", "DEFAULT": 0.05, "MIN": 0.0, "MAX": 0.3 },
{ "NAME": "rotationSpeed", "TYPE": "float", "DEFAULT": 30.0, "MIN": 0.0, "MAX": 180.0 },
{ "NAME": "reverseRotation", "TYPE": "bool", "DEFAULT": false },
{ "NAME": "color1", "TYPE": "color", "DEFAULT": [1.0, 0.0, 0.0, 1.0] },
{ "NAME": "color2", "TYPE": "color", "DEFAULT": [1.0, 0.5, 0.0, 1.0] },
{ "NAME": "color3", "TYPE": "color", "DEFAULT": [1.0, 1.0, 0.0, 1.0] },
{ "NAME": "color4", "TYPE": "color", "DEFAULT": [0.0, 1.0, 0.0, 1.0] },
{ "NAME": "color5", "TYPE": "color", "DEFAULT": [0.0, 0.0, 1.0, 1.0] },
{ "NAME": "color6", "TYPE": "color", "DEFAULT": [0.5, 0.0, 1.0, 1.0] }
]
}*/
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
// Normalize position so (0,0) is center, (-1,-1) bottom-left, (1,1) top-right
vec2 normPos = position * 0.5 + 0.5;
// Animate movement using sine wave
float animatedSize = circleSize + moveAmount * sin(u_time * moveSpeed);
// Adjust UV coordinates based on position, maintain aspect ratio
vec2 aspect = vec2(u_resolution.x / u_resolution.y, 1.0);
vec2 centeredUV = (uv - normPos) * aspect;
// Compute radius and angle in polar coordinates
float radius = length(centeredUV);
float angle = atan(centeredUV.y, centeredUV.x);
// Define PI explicitly
const float PI = 3.14159265359;
// Determine rotation direction based on toggle button
float directionMultiplier = reverseRotation ? -1.0 : 1.0;
float animatedRotation = radians(rotation + u_time * rotationSpeed * directionMultiplier);
float rotatedAngle = mod(angle + animatedRotation, 2.0 * PI) / (2.0 * PI);
// Define outer and inner radius for the ring
float outer = animatedSize;
float inner = outer - thickness;
// Create a smooth circular band with anti-aliasing
float ringMask = smoothstep(inner, inner + 0.002, radius) - smoothstep(outer, outer + 0.002, radius);
// Store color inputs into an array
vec3 colors[6];
colors[0] = color1.rgb;
colors[1] = color2.rgb;
colors[2] = color3.rgb;
colors[3] = color4.rgb;
colors[4] = color5.rgb;
colors[5] = color6.rgb;
// Use rotated angle to smoothly blend between 6 colors
float segment = rotatedAngle * 6.0; // Divide circle into 6 segments
float index = floor(segment); // Get segment index as float
float mixFactor = fract(segment); // Smooth interpolation factor
// Ensure index wraps correctly using mod with floating point
vec3 finalColor = mix(colors[int(mod(index, 6.0))], colors[int(mod(index + 1.0, 6.0))], mixFactor);
// Blend final color with a solid black background (no transparency)
vec3 outputColor = mix(vec3(0.0), finalColor, ringMask);
gl_FragColor = vec4(outputColor, 1.0);
}