Fugio Friday: Video Shader Recorder

Happy Fugio Friday!

This week sees the first release of the Media Recorder node that allows recording video and audio files from inside Fugio.

To demonstrate its abilities, I have created a new example patch called Video Shader Recorder (find it in the FFMPEG examples, in the file menu) that allows you to load a video file, apply a custom OpenGL shader to it, and save it out to an MP4 video that’s ready to upload to YouTube.

Download Fugio 2.2.0 for Windows (7, 8, 8.1, 10)

Download Fugio 2.2.0 for macOS (OS X) (Mavericks 10.9+)


  • Added MediaRecorderNode to FFMPEG plugin
  • Added Video Shader Record.fug example
  • Added help link to pins (right/ctrl click to find it in the context menu)
  • Added PlayheadFramesNode to Time plugin


  • Added Time pin to MediaNode


  • FFMPEG Image Convert didn’t let you change the format
  • ImageToTextureNode was aligning to 32 bytes (not 32 bits!)

Fugio Friday – 28th October 2016

Happy Fugio Friday!

This week I’ve been hanging around the Wellcome Genome Campus near Cambridge, where my partner Anna has been doing a week residency with the scientists here. I’ve been exploring the nearby nature reserve, watching the squirrels and geese, and catching up with emails and threads of projects.

I’ve been looking at the way that Fugio is installed on OS X (now macOS), and I’m not entirely satisfied with the process that one has to go through, especially for the initial installation.  I’m experimenting with Homebrew casks, that facilitate installing applications such as Fugio through the same simple command line interface that Homebrew uses to install the libraries that we already use.

Packaging Fugio as a cask would mean installation would comprise of installing Homebrew, then typing “brew cask install fugio” into a Terminal to install the application and all its dependencies.  Much cleaner!  What do you think?

If I do go down this route, it will make releasing some new plugins somewhat easier…

On November 15th I’ll be giving a LASER talk (Leonardo Art Science Evening Rendevous) at Westminster University in Harrow about my art practice and Fugio.  There’s no link for it yet but I’ll post it up when there is, if you fancy coming along and saying hello!

Have a good week, and as ever, let me know any questions and suggestions you may have.

Fugio Friday – 19th August 2016

This is the most significant release for Fugio since its initial launch. Finally, after more than a year, I can share the OpenGL plugin, which brings the raw power of the graphics card to Fugio for high resolution, real-time graphics.

Download Fugio for Windows and OS X (build the source code on Linux, including Raspberry Pi)

I’ve also recorded an hour long video tutorial that will get you started working with OpenGL shaders.

This has been a massive amount of work.  Please share the word, and if you’re feeling very generous, you can always make a donation towards the project here.



  • OpenGL plugin – 24 new nodes with several shader examples
  • Mouse node and input events support for windows
  • Mouse Painter example
  • Nodes: Time, Date, JoinVector4Node, SplitVector4Node


  • LuaPainter::drawLine can take two points
  • Added $ORIGIN to path on Linux to fix launching (reported by Luca)
  • Text Editor now highlights Lua errors


  • Image Save had a hard coded path
  • Fixed mouse zooming
  • Fixed Network deployment (reported by Артем)

Fugio Friday – 5th August 2016


Download Fugio 1.5.1 for Windows, OS X (get the source code)


  • Lots of new examples including network stock checker and audio processing nodes
  • Added group breadcrumb widget to the bottom of the editor window
  • fillRect() method to qt.painter
  • PortAudio input and output now have default device options
  • Added LuaMatrix4x4
  • Save images from NumberMonitorForm by right-clicking on its window
  • Added FileLoadNode
  • Added AutoRangeNode
  • Added MinMaxNode
  • Added RingModulatorNode
  • Added ArrayListPin, VariantListPin
  • Added extra include paths for Lua


  • NetworkRequestNode saves data to a temporary file, rather than always into memory
  • Renamed NetworkRequestNode to GetNode
  • Added RegExp pin to RegExpNode
  • ScaleImageNode can scale to width/height/size
  • Cut/copy/paste now works between patches
  • You can also paste as plain text in any text editor or window


  • Lots of work on cut/copy/paste/delete functionality
  • Fixed all colour selections in editor
  • Fixed StringNode not showing loaded value
  • Fixed StringJoinNode


  • Windows 10 (x86)
  • OS X 10.11 (x64)
  • Ubuntu 16.04 (x64)

New Payment Options

6134-close-up-of-three-credit-cards-pvWhile bigfug.com has always accepted credit and debit card payments, we’re aware that using PayPal isn’t always a great solution for all our international friends.

Today we’re happy to announce that we have integrated a new payment gateway that processes Visa, Mastercard, and American Express cards directly.

