How to Draw a Circle with OpenGL and GLSL
2020-08-31 13:18:00 | | Part 3 of 5
Tested On
- Linux Ubuntu 20.04
- Windows 10
- macOS Catalina
In this tutorial, we're going to write code that shades pixels in a way that produces clearly defined shapes. For example, when we want to draw a circle to the screen, we need to shade all of the pixels within that circle shape black, and all of the outside pixels white.
The same concept applies to a rectangle shape. Black pixels inside the shape, white pixels outside. So one-by-one, we need to go through each pixel and have our shader perform a calculation to determine if the pixel should be either black or white. Let's begin.
How to Draw a Circle with OpenGL
If you've completed part 1 and 2 of this series, you should already be familiar with length and step. length will allow us to determine the distance of a fragment, relative to a pre-defined radius. And step will allow us to shade that pixel white if it falls within the radius' bounds, producing a white circle. We'll begin by writing some shader code, using functions you already know from the last chapter.
Filename: circle.frag
#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() {
vec2 coord = gl_FragCoord.xy / u_resolution;
float circle = draw_circle(coord, 0.3);
vec3 color = vec3(circle);
gl_FragColor = vec4(color, 1.0);
}
We will now explain each line of code, except for lines we've already explained in past chapters.
Line 7-9: We define a draw_circle function that accepts a coord and a radius. We then pass those parameters into step. If the radius is less than the length of the vec2 coord, this function will return a 0.0. In other words, if the length of the coord is greater than the radius (outside the radius), return a 0.0. Otherwise, it is within the radius and should return a 1.0.
Line 12: We get the normalized coordinates of the fragment/pixel that main() is currently processing. This calculation will run on every single fragment, so we will be dealing with millions of coordinates.
Line 13: We pass the coord and a radius of 0.3 into our draw_cirle function. This will return a 1.0 for all pixels within the radius 0.3 from the bottom left corner of the screen, which we store into the vec3 color.
Line 15: We then assign color and an alpha of 1.0 to gl_FragColor, which will shade the pixel the appropriate color.
Run this shader on Linux with the glslViewer circle.frag command. Or within VisualStudio on Windows 10 and macOS.
You can attempt the calculations, yourself, to get a better understanding of how each pixel is shaded. Try it with a fragment at coordinate (58,38):
- With a screen 800px wide by 600px tall, (58,38) would get normalized to (0.0725,0.06333).
- When passed into the length function, the square root of (0.07252 + 0.063332) is 0.0962.
- Because 0.0962 is less than (within) the radius of 0.3, that lower left positioned pixel gets shaded a solid white color.
If you were to calculate each pixel, manually, you would end up with the same image above. So while these calculations are all correct, we still have a part of a circle in the bottom left corner. To center the circle, we just need to offset coord by 0.5.
Filename: circle.frag
#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() {
vec2 coord = gl_FragCoord.xy / u_resolution;
vec2 offset = vec2(0.5, 0.5);
float circle = draw_circle(coord - offset, 0.3);
vec3 color = vec3(circle);
gl_FragColor = vec4(color, 1.0);
}
Now with the same exercise as before:
- With a screen 800px wide by 600px tall, (58, 38) would get normalized to (0.0725, 0.06333).
- Line 14: We then offset coord (0.0725, 0.06333) - (0.5, 0.5) which results in (-0.4275, -0.43667).
- When passed into the length function, the square root of (-0.42752 + -0.436672) is 0.4388.
- Because 0.4388 is greater than (outside) the radius of 0.3, that lower left positioned pixel that's outside the radius of the circle gets shaded a solid black color, this time.
And with a fragment positioned at the center of the screen:
- With a screen 800px wide by 600px tall, (400, 300) would get normalized to (0.5, 0.5).
- Line 14: We then offset coord (0.5,0.5) - (0.5, 0.5) which results in (0, 0).
- When passed into the length function, the square root of (0.02 + 0.02) is 0.0.
- Because 0.0 is less than the radius of 0.3, that center positioned pixel that's within the radius of the circle gets shaded a solid white color.
Run this shader on Linux with the glslViewer circle.frag command and you should see the following image:
Comments
You must log in to comment. Don't have an account? Sign up for free.
Subscribe to comments for this post
Info