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 to start the shader running.

6. Watch the status bar and stop the shader once it is beyond the desired frames. For example if you have the loop lasting 2 seconds, then stop after 120 frames.

7. Go into the frame file directory and delete the excess frames, ie from 121 onwards.

8. Back in Visions of Chaos, 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.

Custom Formula Editor for Visions of Chaos

One of the features I have wanted to implement since the earliest versions of Visions of Chaos has been a formula editor and compiler so users can experiment with their own fractal formulas. This has also been requested many times from various users over the years. Now it is finally possible.

Rather than write my own formula parser and compiler I am using the OpenGL Shading Language. GLSL gvies faster results than any compiler I could code by hand and has an existing well documented syntax. The editor is a full color syntax editor with error highlighting.

Visions of Chaos Formula Compiler

As long as your graphics card GPU and drivers support OpenGL v4 and above you are good to go. Make sure that you have your video card drivers up to date. Old drivers can lead to poor performance and/or lack of support for new OpenGL features. In the past I have had outdated drivers produce corrupted display outputs and even hang the PC running GLSL shader code. Always make sure your video drivers are up to date.

For an HD image (1920×1080 resolution) a Mandelbrot fractal zoomed in at 19,000,000,000 x magnification took 36 seconds on CPU (Intel i7-4770) vs 6 seconds on GPU (GTX 750 Ti). A 4K Mandelbrot image (3840×2160 resolution) at 12,000,000,000 x magnification took 2 minutes and 3 seconds on CPU (Intel i7-6800) and 2.5 seconds on GPU (GTX 1080). The ability to quickly render 8K res images for dual monitor 4K displays in seconds is really nice. Zooming into a Mandelbrot with minimal delays for image redraws really increases the fluidity of exploration.

So far I have included the following fractal formulas with the new Visions of Chaos. All of these sample images can be clicked to open full 4K resolution images.

Buffalo Fractal Power 2

Buffalo Fractal

Buffalo Fractal Power 3

Buffalo Fractal

Buffalo Fractal Power 4

Buffalo Fractal

Buffalo Fractal Power 5

Buffalo Fractal

Burning Ship Fractal Power 2

Burning Ship Fractal

Burning Ship Fractal

Burning Ship Fractal Power 3

Burning Ship Fractal

Burning Ship Fractal

Burning Ship Fractal Power 4

Burning Ship Fractal

Burning Ship Fractal

Burning Ship Fractal Power 5

Burning Ship Fractal

Burning Ship Fractal

Celtic Buffalo Fractal Power 4 Mandelbar

Buffalo Fractal

Celtic Buffalo Fractal Power 5 Mandelbar

Buffalo Fractal

Celtic Burning Ship Fractal Power 4

Burning Ship Fractal

Celtic Mandelbar Fractal Power 2

Celtic Mandelbar Fractal

Celtic Mandelbrot Fractal Power 2

Celtic Mandelbrot Fractal

Celtic Heart Mandelbrot Fractal Power 2

Celtic Heart Mandelbrot Fractal

Heart Mandelbrot Fractal Power 2

Heart Mandelbrot Fractal

Lyapunov Fractals

Lyapunov Fractal

Lyapunov Fractal

Lyapunov Fractal

Lyapunov Fractal

Magnetic Pendulum

Magnetic Pendulum

Mandelbar (Tricorn) Fractal Power 2

Mandelbar Fractal

Mandelbar Fractal Power 3

Mandelbar Fractal

Mandelbar Fractal Power 3 Diagonal

Mandelbar Fractal

Mandelbar Fractal Power 4

Mandelbar Fractal

Mandelbar Fractal Power 5 Horizontal

Mandelbar Fractal

Mandelbar Fractal Power 5 Vertical

Mandelbar Fractal

Mandelbrot Fractal Power 2

Mandelbrot Fractal

Mandelbrot Fractal

Mandelbrot Fractal Power 3

Mandelbrot Fractal

Mandelbrot Fractal

Mandelbrot Fractal Power 4

Mandelbrot Fractal

Mandelbrot Fractal

Mandelbrot Fractal Power 5

Mandelbrot Fractal

Mandelbrot Fractal

Partial Buffalo Fractal Power 3 Imaginary

Partial Buffalo Fractal