As a beneficial side effect of this improvement, the shop also now supports SSL encryption, which will automatically be used at checkout.

And finally, to take advantage of the new gateway, we’ve moved Painting With Light into the shop, too.

The End Game for Software

I think we’re in the end game. I think that Learning to Code is a last ditch cry to wrestle control and we’re too late. I think that the cost of major software is spiralling to zero and, just like the music industry, the focus is now on mass adoption with revenues generated around the product, not from it. It happened on the web and it happened on your phone and in your home. They’re coming for open-source, they’re coming for us lone developers, they’re controlling the formation of culture, and they have a leash for us all. If you’re going to code then code to distrupt, code to disseminate, code with fire and bile and fury. No one may thank you but stand against the wave in the best way you can and hold fast. Coding is a manifestation of imagination and will; whose do you choose?

Fugio Keyboard Support

I’ve been away for a couple of days running a Painting With Light video mapping workshop at Bournemouth University so today I managed to do a little Fugio coding and added a keyboard node to catch any keyboard sequence such as simply pressing R or combinations like CTRL+7 and generate a trigger.

Screenshot 2015-01-21 12.45.12

In this patch pressing R generates a new random number.

Christmas Goodies

I used this Christmas as an excuse to get a few things relating to the various software tools I’m writing at the moment.

The C++ Programming Language 4th Edition by Bjarne Stroustrup replaces my well loved, dog-eared 2nd Edition that I’ve had for many years.  I haven’t felt like I’ve fully  got my head around the new language features of C++ 11 and I enjoy Bjarne’s non-nonsense description of them.  He did create C++ after all…  It’s probably not a book for the absolute beginner but it’s one that I refer to often, always picking up new tricks or refreshing some of the less used techniques.

FugioI’ve been working on my new software called Fugio (pictured above) for over a year now and I want to make it support a variety of hardware, so I got a couple of new things to try it with:

The last game controller I had was an ancient Logitech one that was quite nice until batteries kept leaking inside of it.  I upgraded to the Xbox 360 controller for Windows and wrote a node for Fugio to read all the various parameters from it.  It’s very simple to do with the Microsoft XInput API, although obviously Windows only.

While I love my original Korg nanoKontrol for MIDI control, I felt like I needed something a bit more ‘hitty’ so I plumbed for the Akai MPD18 Compact Pad Controller so I can experiment with triggering off events within Fugio.  I’ve got most of the controls mapped in using PortMidi and am just sorting out a small bug in the MIDI clock code so I’ll be able to use the note repeat controls on the MPD18 in sync with the Fugio playback.

And while not related to software development and much more related to the process of creating visuals and art, I’m very much enjoying reading Sculpting in Time: Reflections on the Cinema by the late, great film director Andrei Tarkovsky.  It’s a no-holds-barred personal rant about his views and experiences on making films and is full of inciteful comments that are giving me much food for thought.

And with that Amazon Affiliate link laiden post done, I will wish you all a very happy New Year and am looking forward to bringing you some new exciting tools in 2015.

Atmospheric Scattering Shader


Just found this rather nice Atmospheric Scattering rendering C++ code over at scratchapixel.com and thought I’d do a quick conversion to a GLSL shader as a test for the Timeline software I’m working on. Works rather nicely…


My (none optimised) fragment shader conversion is:

#version 150

#define M_PI 3.1415926535897932384626433832795

uniform float TimeOfDay; // range 0.0 -> 1.0 (0.0 = Midnight, 0.5 = Midday, etc)

const float RADIUS_EARTH = 6360e3;
const float RADIUS_ATMOSPHERE = 6420e3;
const float RAYLEIGH_SCALE_HEIGHT = 7994;
const float MIE_SCALE_HEIGHT = 1200;
const float SUN_INTENSITY = 20;

const float g = 0.76;

const vec3 betaR = vec3( 5.5e-6, 13.0e-6, 22.4e-6 );    // Rayleigh scattering coefficients at sea level
const vec3 betaM = vec3( 21e-6 );                       // Mie scattering coefficients at sea level

vec3 sunDirection = vec3( 0, 1, 0 );

const int numSamples = 16;
const int numSamplesLight = 8;

struct Ray
    vec3 o; //origin
    vec3 d; //direction (should always be normalized)

struct Sphere
    vec3 pos;   //center of sphere position
    float rad;  //radius

const Sphere SPHERE_EARTH      = Sphere( vec3( 0 ), RADIUS_EARTH );
const Sphere SPHERE_ATMOSPHERE = Sphere( vec3( 0 ), RADIUS_ATMOSPHERE );

