Mandelbrot renderer

The Mandelbrot set is a long-lived fractal that has been rendered on computers for decades. An iterative approach to computing this fractal is:

zn+1 = zn2 + c

This iterative process is repeated either until |z| > 2 or until a maximum number of iterations is reached.

The following 35-line OCaml program uses this iterative process to render the Mandelbrot set using OpenGL, filling each pixel with a color derived from the number of iterations required at that point.

The program begins by opening the Complex module, providing definitions related to complex numbers:

open Complex

The extent of the current window (width and height) in pixels is stored in the variable dim:

let dim = ref (640, 640)

The mandelbrot function performs the iterative process to compute the fractal, returning the number of iterations that were made:

let rec mandelbrot i c z =
  if i=63 || norm2 z > 4. then i else mandelbrot (i+1) c (add (mul z z) c)

The norm2 function calculates |z|2 efficiently.

The reshape function is called by glut whenever the OpenGL window is resized, with the new width and height passed as the labelled arguments w and h, respectively. This implementation simply resizes the OpenGL viewport and initialises the projection matrix for an orthogonal projection over (-2..2, -2..2) in the Argand plane (i.e. ignoring aspect ratio):

let reshape ~w ~h =
  let w = max 1 w and h = max 1 h in
  dim := (w, h);
  GlDraw.viewport 0 0 w h;
  GlMat.mode `projection;
  GlMat.load_identity ();
  GlMat.ortho ~x:(-2., 2.) ~y:(-2., 2.) ~z:(0., 1.);
  GlMat.mode `modelview

The display function is called by glut whenever the display needs to be updated, e.g. when something obscuring our window is moved. This function loops over each of the pixels in the window, computing its x, y coordinate in the Argand plane, using the mandelbrot function to evaluate the fractal, setting the color and then specifying the vertex coordinate to fill the pixel:

let display () =
  let w, h = !dim in
  GlDraw.begins `points;
  for a = 0 to w - 1 do
    for b = 0 to h - 1 do
      let x = 4. *. float a /. float w -. 2. in
      let y = 4. *. float b /. float h -. 2. in
      let i = mandelbrot 0 {re=x; im=y} zero in
      let f i = 0.5 +. 0.5 *. cos(float i *. 0.1) in
      GlDraw.color (f i, f(i + 16), f(i + 32));
      GlDraw.vertex ~x ~y ()
    done;
  done;
  GlDraw.ends ();
  Gl.flush ()

Finally, the main part of the program performs several glut calls to create and initialise a window, specify callbacks for reshaping, redisplaying and handling key-presses before entering the glut main loop:

let () =
  let argv' = Glut.init Sys.argv in
  Glut.initWindowSize ~w:(fst !dim) ~h:(snd !dim);
  ignore (Glut.createWindow ~title:"Mandelbrot set");
  Glut.reshapeFunc ~cb:reshape;
  Glut.displayFunc ~cb:display;
  Glut.keyboardFunc ~cb:(fun ~key ~x ~y -> if key=27 then exit 0);
  Glut.mainLoop ()

This program can be compiled using:

$ ocamlopt -I +lablgl lablgl.cmxa lablglut.cmxa fractal.ml -o fractal

The program can be executed:

$ ./fractal

To display the following image:

This is a very simple Mandelbrot renderer. Nevertheless, this program can render the Mandelbrot set in under one second. The performance of this program can be improved in many different ways.