are you ready for a mindfuck ? are you ready for a mindfuck?

....::::Menu::::....
...::About::...
...::Articles::...
...::Contact::...
...::Home & News::...
...::Links & Credits::... OpenGL logo
Valid XHTML 1.0!

fractal

by Elie De Brauwer

Goal of this file

Everybody has seen fractals somewhere and now we are going to make them. Altough it might seems complicated at first they end up being rather simnple to create. They don't require much higher math and the math in the stair article is more difficult than this one. You only need to now what complex numbers are and how the field of complex numbers is created. This article does not offer a complete introduction to complex numbers, so for easy reading I suggest that you at least know what a complex number is and how you multiply these.

History of this file

  • 21 december 2003: created
  • 3 february 2003: continued and finished

What are fractals ?

First take a look at wikipedia, this gives you a better explanation about fractals than any explanataion that I'm able to give you. In this article I'll provide you with an example application that is able to generate at least two fractals. These are called

The links to wikipedia offer a general discription about the math behind fractals. But in short here is a summary:
First we define a sequence: zn+1=zn2+c, in this sequence z is a complex number (thus having a real and a imaginairy part, looking like a+bj where j*j=-1 or in computer terms just call it (a,b)).
For each pixel on your screen you define a starting complex number (z0) whose parts are the pixel (or a transformation of the pixel) you want to draw. No you develop this series. When n goes to infinity zn can go to zero or it can go to infinity. Now the number of iterations we can do before zk (where k is between 0 and infinity) results in zero (as an optimization in our algorithm we define zero as a circle with midpoint the origin and with radius 2) defines the color of the current pixel.
Now the easy way to define the difference between a Mandelbrot set and a Julia set. If you define c as z0 (different for each point) you obtain a mandelbrot set, if you 'pick' a c as a complex number which is the same for each iteration you obtain a Julia set.

Now that you know

First a little note on speed. Suppose you have a window with a size of 500 by 500 pixels and you use 256 iterations to determine the color, this means that for one frame the inner loop does 64 million cycles. Keep this in mind before calling it 'slow'.
Let's get our hands dirty. Below you can see the code, this code can also be downloaded here

#include <GL/glut.h>
#include <iostream>
#include <cstdio>
using namespace std;

typedef unsigned char uchar;

void disp(void);
void reshape(int x,int y);
void mouse(int button,int state, int x, int y);
void keyb(uchar key,int x,int y);

static int winx;
static int winy;
static double xbegin = -1.8;
static double xend=1.1;
static double ybegin = -1.2;
static double yend=1.2;

Above you can see the definition some global variables and the function prototypes. These variables are used to store the window dimensions and our place in the complex plane. The will be modified by the reshape and the mouse callback.


int main(int argc, char **argv){

  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
  glutCreateWindow("Fractal");
  glutInitWindowSize(600,600);
  // clear the background to white so we can see any holes
  glClearColor(1.0,1.0,1.0,0.0);
  glutDisplayFunc(disp);
  glutReshapeFunc(reshape);
  glutMouseFunc(mouse);
  glutKeyboardFunc(keyb);
  glutMainLoop();
}

Yet another main function, with yet another double buffered window, the only difference is that we use white as the color to clear the window instead of black. This is because we are going to draw each pixel and 'black holes' are difficult to spot on a black background.

void disp(void){
  glClear(GL_COLOR_BUFFER_BIT);

  // begin definition
  glBegin(GL_POINTS);

  // for each pixel on the screen
  for(float i=0;i<winx;i+=0.999){
    for(float j=0;j<winy;j+=0.999){

      // define z_0
      double zIm = j*(yend-ybegin)/(winy*1.0)+ybegin;
      double zRe = i*(xend-xbegin)/(winx*1.0)+xbegin;

      // define c
      // alter this sequence to create a Julia set
      // e.g
      //       double cIm = 0.5;
      //       double cRe = 0.5;

      double cIm = zIm;
      double cRe = zRe;

      // res is the iteration counter
      double temp;
      int res = 0;

      // we take 255 iteration at max, resulting in 255 distinct
      // blue's
      while(res<255 && (zIm*zIm + zRe*zRe)<=4.0 ){
	// z^2= (a+bj)*(a+bj)= a^2 - b^2 + j*(2*a*b)
	//	temp = zRe*zRe - zIm *zIm + i*(xend-xbegin)/(winx*1.0)+xbegin;
	//	zIm =  2*zRe * zIm + j*(yend-ybegin)/(winy*1.0)+ybegin;
	temp = zRe*zRe - zIm *zIm + cRe;
	zIm =  2*zRe * zIm + cIm;
	zRe = temp;
	res++;
      }

      // define the color which is the number of iterations
      // here we link the number of iteration to the blue byte
      glColor3f(0,0,(res*1.0)/255.0);
      glVertex2f(i,j);
    }
  }
  glEnd();

  glutSwapBuffers();
}

The display function, all the magic happens here and I think I have some explaining to do now. First we clear the window, we say OpenGL that we want to draw some point (a whole lot of points). So basicly the code looks like:

 
glBegin(GL_POINTS);
   for(all pixels on the screen){
      determine the color 
      glColor3f(the color)
      glVertex2f(the current pixel)
   }
