1

我目前正在开发一个名为 readPixels的演示。

这个关于 SO 的答案preserveDrawingBuffer是我可以在该选项中找到的大部分信息。

在测试时,我观察到在 WebGL 2 中这个答案仍然正确 - 你必须设置preserveDrawingBuffertrue.

这真的正确吗?

是否有 OpenGL 等价物preserveDrawingBuffer

有什么方法可以设置preserveDrawingBufferfalse仍然调用 readPixels?

这个答案让您看起来可以打电话gl.flush

preserveDrawingBuffer 与刷新上下文有何相同之处?

4

1 回答 1

3

你不需要preserveDrawingBuffer: true打电话readPixels。您需要的是readPixels在退出当前事件之前调用。

规范说,如果您调用任何影响画布的函数(gl.clear、gl.drawXXX),那么浏览器将在下一次合成操作后清除画布。何时发生复合操作取决于浏览器。它可能是在它处理了几个鼠标事件或键盘事件或单击事件之后。订单未定义。定义的是在当前事件退出之前它不会这样做

render
read

const gl = document.querySelector("canvas").getContext("webgl2");

render();
read();  // read in same event

function render() {
  gl.clearColor(.25, .5, .75, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);
}

function read() {
  const pixel = new Uint8Array(4);
  gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
  log(pixel);
}

function log(...args) {
  const elem = document.createElement("pre");
  elem.textContent = [...args].join(' ');
  document.body.appendChild(elem);
}
<canvas></canvas>

在哪里工作

render
setTimeout(read, 1000);  // some other event

不工作

const gl = document.querySelector("canvas").getContext("webgl2");

render();
setTimeout(read, 1000);  // read in other event

function render() {
  gl.clearColor(.25, .5, .75, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);
}

function read() {
  const pixel = new Uint8Array(4);
  gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
  log(pixel);
}

function log(...args) {
  const elem = document.createElement("pre");
  elem.textContent = [...args].join(' ');
  document.body.appendChild(elem);
}
<canvas></canvas>

请注意,由于触发清除的是复合操作(浏览器实际在页面上绘制画布以及其余 HTML),如果画布不在页面上,则它不会被合成并且不会被清除。

换句话说,上面不起作用的情况在这里起作用

// create an offscreen canvas. Because it's offscreen it won't be composited
// and therefore will not be cleared.
const gl = document.createElement("canvas").getContext("webgl2");

render();
setTimeout(read, 1000);  // read in other event

function render() {
  gl.clearColor(.25, .5, .75, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);
}

function read() {
  const pixel = new Uint8Array(4);
  gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
  log(pixel);
}

function log(...args) {
  const elem = document.createElement("pre");
  elem.textContent = [...args].join(' ');
  document.body.appendChild(elem);
}

如果您想调用readPixels其他事件,例如当用户单击某个元素时,那么您至少有 2 个选项

  1. preserveDrawingBuffer: true

  2. 在您的活动中再次渲染

    screenshotButton.addEventListener('click', () => {
       render();
       read();
    });
    

来自规范第 2.2 节

WebGL 在合成操作之前立即将其绘图缓冲区呈现给 HTML 页面合成器,但前提是自上一次合成操作以来至少发生了以下情况之一:

  • 上下文创建
  • 画布调整大小
  • 当绘图缓冲区是当前绑定的帧缓冲区时,已调用 clear、drawArrays 或 drawElements

在呈现绘图缓冲区以进行合成之前,实现应确保所有渲染操作都已刷新到绘图缓冲区。默认情况下,合成后绘图缓冲区的内容将被清除为其默认值,如上表所示。

可以通过设置 WebGLContextAttributes 对象的 preserveDrawingBuffer 属性来更改此默认行为。如果此标志为真,则绘图缓冲区的内容将被保留,直到作者清除或覆盖它们。如果此标志为 false,则在渲染函数返回后尝试使用此上下文作为源图像执行操作可能会导致未定义的行为。这包括 readPixels 或 toDataURL 调用,或将此上下文用作另一个上下文的 texImage2D 或 drawImage 调用的源图像。

于 2017-06-14T02:36:42.757 回答