3

I'm trying to make a Mandelbrot set explorer, which will shade the pixels on the screen based on its coordinate in the window. I've done this before without using shaders but its extremely slow. I can't figure out how to get the position of the fragment so that I can use the algorithm I've already developed to render the Mandelbrot set.

I'm using ljgwl 3. I've been researching all day on how to do this, and I can't find any comprehensive findings on how to get the coordinates. It seems like gl_FragCoord should work and then I could use gl_FragCoord.x and gl_FragCoord.y to get the x and y values, which is all I need for the algorithm, but my screen always ends up being all red. I'm not passing any info from the vertex shader into my fragment shader because I need to render the color of each coordinate in the Mandelbrot based on its x and y values, so the vertex positions aren't helpful (I also don't understand how to get those).

Here is my fragment shader:

    in vec4 gl_FragCoord;
    uniform vec2 viewportDimensions;
    uniform float minX;
    uniform float maxX;
    uniform float minY;
    uniform float maxY;

    vec3 hsv2rgb(vec3 c){
        vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
        vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
        return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
    }

    void main(){
        float x = gl_FragCoord.x;
        float y = gl_FragCoord.y;

        vec2 c = vec2((x* (maxX-minX) / viewportDimensions.x + minX), (y*(maxY-minY)/ viewportDimensions.y + minY));
        vec2 z = c;

        float limit = 1000;
        float iterations = 0;
        for(int i = 0; i < int(limit); i++){
            float t = 2.0 * z.x * z.y + c.y;
            z.x = z.x * z.x - z.y * z.y + c.x;
            z.y = t;

            if(z.x * z.x + z.y *z.y > 4){
                break;
            }

            iterations += 1.0;
        }
        float itRGB = iterations/limit;
        vec3 hsv = vec3(itRGB, 1.0, 1.0);
        vec3 rgb = hsv2rgb(hsv);
        gl_FragColor = vec4(rgb, 1);
    }

I thought that I could use gl_FragCoord without declaring it as in first but it doesn't work either way. vec2 c is attempting to map the current coordinate to a coordinate in the complex number grid based on current resolution of the window.

This is all that's in my vertex shader:

    void main(){
        gl_Position = ftransform();
    }

And the relevant bit of my client code:

    glBegin(GL_POLYGON);
                glVertex2f(-1f, -1f);
                glVertex2f(1f, -1f);
                glVertex2f(1f, 1f);
                glVertex2f(-1f, 1f);
    glEnd();

This is running in my window loop, and just creates the square where the mandelbrot is supposed to render.

This is the output of my working java Mandelbrot program which doesn't use shaders: enter image description here

This is the output of my shader program: enter image description here

Fullscreen: enter image description here

I also have no clue as to how to be able to resize the window properly without the black bars. I am attempting to do this with vec2 c in my code above as I have set the uniforms to be the windows height and width and am using that when mapping the coordinate to the complex number plane, but as gl_FragCoord doesn't seem to work then neither should this. A link to a current guide on lgjwl screen resizing based on glfwCreateWindow would be vastly appreciated.

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
uhin
  • 33
  • 1
  • 3
  • 1
    What exactly do you expect `c` to be? Are you expecting it to be a value on the range [-1, 1]? What are `min/maxX/Y`, and how do they relate to `viewportDimensions`? – Nicol Bolas Mar 24 '19 at 01:35
  • @NicolBolas C is intended to be a coordinate in the complex number grid that the mandelbrot lies in. Right now I have this being from [-2, 2] y axis and [-2, 2] x axis. minX, maxX, minY, maxY, define the domain and range of the complex number plane I want to view, and the viewportDimensions is intended to be used to map those numbers to pixel values of the window. – uhin Mar 24 '19 at 14:48

1 Answers1

3

gl_FragCoord is a built-in input variables, it isn't necessary to declared the input variable gl_FragCoord. The x and y coordinate are window (pixel) coordinate.
The lower left of gl_FragCoord is (0.5, 0.5) and the upper right is (W-0.5, H-0.5), where W and H are the width and the height of the viewport.