Partial Buffalo Fractal Power 3 Real Celtic

Partial Buffalo Fractal

Partial Buffalo Fractal Power 4 Imaginary

Partial Buffalo Fractal

Partial Burning Ship Fractal Power 3 Imageinary

Partial Burning Ship Fractal

Partial Burning Ship Fractal Power 3 Real

Partial Burning Ship Fractal

Partial Burning Ship Fractal Power 4 Imageinary

Partial Burning Ship Fractal

Partial Burning Ship Fractal Power 4 Real

Partial Burning Ship Fractal

Partial Burning Ship Fractal Power 5

Partial Burning Ship Fractal

Partial Burning Ship Fractal Power 5 Mandelbar

Partial Burning Ship Fractal

Partial Celtic Buffalo Fractal Power 4 Real

Partial Celtic Buffalo Fractal

Partial Celtic Buffalo Fractal Power 5

Partial Celtic Buffalo Fractal

Partial Celtic Burning Ship Fractal Power 4 Imaginary

Partial Celtic Burning Ship Fractal

Partial Celtic Burning Ship Fractal Power 4 Real

Partial Celtic Burning Ship Fractal

Partial Celtic Burning Ship Fractal Power 4 Real Mandelbar

Partial Celtic Burning Ship Fractal

Perpendicular Buffalo Fractal Power 2

Perpendicular Buffalo Fractal

Perpendicular Burning Ship Fractal Power 2

Perpendicular Burning Ship Fractal

Perpendicular Celtic Mandelbar Fractal Power 2

Perpendicular Celtic Mandelbar Fractal

Perpendicular Mandelbrot Fractal Power 2

Perpendicular Mandelbrot Fractal

Quasi Burning Ship Fractal Power 3

Quasi Burning Ship Fractal

Quasi Burning Ship Fractal Power 5 Hybrid

Quasi Burning Ship Fractal

Quasi Celtic Heart Mandelbrot Fractal Power 4 Real

Quasi Celtic Heart Mandelbrot Fractal

Quasi Celtic Heart Mandelbrot Fractal Power 4 False

Quasi Celtic Heart Mandelbrot Fractal

Quasi Celtic Perpendicular Mandelbrot Fractal Power 4 False

Quasi Celtic Perpendicular Mandelbrot Fractal

Quasi Celtic Perpendicular Mandelbrot Fractal Power 4 Real

Quasi Celtic Heart Mandelbrot Fractal

Quasi Heart Mandelbrot Fractal Power 3

Quasi Heart Mandelbrot Fractal

Quasi Heart Mandelbrot Fractal Power 4 Real

Quasi Heart Mandelbrot Fractal

Quasi Heart Mandelbrot Fractal Power 4 False

Quasi Heart Mandelbrot Fractal

Quasi Heart Mandelbrot Fractal Power 5

Quasi Heart Mandelbrot Fractal

Quasi Perpendicular Burning Ship Fractal Power 3

Quasi Perpendicular Burning Ship Fractal

Quasi Perpendicular Burning Ship Fractal Power 4 Real

Quasi Perpendicular Burning Ship Fractal

Quasi Perpendicular Celtic Heart Mandelbrot Fractal Power 4 Imaginary

Quasi Perpendicular Celtic Heart Mandelbrot Fractal

Quasi Perpendicular Heart Mandelbrot Fractal Power 4 Imaginary

Quasi Perpendicular Mandelbrot Fractal

Quasi Perpendicular Mandelbrot Fractal Power 4 False

Quasi Perpendicular Mandelbrot Fractal

Quasi Perpendicular Mandelbrot Fractal Power 5

Quasi Perpendicular Mandelbrot Fractal

A lot of the above formulas came from these summaries stardust4ever created.

Fractal Formulas

Fractal Formulas

Fractal Formulas

Fractal Formulas

All of these new custom fractals are fully zoomable to the limit of double precision floating point variables (around 1,000,000,000,000 magnification in Visions of Chaos). The formula compiler is fully supported for movie scripts so you can make your own movies zooming into these new fractals.

This next sample movie is 4K resolution at 60 fps. Each pixel was supersampled as the average of 4 subpixels. This took only a few hours to render. The resulting Xvid AVI was 30 GB before uploading to YouTube.

