Prog blog

How to draw a circle with GLSL

How to draw a circle with GLSL

Looking for a way to draw a circle in WebGL? You've come to the right place. Below are examples of drawing this geometric figure using a fragment shaders. I've been learning how to write shaders for a week now and I decided to post all the examples I managed to understand systematically on my blog. Writing shaders is very satisfying and some solutions to the problems are not as obvious as we would do using standard Canvas HTML API.

To display shaders I used glslCanvas. The creator of this library is Patricio Gonzalez Vivo author The Book of Shaders. I wanted to use the editor from his book, but unfortunately the project is extended by an electron and does not install itself as a library (problem with postinstall).

How to draw a circle.

I used 2 functions to create a circle. Step and length to determine the pixels to color in the circle.

#ifdef GL_ES
precision lowp float;
#endif

uniform vec2 u_resolution;

float draw_circle(vec2 coord, float radius) {
  return step(length(coord), radius);
}

void main() {
  float scaleX;
  float scaleY;
  vec2 scaleXY;
  if (u_resolution.x < u_resolution.y) {
    scaleX = 1.0;
    scaleY = u_resolution.y / u_resolution.x;
    scaleXY = vec2(scaleX, scaleY);
  } else {
    scaleX = u_resolution.x / u_resolution.y;
    scaleY = 1.0;
    scaleXY = vec2(scaleX, scaleY);
  }

  vec2 coord = gl_FragCoord.xy / u_resolution.xy * scaleXY;
  vec2 offset = vec2(0.5, 0.5) * scaleXY;
  float circle = draw_circle(coord - offset, 0.25);
  vec3 color = vec3(circle);

  gl_FragColor = vec4(color, 1.0);
}

How to draw a circle outline.

It was enough to subtract the smaller wheel from the larger one and this way we will get a pixel for the circle outline only.

#ifdef GL_ES
precision lowp float;
#endif

uniform vec2 u_resolution;

float draw_circle(vec2 coord, float radius) {
  return step(length(coord), radius);
}

float draw_circle_outline(vec2 coord, float radius, float stroke) {
  float circle = draw_circle(coord, radius);
  float circle_stroke = draw_circle(coord, radius + stroke);
  return circle_stroke - circle;
}

void main() {
  float scaleX;
  float scaleY;
  vec2 scaleXY;
  if (u_resolution.x < u_resolution.y) {
    scaleX = 1.0;
    scaleY = u_resolution.y / u_resolution.x;
    scaleXY = vec2(scaleX, scaleY);
  } else {
    scaleX = u_resolution.x / u_resolution.y;
    scaleY = 1.0;
    scaleXY = vec2(scaleX, scaleY);
  }

  vec2 coord = gl_FragCoord.xy / u_resolution.xy * scaleXY;
  vec2 offset = vec2(0.5, 0.5) * scaleXY;
  float circle = draw_circle_outline(coord - offset, 0.25, 0.01);
  vec3 color = vec3(circle);

  gl_FragColor = vec4(color, 1.0);
}

Animation of circle and circle outline.

Simple animation changing circle outline position in time.

#ifdef GL_ES
precision lowp float;
#endif

uniform vec2 u_resolution;
uniform float u_time;

float draw_circle(vec2 coord, float radius) {
  return step(length(coord), radius);
}

float draw_circle_outline(vec2 coord, float radius, float stroke) {
  float circle = draw_circle(coord, radius);
  float circle_stroke = draw_circle(coord, radius + stroke);
  return circle_stroke - circle;
}

void main() {
  float cosa = cos(u_time);
  float sina = sin(u_time);

  float scaleX;
  float scaleY;
  vec2 scaleXY;
  if (u_resolution.x < u_resolution.y) {
    scaleX = 1.0;
    scaleY = u_resolution.y / u_resolution.x;
    scaleXY = vec2(scaleX, scaleY);
  } else {
    scaleX = u_resolution.x / u_resolution.y;
    scaleY = 1.0;
    scaleXY = vec2(scaleX, scaleY);
  }

  vec2 coord = gl_FragCoord.xy / u_resolution.xy - vec2(0.5, 0.5);
  vec2 coord1 = coord;
  coord1.x += 0.05 / scaleX * -cosa;
  coord1.y += 0.05 / scaleY * -sina;
  float circle_outline = draw_circle_outline(coord1 * scaleXY, 0.25, 0.01);
  float circle = draw_circle(coord * scaleXY, 0.25);
  vec3 color = vec3(circle_outline + circle);

  gl_FragColor = vec4(color, 1.0);
}

How to draw an arc.

Besides drawing a circle, it is also necessary to check if the drawn pixel of the circle is inside the desired arc angle.

#ifdef GL_ES
precision lowp float;
#endif

