How to Draw a Rectangle with OpenGL and GLSL

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

Now that we understand how a circle can be rendered with OpenGL, we're going slightly modify our shader code to draw a rectangle.

Just like our circle's radius, our shader will need to know the bounds of the rectangle, so it can fill them in with solid white pixels.

How to Draw a Rectangle with OpenGL

Before you read this tutorial, it is important that you complete the previous chapters in this series. Drawing a rectangle requires a fundamental understanding of fragment rendering and the OpenGLstep function. With that out of the way, we can begin.

To render a rectangle, we will need to iterate over every fragment/pixel, and determine if it should be shaded black or white depending on the fragment's position in screen space. A rectangle that is 50% wide and 25% tall will require shader code that draws white pixels for every fragment positioned within those dimensions, and black for every fragment outside. Take a look at the following code, and read our line-by-line breakdown, below. Some lines will be skipped as they've been explained in previous chapters.

Filename: rectangle.frag

#ifdef GL_ES
precision lowp float;
#endif

uniform vec2 u_resolution;

float draw_rectangle(vec2 position, vec2 dimensions) {
    vec2 shaper = vec2(step(position.x, dimensions.x), step(position.y, dimensions.y));
    return shaper.x * shaper.y;
}

void main() {
    vec2 position = gl_FragCoord.xy / u_resolution;
    float rectangle = draw_rectangle(position, vec2(0.5, 0.25));
    vec3 color = vec3(rectangle);

    gl_FragColor = vec4(color, 1.0);
}

Line 7-10: We define a draw_rectangle function that returns a float value of either 1.0 or 0.0.

Line 7: Our function will accept a fragment position and the dimensions (width and height) for the rectangle. These are both vec2. So for a rectangle that is 50% wide and 20% tall, we just need to provide a vec2 of (0.5, 0.25).

Line 8: Here, we check to see if the fragment's position is within the bounds of the rectangle. So in this case, step returns 0.0 if dimensions.x (width of the rectangle) is less than the position.x of the fragment, and a 1.0 if it is greater. If this all sounds confusing, don't worry. We'll explain with some scenarios below.

Line 9: We multiply the x and y values together to ensure that both the x and y positions of the fragment equal 1.0. Both being equal to 1.0 means that the fragment is inside the dimensions of the rectangle on both the x and y axes, as demonstrated below.

position.x position.y Result
0.0 0.0 0.0
0.0 1.0 0.0
1.0 1.0 1.0

Line 13: This is where we invoke the draw_rectangle function and pass in the current fragment's position and the size we want for our rectangle.

Line 14: For each fragment that falls within the bounds of the rectangle, a 1.0 will be returned that we can assign to a color, producing a solid white pixel.

Scenarios Calculated by our OpenGL Rectangle Shader

To better understand our shader code, let's walk through some scenarios to see how the values are being calculated. First, with the fragment/pixel at 0.0:

position = (0.0, 0.0)
dimensions = (0.5, 0.25)

vec2 shaper = vec2(step(position.x, dimensions.x), step(position.y, dimensions.y));

shaper = (step(0.0, 0.5), step(0.0, 0.25))
shaper = (step(0.0, 0.5), step(0.0, 0.25))
shaper = (1.0, 1.0)
shaper = (1.0, 1.0)
return 1.0 * 1.0

rectangle = 1.0
color = (1.0, 1.0, 1.0)
gl_FragColor = (1.0, 1.0, 1.0, 1.0)

Now, with a fragment positioned 25% from the bottom left corner.

position = (0.25, 0.25)
dimensions = (0.5, 0.25)

vec2 shaper = vec2(step(position.x, dimensions.x), step(position.y, dimensions.y));

shaper = (step(0.25, 0.5), step(0.25, 0.25))
shaper = (step(0.25, 0.5), step(0.25, 0.25))
shaper = (1.0, 1.0)
shaper = (1.0, 1.0)
return 1.0 * 1.0

rectangle = 1.0
color = (1.0, 1.0, 1.0)
gl_FragColor = (1.0, 1.0, 1.0, 1.0)

And with a fragment positioned in the middle of the screen.

position = (0.5, 0.5)
dimensions = (0.5, 0.25)

vec2 shaper = vec2(step(position.x, dimensions.x), step(position.y, dimensions.y));

shaper = (step(0.5, 0.5), step(0.5, 0.25))
shaper = (step(0.5, 0.5), step(0.5, 0.25))
shaper = (0.0, 1.0)
shaper = (0.0, 1.0)
return 0.0 * 1.0

rectangle = 0.0
color = (0.0, 0.0, 0.0)
gl_FragColor = (0.0, 0.0, 0.0, 1.0)

