使用gl.readPixels的正确方法是什么?

时间:2018-01-07 16:48:31

标签: webgl drawimage

我想从Three.js演示中获取像素数据。 据我所知,有两种方法可以继续:

1)在2D画布中绘制webGl-canvas并使用Context2D.getImageData:

var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.drawImage(renderer.domElement,0,0);
var data = ctx.getImageData(0,0,w,h).data;

2)直接使用带有readPixels的context3D,如:

var ctx = renderer.domElement.getContext("webgl");
var data = new UInt8Array(w*h*4);
ctx.readPixels(0, 0, w,h, ctx.RGBA, ctx.UNSIGNED_BYTE, data);

这两种方式继续工作并给出相同的结果,但第二种方法几乎比使用context2d.getImageData的方式慢2倍。

听起来很奇怪。如何将3D内容绘制到2D画布中比直接使用context3D更快?我不明白,我几乎可以肯定我没有正确使用gl.readPixels。

然后我的问题是:如何使用gl.readPixels比context2d.drawImage + context2d.getImageData更快?

我试着像那样使用Float32Array

var ctx = renderer.domElement.getContext("webgl");
var data = new Float32Array(w*h*4);
ctx.readPixels(0, 0, w,h, ctx.RGBA, ctx.FLOAT, data);

我认为它应该更快,因为没有从Float到UInt8的转换,但它看起来不像那样,因为我的'data'数组在调用ctx.readPixels之后保持空白

感谢您的帮助!

(请原谅我,如果我的英语不完美,那不是我的母语)

1 个答案:

答案 0 :(得分:2)

在我的机器上,我得到的readPixels比drawImage / getImageData快2到20倍。在MacOS Chrome,Firefox,Windows 10 Chrome和Firefox上进行了测试。 Safari出现readPixels速度较慢。听起来像Safari中的一个错误,实际上正如预期的那样检查Safari Technology Preview Release 46,readPixels比drawImage / getImageData快3到1.2倍

const gl = document.createElement("canvas").getContext("webgl");
const ctx = document.createElement("canvas").getContext("2d");

const w = 512;
const h = 512;

gl.canvas.width = w;
gl.canvas.height = h;
ctx.canvas.width = w;
ctx.canvas.height = h;

const readPixelBuffer = new Uint8Array(w * h * 4);

const tests = [
 { fn: withReadPixelsPreAlloc, msg: "readPixelsPreAlloc", },
 { fn: withReadPixels, msg: "readPixels", },
 { fn: withDrawImageGetImageData, msg: "drawImageGetPixels", },
];

let ndx = 0;
runNextTest();

function runNextTest() {
  if (ndx >= tests.length) {
    return;
  }
  const test = tests[ndx++];
  
  // use setTimeout to give the browser a change to 
  // do something between tests
  setTimeout(function() {
    log(test.msg, "iterations in 5 seconds:", runTest(test.fn));
    runNextTest();
  }, 0);
}

function runTest(fn) {
  const start = performance.now();
  let count = 0;
  for (;;) {
    const elapsed = performance.now() - start;
    if (elapsed > 5000) {
      break;
    }
    fn();
    ++count;
  }
  return count;
}

function withReadPixelsPreAlloc() {
  gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, readPixelBuffer);
}

function withReadPixels() {
  const readPixelBuffer = new Uint8Array(w * h * 4);
  gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, readPixelBuffer);
}

function withDrawImageGetImageData() {
  ctx.drawImage(gl.canvas, 0, 0);
  ctx.getImageData(0, 0, w, h);
}

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

对于转换为float,画布本身以字节存储。没有转换为浮动,您可能会出现GL错误

const gl = document.createElement("canvas").getContext("webgl");
const buf = new Float32Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.FLOAT, buf);
log("ERROR:", glEnumToString(gl, gl.getError()));

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

function glEnumToString(gl, val) {
  if (val === 0) { return 'NONE'; }
  for (key in gl) {
    if (gl[key] === val) { 
      return key;
    }
  }
  return `0x${val.toString(16)}`;
}

检查控制台我看到错误是

WebGL: INVALID_ENUM: readPixels: invalid type