How to Create Animated Shaders With ShaderToy

  Programming

ShaderToy is a very handy tool that lets you create shaders inside your browser. It offers an easy-to-use interface where you can type in your GLSL code and watch it run immediately. You can also record your runs as WebM movies and share them on YouTube and other such websites.

Creating beautiful graphics with GLSL, however, is not an easy task. In fact, creating even simple geometric shapes involves a good amount of math, and requires a slightly different way of thinking. In this tutorial, I’ll show you how to use ShaderToy to create two circles and also animate them.

Prerequisites

To be able to follow along, all you need is a browser that supports WebGL. Of course, if your computer is very old, I suggest you also make sure that it has a graphics card that supports OpenGL 2.0 or higher.

Drawing a Circle

GLSL has no built-in function that lets you draw a circle, or any other geometric shape. In other words, you must always use math–and a little common sense–to draw anything with GLSL.

But first, you must understand that the output of your GLSL program is the color of just one pixel. In ShaderToy, that color, by default, is represented by the fragColor variable. The program runs thousands of times to color all the pixels that are present in ShaderToy’s preview window. By using the fragCoord variable, you can determine the position of the current pixel.

Using the fragCoord variable directly, however, is cumbersome because you can’t be sure about what it’s values will be on different screens. To make sure that the coordinates of your pixels fall in the range (0,0) to (1,1), you must divide fragCoord by iResolution. As you might have guessed, iResolution is a variable containing the resolution of the preview window–or your monitor in full screen mode. Accordingly, make sure that the mainImage() function has the following line:

vec2 uv = fragCoord.xy / iResolution.xy;

Now, to actually draw a circle, you must remember that all points inside a circle are at a distance that is less than or equal to the radius of the circle. Let’s say that radius is 0.3.

float radius = 0.3;

Next, you must decide where the center of the circle should be. I’m going to use (0.4, 0.5) as the center. Although you can use two float variables to hold the x and y coordinates seperately, using a vec2 is easier, and more intuitive.

vec2 center = vec2(0.4, 0.5);

At this point, we can calculate the distance of the current pixel from the center of the circle using a GLSL built-in function called distance().

float d = distance(center, uv);

If the value of d is greater than radius, let’s set fragColor to black. Otherwise, let’s set it to be red, the color of the circle.

if (d > radius) {
    fragColor = vec4(0, 0, 0, 1); // Black
} else {
    fragColor = vec4(1, 0, 0, 1); // Red
}

If you run your program now, you’ll see that we’ve created an ellipse instead of a circle. That’s because our preview window is a rectangle instead of a square. To correct this problem, we must multiply the x-coordinates of the pixels with the aspect ratio of the window. The following code shows you how–make sure you add it immediately after the calculation of uv.

float aspectRatio = iResolution.x / iResolution.y;
uv.x *= aspectRatio;

And now, you should be able to see a perfect circle.

ShaderToy circle jagged

Notice that the edges of the circle are jagged. To make them smoother, we can use the smoothstep() function. Because the smoothstep() function has a built-in if-statement, you can remove the if statement in your program, and replace it with the following lines.

float c = smoothstep(radius, radius - 0.01, d);    
fragColor = vec4(c, 0, 0, 1);

You should now be able to see a much smoother circle. That’s cool, isn’t it?

ShaderToy circle smooth

Drawing Another Circle

What if you want to draw another circle, one with a different color? Well, you just have one fragColor variable. You must make do with it alone. Fortunately, doing so is not too difficult.

Create another circle using the following lines (add them before you set the value of fragColor):

float secondRadius = 0.1;
vec2 secondCenter = vec2(0.8, 0.5);
float secondD = distance(secondCenter, uv);
float secondC = smoothstep(secondRadius, secondRadius - 0.01, secondD);

Of course, the code is a little repetitive, but ignore that for now. We’re still learning the ropes.

To render the second circle along with the first circle, we need to create two vec4 variables, and add them. Each vec4 variable will contain the color of each circle. Let the second circle be green.

vec4 redColor = vec4(c, 0, 0, 1);
vec4 greenColor = vec4(0, secondC, 0, 1);
vec4 sum = redColor + greenColor;

You can now assign the sum to fragColor.

fragColor = sum;

Run the program again to see the two circles.

ShaderToy circles

I hope you found that easy.

Animating the Circles

Animating our circles will be far easier than creating them. I mean, you just have to modify their centers based on some parameter. What could that parameter be? Well, ShaderToy offers a variable called iFrame, containing the current frame of your animation.

There are many different approaches to do this, but, for now, let’s say our animation has 400 frames. By dividing iFrame by 400, we get a value between 0 and 1. We can then add/subtract that value to/from the x-coordinates of our circles’ centers. Here’s how:

float frame = float(iFrame) / 400;

// ...

vec2 center = vec2(0.4 + frame, 0.5);

// ...

vec2 secondCenter = vec2(0.8 - frame, 0.5);

Run the code now to see your circles moving.

Conclusion

You now know how to create shaders using ShaderToy. The shader we created today was extremely simple, but it is animated, and non-trivial. In future tutorials, we’ll creating something more complicated.

If you found this article useful, please share it with your friends and colleagues!