Mandelbrot Set through shaders in GLSL with LOVE2d

2019-06-04 18:32发布

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));
    }

1条回答
我欲成王,谁敢阻挡
2楼-- · 2019-06-04 19:25

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

查看更多
登录 后发表回答