So, if you have been waiting to program your own fractal formulas in Visions of Chaos you now can. I look forward to seeing the custom fractal formulas Visions of Chaos users create. If you do not understand the OpenGL shading language you can still have fun with all the default sample fractal formulas above without doing any coding.

Jason.

GLSL support added to Visions Of Chaos

What is GLSL?

The OpenGL Shading Language (or GLSL) allows you to write programs that run on the GPU rather than the CPU. GPUs these days can have thousands of “cores” so code running on the GPU can be magnitudes faster than running on the CPU. Fractal images are ideal for GLSL because in most fractals each pixel can be calculated independantly of the others so it is ideal for running in parallel.

For example the following pic is a raytraced example of five touching reflective spheres. The CPU version of this code took minutes to render. The GPU shader code takes 65 milliseconds on a not so super Nvidia card.

GLSL Wada Basins

GLSL in Visions Of Chaos

After having been on my to do list for years, I have finally gotten around to adding GLSL support into Visions Of Chaos.

Mandelbulb

The main delay in releasing this new version was converting all the Mandelbulb mode related functions into the GLSL language. Not too difficult, but very tedious. The speed increase for the Mandelbulb mode is amazing.

The output quality is mostly identical to the non-GLSL software mode rendering, so all your existing sample Mandelbulb files will usually continue to load and display as normal. GLSL supports single precision floating point numbers so deep zooms into the bulb will be less defined, but for the majority of renders you won’t notice a difference.

For example, here is a relatively deep zoom into a Mandelbulb using the CPU double precision

Mandelbulb

and here is the same with single point GPU/GLSL single precision

Mandelbulb

Single precision GLSL has the approximate floating point limit/resolution of 0.000001 which Visions Of Chaos now clamps the epsilon value to if it ever gets beyond it and you are using the GLSL calculations. No doubt NVidia and ATI will get the double precision working in future models, but for the time being if you want to do deep zooms into Mandelbulbs and related fractals you will hit the precision wall.

Mandelbox

I will be converting more of the slower modes over to GLSL in the future. Mandelbulbs had to be first as that was the most complex and hence slowest mode in Visions Of Chaos.

Mandelbulb

These are some speed increase results after testing the new Mandelbulb shader code on some different PCs (ranked from worse to best);

NVidia Geforce 9500 GT – 7 to 17 times faster.

NVidia Geforce 8500 GT – 15 to 25 times faster.

NVidia Geforce 8800 GT – 102 to 154 times faster.

NVidia Geforce GTX 570 – 140 to 230 times faster.

Make sure you have the latest drivers for your video card. Updating to the latest version can help improve perfomance.

Mandelbulb

I also included a bunch of sample shaders from the GLSL Sandbox to show off what these rather simple shaders can do on a decent (or even not so decent) graphics card.

Mandelbulb

On a side note I have the above image printed out and stuck on the wall at work in my office as it is one of my more favourite and iconic Mandelbulb images. It freaked this one guy out. “Doesn’t that give you the creeps that picture?!”. I tried to explain what it actually was but all he could relate it to was Alien. “Well it is actually based on a relatively simple mathematical formula that makes all those complex self similar patterns”. His eyes glazed over before I dared mention complex numbers and their three dimensional triplex variants. “Nah man, too bizarre for me!”.

Jason.

Wada Basins Part 2

I have been interested in Wada Basins for some time now. Wada Basins are the fractal like patterns that occur between touching reflective spheres. I finally got around to start adding support for GLSL into Visions Of Chaos. GLSL is the OpenGL Shading Language that allows you to use your graphics card processor (GPU) to do calculations that are magnitudes times faster than your CPU will ever be able to accomplish.

Here is a sample zoom sequence into a wada basin rendered using path tracing. Using the path tracing approach leads to global illumination with colors bleeding into nearby surfaces and soft shadows. If I was to even attempt to render these sort of images on the CPU alone this post would be months away, but harnessing the power of the GPU allowed these snapshots to be done in relatively no time at all.

Wada Basins

Wada Basins

Wada Basins

Wada Basins

Wada Basins

Wada Basins

The new version of Visions Of Chaos supporting GLSL will be out soon “when it’s ready”. I have meant to release it for a while now, but I keep adding new features and making changes.

Jason.