glReadPixels 可以用来从 GL_TEXTURE_3D 读取图层吗?
Posted
技术标签:
【中文标题】glReadPixels 可以用来从 GL_TEXTURE_3D 读取图层吗?【英文标题】:Can glReadPixels be used to read layers from GL_TEXTURE_3D? 【发布时间】:2020-02-16 09:50:47 【问题描述】:我正在尝试读取使用 FBO 渲染的 3D 纹理。这个纹理是如此之大,以至于glGetTexImage
导致GL_OUT_OF_MEMORY
错误,因为nvidia
驱动程序无法为中间存储分配内存*(我想这是为了避免更改目标缓冲区以防万一错误)。
于是我想到了逐层获取这个纹理,在渲染每一层之后使用glReadPixels
。但是glReadPixels
没有图层索引作为参数。它实际显示为将 I/O 定向到特定层的唯一位置是几何着色器中的gl_Layer
输出。那是写作阶段,不是阅读。
当我尝试在渲染每一层之后简单地调用glReadPixels
时,我只得到了第0 层的纹素。所以glReadPixels
至少不会失败一些东西 .
但问题是:我可以使用glReadPixels
获得任意层的 3D 纹理吗?如果没有,考虑到上述内存限制,我应该改用什么?我是否必须在着色器中从 3D 纹理中采样图层以将结果渲染为 2D 纹理,然后再读取 此 2D 纹理?
*这不是猜测,我实际上已经从 nvidia 驱动程序的共享库中追踪到失败的 malloc
调用(以纹理的大小作为参数) .
【问题讨论】:
【参考方案1】:如果您有权访问 GL 4.5 或 ARB_get_texture_sub_image,则可以使用 glGetTextureSubImage
。顾名思义,它用于查询纹理图像数据的子部分。这使您可以读取纹理的切片,而无需一次性获得整个内容。
扩展名似乎是 fairly widely supported,可用于仍受其 IHV 支持的任何实现。
【讨论】:
这个扩展给了我希望......直到我尝试并看到了现实。是的,几乎所有地方都支持它。但似乎 GTX 750Ti 上的 Linuxnvidia
驱动程序在以 glGetTextureSubImage(texture,0, 0,0,z, w,h,1, GL_RGBA,GL_FLOAT, w*h*4*sizeof subpixels[0], subpixels+z*w*h*4);
进行调用时获取了整个纹理,并且仅将数据从中间 malloc
ed 存储复制到目标缓冲区。所以我得到了和glGetTexImage
一样的失败。【参考方案2】:
是的,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 上分配的每个纹理都映射到进程的地址空间(至少在 Linux/nvidia
上)。因此,如果您的进程没有malloc
甚至一半可用的 RAM,那么这些大型映射可能已经使用了它的地址空间。再加上一点内存碎片,你会得到GL_OUT_OF_MEMORY
或malloc
失败,或std::bad_alloc
比预期更早的地方。
正确的长期解决方案是拥抱 64 位现实并将您的应用编译为 64 位代码。这就是我最终要做的事情,抛弃了所有这些一层一层的杂乱无章并大大简化了代码。
【讨论】:
【参考方案3】:因此,一旦获得 3D 纹理,您就可以这样做:
for (z=0;z<z_resolution_of_your_txr;z++)
render_textured_quad(using z slice of 3D texture);
glReadPixels(...);
最好将 QUAD 大小与您的 3D 纹理 x,y 分辨率相匹配并使用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));
//---------------------------------------------------------------------------
因此您需要在每次切片渲染之前更改切片制服。渲染本身只是单个 QUAD 覆盖屏幕 而视口匹配纹理 x,y 分辨率...
【讨论】:
正如我在 OP 中所说,这个确切的循环在每次迭代时为我提供了来自切片 0 的数据。 @Ruslan 那么你没有正确渲染你用什么来提取 3D 纹理纹理? 我 am 渲染正确:当我的分辨率较低以便glGetTexImage
不会失败时,它会返回预期的数据,而glReadPixels
总是返回切片的数据0.
@Ruslan 如果你想使用glReadPixels
,它并不是无关紧要的......因为深度测试可能会禁止你在渲染第一个图层后渲染任何其他图层......剔除面可能会抛出如果错误的多边形缠绕,则移除所有渲染层......您是渲染到屏幕还是不可见的 FrameBuffer ?
@Ruslan 这几乎不是相反的证明,因为glGetTexImage
是从纹理而不是从渲染的帧缓冲区复制数据,因此它与渲染无关。因此,如果glReadPixels
不正常,glGetTexImage
is 这意味着很可能渲染不正常。您可以通过查看渲染层来目视检查...是否与第一个不同以上是关于glReadPixels 可以用来从 GL_TEXTURE_3D 读取图层吗?的主要内容,如果未能解决你的问题,请参考以下文章
glReadPixels() 设置 GL_INVALID_OPERATION 错误