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
这样的东西都可以接受另外四个参数sourceX
、sourceY
、sourceWidth
和sourceHeight
来指定要使用的图像/画布的矩形区域?
【问题讨论】:
至少在大多数嵌入式 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
不能。
我个人不会担心,但如果您想检查,请使用texImage2D
和texSubImage2D
上传数以万计的纹理(不要只计时一个,因为图形是流水线的)。如果您的纹理很大并且您要更新的部分小于纹理的 25%,您可能会发现 texSubImage2D
更快。至少这是我上次检查时发现的。大多数当前的驱动程序至少进行了优化,如果您调用 texSubImage2D
并且碰巧要替换整个内容,它们将在内部调用 texImage2D
代码。
更新
你可以做几件事
对于图像,您可以使用fetch
和ImageBitmap
将图像的一部分加载到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:有没有一种有效的方法可以只上传图像/画布的一部分作为纹理?的主要内容,如果未能解决你的问题,请参考以下文章
TeaPot 用webgl画茶壶 Phong Shading