So our goal is to draw some fractals.
But what is a fractal:
A fractal is a mathematical set that typically displays self-similar patterns, which means it is “the same from near as from far”.
To explain that a bit more, a fractal is the shape that derives by drawing a specific set of numbers that obey a specific mathematical formula.
Fractals are known to the public because of their bizarre illustrations and their illusive and mind-bending finite infinity. Some fractal shapes are shown below.
These sets come from an iterative procedure on a specific function that keeps refeeding itself, and what is checked is wether that iteration will converge somewhere, or it will not.
As thought, the combinations that can be made following that simple principle are pretty much infinite.
Two very famous implementations of that are the two worldwide famous sets, the Mandelbrot Set and the Julia Set.
A generalized equation of the Mandelbrot set is the following:
So for each c in a set and starting of an initial value of z, we make iterations following the equation above. If the series converges, then the c that we tested belongs in the Mandelbrot Set, if it doesn’t it does not belong in the Set.
Both so in order to visualize them onto the screen, we are actually projecting them onto the complex plane. Their real part is the x coordinate and their imaginary part is the y coordinate.
The Julia Set is pretty similar with the mandelbrot one. The general formula of a Julia Set is the following:
Both P(z) and Q(z) are complex polynomials without common divisors. In most of the cases R(z) also contains a c complex constant that allows us to tweak the geometry and get different shapes.
On a Julia Set what we are doing is to test the z this time. we are calculating the R(z) and feeding it back as the z of the next iteration. If the series of those numbers converges then that z is in the set, if it doesn’t its not.
Again as said, so in order to visualize them onto the screen, we are again projecting them onto the Complex Plane.
And here is where the pixel shader comes to the play. A pixel shader is a specific code snippet that is called everytime opengl is meant to draw a pixel on the screen. The shader code is responsible for the color getting of the appropriate object texture, or the imposing of an arbitrary color on the pixel based on little or very complicated coding.
That is what makes a pixel shader that powerful. We can use them in order to apply effects on the coloring of a whole mesh. And for that reason they are massively used to create realistic water effects ( the calculation of the refraction and reflection of light on the surface of the water), edge detection and outline of objects, and many many many more effects.
Pixel Shader for Fractals
How do we apply that on fractals. As written above, what we are meant to do is to project some complex numbers on the screen. So we need to somehow index our screen, make it behave like we are drawing on a coordinate system. So this is possible by defining a simple plane that covers the whole screen, and use its texture coordinates (that are accesible from the shader) as the real and imaginary parts of the complex number we are meant to test.
My implementations are based on some code i found on the net (references can be found on the end of the article).
The first implementation has nothing to do with shaders. It is using the cpu to make the iterations in order to populate a Julia Set. As a domain for the tested z complex numbers are all the coordinates inside a given image size. For each converged iteration we add color to the processed pixel position.
In practise, with this way i tried to visualize the Julia Set that includes the mandelbrot fractal:
where c is a real number equal to:
What is great about this particular julia set is to make all the visualizations altering that μ parameter. This will create different fractals and will show us the progression from one to the other. So here is the result of the calculation of 400 frames iterating in that [0-4] space:
You can notice the mandelbrot fractal coming up, it is for parameter values > 3.
In the second implementation, we are going to use the GPU and that is going to be by using pyOpenGL and a pixel shader. So inside the drawing callback we define a plane that covers the whole window, together with its texture coordinates. During the initialization we are loading a texture palette, in order to give on our drawing some nice colors. Inside the pixel shader code, we are using the plane’s texture coordinates in order to give values to the complex numbers. The iterations happen inside the shader which is painting every pixel that has not converged and leaves a black color on the pixels where the iteration converged. In contrast with the previous way, the set here is specific, the complex c is calculated the same way everytime for every different pixel, so nothing is animated. The number of iterations actually increase the number of drawn fractals. Actually the more iterations we define, the more detailed the drawing will be.
With the use of some keyboard and mouse callbacks, we are able to increase or reduce the number of iterations that should be done on every frame and therefore view more details on the fractal.
Here is the result:
- Drawing 3D fractals
- Customize the fractal generation and try to animate it.