OpenGL: Read pixels from FrameBuffer for picking rounded up to 255 (0xFF)
我正在尝试通过将vao id打包到RGBA并将其渲染到屏幕外的缓冲区中来实现对象选择,然后尝试使用缓冲区对象进行读取。
我通过创建纹理和z缓冲区RenderBuffer对象,然后将它们附加到FrameBuffer对象,来渲染到屏幕外缓冲区:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | /* create a framebuffer object */ glGenFramebuffers(1, &fbo); /* attach the texture and the render buffer to the frame buffer */ glBindFramebuffer(GL_FRAMEBUFFER, fbo); /* generate a texture id */ glGenTextures(1, &tex); /* bind the texture */ glBindTexture(GL_TEXTURE_2D, tex); /* create the texture in the GPU */ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, WINDOW_SIZE_X, WINDOW_SIZE_Y , 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); /* set texture parameters */ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); /* unbind the texture */ glBindTexture(GL_TEXTURE_2D, 0); /* create a renderbuffer object for the depth buffer */ glGenRenderbuffers(1, &rbo); /* bind the texture */ glBindRenderbuffer(GL_RENDERBUFFER, rbo); /* create the render buffer in the GPU */ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT , WINDOW_SIZE_X, WINDOW_SIZE_Y); /* unbind the render buffer */ glBindRenderbuffer(GL_RENDERBUFFER, 0); /* attach the texture and the render buffer to the frame buffer */ glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT , GL_RENDERBUFFER, rbo); // check the frame buffer if (glCheckFramebufferStatus( GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { std::cout <<"Framebuffer status not complete" << '\ '; } /* handle an error : frame buffer incomplete */ /* return to the default frame buffer */ glBindFramebuffer(GL_FRAMEBUFFER, 0); |
渲染完成后,我还生成了一个像素缓冲区对象以从FrameBuffer中读取:
1 2 3 4 5 6 | /* generate the pixel buffer object */ glGenBuffers(1,&pbo); glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); glBufferData(GL_PIXEL_PACK_BUFFER, WINDOW_SIZE_X * WINDOW_SIZE_Y * 4, nullptr, GL_STREAM_READ); /* to avoid weird behaviour the first frame the data is loaded */ glReadPixels(0, 0, WINDOW_SIZE_X, WINDOW_SIZE_Y, GL_BGRA, GL_UNSIGNED_BYTE, 0); |
然后在我的渲染循环中,将其绑定并渲染到该屏幕外的FrameBuffer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | GLubyte red, green, blue, alpha; /* bind the frame buffer */ glBindFramebuffer(GL_FRAMEBUFFER, fbo); /* clear the frame buffer */ glClearColor(0,0,0,0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* select the shader program */ glUseProgram(pickProgram); /* set the object color */ /*alpha = house.vaoId & 0xFF; blue = (house.vaoId >> 8) & 0xFF; green = (house.vaoId >> 16) & 0xFF; red = (house.vaoId >> 24) & 0xFF; */ GLuint objectId = 5; alpha = objectId & 0xFF; blue = (objectId >> 8) & 0xFF; green = (objectId >> 16) & 0xFF; red = (objectId >> 24) & 0xFF; //Upload the packed RGBA values to the shader glUniform4f(baseColorUniformLocation, red, green ,blue, alpha); //prepare to draw the object pvm = projectionMatrix*viewMatrix*house.modelMatrix; glUniformMatrix4fv(offScreenMatrixUniformLocation, 1, GL_FALSE, glm::value_ptr(pvm)); /* draw the object*/ glBindVertexArray(house.getVaoId()); glDrawRangeElements(GL_TRIANGLES,0,42,42,GL_UNSIGNED_SHORT,NULL); glBindVertexArray(0); //check that our framebuffer is ok if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { std::cout <<"Framebuffer Error" << '\ '; } GLuint temp; //get the object id from the read pixels temp = get_object_id(); <--- this function is explained later /* return to the default frame buffer */ glBindFramebuffer(GL_FRAMEBUFFER, 0); |
渲染到屏幕外缓冲区的片段着色器非常简单:
1 2 3 4 5 6 7 8 | #version 420 uniform vec4 BaseColor; layout(location=0) out vec4 fragColor; void main() { fragColor = BaseColor; } |
这是在屏幕外渲染期间调用的函数,用于从帧缓冲区中提取打包的RGBA:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | GLuint Engine::get_object_id() { static int frame_event = 0; GLuint object_id; int x, y; GLuint red, green, blue, alpha, pixel_index; //GLuint read_pbo, map_pbo; GLubyte* ptr; /* read one pixel buffer */ glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo_a); /* map the other pixel buffer */ ///////////////////// NOTE :5th argument, BGRA or RGBA, doesn't make a difference right? glReadPixels(0, 0, WINDOW_SIZE_X, WINDOW_SIZE_Y, GL_BGRA, GL_UNSIGNED_BYTE, 0); ptr = (GLubyte*)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_WRITE); /* get the mouse coordinates */ /* OpenGL has the {0,0} at the down-left corner of the screen */ glfwGetMousePos(&x, &y); y = WINDOW_SIZE_Y - y; object_id = -1; if (x >= 0 && x < WINDOW_SIZE_X && y >= 0 && y < WINDOW_SIZE_Y){ ////////////////////////////////////////// //I have to admit I don't understand what he does here /////////////////////////////////////////// pixel_index = (x + y * WINDOW_SIZE_X) * 4; blue = ptr[pixel_index]; green = ptr[pixel_index + 1]; red = ptr[pixel_index + 2]; alpha = ptr[pixel_index + 3]; object_id = alpha +(red << 24) + (green << 16) + (blue << 8); } glUnmapBuffer(GL_PIXEL_PACK_BUFFER); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); return object_id; } |
问题在于,本应从屏幕外的帧缓冲区读取像素并给我vao_id的最后一段代码具有以下怪异行为:
如果打包到RGBA中的4个字节中的任何一个(通过着色器发送)不是0,则该字节在另一端显示为0xFF。
所以如果我发送
1 | 00000000 00000000 00000000 00000001 |
我会得到
1 | 00000000 00000000 00000000 11111111 |
或者如果我发送
1 | 00000000 00010000 00000000 00000000 |
我会得到
1 | 00000000 11111111 00000000 00000000 |
...当我使用get_object_id()读取像素时。
如果将纹理绑定并渲染到普通FrameBuffer上的四边形上,则传递给屏幕外渲染的颜色在四边形上会正确显示。但是,由get_object_id()读取的像素会使发送的每个字节都向上舍入为255(0xFF)。因此,我的猜测是最终功能存在问题。
片段着色器输出的[0,1]范围内的值在写入帧缓冲区时会重新映射为[0,255]。
1 | glUniform4f(baseColorUniformLocation, red / 255.0f, green / 255.0f...); |