Wednesday, 18 April 2012

Circular gradient shader

This is something I started coding today before I realised it wasn't available in the BitmapData APIs: the gradient fill available comes in two varieties, linear and radial. But the latter is always based on the distance from the centre, never the bearing from the centre as I wanted. So I wrote a shader for it, to generate a result like this:

The shader is different from others I've written in that it has no image input, just a pixel output. The Pixel Bender Toolkit complains about this but it still works, at least as a Flash Player shader. It still has access to the coordinates of the pixel being drawn and any parameters passed in.

The full shader code is embedded in the Wonderfl example here, with the main evaluatePixel routine below:

void evaluatePixel()
        // calculate where we are relative to the center
        float2 relativePos = outCoord() - center;
        // get our bearing relative to start angle, wrap it and scale to (-2, 2)
        float dir = atan(relativePos.y, relativePos.x);
        dir = dir - startAngle;
        float dir2 = dir + TWOPI;
        dir = dir < -PI ? dir2 : dir;
        dir = dir * 4.0 / TWOPI;

        // get colours to interpolate between and how far to interpolate
        float3 colFrom, colTo;
        if (dir < 0.0) {
            if (dir < -1.0) {
                dir2 = dir + 2.0;
                colFrom = col1;
                colTo = col2;
            } else {
                dir2 = dir + 1.0;
                colFrom = col2;
                colTo = col3;
        } else {
            if (dir < 1.0) {
                dir2 = dir;
                colFrom = col3;
                colTo = col4;
            } else {
                dir2 = dir - 1.0;
                colFrom = col4;
                colTo = col1;
        // linearly interpolate
        float3 interp = mix(colFrom, colTo, float3(dir2, dir2, dir2));
        // convert to smooth interpolation through colours
        interp = colFrom + (colTo - colFrom) * smoothStep(colFrom, colTo, interp);
        dst = pixel4(interp.x, interp.y, interp.z, 1.0);

The interpolation is between four colours which are specified as parameters. To determine which two colours to use the bearing is normalised to a value between 0 and 4 and used to pick a quadrant. Obviously more or less colours could be used.

The interpolation is done in the last few lines. It first does a simple blend using the mix function, then a uses the smoothStep function to smooth the blend. If that line is commented out the interpolation still takes place but the colours look spikier:

The other interesting property of the shader is that the colours are not clamped to between 0 and 1, and if values outside that range are passed in even more colours appear as colours interpolate at different rates. E.g. the following was generated with the colours white and black but with some colour values set to +2 and -1.

The colours can't be modified in the Wonderfl example (except by recompiling the shader) but everything else can by moving and clicking the mouse. It's available here and embedded below.

No comments:

Post a Comment