3
votes

I have a simple scene which renders a grid, a plane and a cube. I also have two different shaders. One is a flat shader which renders objects in a random color, the other is a noise shader which renders objects with a noise effect. I want to be able to:

  1. Render the plane and the cube via the noise shader and the grid with the flat shader.
  2. Once the user clicks the scene, I want to render the entire scene to a (offscreen) renderbuffer using flat shader only and print the clicked color acquired via glReadPixels.
  3. Continue rendering the scene using both shaders.

Basically I want to implement color picking. It does not seem to work for the cube and the plane though. The pixel values are always greyscale values from the noise shader.

This is what my drawing routine looks like:

void Draw(const bool offscreen = false)
{
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glm::mat4 projection = glm::perspective(glm::radians(camera.zoom), (float)viewport_width / (float)viewport_height, 0.1f, 100.0f);
    glm::mat4 view = camera.get_view_matrix();

    if (offscreen)
    {
        flat_shader.use();
        flat_shader.set_mat4("projection", projection);
        flat_shader.set_mat4("view", view);
        grid.Draw(&flat_shader);
        box.Draw_offscreen(&flat_shader);
        plane.Draw_offscreen(&flat_shader);
    }
    else
    {
        noise_shader.use();
        noise_shader.set_mat4("projection", projection);
        noise_shader.set_mat4("view", view);
        noise_shader.set_float("iTime", delta_time);
        plane.Draw(&noise_shader);
        box.Draw(&noise_shader);

        flat_shader.use();
        flat_shader.set_mat4("projection", projection);
        flat_shader.set_mat4("view", view);
        grid.Draw(&flat_shader);
    }

    glfwSwapBuffers(window);
}

So if offscreen is false the scene looks like this (normal rendering):

enter image description here

And this is what the scene looks like when offscreen is true:

enter image description here

This is how I create the fbo:

void init_offscreen_buffer()
{
    glGenFramebuffers(1, &fbo_off);
    glGenRenderbuffers(1, &render_buf);
    glBindRenderbuffer(GL_RENDERBUFFER, render_buf);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, viewport_width, viewport_height);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_off);
    glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, render_buf);
    // I also checked for FRAMEBUFFER_COMPLETE
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}

Now when the user clicks the scene I run pick_color_id which prints the color on the pixel clicked.

void pick_color_id(double x, double y)
{
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_off);

    Draw(true);

    GLubyte pixel_color[4];
    glReadBuffer(GL_COLOR_ATTACHMENT0);
    glReadPixels(x, 800 - y - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel_color);
    cout << "---------------------------------------------------------" << endl;
    cout << "Mouse click position:  " << x << "; " << y << endl;
    cout << "Target pixel color:    " << (unsigned int)pixel_color[0] << ";" << (unsigned int)pixel_color[1] << ";" << (unsigned int)pixel_color[2] << endl;
    cout << "---------------------------------------------------------" << endl;
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}

As I see it after I bind the buffer, it should be the render target and it should contain the flat colors for the plane and the cube. Actually it always prints greyscale colors like in the noise shader.

I think that there is something wrong with my fbo setup or usage (maybe both). What am I missing?

1
You want to read data from the frame buffer, thus the target for the framebuffer binding has to be GL_READ_FRAMEBUFFER not GL_DRAW_FRAMEBUFFER for glReadPixels.Rabbid76

1 Answers

3
votes

glReadPixels reads date from the framebuffer, thus the target for the framebuffer binding has to be GL_READ_FRAMEBUFFER not GL_DRAW_FRAMEBUFFER:

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_off);

Draw(true);

GLubyte pixel_color[4];

glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_off);
glReadBuffer(GL_COLOR_ATTACHMENT0);
glReadPixels(x, 800 - y - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel_color);

Respectively

glBindFramebuffer(GL_FRAMEBUFFER, fbo_off);

Draw(true);

GLubyte pixel_color[4];

glReadBuffer(GL_COLOR_ATTACHMENT0);
glReadPixels(x, 800 - y - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel_color);

I recommend to enable Debug Output for to find OpenGL errors. e.g:

#include <iostream>

void GLAPIENTRY DebugCallback( 
    unsigned int  source,
    unsigned int  type,
    unsigned int  id,
    unsigned int  severity, 
    int           length,
    const char   *message,
    const void   *userParam )
{
   std::cout << message << std::endl;
}

void init_opengl_debug() {
    glDebugMessageCallback(&DebugCallback, nullptr );
    glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
    glEnable(GL_DEBUG_OUTPUT);
    glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
}