Authors: John Zou, Justin Derwee-Church, Annie Wang
Find our source code here
What is the problem?
The Julia Set is the set obtained from repeatedly updating a variable of a complex number function using another complex number function. The (Julia) set of points returned from this process on such functions includes those that do not approach infinity and tend to create beautiful and vastly different fractals even when only a small change to the input values is applied. We hope to be able to generate images of fractals from a Julia set that the user can control and manipulate.
What is the something extra?
As we have not done any image generation in Haskell, it will be an interesting learning experience to figure out how to generate raw images from our computed Julia Set. Additionally, we would like to make the program interactive for the user in order to provide inputs for the generation of the set, therefore we will utilize IO methods we learned in class.
Since Julia Set is fractal related to the Mandelbrot set which has many interesting properties including Local connectivity and Self-similarity. For the extra feature, we want to visualize one of the interesting properties like Self-similarity. To show that a self-similar object is exactly or approximately similar to a part of itself.
What did we learn from doing this?
Haskell was suitable for the task and we were able to create 2 visualizers utilizing different methods:
Image Generation (CPU)
First we were able to generate images utilizing pure haskell after learning how to write PNG images to a file! We also added functionality for the user to specify the size of the image and the number of iterations for the Julia Set's recurring function.
To generate the image, we took every pixel and iterated the Julia Set function n times where n is the user's choice. By the definition of the Julia Set, if the magnitude of the complex number that comes out after n iterations was less than 2 then we mark it as in the set and color it accordingly.
This was a great learning experience and we learned quite a few new haskell packages/functions such as Data.Complex to represent complex numbers and Graphics.Image in order to write pixels to a png file.
This worked well for generating single images but was slow due to having to perform n complex number calculations for every pixel. For an image of 1000x1000 and 20 iterations, it took around 5 seconds to finish generating the image due to only having 1 thread on the CPU to use.
Because our image generator worked well but was slow due to a high number of calculations required, John suggested that we find an OpenGL library to utilize the computation power of the GPU and possibly be able to animate the set on the screen. Although the library didn't have much documentation, we were able to find tutorials and demos of it. We quickly realized that the Julia Set logic for our animation wouldn't actually be in Haskell in this case as we needed the GPU to do this heavy lifting (shading the image) and this requires a shader that is written in glsl (OpenGL shading language). However, there would still be Haskell to write as we needed to open the window, orchestrate the animation, and provide functionality.
Our Haskell code for this example consists mostly of setting up the window, loading the shader source code into our program, and buffering the pixels into the shader. We also added functionality that listens for a keypress and closes the window if the user presses escape.
All of the Haskell code written for this uses functions of the Graphics.Rendering.OpenGL package and the Graphics.UI.GLFW package. We learned quite a bit about the pipeline used in OpenGL as well as how shaders automatically parallelize (separate) the work for each pixel.
For simplicity and since it wasn't in Haskell anyways, we used one of the Mandelbrot shaders provided in the tutorial and modified it slightly for our coloring.