Wiki

Clone wiki

Core / MandelbrotShader

Back to Built-in Shader Packs


Mandelbrot Shader

Introduction

'Mandelbrot' is an example shader provided in Codea's 'Patterns' Shaders Pack. Applied to a rectangular mesh with appropriate texture co-ordinates, it renders a representation of part of the Mandelbrot set.

In addition to the Codea-supplied modelViewProjection, the shader uses the following 'uniform' variables:

VariableTypeV/FComment
maxIterintFThe maximum number of iterations for the sequence of complex numbers. A value of 255 is recommended.
minRefloatFThe real component of the complex number mapped to texture co-ordinate (0.0, 0.0).
minImfloatFThe imaginary component of the complex number mapped to the texture co-ordinate (0.0, 0.0)
fRefloatFThe range of the real component of the complex numbers mapped to the 1.0 range of the 'u' texture co-ordinate.
fImfloatFThe range of the imaginary component of the complex numbers mapped to the 1.0 range of the 'v' texture co-ordinate.

Vertex shader

The vertex shader simply passes the 'attribute' variable texCoord on to the accompanying fragment shader as 'varying' variable vTexCoord:

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

gl_Position is a variable that is intended for outputting the vertex position in homogenous co-ordinates (that is, as a vec4 value). All vertex shaders must write a value into that variable. (See Section 7.1 'Vertex Shader Special Variables' of the GLSL ES specification.) Here, the modelViewProjection 4x4 matrix is applied to the vertex's position. modelViewProjection is a 'uniform' mat4 variable supplied automatically by Codea when the shader is used with a mesh. It is the current model matrix * view matrix * projection matrix. position is a vec4 'attribute' variable, also supplied automatically by Codea from the mesh.

Fragment shader

The fragment shader makes use of the 'varying' vTextCoord passed from the vertex shader and the 'uniform' variables set out above, to set gl_FragColor (which outputs the colour of the fragment - see Section 7.2 'Fragment Shader Special Variables' of the GLSL ES specification).

gl_FragColor is set to a vec4 constructed from variables r, g and b and an alpha (opacity) channel set to 1.0. r, g and b are set based on the ratio (c) of the number of iterations (iter) to the maximum possible number of interations (maxIter). By multiplying c by three different prime numbers (11.0, 5.0 and 7.0) and then using the built-in mod() function (see Section 8.3 'Common Functions' of the GLSL ES specification) different ratios can be mapped to attractive bands of colour. This is the same colouring algorithm used in Codea's 'Mandelbrot' Example Project.

float c = float(iter) / float(maxIter);
float r = mod(c * 11.0, 1.0);
float g = mod(c * 5.0, 1.0);
float b = mod(c * 7.0, 1.0);
gl_FragColor = vec4(r, g, b, 1.0);

The number of iterations is determined by a while {} loop. The loop repeats until the maximum number of iterations has been completed or the modulus of the complex number is greater than or equal to 2 (equivalently, (zRe2 + zIm2) >= 4.0).

int iter = 0;
...
while ((iter <= maxIter) && ((zRe2 + zIm2) < 4.0)) {
	zIm = 2.0 * zRe * zIm + cIm;
	zRe = zRe2 - zIm2 + cRe;
	iter++;
	zRe2 = zRe * zRe;
	zIm2 = zIm * zIm;
}

During each iteration, the complex number (zRe, zIm) is squared and a constant complex number parameter (cRe, cIm) is added to it:

// (zRe + i * zIm)^2 + (cRe + i * cIm) is:
// Real part:      zRe * zRe - zIm * zIm + cRe
// Imaginary part: 2.0 * zRe * zIm + cIm
zRe2 = zRe * zRe;
zIm2 = zIm * zIm;
zIm = 2.0 * zRe * zIm + cIm;
zRe = zRe2 - zIm2 + cRe;

The constant complex parameter is determined by the texture co-ordinates (vTexCoord), scaled and translated by the values of the relevant 'uniform' variables:

float cRe = minRe + fRe * vTexCoord.x;
float cIm = minIm + fIm * vTexCoord.y;

The iteration starts at the critical point of zRe = 0.0, zIm = 0.0 and after the first iteration, the complex number is equal to the constant complex parameter:

float zRe = cRe;
float zIm = cIm;
float zRe2 = zRe * zRe;
float zIm2 = zIm * zIm;

Example of use

The code below is a simple example of the use of the shader:

-- With acknowledgements to:
-- Mandelbrot set explorer, by Dr. Phillip Alvelda

supportedOrientations(LANDSCAPE_ANY)
function setup()
    re, im, s = -0.65, 0, 1.35
    aspect = WIDTH / HEIGHT
    myMesh = mesh()
    myMesh:addRect(WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
    myMesh.shader = shader("Patterns:Mandelbrot")
    img = image(WIDTH, HEIGHT)
    render()
    print("Tap Viewer to zoom in, Replay to restart.")
end

function draw()
    sprite(img, WIDTH/2, HEIGHT/2)
end

function touched(touch)
    if touch.state ~= ENDED then return end
    re = minRe + touch.x/WIDTH * s * 2 * aspect
    im = minIm + touch.y/HEIGHT * s * 2
    s = s/3
    render()
end

function render()
    minRe = re - s * aspect
    minIm = im - s
    myMesh.shader.minRe = minRe
    myMesh.shader.minIm = minIm
    myMesh.shader.fRe = s * 2 * aspect
    myMesh.shader.fIm = s * 2
    myMesh.shader.maxIter = 255
    setContext(img)
    myMesh:draw()
    setContext()
end

Updated