FBOs for color picking

Hi all, I would like to use the color method for picking with a separate FBO. I have run into the problem that, even though AFAICT I have setup the FBO identically to the default framebuffer, which renders fine, the underlying FBO rendering is not correct.

In an attempt to make a MWE, I ran into another problem where my simple triangle renders white instead of colored. I am not particularly concerned with this issue, but really I am wondering if the workflow I am employing is sensible and if there are any obvious problems. Thank you for your help.

The boilerplate from an existing simple triangle example:


#include <iostream>
#include <vector>
#include <GL/glew.h> // include GLEW and new version of GL on Windows
#include <GLFW/glfw3.h> // GLFW helper library
#include <stdio.h>

int main() {

  // start GL context and O/S window using the GLFW helper library
  if (!glfwInit()) {
    fprintf(stderr, "ERROR: could not start GLFW3
");
    return 1;
  }

  int width = 640;
  int height = 480;

  GLFWwindow* window = glfwCreateWindow(width, height, "Hello Triangle", NULL, NULL);
  if (!window) {
    fprintf(stderr, "ERROR: could not open window with GLFW3
");
    glfwTerminate();
    return 1;
  }
  glfwMakeContextCurrent(window);

  // start GLEW extension handler
  glewExperimental = GL_TRUE;
  glewInit();

  // get version info
  const GLubyte* renderer = glGetString(GL_RENDERER); // get renderer string
  const GLubyte* version = glGetString(GL_VERSION); // version as a string
  printf("Renderer: %s
", renderer);
  printf("OpenGL version supported %s
", version);

  // tell GL to only draw onto a pixel if the shape is closer to the viewer
  glEnable(GL_DEPTH_TEST); // enable depth-testing
  glDepthFunc(GL_LESS); // depth-testing interprets a smaller value as "closer"
  glDepthMask(GL_TRUE); // Enable depth test (z-buffer)

The FBO, VAO, and Program setup:


  ////////////////// Setup VBO ///////////////////
  float points[] = {
   0.0f,  0.5f,  0.0f,
   0.5f, -0.5f,  0.0f,
  -0.5f, -0.5f,  0.0f
  };

  uint8_t colors[] = {
   100,  100,  0,
   100,  100,  0,
   100,  100,  0
  };

  uint8_t colors_fbo[] = {
   100,  0,  100,
   100,  0,  100,
   100,  0,  100
  };

  uint32_t indices[] = {0, 1, 2}; // one triangle


  GLuint vao = 0;
  std::vector<GLuint> vbos;
  glGenVertexArrays(1, &vao);
  glBindVertexArray(vao);

  vbos.resize(3); // xyz, colors, indices
  glGenBuffers(3, vbos.data());

  glBindBuffer(GL_ARRAY_BUFFER, vbos[0]);
  glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), points, GL_STATIC_DRAW);
  glEnableVertexAttribArray(0);
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);

  glBindBuffer(GL_ARRAY_BUFFER, vbos[1]);
  glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(uint8_t), colors, GL_STATIC_DRAW);
  glEnableVertexAttribArray(1);
  glVertexAttribPointer(1, 3, GL_UNSIGNED_BYTE, GL_TRUE, 0, NULL);

  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos[2]);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * sizeof(uint32_t), indices, GL_STATIC_DRAW);
  //  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glBindVertexArray(0);
  /////////////////////////////////////////

  //////////////////// Setup FBO //////////
  GLuint fbo = 0;
  GLuint color = 0;
  GLuint depth = 0;
  GLuint fbo_vao = 0;
  std::vector<GLuint> fbo_vbos;

  glGenFramebuffers(1, &fbo);
  glBindFramebuffer(GL_FRAMEBUFFER, fbo);
  glGenTextures(1, &color);
  glBindTexture(GL_TEXTURE_2D, color);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, width, height, 0, GL_RGB, GL_FLOAT, NULL);
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                         color, 0);

  glGenRenderbuffers(1, &depth);
  glBindRenderbuffer(GL_RENDERBUFFER, depth);
  glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height);
  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth);
  GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
  if (status != GL_FRAMEBUFFER_COMPLETE){
    std::cerr<<"Framebuffer fail, :"<<status<<std::endl;
  }

  // same points, different colors
  glGenVertexArrays(1, &fbo_vao);
  glBindVertexArray(fbo_vao);
  fbo_vbos.resize(3);
  glGenBuffers(3, fbo_vbos.data());
  glBindBuffer(GL_ARRAY_BUFFER, fbo_vbos[0]);
  glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), points, GL_STATIC_DRAW);
  glEnableVertexAttribArray(0);
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);

  glBindBuffer(GL_ARRAY_BUFFER, fbo_vbos[1]);
  glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(uint8_t), colors_fbo, GL_STATIC_DRAW);
  glEnableVertexAttribArray(1);
  glVertexAttribPointer(1, 3, GL_UNSIGNED_BYTE, GL_TRUE, 0, NULL);

  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, fbo_vbos[2]);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * sizeof(uint32_t), indices, GL_STATIC_DRAW);

  glBindFramebuffer(GL_FRAMEBUFFER, 0);
  ////////////////////////////////////////


  /////////////////// Setup Program ///////
  const char* vertex_shader =
    "#version 330 core
