WebGL:有没有一种有效的方法只将图像/画布的一部分上传为纹理?

Jor*_*Jor 6 javascript performance html5 canvas webgl

我正在开发一个基于2D层的应用程序,我想使用WebGL进行合成.这些层可以相对于彼此移位,并且每个帧仅每个层的小(矩形)部分可以改变.然而,该矩形部分的宽度和高度可能不可预测地变化.我想在每个图层上使用一个画布(2D)和一个纹理,每个画布在每个画布上仅重绘已修改的图层部分,然后将该小区域上传到GPU以将相应的部分更新为纹理,在GPU为我做合成之前.但是我还没有找到一种将图像的一部分上传到纹理的一部分的有效方法.似乎texSubImage2D() 可以更新纹理的一部分,但只采用完整的图像/画布,并且似乎不可能指定要使用的图像的矩形区域.

我想到了一些这样做的方法,但每个方法似乎都有明显的开销:

  • 使用getImageData()+ texSubImage2D()只上传更改的部分(将画布像素数据转换为ImageData的开销)
  • 用每个帧重新上传整个图层画布 texImage2D()
  • 或创建/调整一个小canvas2D到正确的尺寸,以适合每一层修改,然后用texSubImage2D()它来发送它来更新相关的纹理(内存预留开销)

那么,有没有办法为纹理指定图像/画布的一部分?喜欢的东西texImagePart2D()texSubImagePart2D,这将既接受四个参数,sourceX,sourceY,sourceWidthsourceHeight指定图像/画布使用的矩形区域?

gma*_*man 5

遗憾的是,没有办法上传画布/图像的一部分.

WebGL所基于的OpenGL ES 2.0没有提供这样做的方法.OpenGL ES 3.0提供了一种将较小的源矩形上传到纹理或纹理部分的方法,因此下一版本的WebGL可能会提供该功能.

现在你可以有一个单独的画布来帮助上传.首先调整画布大小以匹配您要上传的部分

canvasForCopying.width = widthToCopy;
canvasForCopying.height= heightToCopy;
Run Code Online (Sandbox Code Playgroud)

然后将要复制的画布部分复制到画布进行复制

canvasForCopying2DContext.drawImage(
    srcCanvas, srcX, srcY, widthToCopy, heightToCopy,
    0, 0, widthToCopy, heightToCopy);
Run Code Online (Sandbox Code Playgroud)

然后使用它上传到您想要的纹理.

gl.texSubImage2D(gl.TEXTURE_2D, 0, destX, destY, gl.RGBA, gl.UNSIGNED_BYTE, 
                 canvasForCopying);
Run Code Online (Sandbox Code Playgroud)

getImageData可能会慢一点,因为浏览器必须调用readPixels获取图像数据并停止图形管道.浏览器不必这样做drawImage.

至于为什么texImage2D有时可能比texSubImage2D它依赖于驱动程序/ GPU 更快,但显然有时texImage2D可以使用DMA实现,而texSubImage2D不能.texImage2D也可以通过制作一个新的纹理流水线,懒洋洋地丢弃旧的纹理texSubImage2D.

我个人不会担心它,但如果你想检查一下,使用两者同时上传成千上万的纹理(texImage2D并且texSubImage2D不要只计时一个图形是流水线的).texSubImage2D如果纹理很大并且要更新的部分小于纹理的25%,您可能会发现更快.至少那是我上次检查时发现的.大多数当前的驱动程序至少是优化的,因为如果你打电话texSubImage2D并碰巧正在替换整个内容,他们将在texImage2D内部调用代码.

更新

你可以做几件事

  1. 对于图像可以使用fetchImageBitmap一个图像的一部分加载到ImageBitamp其然后可以上传.获取图像的一部分的示例

    在下面的示例中,我们调用fetch,然后获取Blob并使用该blob ImageBitmap仅使用图像的一部分.结果可以传递给texImage2D但是为了简洁起见,样本只是在2d画布中使用它.

fetch('https://i.imgur.com/TSiyiJv.jpg', {mode: 'cors'})
.then((response) => {
  if (!response.ok) {
    throw response;
  }
  return response.blob();
})
.then((blob) => {
  const x = 451;
  const y = 453;
  const width = 147;
  const height = 156;
  return createImageBitmap(blob, x, y, width, height);
}).then((bitmap) => {
  useit(bitmap);
}).catch(function(e) {
  console.error(e);
});

// -- just to show we got a portion of the image

function useit(bitmap) {
  const ctx = document.createElement("canvas").getContext("2d");
  document.body.appendChild(ctx.canvas);
  ctx.drawImage(bitmap, 0, 0);
}
Run Code Online (Sandbox Code Playgroud)

  1. 在WebGL2中有gl.pixelStorei设置

    UNPACK_ROW_LENGTH //源的一排多少像素是UNPACK_SKIP_ROWS //多少行从源UNPACK_SKIP_PIXELS开始跳过//多少像素从源左边跳到

    因此,使用这3个设置,您可以告诉webgl2源更宽,但您想要的部分更小.你传递一个较小的宽度,texImage2D上面的3个设置有助于告诉WebGL如何获得较小的部分和从哪里开始.