是否可以使用 glReadPixels 从 GL_TEXTURE_3D 读取图层?

Can glReadPixels be used to read layers from GL_TEXTURE_3D?

我正在尝试读取使用 FBO 渲染的 3D 纹理。此纹理太大,导致 glGetTexImage 由于 nvidia 驱动程序无法为中间存储分配内存而导致 GL_OUT_OF_MEMORY 错误 *(需要,我假设,以避免在发生错误时更改目标缓冲区)。

于是我想到了逐层获取这个纹理,在我渲染每一层之后使用glReadPixels。但是 glReadPixels 没有图层索引作为参数。它实际显示为将 I/O 指向特定层的唯一地方是几何着色器中的 gl_Layer 输出。那是写作阶段,不是阅读。

当我尝试在渲染每一层之后简单地调用 glReadPixels 时,我只得到了第 0 层的纹素。所以 glReadPixels 至少不会失败 某事.

但问题是:我可以使用 glReadPixels 获得 3D 纹理的任意层吗?如果不是,考虑到上述内存限制,我应该使用什么?我是否必须在着色器中从 3D 纹理采样图层以将结果渲染为 2D 纹理,然后阅读 此 2D 纹理


*这不是猜测,实际上我已经追踪到失败的 malloc 调用(纹理大小为参数)来自 nvidia 驱动程序的共享库。

因此,一旦获得 3D 纹理,您就可以执行以下操作:

for (z=0;z<z_resolution_of_your_txr;z++)
 {
 render_textured_quad(using z slice of 3D texture);
 glReadPixels(...);
 }

最好匹配您的 3D 纹理 x,y 分辨率的 QUAD 大小并使用 GL_NEAREST 过滤...

这会很慢,所以如果您不在 Intel 上并且想要更快,您可以使用渲染到 2D 纹理,并在目标 2D 纹理上使用 glGetTexImage 而不是 glReadPixels

这里是渲染切片 z 的着色器示例:

顶点:

//------------------------------------------------------------------
#version 420 core
//------------------------------------------------------------------
uniform float aspect;
layout(location=0) in vec2 pos;

out smooth vec2 vpos;
//------------------------------------------------------------------
void main(void)
    {
    vpos=pos;
    gl_Position=vec4(pos.x,pos.y*aspect,0.0,1.0);
    }
//------------------------------------------------------------------

片段:

//------------------------------------------------------------------
#version 420 core
//------------------------------------------------------------------
uniform float slice=0.25;       // <0,1> slice of txr
in smooth vec2      vpos;
uniform sampler3D   vol_txr;    // 3D texture unit used
out layout(location=0) vec4 frag_col;
void main()
    {
    frag_col=texture(vol_txr,vec3(0.5*(vpos+1.0),slice));
    }
//---------------------------------------------------------------------------

因此您需要在每个切片渲染之前更改切片统一。渲染本身只是覆盖屏幕 <-1,+1> 的单个 QUAD,而视口匹配纹理 x,y 分辨率...

是的,glReadPixels可以从 3D 纹理中读取其他切片。只需使用 glFramebufferTextureLayer 将正确的当前切片附加到 FBO,而不是将完整的 3D 纹理作为颜色附件附加。这是 glGetTexImage 的替换代码(应事先生成一个特殊的 FBO,fboForTextureSaving):

GLint origReadFramebuffer=0, origDrawFramebuffer=0;
gl.glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &origReadFramebuffer);
gl.glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &origDrawFramebuffer);
gl.glBindFramebuffer(GL_FRAMEBUFFER, fboForTextureSaving);
for(int layer=0; layer<depth; ++layer)
{
    gl.glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                 texture, 0, layer);
    checkFramebufferStatus("framebuffer for saving textures");
    gl.glReadPixels(0,0,w,h,GL_RGBA,GL_FLOAT, subpixels+layer*w*h*4);
}
gl.glBindFramebuffer(GL_READ_FRAMEBUFFER, origReadFramebuffer);
gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, origDrawFramebuffer);

不管怎么说,这都不是长久之计。 GL_OUT_OF_MEMORY 大纹理错误的第一个原因实际上并不缺少 RAM 或 VRAM。它更微妙:在 GPU 上分配的每个纹理都映射到进程的地址 space(至少在 Linux/nvidia 上)。因此,如果您的进程没有 malloc 可用 RAM 的一半,那么它的地址 space 可能已经被这些大型映射使用了。再加上一点内存碎片,你要么 GL_OUT_OF_MEMORY,要么 malloc 失败,或者 std::bad_alloc 比预期更早。

正确的长期解决方案是接受 64 位现实并将您的应用程序编译为 64 位代码。这就是我最终所做的,放弃了所有这些逐层的混乱并大大简化了代码。

如果您可以访问 GL 4.5 或 ARB_get_texture_sub_image,您可以使用 glGetTextureSubImage。顾名思义,它用于查询纹理图像数据的子部分。这使您可以读取纹理的切片,而不必一次获得全部内容。

该扩展似乎 fairly widely supported,可用于仍受其 IHV 支持的任何实现。