1

I'm trying to render a Mandelbrot Set using GLSL, but all I get is a full circle... I have checked the maths a lot, and I simply cannot find the error, so I thought maybe the problem was semantic.

Is anyone able to see what's wrong?

Also, could anyone give me insights on organization, structure, etc? I'm trying to learn proper coding, but it's hard to find material on styling.

Obs.: The shader can be applied over any image

The idea is simple (you may skip this):

  • checkConvergence returns true if z has not diverged (i.e., abs(z) < 4
    • sumSquare returns the complex multiplication (z.r, z.i)*(z.r, z.i) - (c.r, c.i), where K.r = Re(K) and K.i = Im(K)
    • iterate is the actual algorithm: it keeps squaring z until it diverges or it reaches a maximum number of iterations (tol)
    • clr is parameterized function which returns a RGB array which depends on n
    • finally, effect is where the magic should happen, by doing 2 things:
      • It takes the coordinates of the pixel (GLSL normalizes to the interval(0, 1) and normalizes it to the Mandelbrot set size (-2
      • It calls clr over iterate over the coordinates.
GLSLShader = love.graphics.newShader[[
    vec2 c = vec2(1.0, 0.0);
    int tol = 100;

    vec4 black = vec4(0.0, 0.0, 0.0, 1.0);
    vec4 white = vec4(1.0, 1.0, 1.0, 1.0);

    bool checkConvergence(vec2 z) {
      return (z[0]*z[0] + z[1]*z[1] < 4);
    }

    vec2 sumSquare(vec2 z) {
      return vec2(z[0]*z[0] - z[1]*z[1] - c[0], 2 * z[0] * z[1] - c[1]);
    }

    int iterate(vec2 z) {
      int n = 0;
      while (checkConvergence(z) && (n < tol)) {
        vec2 z =  sumSquare(z);
        n = n + 1;
      }
      return n;
    }

    vec4 clr(int n){
      if(n == tol){return vec4(0.0,0.0,0.0,1.0);}

      int r, g, b;
      r = int(255*n + 47*(1-n));
      g = int(180*n + 136*(1-n));
      b = int(38*n + 255*(1-n));

      if (r > 255) {r = 255;}
      else{ 
        if(r<0){r = 0;}
      }

      if (g > 255) {g = 255;}
      else{
        if(g<0){g = 0;}
      }

      if (b > 255) {b = 255;}
      else{
        if(b<0){b = 0;}
      }

      return vec4(r, g, b, 1.0);

    }

    vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords){
      vec2 z = vec2(texture_coords.x*4-2, texture_coords.y*4-2);

      return clr(iterate(z));
    }
  ]]

UPDATE

I've tried some suggestions from @WeatherVane:

  • Add C instead of subtracting it;
  • Start z as (0.0, 0.0) and pass the starting point of the iteration as C;

But all to no avail, I still get a circle. I also tried using more iterations

I tried to simplify the coding without changing much.

I added a condition to the clr function, which returns green if n is smaller than 0 or greater than 1. The strange thing is that the circle is black and the rest of the screen is white, but I don't see where this white can come from (since clr never returns white for 0 < n < 1 ).

    vec2 c = vec2(0.0, 0.0);
    int tol = 1000;

    vec4 black = vec4(0.0, 0.0, 0.0, 1.0);
    vec4 white = vec4(1.0, 1.0, 1.0, 1.0);
    vec4 green = vec4(0.0, 1.0, 0.0, 1.0);

    int iterate(vec2 z) {
      int n = 0;

      while ( (z[0]*z[0] + z[1]*z[1] < 4) && (n < tol) ) {
        vec2 z =  vec2( z[0]*z[0] - z[1]*z[1]  + c[0], 2 * z[0] * z[1] + c[1] );
        n = n + 1;
      }

      return n;
    }

    vec4 clr(int n){
        n = n / tol;

        if(n == 1){return black;}
        if(n > 1 || n < 0){return green;}

        int r, g, b;
        r = int(255*n + 47*(1-n));
        g = int(180*n + 136*(1-n));
        b = int(38*n + 255*(1-n));

        return vec4(r, g, b, 1.0);

      }

    vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords){
      vec2 z = vec2(texture_coords.x*4-2, texture_coords.y*4-2);

      return clr(iterate(z));
    }
Vitu Tomaz
  • 61
  • 1
  • 8

2 Answers2

2

The starting point of the iteration should be passed as vector c, while z start from {0.0.0.0}.

As you can find in https://en.wikipedia.org/wiki/Mandelbrot_set

a complex number c is part of the Mandelbrot set if, when starting with z = 0 and applying the iteration z = z² + c repeatedly, the absolute value of z remains bounded however large n gets.

You are using vec4 for colors but instead of using float, in your code you are calculating the RGB component with integers value. You should cast to float and normalize each component to the (0.0,1.0) range. I tried to correct your code, but I'm afraid I don't really know lua nor love2d and I wasn't able to use texture_coords, so I used screen_coords. The best I could do is this:

function love.load()
    GLSLShader = love.graphics.newShader[[

        vec4 black = vec4(0.0, 0.0, 0.0, 1.0);
        vec4 white = vec4(1.0, 1.0, 1.0, 1.0);
        int max_iter = 1024;

        vec4 clr(int n){
            if(n == max_iter){return black;}

            float m = float(n)/float(max_iter);
            float r = float(mod(n,256))/32;
            float g = float(128 - mod(n+64,127))/255;
            float b = float(127 + mod(n,64))/255;

            if (r > 1.0) {r = 1.0;}
            else{ 
                if(r<0){r = 0;}
            }

            if (g > 1.0) {g = 1.0;}
            else{
                if(g<0){g = 0;}
            }

            if (b > 1.0) {b = 1.0;}
            else{
                if(b<0){b = 0;}
            }
            return vec4(r, g, b, 1.0);
        }

        vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords){

            vec2 c = vec2((screen_coords[0]-500)/200,(screen_coords[1]-300)/200);
            vec2 z = vec2(0.0,0.0);
            vec2 zn = vec2(0.0,0.0);
            int n_iter = 0;
            while ( (z[0]*z[0] + z[1]*z[1] < 4) &&  (n_iter < max_iter) ) {
                zn[0] = z[0]*z[0] - z[1]*z[1] + c[0];
                zn[1] = 2*z[0]*z[1] + c[1];
                z[0] = zn[0];
                z[1] = zn[1];
                n_iter++;
            }
            return clr(n_iter);
        }
    ]]
