10

我正在尝试使用以下代码段将此页面canvas上的元素转换为 png(例如,在 JavaScript 控制台中输入):

(function convertCanvasToImage(canvas) {
  var image = new Image();
  image.src = canvas.toDataURL("image/png");
  return image;
})($$('canvas')[0]);

不幸的是,我得到的 png 是完全空白的。另请注意,调整页面大小后,原始画布变为空白。

为什么会canvas出现空白?如何将其转换canvas为 png?

4

3 回答 3

17

Kevin Reid 的preserveDrawingBuffer建议是正确的,但(通常)有更好的选择。tl;dr 是最后的代码。

将渲染网页的最终像素放在一起可能会很昂贵,并将其与渲染 WebGL 内容进行协调更是如此。通常的流程是:

  1. JavaScript 向 WebGL 上下文发出绘图命令
  2. JavaScript 返回,将控制权返回给主浏览器事件循环
  3. WebGL 上下文将绘图缓冲区(或其内容)转换为合成器,以便集成到当前正在屏幕上呈现的网页中
  4. 页面,带有 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 绘图缓冲区仍然完好且可访问的时候,您应该可以轻松访问像素。您使用相同toDataUrlreadPixels其他方式使用的调用,重要的是时机。

在这里,您可以两全其美。您获得了绘图缓冲区的副本,但您无需在每一帧中都为它付费,即使是那些您不需要副本的帧(可能是其中的大多数),就像您将preserveDrawingBuffer设置为 true 一样。

在您提供的示例中,只需将代码添加到底部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);
}
于 2012-09-23T00:12:54.827 回答
3

这里有一些东西可以尝试。我不知道是否需要这些中的任何一个完成这项工作,但它们可能会有所作为。

  • 添加preserveDrawingBuffer: truegetContext属性。
  • 尝试使用稍后的动画教程来执行此操作;即在画布上反复绘制而不是一次。
于 2012-09-22T01:27:43.580 回答
0

toDataURL()从 Buffer 中读取数据。

我们不需要使用preserveDrawingBuffer: true

在读取数据之前,我们还需要使用render()

最后:

renderer.domElement.toDataURL();
于 2020-07-07T15:32:33.390 回答