Week 11 Shaders Exercise: Julia Sets

Getting Started

Click here to download the repository of skeleton code for this exercise (and for assignment 5). You will be editing the file julia.frag, and you will run julia.html to view the results


A fractal is an object which is "self-similar," possibly across an infinite amount of rescaling. Roughly, what this means is if we zoom in on the shape, we will see the shape repeated. Or in other words, the shape is made up of itself.

One kind of fractal emerges when we consider the behavior of the following recursive equation

\[ z_{n+1} = z_n^2 + c \]

where zn, zn+1, and c are all complex numbers, zn is complex multiplication of zn with itself, c is some constant complex number specified in advance, and z0 is some initial condition specified in advanced. For a fixed c, we can try different initial conditions and see how long it takes them to "escape," or move beyond a certain radius, or "complex modulus" from the origin.

Your task will be to implement a real time viewer for Julia sets where you can move around and zoom in and out. You will implement a fragment shader in which every pixel is a different initial condition. You will then need to deal with the following uniforms:

  • vec2 uCenter: The center of the viewing window on the complex plane (uCenter.x + i * uCenter.y)
  • vec2 uC: The constant complex number that's added at each iteration.
  • float uScale: The scale of the viewing window
  • uEscape: The escape radius of the equation. We consider a point escaped when \[ \text{real}^2 + \text{imag}^2 > \text{uEscape}^2 \]
  • MAX_ITERS: This is not a uniform per se, but a constant defined at the top of the shader which defines how many iterations you should check before giving up and capping the escape number as MAX_ITERS.
  • vec3 uPows: A variable that tells us how to convert the escape number into a red, green, or blue channel. Let N be the number of iterations it takes a point to escape, let I be MAX_ITERS, and let uPows = (a, b, c). Then the RGB values should be \[ R = a^{-N/I}, G = b^{-N/I}, B = c^{-N/I} \]

Note that the equation for the complex number that a fragment location u_position represents should be
uScale*v_position - uCenter

Code to write: You only need to edit fractal.frag for this task. You do not need to touch the vertex shader or HTML file

Gui Tester: fractal.html

  • Complex numbers can be implemented as a vec2 type, where the x component is the real part and the y component is the imaginary part. Complex addition is then the same as vector addition, but there is a different rule for complex multiplication. In particular, if \[a = x_a + iy_a \] and \[ b = x_b + iy_b \] then \[ ab = (x_ax_b - y_ay_b) + i(x_ay_b + x_by_a) \]
  • Types are a real killer on this one. For instance, you will be making a for loop to count how many iterations it takes before a point escapes, and you will eventually write a function of this number to a color channel. Because the color channels are expecting floats, though, and because there is no explicit casting, you will need your loop iterator to be a float.

Animation of A Working Implementation