Creating GLSL Animated GIF loops

Animated GIF Loop 010

This post is about creating animated GIF loops with GLSL and Visions of Chaos. It will cover the basics of the GL shading language and give some simple examples of how to start creating animated GIFs. I am nowhere near an expert when coding shaders so while hopefully being helpful to someone starting with shaders it is mainly to help myself get better at coding GLSL. There is no better way to learn something than by trying to explain it to someone else.

You have probably seen examples of animated GIFs that loop. Those short few second videos that loop seamlessly. They usually show some sort of interesting graphical display. There are endless possibilities.

Getting Started

To begin, start Visions of Chaos and then select Mode->OpenGL Shading Language->Shader Editor. If the menu is disabled you probably need to update your video card drivers so they support GLSL.

Note: All of the shaders in this tutorial are included with Visions of Chaos. They have the same names as under the preview GIF animations. You can load them and play with the code as you go along.


Creating A New Shader

Click the New button on the GLSL Shader Editor dialog and give your shader a name. I called mine “Animated GIF Loop 001” but you can use any name you like. The name you specify here is the name of the GL shader file, so pick something memorable.

You will then see a simple basic shader code created for you.


#version 120

uniform float time;
uniform vec2 resolution;

void main(void)
{
    gl_FragColor = vec4(1.0,1.0,1.0,1.0);
}

The first line is a version declaration telling GLSL the code supports version 2.1 of the GLSL language. Version 2.1 is fairly old now so most graphics cards support it.

The two uniform lines are values that are passed to the shader from Visions of Chaos when it is run. The time variable is how many seconds the shader has been running for. Resolution contains the X and Y pixel dimensions of the image.

Next up is the main function. This is where every shader begins running at. The code within the main function is run for every pixel of the image simultaneously. This is why shaders can run so fast. You are not looping through each pixel one at a time. The graphics card GPU calculates multiple pixels at once.

gl_FragColor sets the pixel color. The vec4 is a four component vector of values representing the red, green, blue and alpha intensities. These values should be between 0 and 1. Alpha should always be 1 for our purposes. In the example code, setting all 4 values to 1 results in a white pixel.

When you click the OK button on the GLSL Shader Editor dialog the shader will run and you will see a (boring) plain white image.


Changes Over Time

The most important factor in animation is changing “things” over time. For this we use the uniform time variable. Every frame the shader is displayed Visions of Chaos passes in how much time has passed in seconds since the shader was first started. This allows you to use that variable for animation.

For a first example, let’s use the time to animate the background boring white color by changing the gl_FragColor line.


#version 120

uniform float time;
uniform vec2 resolution;

void main(void)
{
    gl_FragColor = vec4(time,0.0,0.0,1.0);
}

Now when you run the shader you will see the image color fade in from black to pure red over the period of 1 second. This is because time starts at 0 and then increases as the shader runs. Once time gets past 1 GLSL clamps it to 1 so the color stays full red intensity.

If you want the fade in to take longer (say 5 seconds), change the time in gl_FragColor to time/5.0 so it takes 5 times as long to reach full intensity red.

We can also use the fract command. Fract returns the fractional part of any number. So 1.34 returns 0.34. 543.678 returns 0.678. If we use fract on the time we get a repeating 0 to 1 value. This causes the black to red to repeat every 2 seconds.


#version 120

uniform float time;
uniform vec2 resolution;

void main(void)
{
    gl_FragColor = vec4(fract(time/2.0),0.0,0.0,1.0);
}

If this shader was saved as an animated gif movie it would be fairly boring. An increase in color from black to red then a sudden jump back to black to then increase once again to red. For animated gifs we want a smooth loop so when the gif restarts it is not noticeable.


Sine Looping

Using a Sine wave for animation makes it simpler to do nice repeating animations.

There is some sine math explanations here that helped.

The GLSL shader code now becomes


#version 120

uniform float time;
uniform vec2 resolution;

float animationSeconds = 2.0; // how long do we want the animation to last before looping
float piTimes2 = 3.1415926536*2.0;

void main(void)
{
    // sineVal is a floating point value between 0 and 1
    // starts at 0 when time = 0 then increases to 1.0 when time is half of animationSeconds and then back to 0 when time equals animationSeconds
    float sineVal = sin(piTimes2*(time-0.75)/animationSeconds)/2.0+0.5; 
    gl_FragColor = vec4(sineVal,0.0,0.0,1.0);
}

