Wiki
Clone wikiCore / MandelbrotShader
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:
Variable | Type | V/F | Comment |
---|---|---|---|
maxIter | int | F | The maximum number of iterations for the sequence of complex numbers. A value of 255 is recommended. |
minRe | float | F | The real component of the complex number mapped to texture co-ordinate (0.0, 0.0) . |
minIm | float | F | The imaginary component of the complex number mapped to the texture co-ordinate (0.0, 0.0) |
fRe | float | F | The range of the real component of the complex numbers mapped to the 1.0 range of the 'u' texture co-ordinate. |
fIm | float | F | The 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