Introduction
Lately, I’ve been working with a student interested in computer graphics. Right now, we’re stuck in terminal land (i.e., the land of bullshit std::cin-based menus), so drawing anything is kind of out of the question. Nonetheless, I thought converting my Mandelbrot set program into a standard I/O-based program would be pretty easy. I was right about this and started thinking about other fractals I could render this way.
This led me back to Wikipedia to look at other escape-time fractals. In particular, I picked out the Burning Ship fractal and an animation of various Julia sets to work on. Besides standard I/O implementations (which lack animation), I also wanted to implement these in OpenGL by forking my Mandelbrot set program.
Getting these new fractals working was pretty simple.
The Problem
The Burning Ship fractal is much simpler than the animated Julia sets, so I’ll cover that first. If you recall, the
series z0 = 0
, zn = zn-12 + c
defines the
Mandelbrot set. A complex coordinate c
is in the Mandelbrot set when the z
’s magnitude does not diverge to
infinity for that complex coordinate. The Burning Ship fractal is the same. However, the series is
z0 = 0
,
zn = (|Re(zn-1)| + |Im(zn-1)| * i)2 + c
. In
GLSL, you can implement this version by simply applying the
abs
function to the z
vector. No biggie.

The Julia sets are a little harder to get working. Here, the defining series is still
zn = zn-12 + c
. However, z0
is now the varying
complex coordinate, and c
is a complex constant. Selecting the constant c
is crucial to getting any interesting
output. In my case, I’ve chosen 0.7885 * eA * i
, where A
varies between 0
and 2π
. Yes, I
did select this based on Wikipedia. I’m a programmer, so I’m not a serious engineer. Please don’t trust me to build a
bridge. You’ll die! Implementing this requires new software on the host side, so a simple shader change won’t cut it
like with the Burning Ship.

The Solution
For the Burning Ship, my solution is nearly identical to my Mandelbrot set implementation. It’s really as simple as a small shader edit changing:
for (i = 0; length(z) <= 65536 && i < max_iterations; ++i)
{
z = cx_multiply(z, z) + c;
}
to
for (i = 0; length(z) <= 65536 && i < max_iterations; ++i)
{
z = cx_multiply(abs(z), abs(z)) + c;
}
That’s really it.

There are changes in the shader and host programs for the Julia sets. On
the shader side, I first had to introduce a new uniform for the value c
. I also had to modify the initial
definition of z
. In the Mandelbrot program, z
starts as vec2(0, 0)
. However, for the Julia sets, it’s
initialized to the fragment coordinate scaled between -2
and 2
on both axes. On the host side, I’m computing and
supplying the value of c
.
I’ve introduced a few constants (A_MIN
, A_MAX
, and ANIMATION_SPEED
) to do this. A_MIN
(a/k/a 0
) and A_MAX
(a/k/a 2π
) represent the range of values to compute c
from. c
itself is computed assuming
B * eA * i = B * cos(A) + B * sin(A) * i
. Once per frame, this is computed for B = 0.7885
.
The value of A
varies in steps of 0.01
multiplied by ANIMATION_SPEED
and scaled by the frame’s delta time. It
always starts at 0
. This behavior reflects when A
reaches A_MAX
or A_MIN
(i.e.,
An = An-1 + 0.01
, A0 = 0
until
An = 2π
. After that point An = An-1 - 0.01
,
A0 = 2π
until An = 0
). The result is a smooth transition from 0
to
2π
.

Since the Julia sets are not a single fractal but an animated transition through a series of fractals, I’ve also added a pause function. This allows users to stop the animation at any point and inspect any part of a particular frame. Getting this to work was a little finicky because pausing is a boolean, whereas the other controls are continuous.
In both cases, the result is a nice rendering of the respective fractals with the same parallel computation as my Mandelbrot program.

Thoughts
I’m writing this immediately after finishing the previous Megatech-Vulkan article, so I don’t have much new on my mind. These programs mainly show how much you can get done without a solid mathematical foundation. I’m not mathematically skilled enough to explain why these programs generate the interesting images they do. On the other hand, I clearly am clever enough to make them work. I guess there’s a long tradition of this in the software world. Dijkstra is probably rolling in his grave.