The animationSeconds variable allows us to change the length the animation runs for before repeating. In this case it is 2 seconds.

Sine Wave

If you look at the above diagram the lower part of the sine wave marked “one wave cycle” (from lowest trough to the next lowest trough) is the shape the sineVal code calculates and scales it to between 0 and 1. So the value starts at 0, curves up to 1 and then back down to 0.

When we run this new shader code we get the screen smoothly fading between black to red and then back to black every 2 seconds.

We can modify the gl_FragColor line to also fade the blue color component in and out. The blue component is specified as 1.0-sineVal which means when red is at its highest intensity blue is at its lowest.


#version 120

uniform float time;
uniform vec2 resolution;

float animationSeconds = 2.0; // how long do we want the animation to last before looping
float piTimes2 = 3.1415926536*2.0;

void main(void)
{
    // sineVal is a floating point value between 0 and 1
    // starts at 0 when time = 0 then increases to 1.0 when time is half of animationSeconds and then back to 0 when time equals animationSeconds
    float sineVal = sin(piTimes2*(time-0.75)/animationSeconds)/2.0+0.5; 
    gl_FragColor = vec4(sineVal,0.0,1.0-sineVal,1.0);
}

The above code results in the following GIF animation.

Animated GIF Loop 001

Animated GIF Loop 001

A quick note here. Browsers do not seem to show animated GIFs at the correct frame rate (*or maybe they do not like 60 fps GIFs). Anyway, to see the GIFs at the correct frame rate you may need to download them and open them natively.


Setting Individual Pixel Colors

OK, so far we have used the time uniform value to animate the entire image changing color. Now let’s cover how to change each pixel within the image.

This is the new shader code


#version 120

uniform float time;
uniform vec2 resolution;

float animationSeconds = 2.0; // how long do we want the animation to last before looping
float piTimes2 = 3.1415926536*2.0;

void main(void)
{
    //uv is pixel coordinates between -1 and +1 in the X and Y axiis with aspect ratio correction
    vec2 uv = (2.0*gl_FragCoord.xy-resolution.xy)/resolution.y;

    // sineVal is a floating point value between 0 and 1
    // starts at 0 when time = 0 then increases to 1.0 when time is half of animationSeconds and then back to 0 when time equals animationSeconds
    float sineVal = sin(piTimes2*(time-0.75)/animationSeconds)/2.0+0.5; 

    //shade pixels across the image depending on their X and Y coordinates - animated using sineVal
    gl_FragColor = vec4(gl_FragCoord.x/resolution.x,sineVal,1.0-gl_FragCoord.y/resolution.y,1.0);
}

The uv calculation gets the current pixel X and Y coordinate scaled to between -1 and +1. So the top left corner of the image is vec2(-1.0,-1.0) and the lower right corner of the image is vec2(1.0,1.0). Note that aspect ratio is corrected here too. That means when the shader is run on a non-square image the pixels will be correctly stretched. This will be more apparent in the next circle part. The circle remains a true circle no matter what size the image is stretched to.

Changing the gl_FragColor line scales the red component on the X axis and blue component on the Y axis. Using sineVal for the green component makes the green of each pixel fade in and out. Running this shader gives you a shaded image now with interpolations of color between black, red, blue and purple tones while mixing with the fading intensities of green. Using gl_FragCoord.x/resolution.x uses the pixel coordinate over the image size in pixels. This means no matter what the image dimensions the red component will go from 0 to 1 across the image.

Animated GIF Loop 002

Animated GIF Loop 002

Drawing Circles And Squares

Next up, drawing primitive circle and square shapes.


#version 120

uniform float time;
uniform vec2 resolution;

float animationSeconds = 2.0; // how long do we want the animation to last before looping
float piTimes2 = 3.1415926536*2.0;

