Shadertoy Tutorial Part 3 - Squares and Rotation

Published: Thursday, March 11, 2021

Greetings, friends! In the previous article, we learned how to draw circles and animate them. In this tutorial, we will learn how to draw squares and rotate them using a rotation matrix.

How to Draw Squares

Drawing a square is very similar to drawing a circle except we will use a different equation. In fact, you can draw practically any 2D shape you want if you have an equation for it!

The equation of a square is defined by the following:

text
Copied! ⭐️
max(abs(x),abs(y)) = r

x = x-coordinate on graph
y = y-coordinate on graph
r = radius of square

We can re-arrange the variables to make the equation equal to zero:

text
Copied! ⭐️
max(abs(x), abs(y)) - r = 0

To visualize this on a graph, you can use the Desmos calculator to graph the following:

text
Copied! ⭐️
max(abs(x), abs(y)) - 2 = 0

If you copy the above snippet and paste it into the Desmos calculator, then you should see a graph of a square with a radius of two. The center of the square is located at the origin, (0, 0).

Screenshot of the Demos calculator. The equation for a square is on the left. The plot of the square is on the right.

You can also include an offset:

text
Copied! ⭐️
max(abs(x - offsetX), abs(y - offsetY)) - r = 0

offsetX = how much to move the center of the square in the x-axis
offsetY = how much to move the center of the square in the y-axis

Screenshot of the Demos calculator. The equation for a square is on the left. The plot of the square is on the right. The square is offset 2 units to the right and 2 units up from the origin.

The steps for drawing a square using a pixel shader is very similar to the previous tutorial where we created a circle. Instead, we'll be creating a function specifically for a square.

glsl
Copied! ⭐️
vec3 sdfSquare(vec2 uv, float size, vec2 offset) {
  float x = uv.x - offset.x;
  float y = uv.y - offset.y;
  float d = max(abs(x), abs(y)) - size;

  return d > 0. ? vec3(1.) : vec3(1., 0., 0.);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
  vec2 uv = fragCoord/iResolution.xy; // <0, 1>
  uv -= 0.5; // <-0.5,0.5>
  uv.x *= iResolution.x/iResolution.y; // fix aspect ratio

  vec2 offset = vec2(0.0, 0.0);

  vec3 col = sdfSquare(uv, 0.2, offset);

  // Output to screen
  fragColor = vec4(col,1.0);
}

Yay! Now we have a red square! 🟥

Screenshot of Shadertoy with the canvas on the left and code on the right. There is a red square in the center of the canvas.

Rotating shapes

You can rotate shapes by using a rotation matrix given by the following notation:

Equation for rotation matrix. R equals a two by two matrix. Top-left: cosine of theta. Top-right: negative sine of theta. Bottom-left: sine of theta. Bottom-right: cosine of theta.

Matrices can help us work with multiple linear equations and linear transformations. In fact, a rotational matrix is a type of transformation matrix. We can use matrices to perform other transformations such as shearing, translation, or reflection.

tip
If you want to play around with matrix arithmetic, you can use either the Demos Matrix Calculator or WolframAlpha. If you need a refresher on matrices, you can watch this amazing video by Derek Banas on YouTube.

We can use a graph I created on Desmos to help visualize rotations. I have created a set of parametric equations that use the rotation matrix in its linear equation form.

Screenshot of the Demos calculator. The equation for a rotated square is on the left with two parametric equations, where the variable t ranges between zero and two pi. The plot of the square rotated 45 degrees is on the right.

The linear equation form is obtained by multiplying the rotation matrix by the vector [x,y] as calculated by WolframAlpha. The result is an equation for the transformed x-coordinate and transformed y-coordinate after the rotation.

In Shadertoy, we only care about the rotation matrix, not the linear equation form. I only discuss the linear equation form for the purpose of showing rotations in Desmos.

We can create a rotate function in our shader code that accepts UV coordinates and an angle by which to rotate the square. It will return the rotation matrix multiplied by the UV coordinates. Then, we'll call the rotate function inside the sdfSquare function by passing in our XY coordinates, shifted by an offset (if it exists). We will use iTime as the angle, so that the square animates.

glsl
Copied! ⭐️
vec2 rotate(vec2 uv, float th) {
  return mat2(cos(th), sin(th), -sin(th), cos(th)) * uv;
}

vec3 sdfSquare(vec2 uv, float size, vec2 offset) {
  float x = uv.x - offset.x;
  float y = uv.y - offset.y;
  vec2 rotated = rotate(vec2(x,y), iTime);
  float d = max(abs(rotated.x), abs(rotated.y)) - size;

  return d > 0. ? vec3(1.) : vec3(1., 0., 0.);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
  vec2 uv = fragCoord/iResolution.xy; // <0, 1>
  uv -= 0.5; // <-0.5,0.5>
  uv.x *= iResolution.x/iResolution.y; // fix aspect ratio

  vec2 offset = vec2(0.0, 0.0);

  vec3 col = sdfSquare(uv, 0.2, offset);

  // Output to screen
  fragColor = vec4(col,1.0);
}

Notice how we defined the matrix in Shadertoy. Let's inspect the rotate function more closely.

glsl
Copied! ⭐️
vec2 rotate(vec2 uv, float th) {
  return mat2(cos(th), sin(th), -sin(th), cos(th)) * uv;
}

According to this wiki on GLSL, we define a matrix by comma-separated values, but we go through the matrix column-first. Since this is a matrix of type mat2, it is a 2x2 matrix. The first two values represent the first column, and the last two values represent the second-column. In tools such as WolframAlpha, you insert values row-first instead and use square brackets to separate each row. Keep this in mind as you're experimenting with matrices.

Our rotate function returns a value that is of type vec2 because a 2x2 matrix (mat2) multiplied by a vec2 vector returns another vec2 vector.

When we run the code, we should see the square rotate in the clockwise direction.

Red square rotating in the clockwise direction about the origin.

Conclusion

In this lesson, we learned how to draw a square and rotate it using a transformation matrix. Using the knowledge you have gained from this tutorial and the previous one, you can draw any 2D shape you want using an equation or SDF for that shape!

In the next article, I'll discuss how to draw multiple shapes on the canvas while being able to change the background color as well.

Resources