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

时间:2014-01-05 05:30:59

标签: javascript html5 performance canvas webgl

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

我想过几种方法,但每种方法似乎都有明显的开销:

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

那么,有没有办法为纹理指定图像/画布的一部分?像texImagePart2D()texSubImagePart2D之类的东西,它们都会接受另外四个参数,sourceXsourceYsourceWidthsourceHeight来指定矩形区域要使用的图像/画布?

1 个答案:

答案 0 :(得分:5)

不幸的是,没有办法上传画布/图像的一部分。

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

现在您可以使用单独的画布来帮助上传。首先调整画布大小以匹配您要上传的部分

canvasForCopying.width = widthToCopy;
canvasForCopying.height= heightToCopy;

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

canvasForCopying2DContext.drawImage(
    srcCanvas, srcX, srcY, widthToCopy, heightToCopy,
    0, 0, widthToCopy, heightToCopy);

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

gl.texSubImage2D(gl.TEXTURE_2D, 0, destX, destY, gl.RGBA, gl.UNSIGNED_BYTE, 
                 canvasForCopying);

getImageData可能会变慢,因为浏览器必须调用readPixels 获取图像数据并停止图形管道。浏览器不必为drawImage执行此操作。

至于为什么texImage2D有时可能比texSubImage2D更快,这取决于驱动程序/ GPU,但显然有时texImage2D可以使用DMA实现,而texSubImage2D则不能。 texImage2D也可以通过制作新纹理进行流水线操作,并且懒惰地丢弃texSubImage2D不能的旧纹理。

就我个人而言,我不担心,但是如果你想检查一下,请使用texImage2DtexSubImage2D上传数万个纹理的时间(不要只用一个图形为流水线的时间) 。如果纹理很大并且要更新的部分小于纹理的25%,您可能会发现texSubImage2D更快。至少那是我上次检查时发现的。大多数当前的驱动程序至少已经过优化,因为如果您调用texSubImage2D并碰巧正在替换整个内容,则会在内部调用texImage2D代码。

更新

你可以做几件事

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

    在下面的示例中,我们调用Blob,然后获取ImageBitmap并使用该blob仅使用部分图像制作texImage2D。结果可以传递给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); },但为了简洁起见,样本只是在2d画布中使用它。

  2. gl.pixelStorei

    1. 在WebGL2中有texImage2D次设置

      UNPACK_ROW_LENGTH //源一行的像素数   UNPACK_SKIP_ROWS //从源头开始跳过多少行   UNPACK_SKIP_PIXELS //从源左侧跳过多少像素

      因此,使用这3个设置,您可以告诉webgl2源更宽,但您想要的部分更小。您将较小的宽度传递给{{1}},上面的3个设置有助于告诉WebGL如何获得较小的部分以及从哪里开始。