void main(void)
{
    //uv is pixel coordinates between -1 and +1 in the X and Y axiis with aspect ratio correction
    vec2 uv = (2.0*gl_FragCoord.xy-resolution.xy)/resolution.y;

    // sineVal is a floating point value between 0 and 1
    // starts at 0 when time = 0 then increases to 1.0 when time is half of animationSeconds and then back to 0 when time equals animationSeconds
    float sineVal = sin(piTimes2*(time-0.75)/animationSeconds)/2.0+0.5; 

    float circleRadius = 0.5; //radius of circle - 0.5 = 25% of texture size (2.0) so circle will fill 50% of image
    vec2 circleCenter = vec2(-0.8,0.0); //center circle on the image
    
    float squareRadius = 0.5; //radius of circle - 0.5 = 25% of texture size (2.0) so circle will fill 50% of image
    vec2 squareCenter = vec2(0.8,0.0); //center circle on the image
    
    vec4 color = vec4(0.0); //init color variable to black

    //test if pixel is within the circle
    if (length(uv-circleCenter)<circleRadius)
    {
        color = vec4(1.0,1.0,1.0,1.0);
    } 
    //test if pixel is within the square
    else if ((abs(uv.x-squareCenter.x)<squareRadius)&&(abs(uv.y-squareCenter.y)<squareRadius))
    {
        color = vec4(1.0,1.0,1.0,1.0);
    } 
    else {
    //else pixel is the pulsating colored background
        color = vec4(uv.x,sineVal,1.0-uv.y,1.0); 
    }
   
    gl_FragColor = color;
}

The code is starting to get more lengthy now, but is still relatively simple.

The position and size for a circle and square are specified. The circle is centered to the left of the image and the square is centered to the right of the image.

With a few if else statements we can test if the current pixel being calculated is within the circle or square. Length is a built in GLSL function that returns the length of the vector passed. Basically this is the same as using sqrt(sqr(x2-x1)+sqr(y2-y1)) to find the distance between 2 points. For checking a point within a square simpler absolute value math can be used. If the pixel does not fall within either shape it is shaded using the background color.

Animated GIF Loop 003

Animated GIF Loop 003

Note that the color distortion of the lower right corners of the square and circle flaring out are a side effect of the limited 256 colors for GIF animations. Something to be aware of if you are using a wide gammut of colors like that example.


Animating Shapes Size And Position

Now let’s get the shapes moving and changing size.


#version 120

uniform float time;
uniform vec2 resolution;

float animationSeconds = 2.0; // how long do we want the animation to last before looping
float piTimes2 = 3.1415926536*2.0;

void main(void)
{
    //uv is pixel coordinates between -1 and +1 in the X and Y axiis with aspect ratio correction
    vec2 uv = (2.0*gl_FragCoord.xy-resolution.xy)/resolution.y;

    // sineVal is a floating point value between 0 and 1
    // starts at 0 when time = 0 then increases to 1.0 when time is half of animationSeconds and then back to 0 when time equals animationSeconds
    float sineVal = sin(piTimes2*(time-0.75)/animationSeconds)/2.0+0.5; 

    float circleRadius = 0.2+(1.0-sineVal)*0.4; //radius of circle
    vec2 circleCenter = vec2(-0.8+sineVal*1.6,0.0); //center circle on the image
    
    float squareRadius = 0.2+sineVal*0.2; //radius of circle
    vec2 squareCenter = vec2(0.8,0.0); //center circle on the image
    
    vec4 color = vec4(0.0); //init color variable to black

    //test if pixel is within the circle
    if (length(uv-circleCenter)<circleRadius)
    {
        color = vec4(1.0,1.0,1.0,1.0);
    } 
    //test if pixel is within the square
    else if ((abs(uv.x-squareCenter.x)<squareRadius)&&(abs(uv.y-squareCenter.y)<squareRadius))
    {
        color = vec4(1.0,1.0,1.0,1.0);
    } 
    else {
    //else pixel is black
        color = vec4(0.0,0.0,0.0,1.0); 
    }
   
    gl_FragColor = color;
}

For this example I have changed the background just to black.

The main change from the last shader code is in the radius and center declarations. We now use the sineVal to animate the sizes and positions.

Animated GIF Loop 004

Animated GIF Loop 004

Blending Colors

This next example shows how to have 3 red, green and blue circles’ colors blend as they overlap.

Calculating the positions of the circles is done using the mix function. Mix takes a from and to vector and returns a value between the two. The value is a percentage of the distance passed in. In this case we use the sineVal variable that goes from 0 to 1 and back again.


#version 120

uniform float time;
uniform vec2 resolution;

float animationSeconds = 2.0; // how long do we want the animation to last before looping
float piTimes2 = 3.1415926536*2.0;

