如何将 LWJGL 游戏场景渲染到 ByteBuffer?

How to render a LWJGL game scene to a ByteBuffer?

我目前正在进行一个项目,该项目涉及将 LWJGL 游戏场景渲染为视频流而不是 window。我相信如果我将游戏场景渲染为中间格式(例如 ByteBuffer),我可以实现这一点。我正在尝试扩展 LWJGL VoxelGame demo 作为概念证明。

我找到了一个 and a forum post 但我没能成功。我是 OpenGL 和 LWJGL 的初学者,我正在努力寻找相关的可理解文档。

在渲染循环 (runUpdateAndRenderLoop) 开始时,函数 glBindFramebuffer 被调用。据我了解,它将 FBO 绑定到当前上下文,以便任何渲染都将定向到它。

我曾尝试使用 glGetTexImageglReadPixels 来填充 ByteBuffer,但没有成功。我也在 glBlitFramebuffer 之后尝试过,因为我想将完整的渲染图像获取到 ByteBuffer。

如何将当前游戏场景渲染到 ByteBuffer?有没有更好的方法将游戏场景渲染到视频流而不是中间 ByteBuffer?

private void runAndUpdateRenderLoop() {
    // ...
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
    byte[] pixels = new byte[width * height * 4];
    ByteBuffer buffer = ByteBuffer.allocateDirect(pixels.length).order(ByteOrder.nativeOrder());
    glGetTexImage(GL_TEXTURE_BUFFER, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
    glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
    glfwSwapBuffers(window);
}

你的代码中有一堆问题,甚至还没有看到问题的核心,因为它在 // ... 部分:

  • 当你尝试在无头设置中渲染时,你不会使用 glfwSwapBuffers,只有当你渲染到上下文后台缓冲区并想将它交换到屏幕时才需要.

  • 你不需要glGetTexImageglReadPixels,它们都是从显存读取数据到系统内存。在您的情况下,您正在读取 buffer 中的纹理数据,然后用后缓冲区数据覆盖它,后缓冲区数据应该是空的,因为您从未写入它。

  • 你肯定不要glBlitFramebuffer,那是为了从显存复制到显存。你真的需要停下来阅读文档,在你的视频驱动程序中使用随机函数肯定会导致它崩溃。

  • 您需要启用 OpenGL 的调试层验证,尤其是在尝试所有这些随机函数时。与此相关的是,您需要检查您的帧缓冲区设置 (glCheckFramebufferStatusEXT),我愿意打赌您没有这样做,但您没有显示您的代码。

  • 你没有显示你的帧缓冲区绑定,所以我不能确定你是否在这样做,但你需要将帧缓冲区绑定为“读取”( GL_READ_FRAMEBUFFER) 在阅读之前。我只看到一个“绘制”帧缓冲区绑定,而你只是随机清除它。

  • 最后,你不应该在从视频内存读取操作时停止 CPU,尤其是当你有你应该做的事情时,比如视频编码。以循环方式使用像素缓冲对象对传输进行双倍或三倍缓冲,因此不会出现停顿。

@Blindy 的回答 100% 正确,您应该接受它作为答案。

但是,如果您想要 直接 工作解决方案的代码,请在

之后插入以下内容

glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);

但是之前

glfwSwapBuffers(window);

代码:

ByteBuffer bb = org.lwjgl.system.MemoryUtil.memAlloc(width * height * 4);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, bb);
// Test with stb_image to write as jpeg:
// org.lwjgl.stb.STBImageWrite.stbi_flip_vertically_on_write(true);
// org.lwjgl.stb.STBImageWrite.stbi_write_jpg("frame.jpg", width, height, 4, bb, 50);
org.lwjgl.system.MemoryUtil.memFree(bb);

这将导致 ByteBuffer bb 保存当前帧的像素数据。

然而,正如@Blindy 也指出的那样,这是一个严重的 GPU 停顿,因为您被迫等待当前帧的数据完全渲染并强制 GPU->CPU 传输到您的字节缓冲区。 当您启用调试消息输出时,Nvidia 的驱动程序也会向您大喊:

Pixel-path performance warning: Pixel transfer is synchronized with 3D rendering.

根据您的实际用例,其他方法可能更有用,例如直接从 GPU 内存编码视频(例如使用 NVENC)。