Search code examples
webglwebgl2

get current pixel position on webGL2 fragment shader


I created a simple webGL script, it apply pixel color depending on (x,y) pixel position

What I get:

out

here's what I did:

#ifdef GL_ES
precision mediump float;
#endif

uniform float width;
uniform float height;
uniform float time;

void main() {
  vec2 u_resolution = vec2(width, height);
    vec2 st = gl_FragCoord.xy / u_resolution;
    gl_FragColor = vec4(st.x, st.y, 0.5, 1.0);
}

Codepen: Hello WebGL

I'm trying to convert it to webGL2 but I don't know how to get current pixel position.

here's what I tried:

#version 300 es
#ifdef GL_ES
precision mediump float;
#endif

uniform float width;
uniform float height;
uniform float time;
out vec4 color;

void main() {
  vec2 u_resolution = vec2(width, height);
    vec2 st = color.xy / u_resolution;
    color = vec4(st.x, st.y, 0.5, 1.0);
}

Codepen: Hello WebGL2

How to get current pixel position in webgl2?


Solution

  • gl_FragCoord is still the correct way in WebGL2

    var canvas = document.body.appendChild(document.createElement("canvas"));
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    var gl = canvas.getContext("webgl2");
    
    //************** Shader sources **************
    var vertexSource = `
    #version 300 es
    in vec2 position;
    void main() {
      gl_Position = vec4(position, 0.0, 1.0);
    }
    `;
    
    var fragmentSource = `
    #version 300 es
    #ifdef GL_ES
    precision mediump float;
    #endif
    
    uniform float width;
    uniform float height;
    uniform float time;
    out vec4 color;
    
    void main() {
      vec2 u_resolution = vec2(width, height);
    	vec2 st = gl_FragCoord.xy / u_resolution;
    	color = vec4(st.x, st.y, 0.5, 1.0);
    }`;
    
    window.addEventListener("resize", onWindowResize, false);
    
    function onWindowResize() {
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
      gl.viewport(0, 0, canvas.width, canvas.height);
      gl.uniform1f(widthHandle, window.innerWidth);
      gl.uniform1f(heightHandle, window.innerHeight);
    }
    
    //Compile shader and combine with source
    function compileShader(shaderSource, shaderType) {
      var shader = gl.createShader(shaderType);
      gl.shaderSource(shader, shaderSource);
      gl.compileShader(shader);
      if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        throw "Shader compile failed with: " + gl.getShaderInfoLog(shader);
      }
      return shader;
    }
    
    //From https://codepen.io/jlfwong/pen/GqmroZ
    //Utility to complain loudly if we fail to find the attribute/uniform
    function getAttribLocation(program, name) {
      var attributeLocation = gl.getAttribLocation(program, name);
      if (attributeLocation === -1) {
        throw "Cannot find attribute " + name + ".";
      }
      return attributeLocation;
    }
    
    function getUniformLocation(program, name) {
      var attributeLocation = gl.getUniformLocation(program, name);
      if (attributeLocation === -1) {
        throw "Cannot find uniform " + name + ".";
      }
      return attributeLocation;
    }
    
    //************** Create shaders **************
    
    //Create vertex and fragment shaders
    var vertexShader = compileShader(vertexSource.trim(), gl.VERTEX_SHADER);
    var fragmentShader = compileShader(fragmentSource.trim(), gl.FRAGMENT_SHADER);
    
    //Create shader programs
    var program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);
    
    gl.useProgram(program);
    
    //Set up rectangle covering entire canvas
    var vertexData = new Float32Array([
      -1.0,
      1.0, // top left
      -1.0,
      -1.0, // bottom left
      1.0,
      1.0, // top right
      1.0,
      -1.0 // bottom right
    ]);
    
    //Create vertex buffer
    var vertexDataBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexDataBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);
    
    // Layout of our data in the vertex buffer
    var positionHandle = getAttribLocation(program, "position");
    
    gl.enableVertexAttribArray(positionHandle);
    gl.vertexAttribPointer(
      positionHandle,
      2, // position is a vec2 (2 values per component)
      gl.FLOAT, // each component is a float
      false, // don't normalize values
      2 * 4, // two 4 byte float components per vertex (32 bit float is 4 bytes)
      0 // how many bytes inside the buffer to start from
    );
    
    //Set uniform handle
    var timeHandle = getUniformLocation(program, "time");
    var widthHandle = getUniformLocation(program, "width");
    var heightHandle = getUniformLocation(program, "height");
    
    gl.uniform1f(widthHandle, window.innerWidth);
    gl.uniform1f(heightHandle, window.innerHeight);
    
    function draw() {
      //Send uniforms to program
      gl.uniform1f(timeHandle, performance.now());
      //Draw a triangle strip connecting vertices 0-4
      gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
      requestAnimationFrame(draw);
    }
    
    draw();
    html {
      overflow: hidden;
    }
    canvas {
      display: block;
    }

    Some other random tips.

    • These ifdefs are irrelevant

      #ifdef GL_ES
      precision mediump float;
      #endif
      

      Just

      precision mediump float;
      

      is fine.

    • I'm guessing this obvious but why pass in width and height separate?

      How about just

      uniform vec2 u_resolution;
      
    • No reason to call performance.now. The time is passed to your requestAnimationFrame callback

      function draw(time) {
        //Send uniforms to program
        gl.uniform1f(timeHandle, time);
      
        ...
        requestAnimationFrame(draw);
      }
      
      requestAnimationFrame(draw);
      
    • The code checks for compile errors but not link errors

      You should check for link errors

      gl.linkProgram(program);
      if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
        throw "Program link failed with: " + gl.getProgramInfoLog(program);
      }
      
      

      There will be link errors if your varyings don't match and further the spec doesn't require compiling to ever fail even on bad shaders. Rather it only requires if they were bad to fail to link.

    • window.innerWidth

      see: this

    • gl.getUniformLocation returns null if the uniform does not exist

      The code is checking for -1 which is correct for attributes but not for uniforms.

    • throwing on attributes and uniforms not existing

      Of course it's helpful to know they don't exist but it's common to debug shaders by commenting things out or editing. For example lets say nothing appears on the screen. If it was me the first thing I'd do is change the fragment shader to this

      const fragmentSource = `
      #version 300 es
      precision mediump float;
      
      uniform vec2 u_resolution;
      uniform float time;
      out vec4 color;
      
      void main() {
          vec2 st = gl_FragCoord.xy / u_resolution;
          color = vec4(st.x, st.y, 0.5, 1.0);
          color = vec4(1, 0, 0, 1);  // <----------------------
      }`;
      

      Just output a solid color to check if the issue is in the fragment shader or the vertex shader. The moment I do that most WebGL implentations will optimize out u_resolution and the code that throws when looking up locations effectively makes the program undebuggable.

      In fact the code only runs currently because of the previous bug checking for -1 instead of null. With that bug fixed the code crashes beacuse time is optimized out.

    var canvas = document.body.appendChild(document.createElement("canvas"));
    var gl = canvas.getContext("webgl2");
    
    //************** Shader sources **************
    var vertexSource = `
    #version 300 es
    in vec2 position;
    void main() {
      gl_Position = vec4(position, 0.0, 1.0);
    }
    `;
    
    var fragmentSource = `
    #version 300 es
    precision mediump float;
    
    uniform vec2 u_resolution;
    uniform float time;
    out vec4 color;
    
    void main() {
      vec2 st = gl_FragCoord.xy / u_resolution;
      color = vec4(st.x, st.y, 0.5, 1.0);
    }`;
    
    
    function resize() {
      if (canvas.width !== canvas.clientWidth || canvas.height !== canvas.clientHeight) {
        canvas.width = canvas.clientWidth;
        canvas.height = canvas.clientHeight;
        gl.viewport(0, 0, canvas.width, canvas.height);
        gl.uniform2f(resHandle, canvas.width, canvas.height);
      }
    }
    
    //Compile shader and combine with source
    function compileShader(shaderSource, shaderType) {
      var shader = gl.createShader(shaderType);
      gl.shaderSource(shader, shaderSource);
      gl.compileShader(shader);
      if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        throw "Shader compile failed with: " + gl.getShaderInfoLog(shader);
      }
      return shader;
    }
    
    //From https://codepen.io/jlfwong/pen/GqmroZ
    //Utility to complain loudly if we fail to find the attribute/uniform
    function getAttribLocation(program, name) {
      var attributeLocation = gl.getAttribLocation(program, name);
      if (attributeLocation === -1) {
        console.warn("Cannot find attribute", name);
      }
      return attributeLocation;
    }
    
    function getUniformLocation(program, name) {
      var uniformLocation = gl.getUniformLocation(program, name);
      if (uniformLocation === null) {
        console.warn("Cannot find uniform", name);
      }
      return uniformLocation;
    }
    
    //************** Create shaders **************
    
    //Create vertex and fragment shaders
    var vertexShader = compileShader(vertexSource.trim(), gl.VERTEX_SHADER);
    var fragmentShader = compileShader(fragmentSource.trim(), gl.FRAGMENT_SHADER);
    
    //Create shader programs
    var program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
      throw "Program link failed with: " + gl.getProgramInfoLog(program);
    }
    
    gl.useProgram(program);
    
    //Set up rectangle covering entire canvas
    var vertexData = new Float32Array([
      -1.0,
      1.0, // top left
      -1.0,
      -1.0, // bottom left
      1.0,
      1.0, // top right
      1.0,
      -1.0 // bottom right
    ]);
    
    //Create vertex buffer
    var vertexDataBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexDataBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);
    
    // Layout of our data in the vertex buffer
    var positionHandle = getAttribLocation(program, "position");
    
    gl.enableVertexAttribArray(positionHandle);
    gl.vertexAttribPointer(
      positionHandle,
      2, // position is a vec2 (2 values per component)
      gl.FLOAT, // each component is a float
      false, // don't normalize values
      2 * 4, // two 4 byte float components per vertex (32 bit float is 4 bytes)
      0 // how many bytes inside the buffer to start from
    );
    
    //Set uniform handle
    var timeHandle = getUniformLocation(program, "time");
    var resHandle = getUniformLocation(program, "u_resolution");
    
    function draw(time) {
      resize();
      //Send uniforms to program
      gl.uniform1f(timeHandle, time);
      //Draw a triangle strip connecting vertices 0-4
      gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
      requestAnimationFrame(draw);
    }
    
    requestAnimationFrame(draw);
    html,body {
      height: 100%;
      margin: 0;
    }
    canvas {
      width: 100%;
      height: 100%;
      display: block;
    }