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

Posted

技术标签:

【中文标题】WebGL:有没有一种有效的方法可以只上传图像/画布的一部分作为纹理?【英文标题】:WebGL: Is there an efficient way to upload only part of an image/canvas as a texture? 【发布时间】:2014-01-22 16:56:24 【问题描述】:

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

我已经想到了几种方法,但每种方法似乎都有明显的开销:

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

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

【问题讨论】:

至少在大多数嵌入式 GPU 中,TexSubImage2D API 会更慢。使用 TexImage2D 本身可能会更好。 所以没有比重新上传整个纹理更快的方法了,即使只是修改了一部分?但我不明白,如果它只上传一个小矩形而不是整个纹理,为什么会更慢?是因为修改现有纹理很慢吗? 【参考方案1】:

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

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 代码。

更新

你可以做几件事

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

    在下面的示例中,我们调用 fetch,然后获取 Blob 并使用该 blob 生成仅包含图像一部分的 ImageBitmap。结果可以传递给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);

    在 WebGL2 中有 gl.pixelStorei 设置

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

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

【讨论】:

非常感谢您的详细解答。我想我会使用 drawImage / texSubImage2D 解决方法。问候。

以上是关于WebGL:有没有一种有效的方法可以只上传图像/画布的一部分作为纹理?的主要内容,如果未能解决你的问题,请参考以下文章

WebGL画一个10px大小的点

WebGL入门画一个旋转的cube

TeaPot 用webgl画茶壶 Phong Shading

有没有一种快速的方法可以从 WebGL / Javascript 中的几个帧缓冲区中获取平均颜色?

TeaPot 用webgl画茶壶

在android中将多个图像上传到服务器的最快方法