uniform vec2 u_resolution;

float draw_circle(vec2 coord, float radius) {
  return step(length(coord), radius);
}

float arc_segment(vec2 coord, float startAngle, float endAngle) {
  const float kInvPi = 1.0 / 3.14159265;
  float angle = atan(coord.x, coord.y) * kInvPi * 0.5;
  angle = fract(angle - startAngle);
  float segment = step(angle, endAngle);
  segment *= step(0.0, angle);
  return mix(segment, 1.0, step(1.0, endAngle));
}

float draw_arc(vec2 coord, float radius, float startAngle, float endAngle) {
  float circle = draw_circle(coord, radius);
  circle *= arc_segment(coord, startAngle, endAngle);
  return circle;
}

void main() {
  float scaleX;
  float scaleY;
  vec2 scaleXY;
  if (u_resolution.x < u_resolution.y) {
    scaleX = 1.0;
    scaleY = u_resolution.y / u_resolution.x;
    scaleXY = vec2(scaleX, scaleY);
  } else {
    scaleX = u_resolution.x / u_resolution.y;
    scaleY = 1.0;
    scaleXY = vec2(scaleX, scaleY);
  }
  vec2 coord = gl_FragCoord.xy / u_resolution.xy * scaleXY;
  vec2 offset = vec2(0.5, 0.5) * scaleXY;
  vec2 arcCoord = coord - offset;
  float arc = draw_arc(arcCoord, 0.25, 0.1, 0.8);
  vec3 color = vec3(arc);
  gl_FragColor = vec4(color, 1.0);
}

How to draw a pacman.

Simple pacman animation using the arc shown earlier.

#ifdef GL_ES
precision lowp float;
#endif

uniform vec2 u_resolution;
uniform float u_time;

float draw_circle(vec2 coord, float radius) {
  return step(length(coord), radius);
}

float arc_segment(vec2 coord, float startAngle, float endAngle) {
  const float kInvPi = 1.0 / 3.14159265;
  float angle = atan(coord.x, coord.y) * kInvPi * 0.5;
  angle = fract(angle - startAngle);
  float segment = step(angle, endAngle);
  segment *= step(0.0, angle);
  return mix(segment, 1.0, step(1.0, endAngle));
}

float draw_arc(vec2 coord, float radius, float startAngle, float endAngle) {
  float circle = draw_circle(coord, radius);
  circle *= arc_segment(coord, startAngle, endAngle);
  return circle;
}

void main() {
  float scaleX;
  float scaleY;
  vec2 scaleXY;
  if (u_resolution.x < u_resolution.y) {
    scaleX = 1.0;
    scaleY = u_resolution.y / u_resolution.x;
    scaleXY = vec2(scaleX, scaleY);
  } else {
    scaleX = u_resolution.x / u_resolution.y;
    scaleY = 1.0;
    scaleXY = vec2(scaleX, scaleY);
  }
  
  vec2 coord = gl_FragCoord.xy / u_resolution.xy * scaleXY;
  vec2 offset = vec2(0.5, 0.5) * scaleXY;
  vec2 arcCoord = coord - offset;
  float gap = 0.06;
  float startAngle = 0.75 + gap;
  float speed = 5.0;
  float a = sin(u_time * speed) * cos(u_time * speed) * 2.0 + 0.1;
  float start = mix(startAngle, startAngle - gap, a);
  float end = mix(1.0 - gap * 2.0, 1.0, a);
  float arc = draw_arc(arcCoord, 0.25, start, end);
  vec3 color = vec3(arc);
  gl_FragColor = vec4(color, 1.0);
}

How to draw an arc outline.

Very similar to arc. Only that instead of circle we will use circle outline.

#ifdef GL_ES
precision lowp float;
#endif

uniform vec2 u_resolution;

float draw_circle(vec2 coord, float radius) {
  return step(length(coord), radius);
}

float draw_circle_outline(vec2 coord, float radius, float stroke) {
  float circle = draw_circle(coord, radius);
  float circle_stroke = draw_circle(coord, radius + stroke);
  return circle_stroke - circle;
}

float arc_segment(vec2 coord, float startAngle, float endAngle) {
  const float kInvPi = 1.0 / 3.14159265;
  float angle = atan(coord.x, coord.y) * kInvPi * 0.5;
  angle = fract(angle - startAngle);
  float segment = step(angle, endAngle);
  segment *= step(0.0, angle);
  return mix(segment, 1.0, step(1.0, endAngle));
}

float draw_arc_outline(vec2 coord, float radius, float stroke, float startAngle, float endAngle) {
  float circle_outline = draw_circle_outline(coord, radius, stroke);
  circle_outline *= arc_segment(coord, startAngle, endAngle);
  return circle_outline;
}