void main(void)
{
    //uv is pixel coordinates between -1 and +1 in the X and Y axiis with aspect ratio correction
    vec2 uv = (2.0*gl_FragCoord.xy-resolution.xy)/resolution.y;

    // sineVal is a floating point value between 0 and 1
    // starts at 0 when time = 0 then increases to 1.0 when time is half of animationSeconds and then back to 0 when time equals animationSeconds
    float sineVal = sin(piTimes2*(time-0.75)/animationSeconds)/2.0+0.5; 

    float circle1Radius = 0.2+(1.0-sineVal)*0.2; //radius of circle
    vec2 circle1Center = mix(vec2(-0.8,0.0),vec2(0.8,0.0),sineVal);
    
    float circle2Radius = 0.2+(sineVal)*0.2; //radius of circle
    vec2 circle2Center = mix(vec2(0.0,-0.8),vec2(0.0,0.8),sineVal);
    
    float circle3Radius = 0.2+(1.0-sineVal)*0.2; //radius of circle
    vec2 circle3Center = mix(vec2(-0.8,-0.55),vec2(0.8,0.8),sineVal);
    
    vec4 color = vec4(0.0); //init color variable to black

    //default pixel color is black
    color = vec4(0.0,0.0,0.0,1.0); 
    //test if pixel is within the circle
    if (length(uv-circle1Center)<circle1Radius)
    {
        color += vec4(1.0,0.0,0.0,1.0);
    } 
    if (length(uv-circle2Center)<circle2Radius)
    {
        color += vec4(0.0,0.0,1.0,1.0);
    } 
    if (length(uv-circle3Center)<circle3Radius)
    {
        color += vec4(0.0,1.0,0.0,1.0);
    } 
   
    gl_FragColor = color;
}

Animated GIF Loop 005

Animated GIF Loop 005

Smoothing Out Rough Edges

To help reduce the aliasing jagged edges you can use the smoothstep function to blend between two other values. Similar to the mix function.

For this shader the circles are changed into torus shapes.


#version 120

uniform float time;
uniform vec2 resolution;

float animationSeconds = 2.0; // how long do we want the animation to last before looping
float piTimes2 = 3.1415926536*2.0;

void main(void)
{
    //uv is pixel coordinates between -1 and +1 in the X and Y axiis with aspect ratio correction
    vec2 uv = (2.0*gl_FragCoord.xy-resolution.xy)/resolution.y;

    // sineVal is a floating point value between 0 and 1
    // starts at 0 when time = 0 then increases to 1.0 when time is half of animationSeconds and then back to 0 when time equals animationSeconds
    float sineVal = sin(piTimes2*(time-0.75)/animationSeconds)/2.0+0.5; 

    float torus1Radius = 0.2+(1.0-sineVal)*0.4;
    vec2 torus1Center = mix(vec2(-0.8,0.0),vec2(0.8,0.0),sineVal);
    
    float torus2Radius = 0.2+(sineVal)*0.4;
    vec2 torus2Center = mix(vec2(0.8,0.0),vec2(-0.8,0.0),sineVal);
    
    float torus3Radius = 0.2+(1.0-sineVal)*0.2;
    vec2 torus3Center = vec2(0.0,0.0);
    
    float torusWidth = 0.1;
    float torusSmoothsize = 0.03;

    vec4 color = vec4(0.0); //init color variable to black

    //default pixel color is black
    color = vec4(0.0,0.0,0.0,1.0); 
    float c;
    c = smoothstep(torusWidth,torusWidth-torusSmoothsize,(abs(length(uv-torus1Center)-torus1Radius)));        
    color += vec4(c,0.0,0.0,1.0);
    c = smoothstep(torusWidth,torusWidth-torusSmoothsize,(abs(length(uv-torus2Center)-torus2Radius)));        
    color += vec4(0.0,c,0.0,1.0);
    c = smoothstep(torusWidth,torusWidth-torusSmoothsize,(abs(length(uv-torus3Center)-torus3Radius)));        
    color += vec4(0.0,0.0,c,1.0);
   
    gl_FragColor = color;
}

Animated GIF Loop 006

Animated GIF Loop 006

Checkerboard

Thanks to this post on StackOverflow we can get a simple function working to calculate if a pixel should be black or white on a checkerboard pattern.


#version 120

uniform float time;
uniform vec2 resolution;

float animationSeconds = 2.0; // how long do we want the animation to last before looping
float piTimes2 = 3.1415926536*2.0;

vec2 rotate(vec2 v, float a) {
	float angleInRadians = radians(a);
	float s = sin(angleInRadians);
	float c = cos(angleInRadians);
	mat2 m = mat2(c, -s, s, c);
	return m * v;
}

