So far, almost everything we've created has been based on circles.
Circles are useful, but many user interfaces, games, and procedural graphics rely on rectangles.
Buttons.
Windows.
Panels.
Progress bars.
Health bars.
Cards.
Menus.
All of these begin with a simple rectangle.
Unlike traditional graphics, we won't import an image.
We'll generate the rectangle mathematically.
Think about a sheet of paper.
A rectangle isn't defined by a center like a circle.
Instead, it is defined by four edges.
Every pixel asks a simple question.
"Am I inside all four edges?"
If the answer is yes, the pixel belongs to the rectangle.
As always, we'll use UV coordinates.
vec2 uv = vUv;
Remember that both coordinates range from 0.0 to 1.0.
This makes it easy to describe positions on the screen.
Let's create the left and right edges.
float left = step(0.30, uv.x);
float right = 1.0 - step(0.70, uv.x);
Pixels between these values become white.
Everything else becomes black.
Now do the same for the vertical direction.
float bottom = step(0.30, uv.y);
float top = 1.0 - step(0.70, uv.y);
Again, only pixels inside these limits remain.
To create the rectangle, multiply the four results.
float rect =
left *
right *
bottom *
top;
Only pixels that satisfy every condition remain visible.
This creates a perfect rectangle.
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 vUv;
void main(){
vec2 uv = vUv;
float left = step(0.30, uv.x);
float right = 1.0 - step(0.70, uv.x);
float bottom = step(0.30, uv.y);
float top = 1.0 - step(0.70, uv.y);
float rect = left * right * bottom * top;
gl_FragColor = vec4(vec3(rect), 1.0);
}
There are no textures.
No imported images.
Every pixel simply decides whether it belongs inside the rectangle.
Move the edges farther apart.
left = step(0.20, uv.x);
right = 1.0 - step(0.80, uv.x);
The rectangle becomes wider.
Move the top and bottom edges.
bottom = step(0.15, uv.y);
top = 1.0 - step(0.85, uv.y);
Now it becomes taller.
Use equal spacing on both axes.
0.35
0.65
The result is a square.
Because a square is simply a rectangle with equal sides.
Replace step() with smoothstep().
float left = smoothstep(0.29, 0.31, uv.x);
float right = 1.0 - smoothstep(0.69, 0.71, uv.x);
Repeat this for the remaining edges.
The rectangle now has smooth edges instead of sharp ones.
This is useful for modern user interfaces.
Multiply the rectangle by any colour.
vec3 color = vec3(0.2, 0.8, 1.0) * rect;
gl_FragColor = vec4(color, 1.0);
Now only the rectangle is coloured.
Let's make the rectangle grow.
float size = 0.20 + sin(uTime) * 0.05;
Now use that value for the edges.
left = step(0.5 - size, uv.x);
right = 1.0 - step(0.5 + size, uv.x);
Do the same for the vertical edges.
The rectangle now expands and shrinks smoothly.
Remember the previous lesson.
Shapes are just numbers.
That means we can combine a rectangle with a circle.
float shape = rect + circle;
Or subtract one.
float shape = rect - circle;
Procedural graphics become much more interesting when different primitives work together.
Rectangles appear almost everywhere.
Some common examples include
Learning rectangles is one of the biggest steps toward creating complete procedural interfaces.
Create a thin rectangle.
Increase its width.
Turn it into a square.
Animate its size using sin().
Combine it with a circle.
Subtract a circle from the middle.
Each experiment builds your understanding of procedural construction.
Can you create these effects?
Try solving them before moving on.
You'll discover that almost every procedural interface starts with these same building blocks.
Today we learned how to create rectangles using only UV coordinates and the step() function.
Instead of measuring distance from a point, we defined four boundaries and checked whether each pixel stayed inside them.
Together with circles, rectangles become one of the most important procedural primitives you'll ever use.