You've to map gl_FragCoord.x to the range [minX, maxX] and gl_FragCoord.y to the range [minY, maxy].
I recommend to us the GLSL function mix for this.
viewportDimensions is assumed to contain the with and the height of the viewport rectangle in window (pixel) coordinates.

vec2 c = mix(vec2(minX, minY), vec2(maxX, maxY), gl_FragCoord.xy / viewportDimensions.xy);

See the (WebGL) example, where I applied the suggestions to the the fragment shader of the question.
The bounds are initialized as follows

minX = -2.5
minY = -2.0
maxX =  1.5
maxY =  2.0 

(function loadscene() {    

var canvas, gl, vp_size, prog, bufObj = {};

function initScene() {

    canvas = document.getElementById( "ogl-canvas");
    gl = canvas.getContext( "experimental-webgl" );
    if ( !gl )
      return;

    progDraw = gl.createProgram();
    for (let i = 0; i < 2; ++i) {
        let source = document.getElementById(i==0 ? "draw-shader-vs" : "draw-shader-fs").text;
        let shaderObj = gl.createShader(i==0 ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER);
        gl.shaderSource(shaderObj, source);
        gl.compileShader(shaderObj);
        let status = gl.getShaderParameter(shaderObj, gl.COMPILE_STATUS);
        if (!status) alert(gl.getShaderInfoLog(shaderObj));
        gl.attachShader(progDraw, shaderObj);
        gl.linkProgram(progDraw);
    }
    status = gl.getProgramParameter(progDraw, gl.LINK_STATUS);
    if ( !status ) alert(gl.getProgramInfoLog(progDraw));
    progDraw.inPos = gl.getAttribLocation(progDraw, "inPos");
    progDraw.minX = gl.getUniformLocation(progDraw, "minX");
    progDraw.maxX = gl.getUniformLocation(progDraw, "maxX");
    progDraw.minY = gl.getUniformLocation(progDraw, "minY");
    progDraw.maxY = gl.getUniformLocation(progDraw, "maxY");
    progDraw.viewportDimensions = gl.getUniformLocation(progDraw, "viewportDimensions");
    gl.useProgram(progDraw);

    var pos = [ -1, -1, 1, -1, 1, 1, -1, 1 ];
    var inx = [ 0, 1, 2, 0, 2, 3 ];
    bufObj.pos = gl.createBuffer();
    gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
    gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( pos ), gl.STATIC_DRAW );
    bufObj.inx = gl.createBuffer();
    bufObj.inx.len = inx.length;
    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
    gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( inx ), gl.STATIC_DRAW );
    gl.enableVertexAttribArray( progDraw.inPos );
    gl.vertexAttribPointer( progDraw.inPos, 2, gl.FLOAT, false, 0, 0 ); 
    
    gl.enable( gl.DEPTH_TEST );
    gl.clearColor( 0.0, 0.0, 0.0, 1.0 );

    window.onresize = resize;
    resize();
    requestAnimationFrame(render);
}

function resize() {
    //vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
    vp_size = [window.innerWidth, window.innerHeight];
    //vp_size = [256, 256]
    canvas.width = vp_size[0];
    canvas.height = vp_size[1];
}

function render(deltaMS) {

    gl.viewport( 0, 0, canvas.width, canvas.height );
    gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
   
    gl.uniform1f(progDraw.minX, -2.5);
    gl.uniform1f(progDraw.minY, -2.0);
    gl.uniform1f(progDraw.maxX, 1.5);
    gl.uniform1f(progDraw.maxY, 2.0);
    gl.uniform2f(progDraw.viewportDimensions, canvas.width, canvas.height);
    gl.drawElements( gl.TRIANGLES, bufObj.inx.len, gl.UNSIGNED_SHORT, 0 );
    
    requestAnimationFrame(render);
}  

