4

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?

チーズパン
  • 2,752
  • 8
  • 42
  • 63
  • 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 Apr 05 '20 at 17:30

1 Answers1

3

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);
}
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • Unfortunately neither of the solutions work. Could you elaborate a little more? I. e. does buffer creation still target `GL_DRAW_FRAMEBUFFER`? What about the `GL_RENDERBUFFER`, does that look right to you? – チーズパン Apr 06 '20 at 14:30
  • 1
    @チーズパン For the buffer creation you can use `GL_DRAW_FRAMEBUFFER` or `GL_FRAMEBUFFER`. The render buffer looks corrects. Read about [Debug Output](https://www.khronos.org/opengl/wiki/Debug_Output) possibly it helps to find the issue. – Rabbid76 Apr 06 '20 at 14:44
  • 1
    I did find my bug, it was unrelated to buffers though. I was passing the wrong viewport dimensions while I created the framebuffer. I will still accept your answer, because it contains relevant information! Would be nice if you added the "Debug"-reference to your original answer, imho. Sorry for the inconvenience. – チーズパン Apr 06 '20 at 15:03