vec3 checker(in float u, in float v, in float checksPerUnit)
{
  float fmodResult = mod(floor(checksPerUnit * u) + floor(checksPerUnit * v), 2.0);
  float col = max(sign(fmodResult), 0.0);
  return vec3(col, col, col);
}

void main(void)
{
    //uv is pixel coordinates between -1 and +1 in the X and Y axiis with aspect ratio correction
    vec2 uv = (2.0*gl_FragCoord.xy-resolution.xy)/resolution.y;

    // sineVal is a floating point value between 0 and 1
    // starts at 0 when time = 0 then increases to 1.0 when time is half of animationSeconds and then back to 0 when time equals animationSeconds
    float sineVal = sin(piTimes2*(time-0.75)/animationSeconds)/2.0+0.5; 

    //rotate the uv coordinates between 0 and 180 degrees during the animationSeconds time length
    vec2 rotated_uv = rotate(uv,-time/animationSeconds*180);

    //get the pixel checker color by passing the rotated coordinate into the checker function
    vec4 color = vec4(checker(rotated_uv.x, rotated_uv.y, 5.0 * sineVal),1.0);

    gl_FragColor = color;
}

Using the sineVal as previously gets the size of the checkers growing and shrinking for a clean loop.

For the rotation, time divided by animationSeconds is used. This ensures the checkerboard rotates continuously in the same direction for a cleaner loop.

Animated GIF Loop 007

Animated GIF Loop 007

Multiple RGB Checkerboards

Now let’s use 3 checkerboards in red, green and blue, overlapped.


#version 120

uniform float time;
uniform vec2 resolution;

float animationSeconds = 2.0; // how long do we want the animation to last before looping
float piTimes2 = 3.1415926536*2.0;

vec2 rotate(vec2 v, float a) {
    float angleInRadians = radians(a);
    float s = sin(angleInRadians);
    float c = cos(angleInRadians);
    mat2 m = mat2(c, -s, s, c);
    return m * v;
}

float checker(in float u, in float v, in float checksPerUnit)
{
  float fmodResult = mod(floor(checksPerUnit * u) + floor(checksPerUnit * v), 2.0);
  float col = max(sign(fmodResult), 0.0);
  return col;
}

void main(void)
{
    //uv is pixel coordinates between -1 and +1 in the X and Y axiis with aspect ratio correction
    vec2 uv = (2.0*gl_FragCoord.xy-resolution.xy)/resolution.y;

    // sineVal is a floating point value between 0 and 1
    // starts at 0 when time = 0 then increases to 1.0 when time is half of animationSeconds and then back to 0 when time equals animationSeconds
    float sineVal = sin(piTimes2*(time-0.75)/animationSeconds)/2.0+0.5; 

    //rotate the uv coordinates between 0 and 180 degrees during the animationSeconds time length
    vec2 rotated_uv = rotate(uv,time/animationSeconds*180);

    //get the pixel checker color by passing the rotated coordinate into the checker function
    vec4 color = vec4(checker(rotated_uv.x, rotated_uv.y, 5.0 * sineVal),checker(rotated_uv.x, rotated_uv.y, 3.0 * sineVal),checker(rotated_uv.x, rotated_uv.y, 4.0 * sineVal),1.0);

    gl_FragColor = color;
}

The checker function is changed to return a single float value. 0.0 for black or 1.0 for white.

Color is now calculated by each of the RGB components being a different sized checkerboard.

Animated GIF Loop 008

Animated GIF Loop 008

Multiple RGB Checkerboards Tweaked

Same as previous, but now the red checkerboard does not rotate while the green and blue checkerboards rotate in opposite directions.


#version 120

uniform float time;
uniform vec2 resolution;

float animationSeconds = 2.0; // how long do we want the animation to last before looping
float piTimes2 = 3.1415926536*2.0;

vec2 rotate(vec2 v, float a) {
    float angleInRadians = radians(a);
    float s = sin(angleInRadians);
    float c = cos(angleInRadians);
    mat2 m = mat2(c, -s, s, c);
    return m * v;
}

float checker(in float u, in float v, in float checksPerUnit)
{
  float fmodResult = mod(floor(checksPerUnit * u) + floor(checksPerUnit * v), 2.0);
  float col = max(sign(fmodResult), 0.0);
  return col;
}

