为什么我的画布在转换为图像后会变成空白?

Ran*_*lue 10 javascript canvas webgl

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

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

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

为什么canvas空白?我该如何将其转换canvas为png?

Bre*_*nny 17

凯文里德的preserveDrawingBuffer建议是正确的,但有(通常)更好的选择.tl; dr是最后的代码.

将渲染网页的最终像素组合在一起并将其与渲染WebGL内容进行协调可能会非常昂贵.通常的流程是:

  1. JavaScript向WebGL上下文发出绘制命令
  2. JavaScript返回,将控制权返回给主浏览器事件循环
  3. WebGL上下文将绘图缓冲区(或其内容)转换为合成器,以便集成到当前正在屏幕上呈现的网页中
  4. 带有WebGL内容的页面显示在屏幕上

请注意,这与大多数OpenGL应用程序不同.在这些内容中,渲染内容通常直接显示,而不是与页面上的一堆其他内容合成,其中一些实际上可能位于WebGL内容之上并与之混合.

在步骤3之后,WebGL规范被更改为将绘图缓冲区视为基本上为空.您在devtools中运行的代码将在步骤4之后出现,这就是为什么您得到一个空缓冲区.对规范的这一更改允许在平台上实现大幅性能改进,其中步骤3之后的消隐基本上是硬件中实际发生的情况(如在许多移动GPU中).如果你想在第3步之后解决这个问题,有时会复制WebGL内容,浏览器必须在第3步之前总是复制绘图缓冲区,这会使你的帧速率在某些平台上急剧下降.

您可以这样做并强制浏览器进行复制并通过设置preserveDrawingBuffer为true来保持图像内容的可访问性.从规格:

可以通过设置WebGLContextAttributes对象的preserveDrawingBuffer属性来更改此默认行为.如果此标志为true,则应保留绘图缓冲区的内容,直到作者清除或覆盖它们为止.如果此标志为false,则在返回呈现函数后尝试使用此上下文作为源图像执行操作可能会导致未定义的行为.这包括readPixels或toDataURL调用,或使用此上下文作为另一个上下文的texImage2D或drawImage调用的源图像.

在您提供的示例中,代码只是更改上下文创建行:

gl = canvas.getContext("experimental-webgl", {preserveDrawingBuffer: true});
Run Code Online (Sandbox Code Playgroud)

请记住,它会强制某些浏览器中的慢速路径和性能受到影响,具体取决于渲染的内容和方式.在大多数桌面浏览器中你都应该没问题,因为这些浏览器实际上并不需要复制,而且这些浏览器构成了绝大多数支持WebGL的浏览器......但仅限于现在.

但是,还有另一种选择(在规范的下一段中有点令人困惑地提到).

基本上,您在第2步之前自己制作副本:在完成所有绘制调用之后但在您从代码中将控制权返回给浏览器之前.这是当WebGL绘图缓冲区仍然可以访问时,您应该可以轻松访问像素.你使用相同toDataUrl或者readPixels你会使用的电话,这只是重要的时间.

在这里,您将获得两全其美.你得到了一个绘图缓冲区的副本,但是你不需要在每一帧中付费,即使那些你不需要副本(可能是其中大部分)的那些,就像你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);
}
Run Code Online (Sandbox Code Playgroud)