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
I used 2 functions to create a circle. Step
length
#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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
A moving circular maze created with multiple arc outline rotated by the function changing the startAngle
#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);
}
To add antialiasing when drawing a circle we can use the smoothstep
step
smoothstep
#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.