Yukulélé
Yukulélé

Reputation: 17062

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?

Upvotes: 2

Views: 3389

Answers (1)

user128511
user128511

Reputation:

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

Upvotes: 7

Related Questions