void main() {
  float scaleX;
  float scaleY;
  vec2 scaleXY;
  if (u_resolution.x < u_resolution.y) {
    scaleX = 1.0;
    scaleY = u_resolution.y / u_resolution.x;
    scaleXY = vec2(scaleX, scaleY);
  } else {
    scaleX = u_resolution.x / u_resolution.y;
    scaleY = 1.0;
    scaleXY = vec2(scaleX, scaleY);
  }
  vec2 coord = gl_FragCoord.xy / u_resolution.xy * scaleXY;
  vec2 offset = vec2(0.5, 0.5) * scaleXY;
  vec2 arcCoord = coord - offset;
  float arc = draw_arc_outline(arcCoord, 0.25, 0.01, 0.1, 0.8);
  vec3 color = vec3(arc);
  gl_FragColor = vec4(color, 1.0);
}

How to draw a circular maze animation.

A moving circular maze created with multiple arc outline rotated by the function changing the startAngle in time.

#ifdef GL_ES
precision lowp float;
#endif

uniform vec2 u_resolution;
uniform float u_time;

float draw_circle(vec2 coord, float radius) {
  return step(length(coord), radius);
}

float draw_circle_outline(vec2 coord, float radius, float stroke) {
  float circle = draw_circle(coord, radius);
  float circle_stroke = draw_circle(coord, radius + stroke);
  return circle_stroke - circle;
}

float arc_segment(vec2 coord, float startAngle, float endAngle) {
  const float kInvPi = 1.0 / 3.14159265;
  float angle = atan(coord.x, coord.y) * kInvPi * 0.5;
  angle = fract(angle - startAngle);
  float segment = step(angle, endAngle);
  segment *= step(0.0, angle);
  return mix(segment, 1.0, step(1.0, endAngle));
}

float draw_arc_outline(vec2 coord, float radius, float stroke, float startAngle, float endAngle) {
  float circle_outline = draw_circle_outline(coord, radius, stroke);
  circle_outline *= arc_segment(coord, startAngle, endAngle);
  return circle_outline;
}

float fr(float st) {
  return fract(sin(dot(st, 1.0)));
}

void main() {
  float scaleX;
  float scaleY;
  vec2 scaleXY;
  if (u_resolution.x < u_resolution.y) {
    scaleX = 1.0;
    scaleY = u_resolution.y / u_resolution.x;
    scaleXY = vec2(scaleX, scaleY);
  } else {
    scaleX = u_resolution.x / u_resolution.y;
    scaleY = 1.0;
    scaleXY = vec2(scaleX, scaleY);
  }
  vec2 coord = gl_FragCoord.xy / u_resolution.xy * scaleXY;
  vec2 offset = vec2(0.5, 0.5) * scaleXY;
  vec2 arcCoord = coord - offset;
  float stroke = 0.03;
  float gap = 0.003;
  float a = (u_time * 1.0);
  float maze;
  for(float i = 2.0; i < 17.0; ++ i)
  {
    maze += draw_arc_outline(arcCoord, 0.5 - stroke * i, stroke - gap, 0.05 + mix(0.0, fr(0.01 * i), a), 0.9);
  }
  vec3 color = vec3(maze);
  gl_FragColor = vec4(color, 1.0);
}

How to draw a circle with antialiasing.

To add antialiasing when drawing a circle we can use the smoothstep function. Below you can see the difference between the step and smoothstep function when drawing a circle.

#ifdef GL_ES
precision lowp float;
#endif

uniform vec2 u_resolution;

float draw_circle(vec2 coord, float radius) {
  return step(length(coord), radius);
}

float draw_smooth_circle(vec2 coord, float radius) {
  return smoothstep(radius, radius - 2.0 / u_resolution.y, length(coord));
}

void main() {
  float scaleX;
  float scaleY;
  vec2 scaleXY;
  if (u_resolution.x < u_resolution.y) {
    scaleX = 1.0;
    scaleY = u_resolution.y / u_resolution.x;
    scaleXY = vec2(scaleX, scaleY);
  } else {
    scaleX = u_resolution.x / u_resolution.y;
    scaleY = 1.0;
    scaleXY = vec2(scaleX, scaleY);
  }
  
  vec2 coord = gl_FragCoord.xy / u_resolution.xy * scaleXY;
  vec2 offset = vec2(0.5, 0.5) * scaleXY;
  float circle = draw_circle(coord - offset + vec2(0.24, 0), 0.24);
  float smooth_circle = draw_smooth_circle(coord - offset - vec2(0.24,0), 0.24);
  vec3 color = vec3(circle + smooth_circle);
  
  gl_FragColor = vec4(color, 1.0);
}

That's it for now. If I get another idea to create something interesting from the circle, I will update this post.