As you can see, the pixel in the first and second scenarios fall within the bounds of the rectangle, producing an RGBA value of (1.0, 1.0, 1.0, 1.0). But in the third scenario, a solid black pixel is produced.

Run this shader on Linux with the glslViewer rectangle.frag command. Or within VisualStudio on Windows 10 and macOS. You should see the following rectangle in the bottom left corner of the screen.

Positioning the OpenGL Rectangle with an Offset

Now all we have left to do is to provide an offset so that we're not always drawing a rectangle from the bottom left corner of the screen.

Filename: rectangle.frag

#ifdef GL_ES
precision lowp float;
#endif

uniform vec2 u_resolution;

float draw_rectangle(vec2 position, vec2 dimensions, vec2 offset) {
    vec2 shaper = vec2(step(offset.x, position.x), step(offset.y, position.y));
    shaper *= vec2(step(position.x, offset.x + dimensions.x), step(position.y, offset.y + dimensions.y));

    return shaper.x * shaper.y;
}

void main() {
    vec2 position = gl_FragCoord.xy / u_resolution;
    float rectangle = draw_rectangle(position, vec2(0.5, 0.5), vec2(0.25, 0.25));
    vec3 color = vec3(rectangle);

    gl_FragColor = vec4(color, 1.0);
}

Line 8: Now, we're checking to see if the fragment's positioned above and to the right of the bottom/left bounds of the rectangle. For example, if position.x is less than offset.x, it is not to the right and will return 0.0. Otherwise, it will return a 1.0. Same for y and checking to see if the pixel is above.

Line 9: We also check to see if the fragment's positioned below and to the left of the top/right bounds of the rectangle. For example, if offset.x + dimensions.x is less than position.x, it is not to the left and will return 0.0. Otherwise, it will return a 1.0. Same for y and checking to see if the pixel is below.

Scenarios Calculated by our OpenGL Rectangle with Offset Shader

To better understand our shader code, let's walk through some scenarios to see how the values are being calculated. First, with the fragment/pixel at 0.0:

position = (0.0, 0.0)
dimensions = (0.5, 0.5)
offset = (0.25, 0.25)

shaper = (step(0.25, 0.0), step(0.25, 0.0))
shaper = (step(0.25, 0.0), step(0.25, 0.0))
shaper = (1.0, 1.0)

shaper = (0.0, 0.0) * (step(0.0, 0.25 + 0.5), step(0.0, 0.25 + 0.5))
shaper = (0.0, 0.0) * (step(0.0, 0.75), step(0.0, 0.75))
shaper = (0.0, 0.0) * (0.0, 0.0)
shaper = (0.0, 0.0) * (0.0, 0.0)
return 0.0 * 0.0

rectangle = 0.0
color = (0.0, 0.0, 0.0)
gl_FragColor = (0.0, 0.0, 0.0, 1.0)

Now, with a fragment positioned in the middle of the screen.

position = (0.5, 0.5)
dimensions = (0.5, 0.5)
offset = (0.25, 0.25)

shaper = (step(0.25, 0.5), step(0.25, 0.5))
shaper = (step(0.25, 0.5), step(0.25, 0.5))
shaper = (1.0, 1.0)

shaper = (1.0, 1.0) * (step(0.5, 0.25 + 0.5), step(0.5, 0.25 + 0.5))
shaper = (1.0, 1.0) * (step(0.5, 0.75), step(0.5, 0.75))
shaper = (1.0, 1.0) * (1.0, 1.0)
shaper = (1.0, 1.0) * (1.0, 1.0)
return 1.0 * 1.0

rectangle = 1.0
color = (1.0, 1.0, 1.0)
gl_FragColor = (1.0, 1.0, 1.0, 1.0)

You should end up with the following image after running your shader.

Now that we understand how the rendering is calculated, we can optimize our shader code a bit more, by passing our vectors into step, rather than the individual x and y values.

#ifdef GL_ES
precision lowp float;
#endif

uniform vec2 u_resolution;

float draw_rectangle(vec2 position, vec2 dimensions, vec2 offset) {
    vec2 shaper = step(offset, position);
    shaper *= step(position, offset + dimensions);

    return shaper.x * shaper.y;
}

void main() {
    vec2 position = gl_FragCoord.xy / u_resolution;
    float rectangle = draw_rectangle(position, vec2(0.5, 0.5), vec2(0.25, 0.25));
    vec3 color = vec3(rectangle);

    gl_FragColor = vec4(color, 1.0);
}

This concludes our tutorial on drawing rectangles with OpenGL. Please sign up to get notified when the next chapter of this series becomes available.

This tutorial is an excerpt from our Pixel Manipulation with OpenGL Fragment Shaders course. If you'd like to learn more about how to shade pixels to produce 2D and 3D effects, consider signing up today.

Want To See More Exercises?

View Exercises View Courses

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