How to Draw a Circle with OpenGL and GLSL

2020-08-31 13:18:00 | #programming #glsl #opengl #webgl | Part 3 of 4

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 position, float radius) {
    return step(length(position), radius);
}

void main() {
    vec2 position = gl_FragCoord.xy / u_resolution;
    float circle = draw_circle(position, 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 position and a radius. We then pass those parameters into step. If the radius is less than the length of the vec2 position, this function will return a 0.0. In other words, if the length of the position 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 position 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 positions.

Line 13: We pass the position 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 position (58,38):

  1. With a screen 800px wide by 600px tall, (58,38) would get normalized to (0.0725,0.06333).
  2. When passed into the length function, the square root of (0.07252 + 0.063332) is 0.0962.
  3. 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 the position by 0.5.

Filename: circle.frag

#ifdef GL_ES
precision lowp float;
#endif

uniform vec2 u_resolution;

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

void main() {
    vec2 position = gl_FragCoord.xy / u_resolution;
    vec2 offset = vec2(0.5, 0.5);
    float circle = draw_circle(position - offset, 0.3);
    vec3 color = vec3(circle);

    gl_FragColor = vec4(color, 1.0);
}

Now with the same exercise as before:

  1. With a screen 800px wide by 600px tall, (58, 38) would get normalized to (0.0725, 0.06333).
  2. Line 14: We then offset the position (0.0725, 0.06333) - (0.5, 0.5) which results in (-0.4275, -0.43667).
  3. When passed into the length function, the square root of (-0.42752 + -0.436672) is 0.4388.
  4. 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:

  1. With a screen 800px wide by 600px tall, (400, 300) would get normalized to (0.5, 0.5).
  2. Line 14: We then offset the position (0.5,0.5) - (0.5, 0.5) which results in (0, 0).
  3. When passed into the length function, the square root of (0.02 + 0.02) is 0.0.
  4. 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:

Want To See More Exercises?

View Exercises

Comments

You must log in to comment. Don't have an account? Sign up for free.

Subscribe to comments for this post

Want To Receive More Free Content?

Would you like to receive free resources, tailored to help you reach your IT goals? Get started now, by leaving your email address below. We promise not to spam. You can also sign up for a free account and follow us on and engage with the community. You may opt out at any time.



Tell Us About Your Project









Contact Us

Do you have a specific IT problem that needs solving or just have a general IT question? Use the contact form to get in touch with us and an IT professional will be with you, momentarily.

Hire Us

We offer web development, enterprise software development, QA & testing, google analytics, domains and hosting, databases, security, IT consulting, and other IT-related services.

Free IT Tutorials

Head over to our tutorials section to learn all about working with various IT solutions.

Contact