这很简单。当您制作 WebGL 画布时,它有 2 个缓冲区,绘图缓冲区(也称为后缓冲区)和显示缓冲区(也称为前缓冲区)。
您绘制到绘图缓冲区。
如果你在画布上画了一些东西,这意味着你在设置渲染到画布时调用gl.clear 或gl.draw???,那么浏览器会将画布标记为“需要合成”。当前事件退出后,下次浏览器合成页面(将所有元素绘制在一起)时,要么将绘图缓冲区复制到显示缓冲区,要么交换绘图缓冲区和显示缓冲区。
它的作用取决于浏览器和许多其他因素。如果您设置preserveDrawingBuffer: true,那么它总是将绘图缓冲区复制到显示缓冲区。如果preserveDrawingBuffer 为假(默认值),则交换或复制取决于浏览器和许多其他因素,但无论如何,当preserveDrawingBuffer 为假时,WebGL 将在交换或复制后清除绘图缓冲区,这样您就不能说出区别,这样不管它选择哪一个,结果都是一样的。
它有 2 个缓冲区,因为浏览器希望能够并行运行。有了这个设计,它可以在任何时候或需要时使用显示缓冲区来绘制页面,因为它包含了你上次渲染的结果。如果没有它,如果您只有绘图缓冲区并再次开始绘图,并且并行地浏览器将所有元素组合在一起,它可能会在最终使用绘图缓冲区时从绘图缓冲区中获取您绘制的一半图像。
请注意,有 2 个缓冲区这一事实大部分是您可以忽略的。从编程 WebGL 的角度来看,实际上只有 2 个后果
如果 preserveDrawingBuffer 为 false(默认值),则绘图缓冲区将被清除,然后浏览器合成页面。其效果是,每次更新时都需要绘制所有内容。你不能每帧画一点。如果要查看 3 个圆圈,则需要在同一帧中绘制 3 个圆圈。这对于大约 99% 的游戏和大约 99% 的 WebGL 应用程序来说是正常的。在 OpenGL、DirectX、Metal、Vulkan 等中也是如此。在这些系统中编写的游戏也会在每一帧中绘制所有内容。
-
如果您打算使用 canvas.toDataURL 或 canvas.toBlob 或 gl.readPixels 或任何其他方式从 WebGL 画布获取数据,除非您在同一事件中阅读它,那么当您尝试时可能会很清楚阅读它。
换句话说,如果你这样做
function render() {
// draw stuff with WebGL
}
function renderLoop() {
render();
requestAnimationFrame(renderLoop);
}
someButton.addEventListener('click', () => {
// take screenshot
const screenshotDataURL = webglCanvas.toDataURL();
});
屏幕截图可能会失败,因为当用户单击someButton 时,WebGL 可能会清除绘图缓冲区。
解决方案是设置preserveDrawingBuffer: true 或确保在同一事件中呈现
someButton.addEventListener('click', () => {
// !!! render in the click event
render();
// take screenshot
const screenshotDataURL = webglCanvas.toDataURL();
});
同样,如果您想在多个帧上绘制,例如绘制程序,那么最简单的解决方案是在创建 WebGL 上下文时设置preserveDrawingBuffer: true。
为了增加混淆,您提到了渲染缓冲区和帧缓冲区。这些是 WebGL 中的特定内容。
渲染缓冲区类似于纹理,但它与纹理不同,它不能用作着色器的输入。只能作为输出使用。
帧缓冲区是纹理和渲染缓冲区的集合。当您想要渲染到纹理时,您可以将一个或多个纹理和渲染缓冲区附加到帧缓冲区。然后告诉 WebGL 你想渲染到帧缓冲区而不是画布。完成后,您可以使用结果呈现到画布上。
有关示例,请参阅 https://webglfundamentals.org/webgl/lessons/webgl-render-to-texture.html。