能否轻松地从 WebGL 纹理对象创建 HTML 图像元素?

13
我想将我已经渲染的 WebGLTexture 对象转换成一个 HTML 图像元素。这样可以显示离屏渲染的结果以便进行调试。与我的当前调试方法——将纹理渲染到全屏四边形相比,这应该更容易实现。
在 WebGL 中,从图像元素创建纹理非常容易:
var image = new Image();
image.src = "myImg.jpg";

// image loads...

var texture = gl.createTexture();
gl.bindTexture(texture);
gl.texImage2D(_gl.TEXTURE_2D, 0, _gl.RGBA, _gl.RGBA, _gl.UNSIGNED_BYTE, image);

图片加载和解码完全由系统自动处理。

有没有类似简单的方法来实现相反的操作?即:

// This doesn't work
var img = new Image(texture);

// But maybe this could
var img = createImageFromTexture(texture);

function createImageFromTexture(texture) {
    // ... some combination of tricks ...
}

如果有一种方法可以做到这一点,我相信它在除了调试以外的其他情境下也会非常有用。我会继续尝试找到一种方法来实现它,但我感觉肯定已经有人尝试过了。


我在寻找一种方法时看到了这个链接: https://dev59.com/WlPTa4cB1Zd3GeqPjG1M - uotonyh
你是否找到了一个方法? - Jón Trausti Arason
3个回答

25
你可以创建一个由纹理支持的framebuffer,然后使用gl.readPixels()从framebuffer中读取原始像素数据。一旦你获取到了像素数据,你就可以使用ImageData将它们复制到一个2D画布中。然后,通过将图像的src属性设置为canvas.toDataURL(),你可以构造一个图像。
function createImageFromTexture(gl, texture, width, height) {
    // Create a framebuffer backed by the texture
    var framebuffer = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);

    // Read the contents of the framebuffer
    var data = new Uint8Array(width * height * 4);
    gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, data);

    gl.deleteFramebuffer(framebuffer);

    // Create a 2D canvas to store the result 
    var canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    var context = canvas.getContext('2d');

    // Copy the pixels to a 2D canvas
    var imageData = context.createImageData(width, height);
    imageData.data.set(data);
    context.putImageData(imageData, 0, 0);

    var img = new Image();
    img.src = canvas.toDataURL();
    return img;
}

虽然这种方法可以工作,但它似乎是一种非常低效的解决方案。有更好的API吗? - akaltar
你在哪里使用这个结果?用canvas代替制作img是否可行?另一个选择是使用ImageData和createImageBitmap创建ImageBitmap而不是img。 - nkron
我想在React DOM的<img>元素中显示使用WebGL着色器进行重渲染的内容。该内容是即时生成的,但除此之外是静态的。我使用一个单独的WebGL上下文来为不同的设置生成一系列预览,并且按钮显示预览。 - akaltar
2
从您的建议开始,我实际上找到了canvas.toBlob,然后使用URL.createObjectURL应该显着提高性能。将进行测试,如果效果不错可能会发布答案。 - akaltar
是的,在支持它的浏览器中,“toBlob”将会是一种改进。它将允许主线程在编码图像时处理其他任务。但它仍然需要图像编码和分配来保存已编码的图像。如果您可以将“img”的更改为“canvas”,并将图像数据放入画布中,则可能是最大的改进。 - nkron
ImageBitmapRenderingContext 这可能是一个更好的解决方案,但我还没有追求更高的性能。 - akaltar

2

0

WebGL上下文是一个画布,有几种良好记录的方法可以从画布中获取图像,因此基本的方法将如下所示:

  1. 在全屏(或者称为全画布)四边形上绘制您的纹理。最好在事先调整画布大小,使画布与您要绘制的纹理图像具有1:1的像素比例。
  2. 使用类似于这里描述的方法来从画布中获取图像数据。

如果您只想保存图像,可能需要将其发送到服务器进行处理,但如果您只想在渲染该页面的同一页面上显示该图像,则可以将给定的dataURI设置为img标签的src属性,它应该正确显示。


1
我正在努力使用第2步的方法从WebGL画布中获取图像。目前我得到的只是完全透明的.png图像。是否有什么我需要在我的画布中设置但我忽略了的东西? - James Bedford

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接