Dependent on what you try to achieve. You may read pixels from the framebuffer directly - for instance to take screenshots.
But its almost always a better idea to use a Framebuffer Object (FBO). The FBO itself does little - but you can attach color & depth textures to it and bind it as a render target. This way you can render to textures which you can then use in shaders for further processing.
At the end of the render pipeline you may need to render to the screen by setting the render target to NULL. Usually what people do it to have a postprocessing shader do that. A simple postprocessing shader could, for example, adjust gamma and saturation. But you can also do complicated things like SSAO, FXAA, fog, outlines etc. but most require you to have a depth-buffer texture too.
Simplest implementation:
// CREATE FBO AND ATTACH COLOR & DEPTH BUFFER (USE glGetError() TO CHECK RESULT!)
GLuint fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);
// ENABLE FBO
glBindFramebuffer(GL_FRAMEBUFFER, fbo); // USE 0 TO DISABLE FBO
Make sure to have the depth buffer attachment to have the GL_DEPTH_COMPONENT format, the color attachment (texture) should have NATIVE_PIXEL_FORMAT_32BPP. From my experience using glTexStorage2D is not neccessary, but maybe check that out too. Using glTexImage2D should do the trick when creating render targets.
However, there are a few pitfalls when using FBOs. For example, you should always use glViewport to adjust the output to the size of the textures. Depth and Color must be the same size and the right format. You may also want to use glDrawBuffer and glReadBuffer to fix some problems that may occour from switching to FBOs and back.
Also glCheckFramebufferStatus(GL_FRAMEBUFFER) can help you with debugging your rendering pipeline in case of errors.
When using the render target textures in shaders (e.g. postprocessing), just unbind the FBO or the textures and use “uniform sampler2D” for both the color & depth. As I remember there is another sampler type for the depth buffer but I personally never needed it. You may simple do: float z = texture2D(DepthMap, co).z;