bool intersect( in Ray ray, in Sphere sphere, out float t0, out float t1 )
    vec3 oc = ray.o - sphere.pos;
    float b = 2.0 * dot(ray.d, oc);
    float c = dot(oc, oc) - sphere.rad*sphere.rad;
    float disc = b * b - 4.0 * c;

    if (disc < 0.0)
        return false;

   float q;
    if (b < 0.0)         q = (-b - sqrt(disc))/2.0;     else         q = (-b + sqrt(disc))/2.0;       t0 = q;     t1 = c / q;     // make sure t0 is smaller than t1     if (t0 > t1) {
        // if t0 is bigger than t1 swap them around
        float temp = t0;
        t0 = t1;
        t1 = temp;

    // if t1 is less than zero, the object is in the ray's negative direction
    // and consequently the ray misses the sphere
    if (t1 < 0.0)
        return false;

    if( t0 < 0.0 )
        t0 = 0;

    return( true );

vec3 computeIncidentLight( in Ray r )
    float       t0, t1;

    if( !intersect( r, SPHERE_ATMOSPHERE, t0, t1 ) )
        return vec3( 1 );

    float segmentLength = ( t1 - t0 ) / numSamples;
    float tCurrent = t0;

    vec3 sumR = vec3( 0 );
    vec3 sumM = vec3( 0 );

    float opticalDepthR = 0;
    float opticalDepthM = 0;

    float mu = dot( r.d, sunDirection );
    float phaseR = 3 / ( 16 * M_PI ) * ( 1 + mu * mu );
    float phaseM = 3 / (  8 * M_PI ) * ( ( 1 - g * g ) * ( 1 + mu * mu ) ) / ( ( 2 + g * g ) * pow( 1 + g * g - 2 * g * mu, 1.5 ) );

    for( int i = 0; i < numSamples ; i++ )
        vec3    samplePosition = r.o + r.d * ( tCurrent + 0.5 * segmentLength );
        float   height = length( samplePosition ) - RADIUS_EARTH;

        // compute optical depth for light

        float hr = exp( -height / RAYLEIGH_SCALE_HEIGHT ) * segmentLength;
        float hm = exp( -height / MIE_SCALE_HEIGHT      ) * segmentLength;

        opticalDepthR += hr;
        opticalDepthM += hm;

        // light optical depth

        Ray lightRay = Ray( samplePosition, sunDirection );

        float lmin, lmax;

        intersect( lightRay, SPHERE_ATMOSPHERE, lmin, lmax );

        float segmentLengthLight = lmax / numSamplesLight;
        float tCurrentLight = 0;
        float opticalDepthLightR = 0;
        float opticalDepthLightM = 0;
        int j = 0;

        for( ; j < numSamplesLight ; j++ )
            vec3 samplePositionLight = lightRay.o + lightRay.d * ( tCurrentLight + 0.5 * segmentLengthLight );

            float heightLight = length( samplePositionLight ) - RADIUS_EARTH;

            if( heightLight < 0 )

            opticalDepthLightR += exp( -heightLight / RAYLEIGH_SCALE_HEIGHT ) * segmentLengthLight;
            opticalDepthLightM += exp( -heightLight / MIE_SCALE_HEIGHT      ) * segmentLengthLight;

            tCurrentLight += segmentLengthLight;

        if( j == numSamplesLight )
            vec3 tau = betaR * ( opticalDepthR + opticalDepthLightR ) + betaM * 1.1 * ( opticalDepthM + opticalDepthLightM );
            vec3 attenuation = exp( -tau );

            sumR += hr * attenuation;
            sumM += hm * attenuation;

        tCurrent += segmentLength;

    return( SUN_INTENSITY * ( sumR * phaseR * betaR + sumM * phaseM * betaM ) );

void main()
    const int width = 512;
    const int height = 512;

    float a = mod( TimeOfDay - 0.5, 1 ) * 2.0 * M_PI;

    sunDirection = normalize( vec3( 0, cos( a ), sin( a ) ) );

    float x = 2 * ( gl_FragCoord.x + 0.5 ) / ( width  - 1 ) - 1;
    float y = 2 * ( gl_FragCoord.y + 0.5 ) / ( height - 1 ) - 1;

    float z2 = x * x + y * y; 

    if( z2 <= 1 )
        float phi   = atan( y, x );
        float theta = acos( 1 - z2 );

        vec3 dir = vec3( sin( theta ) * cos( phi ), cos( theta ), sin( theta ) * sin( phi ) );
        vec3 pos = vec3( 0, RADIUS_EARTH + 1, 0 );

        gl_FragColor = vec4( computeIncidentLight( Ray( pos, normalize( dir ) ) ), 1 );
        gl_FragColor = vec4( 0 );