如何从 OpenGL 中的帧缓冲区纹理中采样像素?

Posted

技术标签:

【中文标题】如何从 OpenGL 中的帧缓冲区纹理中采样像素?【英文标题】:How to sample a pixel from a framebuffer texture in OpenGL? 【发布时间】:2020-12-21 01:06:58 【问题描述】:

我正在使用 ImGUI 为图像创建颜色选择器 OpenGL 应用程序。我设法通过将图像加载到glTexImage2D 并使用ImGUI::Image() 来加载图像。 现在我想实现一个方法,它可以在鼠标左键单击的情况下确定像素的颜色。

这是我加载纹理,然后将其分配给帧缓冲区的方法:

bool LoadTextureFromFile(const char *filename, GLuint *out_texture, int *out_width, int *out_height,ImVec2 mousePosition ) 

    // Reading the image into a GL_TEXTURE_2D
    int image_width = 0;
    int image_height = 0;
    unsigned char *image_data = stbi_load(filename, &image_width, &image_height, NULL, 4);
    if (image_data == NULL)
        return false;

    GLuint image_texture;
    glGenTextures(1, &image_texture);
    glBindTexture(GL_TEXTURE_2D, image_texture);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image_width, image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    stbi_image_free(image_data);
    glBindTexture(GL_TEXTURE_2D, 0);

    *out_texture = image_texture;
    *out_width = image_width;
    *out_height = image_height;

    // Assigning texture to Frame Buffer
    unsigned int fbo;
    glGenFramebuffers(1, &fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, image_texture, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, image_texture, 0);

    if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
        std::cout<< "Frame buffer is done."<< std::endl;
    
    return true;

不幸的是,上面的代码导致了一个完全空白的屏幕。我想,在设置帧缓冲区时我错过了一些东西。

这是方法,我想使用鼠标坐标对帧缓冲区纹理进行采样:

    void readPixelFromImage(ImVec2 mousePosition) 
        unsigned char pixels[4];
        glReadBuffer(GL_COLOR_ATTACHMENT0);
        glReadPixels(GLint(mousePosition.x), GLint(mousePosition.y), 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
    
        std::cout << "r: " << static_cast<int>(pixels[0]) << '\n';
        std::cout << "g: " << static_cast<int>(pixels[1]) << '\n';
        std::cout << "b: " << static_cast<int>(pixels[2]) << '\n';
        std::cout << "a: " << static_cast<int>(pixels[3]) << '\n' << std::endl;
    

感谢任何帮助!

【问题讨论】:

当心鼠标坐标指的是与 OpenGL 坐标不同的窗口坐标...如果 ysy 视图大小,您很可能应该反转 y 位置,然后使用 @987654329 @ ...如果您在窗口中有面板和其他东西,您也需要考虑这一点...您也可以直接从纹理中使用pick textels ...在图像渲染和从中选择glReadPixels也很重要同一个线程,...因为您很可能会在某些情况下选择您的图像当时可能是空白的。 见OpenGL 3D-raypicking with high poly meshes有glReadPixel直接帧缓冲的例子(没有PBO/FBO的附件)直接选择纹理你可以使用glGetTexImage 【参考方案1】:

您的代码中确实缺少一些东西:

您设置了一个仅包含一个纹理缓冲区的新帧缓冲区。这没关系,所以 glCheckFramebufferStatus 等于 GL_FRAMEBUFFER_COMPLETE。但是您的帧缓冲区没有附加渲染缓冲区。如果你想在屏幕上渲染你的图像,你应该使用默认的帧缓冲区。此帧缓冲区是从您的 GL 上下文创建的。 但是,文档说:默认帧缓冲区无法更改其缓冲区附件,[...] https://www.khronos.org/opengl/wiki/Framebuffer。因此,将纹理或渲染缓冲区附加到默认 FB 肯定是不可能的。但是,您可以像以前一样生成一个新的 FB,对其进行渲染,最后将结果(或对缓冲区进行 blit)渲染到您的默认 FB。也许这种技术的一个很好的起点是https://learnopengl.com/Advanced-Lighting/Deferred-Shading

此外,如果您打算只从 GPU 读取渲染值,则使用 renderbuffer 而不是 texture 会更高效。您甚至可以将多个渲染缓冲区附加到您的帧缓冲区(如在延迟着色中)。示例:您可以使用第二个渲染缓冲区来渲染对象/实例 ID(因此,渲染缓冲区将是单通道整数),您的第一个渲染缓冲区将用于正常绘图。使用 glReadPixels 读取第二个渲染缓冲区,您可以直接读取绘制的实例,例如鼠标位置。这样,您可以非常高效地启用鼠标选择。

【讨论】:

以上是关于如何从 OpenGL 中的帧缓冲区纹理中采样像素?的主要内容,如果未能解决你的问题,请参考以下文章

Opengl 和 Webgl:从附加到当前帧缓冲区的纹理中采样

opengl - 比我设置的更小的帧缓冲纹理?

✠OpenGL-8-阴影

帧缓冲区和在opengl中使用着色器

无法从 OpenGL 中的着色器访问先前渲染的纹理

OpenGL ES之如何传输一个超大数组给着色器程序