"
    "layout (location = 0) in vec3 position;"
    "layout (location = 1) in vec3 color;"
    "flat out vec3 vColor;"
    "void main() {"
    "  vColor = color;"
    "  gl_Position = vec4(vp, 1.0);"
    "}";

  const char* fragment_shader =
    "#version 330 core
"
    "flat in vec3 vColor;"
    "out vec4 fColor;"
    "void main() {"
    "  fColor = vec4(vColor, 1.0f);"
    "}";

  GLuint vs = glCreateShader(GL_VERTEX_SHADER);
  glShaderSource(vs, 1, &vertex_shader, NULL);
  glCompileShader(vs);
  GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
  glShaderSource(fs, 1, &fragment_shader, NULL);
  glCompileShader(fs);

  GLuint shader_programme = glCreateProgram();
  glAttachShader(shader_programme, fs);
  glAttachShader(shader_programme, vs);
  glLinkProgram(shader_programme);
  ///////////////////////////////////////////

The render loop:


  float pixel_value[3];
  while(!glfwWindowShouldClose(window)) {
    // wipe the drawing surface clear
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glUseProgram(shader_programme);

    // write to, read from default buffer
    glBindVertexArray(vao);
    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);
    glReadPixels(320, 240, 1, 1, GL_RGB, GL_FLOAT, pixel_value);
    std::cout<<" Pixel value default buffer: "<<pixel_value[0]<<" "<<pixel_value[1]<<" "<<pixel_value[2]<<std::endl;
    glBindVertexArray(0);

    // write to, read from fbo
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glBindVertexArray(fbo_vao);
    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);
    glReadPixels(320, 240, 1, 1, GL_RGB, GL_FLOAT, pixel_value);
    std::cout<<" Pixel value fbo: "<<pixel_value[0]<<" "<<pixel_value[1]<<" "<<pixel_value[2]<<std::endl;
    glBindVertexArray(0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    glfwPollEvents();
    // put the stuff we've been drawing onto the display
    glfwSwapBuffers(window);
  }

  // close GL context and any other GLFW resources
  glfwTerminate();
  return 0;
}

Simple makefile:


OBJS = fbo_mwe.o

minimalfbo: $(OBJS)
	$(CC) -o fbo_mwe $(OBJS) -lm -lGL -lGLU -lGLEW -lglut -lglfw -lstdc++

error checking is crucial, i dont see that in your code

having a GL debug context can help you much more because instead of simple error codes you get detailed descriptions for the error

instead of “color” picking, you can render exact integers into an separate FBO attachment (integer texture). and instead of stalling the pipeline when reading back pixels of the FBO, you can use a double-buffered GL_PIXEL_PACK_BUFFER and read from the one that isnt used in the current frame.

Thanks for the tip on the debug context (I have appropriate error checking in my actual program), that will be very helpful going forward.

[QUOTE=john_connor;1289844]instead of “color” picking, you can render exact integers into an separate FBO attachment (integer texture).
[/QUOTE]
This would be perfect for my project. So, please tell me if I am understanding correctly. In the FBO, I will still have my renderbuffer for point locations, but now in the texture2D buffer I will directly set labels as integers instead of color vecs, where the integers are still output by the fragment shader. Then, calling readpixels will give me the original integer value I set in the shader. Saying it now, it makes complete sense, I was still thinking of rendering from a visual perspective rather than simply as generic data.

This is something I had planned on but have no experience with. I will look to gain some, but particularly helpful links* are always appreciated!

  • I thought I had thoroughly scoured for all decent opengl material and tutorials, but somehow had not seen yours. They look outstanding and invaluable to me, thank you for putting them all together!