如何正确检查 webgl 输出

How to properly check webgl output

您可以在下面找到关于 LUMINANCE_ALPHA 的原始问题,但我意识到我的问题是错误的。 真正的问题应该是:

我们如何有效地检查在使用 webgl 绘制的 canvas 上完成的输出值?
使用 webgl canvas 作为图像以二维方式绘制它 canvas 并使用 getImageData() 获取值是个好主意吗?

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

原问题:

我不明白 gl.LUMINANCE_ALPHA 是如何工作的,根据我的理解,它应该以 2 乘 2 的方式获取字节并将第一个值分配给 rgb,将第二个值分配给 alpha。 但是,当我使用 webgl 执行此操作时:

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

我得到 (8, 8, 8, 30) 的颜色,而我期待 (1, 1, 1, 30)

我从 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.

不确定这如何适用于 webgl,因为没有双重。也许我遗漏了 converts each component to floating point 的含义或遗漏了一些 pack/unpack 配置。

这是一个复制问题的片段:

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>

更新

刚发现 alpha 值 (30) 会影响生成的 rgb。但是我不知道到底在做什么,如果它使用 alpha 来计算 rgb 或者它是否从缓冲区读取错误的字节。

当绘制 webgl canvas 到另一个 2d canvas 转换时,正在应用过滤和混合操作,这可能会导致结果出现偏差。虽然您可以通过将 2d 上下文中的 globalCompositeOperation 设置为 copy 来禁用混合,但您仍然 运行 通过未标准化的转换和过滤过程,并且不能保证提供精确的结果。

使用 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>