How to properly check webgl output

2019-08-21 11:47发布

You can find the original question below about LUMINANCE_ALPHA but I realized I was wrong about my problem. The real question should have been :

How can we efficiently check the output value done on a canvas drawn using webgl ?
Is using the webgl canvas as an image to draw it in a 2D canvas and get the values using getImageData() a good idea ?

const webglCanvas = ...;

const offCanvas = document.createElement('canvas');
offCanvas.style.background = 'black';
offCanvas.width = canvas.width;
offCanvas.height = canvas.height;

const context = offCanvas.getContext('2d');
context.drawImage(webglCanvas, 0, 0);
console.log( context.getImageData(0, 0, canvas.width, canvas.height).data );

Original Question :

I don't understand how gl.LUMINANCE_ALPHA works, from my understand it's supposed to get bytes 2 by 2 and assign the first value to rgb and the second value to alpha. However when I do that with webgl :

 gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE_ALPHA, 1, 1, 0, gl.LUMINANCE_ALPHA, gl.UNSIGNED_BYTE, new Uint8Array([1, 30]));

I'm getting a color of (8, 8, 8, 30) while I'm expecting (1, 1, 1, 30).

I got that definition from those specs :

Each element is an luminance/alpha double. The GL converts each component to floating point, clamps to the range [0,1], and assembles them into an RGBA element by placing the luminance value in the red, green and blue channels.

Not sure how this apply to webgl since there is no double. Maybe I'm missing what converts each component to floating point means or missing some pack/unpack configuration.

Here's a snippet replicating the issue:

const vertShaderStr = `
	attribute vec2 a_position;

  void main() {
      gl_Position = vec4(a_position, 0, 1);
  }
`;
const fragShaderStr = `
	 precision mediump float;

   uniform sampler2D u_texture;

   void main() {
        gl_FragColor = texture2D(u_texture, vec2(0, 0));
    }
`

var canvas = document.getElementById('canvas');
canvas.width = 1;
canvas.height = 1;

const gl = canvas.getContext('webgl');

const program = gl.createProgram();

const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertShaderStr);
gl.compileShader(vertexShader);

if ( !gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS) )
  throw new Error('Vertex shader error', gl.getShaderInfoLog(vertexShader));

gl.attachShader(program, vertexShader);

const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragShaderStr);
gl.compileShader(fragmentShader);

if ( !gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS) )
  throw new Error('Fragment shader error', gl.getShaderInfoLog(fragmentShader));

gl.attachShader(program, fragmentShader);

gl.linkProgram(program);

if ( !gl.getProgramParameter(program, gl.LINK_STATUS) )
  throw new Error(gl.getProgramInfoLog(program));
  
gl.useProgram(program);

const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
  1, 1,
  -1, 1,
  1, -1,
  -1, -1
]), gl.STATIC_DRAW);

const positionLocation = gl.getAttribLocation(program, 'a_position');
gl.enableVertexAttribArray(positionLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);

/*** Interresting part here ***/
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 2);
gl.pixelStorei(gl.PACK_ALIGNMENT, 2);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE_ALPHA, 1, 1, 0, gl.LUMINANCE_ALPHA, gl.UNSIGNED_BYTE, 
	new Uint8Array([1, 30]));


gl.activeTexture(gl.TEXTURE0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

const offCanvas = document.createElement('canvas');
offCanvas.style.background = 'black';
offCanvas.width = canvas.width;
offCanvas.height = canvas.height;

const context = offCanvas.getContext('2d');
context.drawImage(canvas, 0, 0);
console.log( context.getImageData(0, 0, canvas.width, canvas.height).data );
<canvas id="canvas"></canvas>

update

Just found out that the alpha value (30) will affect the resulting rgb. But I can't find out what's doing exactly, if it's using alpha to compute rgb or if it's reading the wrong bytes from the buffer.

标签: webgl
1条回答
Summer. ? 凉城
2楼-- · 2019-08-21 12:09

When drawing a webgl canvas to another 2d canvas conversion, filtering and blending operations are being applied which may lead to a skewed result. While you can disable blending by setting the globalCompositeOperation on the 2d context to copy you're still running through a conversion and filtering process which is not standardized and is not guaranteed to provide a precise result.

Using readPixels returns correct results and is the only way to get guaranteed accurate readings from the current color framebuffer. If you need that data to be available to a 2D context you may use ImageData in conjunction with putImageData.

const vertShaderStr = `
	attribute vec2 a_position;

  void main() {
      gl_Position = vec4(a_position, 0, 1);
  }
`;
const fragShaderStr = `
	 precision mediump float;

   uniform sampler2D u_texture;

   void main() {
        gl_FragColor = texture2D(u_texture, vec2(0, 0));
    }
`

var canvas = document.getElementById('canvas');
canvas.width = 1;
canvas.height = 1;

const gl = canvas.getContext('webgl');

const program = gl.createProgram();

const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertShaderStr);
gl.compileShader(vertexShader);

if ( !gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS) )
  throw new Error('Vertex shader error', gl.getShaderInfoLog(vertexShader));

gl.attachShader(program, vertexShader);

const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragShaderStr);
gl.compileShader(fragmentShader);

if ( !gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS) )
  throw new Error('Fragment shader error', gl.getShaderInfoLog(fragmentShader));

gl.attachShader(program, fragmentShader);

gl.linkProgram(program);

if ( !gl.getProgramParameter(program, gl.LINK_STATUS) )
  throw new Error(gl.getProgramInfoLog(program));
  
gl.useProgram(program);

const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
  1, 1,
  -1, 1,
  1, -1,
  -1, -1
]), gl.STATIC_DRAW);

const positionLocation = gl.getAttribLocation(program, 'a_position');
gl.enableVertexAttribArray(positionLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);

/*** Interresting part here ***/
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 2);
gl.pixelStorei(gl.PACK_ALIGNMENT, 2);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE_ALPHA, 1, 1, 0, gl.LUMINANCE_ALPHA, gl.UNSIGNED_BYTE, 
	new Uint8Array([1, 30]));


gl.activeTexture(gl.TEXTURE0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
var readback = new Uint8Array(4);
gl.readPixels(0,0,1,1,gl.RGBA,gl.UNSIGNED_BYTE,readback);

const offCanvas = document.createElement('canvas');
offCanvas.style.background = 'black';
offCanvas.width = canvas.width;
offCanvas.height = canvas.height;

const context = offCanvas.getContext('2d');
context.globalCompositeOperation = 'copy';
context.drawImage(canvas, 0, 0,1,1);
console.log("Canvas",context.getImageData(0, 0, canvas.width, canvas.height).data);
console.log("readPixels", readback );
<canvas id="canvas"></canvas>

查看更多
登录 后发表回答