initScene();

})();
html,body { margin: 0; overflow: hidden; }
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision mediump float;

attribute vec2 inPos;

void main()
{
    gl_Position = vec4( inPos.xy, 0.0, 1.0 );
}
</script>

<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;

uniform vec2 viewportDimensions;
uniform float minX;
uniform float maxX;
uniform float minY;
uniform float maxY;

vec3 hsv2rgb(vec3 c){
    vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

void main()
{
  float x = gl_FragCoord.x;
  float y = gl_FragCoord.y;

  vec2 c = mix(vec2(minX, minY), vec2(maxX, maxY), gl_FragCoord.xy / viewportDimensions.xy);
  vec2 z = c;

  float limit = 64.0;
  float iterations = 0.0;
  for(int i = 0; i < 64; i++){
      float t = 2.0 * z.x * z.y + c.y;
      z.x = z.x * z.x - z.y * z.y + c.x;
      z.y = t;

      if(z.x * z.x + z.y *z.y > 4.0){
          break;
      }

      iterations += 1.0;
  }
  float itRGB = iterations/limit;
  vec3 hsv = vec3(itRGB, 1.0, 1.0);
  vec3 rgb = hsv2rgb(hsv);
  gl_FragColor = vec4(rgb, 1);
}
</script>

<canvas id="ogl-canvas" style="border: none"></canvas>
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • 2
    `mix` is a vector-wise function, so you should use `mix(vec2(minX, minY), vec2(maxX, maxY), gl_FragCoord.xy / viewportDimensions)`. – Nicol Bolas Mar 24 '19 at 14:51
  • I'm not sure if using `mix` is really the best option. The transformation in question can be reduced to a simple multiply-add, and pre-calcultaing the 2 coefficients and sending those to the shader appears more efficient to me – derhass Mar 24 '19 at 14:54
  • I made the changes to my fragment shader as you suggested, but I think that's more of an efficiency thing and doesn't solve my problem. I removed the declaration at the top of the shader but still I'm just getting a red screen. It seems to me that gl_FragCoord is not returning the right value; if I put gl_FragColor = vec4(gl_FragCoord.x, gl_FragCoord.y, 1, 1); it just displays a white screen, meaning that there would be a 1 for both the x and y, for every fragment? Why? That is what is confusing me. – uhin Mar 24 '19 at 14:57
  • @derhass Good point, but hard to say without any measurement. Possibly for a beginner "mix" is easier to understand. Possibly it confuse things even more... – Rabbid76 Mar 24 '19 at 14:57
  • @dergass Again, the problem is not that I don't know how to make a mandelbrot or am looking for a more efficient way, the problem is I don't understand how to use shaders very well and cannot seem to find any reason why my code doesn't work. – uhin Mar 24 '19 at 15:02
  • @uhin In general the shader looks fine. Possibly the issue is the setting of the uniform variables. How do you set them? – Rabbid76 Mar 24 '19 at 15:04
  • 1
    @uhin: "If I put gl_FragColor = vec4(gl_FragCoord.x, gl_FragCoord.y, 1, 1); it just displays a white screen, meaning that there would be a 1 for both the x and y, for every fragment? Why? That is what is confusing me." But that point is explicitely addressed in the answer. It doesnt mean it is 1, it means it is >=1, and that is how it i supposed to. – derhass Mar 24 '19 at 15:05
  • 1
    @derhass OH! I thought that the values of gl_FragCoord were from [0,1]. Thanks! – uhin Mar 24 '19 at 15:09
  • @Rabbid76 Ah, I guess that must be my problem then. I'm not sure how to show my code for where I'm setting my uniforms but in my window loop I am getting the uniform location using glGetUniformLocation(shaderProgram, nameOfUniform); and then setting it using glUniform1f – uhin Mar 24 '19 at 15:13
  • 1
    @Rabbid76 my problem was that I was setting my uniforms after ending the shader program. I fixed it now. Thanks! – uhin Mar 24 '19 at 15:16