Kevin Reid 的preserveDrawingBuffer 建议是正确的,但(通常)有更好的选择。 tl;dr 是最后的代码。
将渲染网页的最终像素放在一起并与渲染 WebGL 内容进行协调可能会很昂贵。通常的流程是:
- JavaScript 向 WebGL 上下文发出绘图命令
- JavaScript 返回,将控制权返回给主浏览器事件循环
- WebGL 上下文将绘图缓冲区(或其内容)转换为合成器,以便集成到当前在屏幕上呈现的网页中
- 页面,带有 WebGL 内容,显示在屏幕上
请注意,这与大多数 OpenGL 应用程序不同。其中,渲染的内容通常直接显示,而不是与页面上的一堆其他内容合成,其中一些实际上可能位于 WebGL 内容之上并与 WebGL 内容混合。
WebGL 规范已更改为在第 3 步之后将绘图缓冲区视为基本上是空的。您在 devtools 中运行的代码在第 4 步之后出现,这就是您得到一个空缓冲区的原因。对规范的这种更改允许在第 3 步之后的消隐基本上是硬件中实际发生的情况(如在许多移动 GPU 中)的平台上实现显着的性能改进。如果您希望在第 3 步之后有时会制作 WebGL 内容的副本,则浏览器必须在第 3 步之前始终制作绘图缓冲区的副本,这会使您的帧率下降在某些平台上急剧下降。
您可以完全做到这一点,并通过将preserveDrawingBuffer 设置为true 来强制浏览器制作副本并保持图像内容可访问。来自规范:
可以通过设置 WebGLContextAttributes 对象的 preserveDrawingBuffer 属性来更改此默认行为。如果此标志为真,则绘图缓冲区的内容将被保留,直到作者清除或覆盖它们。如果此标志为假,则在渲染函数返回后尝试使用此上下文作为源图像执行操作可能会导致未定义的行为。这包括 readPixels 或 toDataURL 调用,或将此上下文用作另一个上下文的 texImage2D 或 drawImage 调用的源图像。
在您提供的示例中,代码只是更改了上下文创建行:
gl = canvas.getContext("experimental-webgl", {preserveDrawingBuffer: true});
请记住,它会在某些浏览器中强制使用较慢的路径,并且性能会受到影响,具体取决于您渲染的内容和方式。在大多数桌面浏览器中应该没问题,实际上不需要制作副本,而且这些浏览器构成了绝大多数支持 WebGL 的浏览器……但仅限于现在。
但是,还有另一种选择(在规范的下一段中有些令人困惑地提到)。
基本上,您在第 2 步之前自己制作副本:在所有绘制调用完成之后,但在您从代码将控制权返回给浏览器之前。这是 WebGL 绘图缓冲区仍然完好且可访问的时候,您应该可以轻松访问像素。您使用与其他方式相同的 toDataUrl 或 readPixels 调用,只是时机很重要。
在这里,您可以两全其美。您获得了绘图缓冲区的副本,但您无需在每一帧中都为它付费,即使是那些您不需要副本的帧(可能是其中的大多数),就像您将 preserveDrawingBuffer 设置为真的。
在您提供的示例中,只需将您的代码添加到 drawScene 的底部,您应该会在下方看到画布的副本:
function drawScene() {
...
var webglImage = (function convertCanvasToImage(canvas) {
var image = new Image();
image.src = canvas.toDataURL('image/png');
return image;
})(document.querySelectorAll('canvas')[0]);
window.document.body.appendChild(webglImage);
}