How to Draw a Rectangle with OpenGL and GLSL
2020-08-31 13:18:00 | | Part 4 of 5
Tested On
- Linux Ubuntu 20.04
- Windows 10
- macOS Catalina
-
Part 3 of 5
‹ How to Draw a Circle with OpenGL and GLSL -
Part 5 of 5
Mouse Detection with OpenGL and GLSL ›
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 coordinates 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 coord, vec2 dimensions) {
vec2 shaper = vec2(step(coord.x, dimensions.x), step(coord.y, dimensions.y));
return shaper.x * shaper.y;
}
void main() {
vec2 coord = gl_FragCoord.xy / u_resolution;
float rectangle = draw_rectangle(coord, 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's coordinates 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 coordinates are 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 coord.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 coordinates 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.
coord.x | coord.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 coordinates 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:
coord = (0.0, 0.0)
dimensions = (0.5, 0.25)
vec2 shaper = vec2(step(coord.x, dimensions.x), step(coord.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.
coord = (0.25, 0.25)
dimensions = (0.5, 0.25)
vec2 shaper = vec2(step(coord.x, dimensions.x), step(coord.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.
coord = (0.5, 0.5)
dimensions = (0.5, 0.25)
vec2 shaper = vec2(step(coord.x, dimensions.x), step(coord.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 coord, vec2 dimensions, vec2 offset) {
vec2 shaper = vec2(step(offset.x, coord.x), step(offset.y, coord.y));
shaper *= vec2(step(coord.x, offset.x + dimensions.x), step(coord.y, offset.y + dimensions.y));
return shaper.x * shaper.y;
}
void main() {
vec2 coord = gl_FragCoord.xy / u_resolution;
float rectangle = draw_rectangle(coord, 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 coord.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 coord.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:
coord = (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.
coord = (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 coord, vec2 dimensions, vec2 offset) {
vec2 shaper = step(offset, coord);
shaper *= step(coord, offset + dimensions);
return shaper.x * shaper.y;
}
void main() {
vec2 coord = gl_FragCoord.xy / u_resolution;
float rectangle = draw_rectangle(coord, 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.
-
Part 3 of 5
‹ How to Draw a Circle with OpenGL and GLSL -
Part 5 of 5
Mouse Detection with OpenGL and GLSL ›
Comments
You must log in to comment. Don't have an account? Sign up for free.
Subscribe to comments for this post
Info