void main(void)
{
    //uv is pixel coordinates between -1 and +1 in the X and Y axiis with aspect ratio correction
    vec2 uv = (2.0*gl_FragCoord.xy-resolution.xy)/resolution.y;

    // sineVal is a floating point value between 0 and 1
    // starts at 0 when time = 0 then increases to 1.0 when time is half of animationSeconds and then back to 0 when time equals animationSeconds
    float sineVal = sin(piTimes2*(time-0.75)/animationSeconds)/2.0+0.5; 

    //rotate the uv coordinates between 0 and 180 degrees during the animationSeconds time length
    vec2 rotated_uv = rotate(uv,time/animationSeconds*180);
    vec2 rotated_uv2 = rotate(uv,-time/animationSeconds*180);

    //get the pixel checker color by passing the rotated coordinate into the checker function
    vec4 color = vec4(checker(uv.x, uv.y, 5.0 * sineVal),checker(rotated_uv.x, rotated_uv.y, 3.0 * sineVal),checker(rotated_uv2.x, rotated_uv2.y, 4.0 * sineVal),1.0);

    gl_FragColor = color;
}

The checker function is changed to return a single float value. 0.0 for black or 1.0 for white.

Animated GIF Loop 009

Animated GIF Loop 009

Multiple RGB Checkerboards Within Checkerboards

Same as previous, but now the “white” checkbaord squares are further divided into smaller checkboards.


#version 120

uniform float time;
uniform vec2 resolution;

float animationSeconds = 2.0; // how long do we want the animation to last before looping
float piTimes2 = 3.1415926536*2.0;

vec2 rotate(vec2 v, float a) {
    float angleInRadians = radians(a);
    float s = sin(angleInRadians);
    float c = cos(angleInRadians);
    mat2 m = mat2(c, -s, s, c);
    return m * v;
}

float checker(in float u, in float v, in float checksPerUnit)
{
  float fmodResult = mod(floor(checksPerUnit * u) + floor(checksPerUnit * v), 2.0);
  if (fmodResult > 0.0) { fmodResult = mod(floor(checksPerUnit * u * 4) + floor(checksPerUnit * v * 4), 2.0); }
  float col = max(sign(fmodResult), 0.0);
  return col;
}

void main(void)
{
    //uv is pixel coordinates between -1 and +1 in the X and Y axiis with aspect ratio correction
    vec2 uv = (2.0*gl_FragCoord.xy-resolution.xy)/resolution.y;
	
    // sineVal is a floating point value between 0 and 1
    // starts at 0 when time = 0 then increases to 1.0 when time is half of animationSeconds and then back to 0 when time equals animationSeconds
    float sineVal = sin(piTimes2*(time-0.75)/animationSeconds)/2.0+0.5; 

    //rotate the uv coordinates between 0 and 180 degrees during the animationSeconds time length
	uv.x += sineVal*2.0-1.0;
    vec2 rotated_uv = rotate(uv,time/animationSeconds*180);
    vec2 rotated_uv2 = rotate(uv,-time/animationSeconds*180);

    //get the pixel checker color by passing the rotated coordinate into the checker function
    vec4 color = vec4(checker(uv.x, uv.y, 5.0 * sineVal),checker(rotated_uv.x, rotated_uv.y, 4.0 * sineVal),checker(rotated_uv2.x, rotated_uv2.y, 6.0 * sineVal),1.0);

    gl_FragColor = color;
}

Animated GIF Loop 010

Animated GIF Loop 010

How To Generate These GIFs Using Visions of Chaos

1. Start Visions of Chaos.

2. Select Mode->OpenGL Shading Language->Shader Editor

3. You can load any of the above samples “Animated GIF Loop” or click the New button and start creating your own.

4. Once you have a loop you like, check the “Create movie frames” checkbox.

5. Click OK.

6. Once the “Create Frame Settings” dialog appears, change the stop after to 120.

7. Click OK to start running the shader and generating the frames. It will auto-stop after 120 frames.

8. The Build Movie Settings dialog will be showing.

9. Change the Movie Format dropdown to Animated GIF and then click Build to generate the GIF file.

10. Post your awesome GIF loop on Twitter etc to be the envy of your fellow nerds.


The Future

This post has only covered the very basics of 2D GLSL shaders for looped animation purposes. I would like to cover 3D in the future.

If this helped you get some nice animated GIF loops going let me know.

For inspiration, check out the most awesome @beesandbombs Twitter channel. Dave has loads of seamlessly looping GIF animations that show what real talent can produce.

Jason.