Quaternion Julia Set Fractal
1. Julia Set
In CS 7490, Prof. Turk showed us some nice pictures of ray traced fractals, which pretty amazed me, so I decided to implement one myself. In this independent project, I developed a program to visualize 2D Julia Set as well as Quaternion Julia Set.
The Julia set is associated with those points z = x + iy on the complex plane for which the series Z(n+1) = Zn^2 + c does not tend to infinity. c is a complex constant, where one gets different Julia sets by setting different c's [1]. In this definition, points in the Julia set are complex numbers which can be directly plotted on the screen by using the real-axis as x-axis and imaginary-axis as y-axis.
If we generalize the definition of Julia Set from complex numbers to quaternions, we can have a set of 4D points that characterize a Quaternion Julia Set. Since we can't visualize 4D space, we can only take a slice of the space and display a 3D object instead.
The Julia set is associated with those points z = x + iy on the complex plane for which the series Z(n+1) = Zn^2 + c does not tend to infinity. c is a complex constant, where one gets different Julia sets by setting different c's [1]. In this definition, points in the Julia set are complex numbers which can be directly plotted on the screen by using the real-axis as x-axis and imaginary-axis as y-axis.
If we generalize the definition of Julia Set from complex numbers to quaternions, we can have a set of 4D points that characterize a Quaternion Julia Set. Since we can't visualize 4D space, we can only take a slice of the space and display a 3D object instead.
2. 2D Julia Set Fractal
Implementing a 2D Julia Set Fractal is quite straight forward. There're three main steps in creating a 2D Julia Set Fractal image.
1. For each pixels in the image, convert them to a complex number. (exp. pixel[10,15] -> 10 + 15i)
2. Use an iterative function to decide whether the complex number tend to become infinity.
3. Give different color to points go to infinity and those don't.
In step 2, we need to decide whether a complex number is within the given Julia set. As we can't truly decide whether after infinite iterations a complex number tends to infinity, what is usually done is that we set a maximum iteration number and a threshold. If the magnitude of a given complex number becomes bigger than the threshold within maximum iterations, we think that point tends to infinity.
1. For each pixels in the image, convert them to a complex number. (exp. pixel[10,15] -> 10 + 15i)
2. Use an iterative function to decide whether the complex number tend to become infinity.
3. Give different color to points go to infinity and those don't.
In step 2, we need to decide whether a complex number is within the given Julia set. As we can't truly decide whether after infinite iterations a complex number tends to infinity, what is usually done is that we set a maximum iteration number and a threshold. If the magnitude of a given complex number becomes bigger than the threshold within maximum iterations, we think that point tends to infinity.
3. Quaternion Julia Set Fractal
When using quaternions as the input, the criteria for deciding an 'escaping' point is still the same. We feed the quaternion to an iterative function and see whether it 'blows' in certain iterations. But there're still things we need to do before we can display a 4D Julia set.
First, we need to define the operation used in the iterative function for the quaternion.
Quaternions, as first described by Hamilton, is an extension of the two-tuple complex numbers into four-tuple. They have the form: q=a+bi+cj+dk,
where i, j, k are all imaginary units, and they have special properties as follows:
ii=jj=kk=-1
ij=-ji=k
ki=-ik=j
jk=-kj=i
Knowing these is enough for implementing a normal Quaternion Julia Set where a function z=z^2+mu is used. And this is what I did in this project. More complex functions such as exponential or higher order of polynomial might obtain more intriguing results.
Now, we know how to decide whether a quaternion point belongs to the Julia Set. But we're still not able to draw that because: 1) the point is in 4d space. 2) Even if we take a slice and 'drag' the 4D object into the 3D space, we can't ray tracing it if we can't get the normal for each points.
For the first problem, we can set a constant value for the forth element in the quaternion and then compute for the set of points in this sub-space.
For the second problem, I used the z-buffer method described in [2]. A z-buffer, which is aligned with the image plane, is used to track the distance from the surface to the eye. So a value in the z-buffer means how long it is from the eye to the nearest surface of the fractal in this direction. Then we compute the gradient of the z-buffer and use that as an approximation of the surface normals.
As for the shading of shadow, it can be done by shooting more rays from the detected surface of the fractal and see if they collide with the fractal. In my program, I used a simple strategy that coarsely approximates the shadow using the z-buffer.
As z-buffer contains information about the distance from eye to one single pixel, we can imagine it as something like a topography of the object in this direction with bumps and ridges. So I basically rendered the shadow for this topography. It's simple, easy to implement, and very fast. Although there surely will be difference between this and a ray-traced shadow, it looks nice in the test scenes.
Below are some results of the program.
First, we need to define the operation used in the iterative function for the quaternion.
Quaternions, as first described by Hamilton, is an extension of the two-tuple complex numbers into four-tuple. They have the form: q=a+bi+cj+dk,
where i, j, k are all imaginary units, and they have special properties as follows:
ii=jj=kk=-1
ij=-ji=k
ki=-ik=j
jk=-kj=i
Knowing these is enough for implementing a normal Quaternion Julia Set where a function z=z^2+mu is used. And this is what I did in this project. More complex functions such as exponential or higher order of polynomial might obtain more intriguing results.
Now, we know how to decide whether a quaternion point belongs to the Julia Set. But we're still not able to draw that because: 1) the point is in 4d space. 2) Even if we take a slice and 'drag' the 4D object into the 3D space, we can't ray tracing it if we can't get the normal for each points.
For the first problem, we can set a constant value for the forth element in the quaternion and then compute for the set of points in this sub-space.
For the second problem, I used the z-buffer method described in [2]. A z-buffer, which is aligned with the image plane, is used to track the distance from the surface to the eye. So a value in the z-buffer means how long it is from the eye to the nearest surface of the fractal in this direction. Then we compute the gradient of the z-buffer and use that as an approximation of the surface normals.
As for the shading of shadow, it can be done by shooting more rays from the detected surface of the fractal and see if they collide with the fractal. In my program, I used a simple strategy that coarsely approximates the shadow using the z-buffer.
As z-buffer contains information about the distance from eye to one single pixel, we can imagine it as something like a topography of the object in this direction with bumps and ridges. So I basically rendered the shadow for this topography. It's simple, easy to implement, and very fast. Although there surely will be difference between this and a ray-traced shadow, it looks nice in the test scenes.
Below are some results of the program.
4. Reference
1. http://paulbourke.net/fractals/juliaset/
2. http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.101.9183&rep=rep1&type=pdf
2. http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.101.9183&rep=rep1&type=pdf