Julia: an experiment with fragment shaders (Codea 1.5)

Shaders are a new feature in Codea version 1.5. The code below shows a simple application, inspired by a sample fragment shader in @tnlogy’s app, Paragraf.

The code uses the ‘–#’ convention that allows it to be pasted into a new project with the Codea tab structure preserved (by a long touch on ‘Add New Project’ and then ‘Paste Into Project’).

The depth uniform on the fragment shader allows the user to move from the display of one aspect of the Julia set to another relatively smoothly - the detail is filled in once the movement has ceased. The navigation uses two parameter.number() - another new feature in Codea version 1.5.

The vertex and fragment shaders were developed in the new Shader Lab. Here, they are stored as strings in table Julia to make it easier to share on the Forum.


--# Main
--
-- Julia
--

function setup()
    parameter.number("cx", -1, 1, 0, changing)
    parameter.number("cy", -1, 1, 2/3, changing)
    m = mesh()
    m:addRect(WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
    m.shader = shader(Julia.vertexShader, Julia.fragmentShader)
end

function draw()
    depth = math.min(64, depth + 1)
    m.shader.cx = cx
    m.shader.cy = cy
    m.shader.depth = depth
    m:draw()
end

function changing()
    depth = 8
end

--# ShaderJulia
Julia = {

vertexShader = [[
//
// Vertex shader: Julia
//

uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec2 texCoord;

varying highp vec2 vTexCoord;

void main()
{
    vTexCoord = texCoord;
    gl_Position = modelViewProjection * position;
}
]],

fragmentShader = [[
//
// Fragment shader: Julia
//

precision highp float;

uniform float cx, cy;
uniform int depth;
varying highp vec2 vTexCoord;

const float aspect = 748.0 / 768.0;
const float scale = 2.0 * 1.0 / sqrt(sqrt(100.0));

vec4 rgb(float h) {
    float r = 0.0;
    float g = 0.0;
    float b = 0.0;
    float i = mod(h, 1.0) * 6.0;
    float x = 1.0 - abs(mod(i, 2.0) - 1.0);
    if (i < 1.0) {
        r = 1.0;
        g = x;
    } else if (i < 2.0) {
        r = x; 
        g = 1.0;
    } else if (i < 3.0) {
        g = 1.0;
        b = x;
    } else if (i < 4.0) {
        g = x;
        b = 1.0;
    } else if (i < 5.0) {
        r = x;
        b = 1.0;
    } else {
        r = 1.0;
        b = x;
    }
    return vec4(r, g, b, 1.0);
}

void main() {
    vec2 z = (2.0 * vTexCoord - 1.0) * vec2(aspect, 1.0);
    vec2 c = vec2(cx, cy);
    float z2min = 100.0;
    for (int i = 0; i < depth; i++ ) {
        z = c + vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y);
        float z2 = dot(z, z);
        if (z2 > 100.0) break;
        z2min = min(z2min, z2);
    }
    gl_FragColor = rgb(1.0 - sqrt(sqrt(z2min)) * scale);
}
]]
}

Nice! Convenient with the depth parameter.