end

function love.draw()

    love.graphics.setShader(GLSLShader)
    love.graphics.rectangle('fill', 0,0,800,600)
    love.graphics.setShader()
end

Which gave me this output:

enter image description here

Bob__
  • 12,361
  • 3
  • 28
  • 42
  • Isn't z the complex number corresponding to the coordinates? Wouldn't z be (x, y) = (1.2, -0.5) at that given point in space? And isn't each iteration independent? I don't understand what you meant... – Vitu Tomaz Jan 07 '16 at 17:12
  • z is the complex number corresponding to the coordinates, but it's initial value is (0,0). The starting point in complex space, say (1.2,-0.5) is passed to the function as c. See one of the links at the right or even my other answer on similar topic: http://stackoverflow.com/questions/34622474/calculate-mandelbrot-set-of-points/34623801#34623801 – Bob__ Jan 07 '16 at 17:30
  • Whether you start the iteration with (0,0) or (x,y) only makes a difference of 1 iteration, since when starting with (0,0) the function value is (x,y) after the first iteration. – Weather Vane Jan 07 '16 at 17:38
  • @WeatherVane yes. Let's say you can start from z = c, a difference of 1 iteration isn't a big deal. – Bob__ Jan 07 '16 at 18:01
  • I made an update in the post. Thanks for the help, but it didn't work :( – Vitu Tomaz Jan 07 '16 at 18:03
  • @Bob__ Tried your suggestion, only got a black screen :( – Vitu Tomaz Jan 07 '16 at 18:24
  • @VituTomaz your color is defined as a vector of float? – Bob__ Jan 07 '16 at 18:48
  • @VituTomaz: switching to screen_coords instead of using texture_coords seems to work. I don't know how to properly set them. – Bob__ Jan 07 '16 at 22:59
  • @Bob__ JESUS, IT WORKED! I tried to simply use screen_coords and cast the RGB values to float, but it didn't work, so I tried your way. Do you know why it worked and mine didn't? Thanks A LOT! – Vitu Tomaz Jan 08 '16 at 03:17
  • @VituTomaz you are welcome. About colors, casting to float is not enough, you should normalize each component to the range (0.0,1.0). I'm still wondering why texture_coords doesn't work. – Bob__ Jan 08 '16 at 08:59
  • @Bob__ I'm fairly new to SO, do you think it's the case of opening a new thread asking specifically why texture_coords doesn't work? – Vitu Tomaz Jan 08 '16 at 14:24
  • @VituTomaz you can open a new thread, of course. Just try to minimize the code as much as possible to show only the unexpected behavior. – Bob__ Jan 08 '16 at 15:15
0
while ( ... ) {
    vec2 z = ...
}

Get rid of vec2, you're declaring a new variable each iteration. Besides that, follow some of @Bob__ 's advice (ie. iterate(vec2 c)).