glEnd();

Easy isn't it ? The only odd thing you might notice is that in my for loop is use 0.999 as in increment. That is because when using integers or floats and 1 as an increment there tend to exist white lines (see the white background remarkt above), possible because some type mismatch or wrong roundoff but this fixes the problem (at least I haven't seen anything while with 0.999 as in incremnt).
Well here comes the difficult part, that is determining what the current color. The first step we take is looking at the current pixel and projecting it into the complex plane (which is the same as defining z0). Next we define c as z0 (the code in this form results in a Mandelbrot set). If we would like to see a Julia set we'd set cIm and cRe to a predefined value (more information on this follows).
Another note is that I have not defined a seperate class for storing complex numbers this is because for this program we can easily store them in 2 integers (cIm is the imaginairy part while cRe is the real part), and we only have to do twee things with them.
Now at this point everything is initialised and we can start iterating. (Another note altough every definition of a fractal talks about recursion, there is no recursion in this algorithm this is mainly because it is easy replaced by this while and it will also be more performant). The condidition in the while loop limits the number of iterations to 256 and the while stops when distance between the current point and the origin is less than two. At first sight you could say that I forgot a square root but I squared the 2 and put a 4 instead so the algorithm shouln't take the square root anymore (which make the program a bit faster for each pixel and there are a lot of pixels). If the squaring of the zn doesn't seem logical this is probably because you are missing some complex number knowledge and I suggest you ask some things to our good friend Google.
Don't forget that the blue byte we have calculated should be betweeon 0 and 1! Offcourse you can add some changes, invert the color scheme, change the colors... you are only limited by your own creativity.

void keyb(uchar key, int x, int y){
  if(key=='Q'||key=='q'){
    exit(0);
  }else if(key=='r'){
    xbegin = -1.8;
    xend=1.1;
    ybegin = -1.2;
    yend=1.2;
    reshape(winx,winy);
  }

}

We implement a 'reset' functionality and a quit functionality. The reset resets the initial boundaries (see initialisation).
void mouse(int button,int state,int x,int y){

  if(state == GLUT_DOWN){
    // remind the Y axis is point down and the X axis is pointing to the right
    y = winy - y;
    double dx = (xend-xbegin);
    double dy = (yend-ybegin);
    if(dx != 0 && dy != 0){
      if(button == GLUT_LEFT){
	xend = x * dx/winx +xbegin  + dx/10;
	xbegin = x * dx/winx +xbegin  - dx/10;
	
	yend = y * dy/winy +ybegin  + dy/10;
	ybegin = y * dy/winy +ybegin  - dy/10;
      }else{
	xend = x * dx/winx +xbegin  + 5*dx;
	xbegin = x * dx/winx +xbegin  - 5*dx;
	
	yend = y * dy/winy +ybegin  + 5*dy;
	ybegin = y * dy/winy +ybegin  - 5*dy;
	
      }
      glutPostRedisplay();
    }else{
      cout << "Underflow detected, zooming aborted" << endl; 
      cout << "Press 'r' to reset" << endl;
    }
  }
}

When you click you can zoom in or zoom out depending on the mouse button you pressed. Zooming is nothing more than changing the boundaries, but fractals are so fun and you can keep zooming so we should consider possible overflow.
Below you can see yet another reshape function, nothing special except that we store the window size for further usage.
void reshape(int x,int y){
  winx =x;
  winy =y;
  glViewport(0,0,x,y);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluOrtho2D(0,x,0,y);
  glMatrixMode(GL_MODELVIEW);
  glutPostRedisplay();
}
And again, the code can be found here.

And now, some eyecandy

snapshot1 The default window with a Mandelbrot set
snapshot2 Going in
snapshot3Zoom with an alternate color scheme, the function
      glColor3f(1-(float)(res&0xf0)/(float)0xf0,
		1-(float)(res&0x0f)/(float)0x0f,
		1-(float)(res&0xaa)/(float)0xaa);
was used to define this color scheme.
snapshot4The default default with this alternate color scheme
snapshot5A Julia set with c=-0.824-0.1711*j
snapshot6The same Julia set as above but more zoomed into it
snapshot7Another Julia set with c= -0.0519 +0.688*j
snapshot8Another Julia set with c= -0.577 +0.478*j with some zooming around the center
snapshot9When you are viewing Julia sets, it's better to alter your viewport to the origin is in the middle of your screen. So
static double xbegin = -1.8;
static double xend=1.8;
static double ybegin = -1.8;
static double yend=1.8;
should give a more beautiful and centered first view. This Julia set has c=-1
snapshot10Another Julia set with c= 0.325 +0.417*j with the normal colors
snapshot11A Julia set with c= 0.325 +0.417*j with the normal colors and some zooming
snapshot12A Julia set with c= 0.325 +0.417*j with a black and white scheme:
  glColor3f(1-res/255,1-res/255,1-res/255);
and some zooming. Fractals are one of the few things that acutally look nice in black and white.

In the end

If you've made your way thru this article you most probably have a fascination for these fractals. If you want to know more, find other c values, ask google and let google surprise you.