Your FBO βprojectionβ on the screen depends on the state of the sampler, in particular, the state of the texture filter is to blame.
By default, if you just bind the texture attachment that you nested from your FBO into the texture block and apply it, it will use LINEAR selections. This differs from blitting directly on the screen, as it would be traditional if you had not used FBO.
The default state table for samplers in OpenGL: http://www.opengl.org/registry/doc/glspec44.core.pdf p. 541, Table 23.18 Textures (state per sampler object)
If you want to reproduce the paint effect without FBO, you need to stretch the square (or two triangles) above your viewport and use the NEAR neighboring selection for your texture filter. Otherwise, it will display adjacent texels in your FBO and interpolate them for each pixel on the screen. This is the reason for your smoother image on the left side, which illustrates the smoothing shape. It is worth noting that this is not even close to the same as MSAA or SSAA, which increase the sampling rate when the geometry is rasterized to correct errors with under-sampling, but it achieves a similar effect.
This is sometimes desirable, however. Many intensive processing algorithms work with a resolution of 1/4, 1/8 or lower, and then use a bilinear or two-sided filter to increase the resolution to view resolution without blocking associated with the selection of nearest neighbors.
The polygon mode state should work fine. You will need to remember to return it back to GL_FILL before you draw your quad over the viewport. Again, it all comes back to governing the state here - your box will require some very specific states to get consistent results. To effectively implement this method, you probably have to implement a more complex control system / batch processor, you can no longer just install glPolygonMode (...) once globally and forget about it :)
UPDATE:
Thanks to the comments from datenwolf, it should be noted that the above discussion of texture filtering was on the assumption that your FBO was in a different resolution than the viewport you were trying to stretch.
If your FBO and viewing area have the same resolution, and you still get these artifacts from LINEAR texture filtering, then you have not correctly set your texture coordinates. The problem with this scenario is that you sample your FBO texture at locations other than the texel centers, and this causes interpolation where there is no need.
Fragments are selected by their centers (not multisample) by default in GLSL, so if you correctly configure the coordinates and positions of the vertex texture, you will not need to do the math texel offset in your texture coordinates of each vertex. Perspective projection can ruin your day if you try to do 1: 1 mapping, although you should either use spelling projection, or better still use the NDC coordinates and not project at all when you draw your square above the viewport.
You can use the following vertex coordinates in the coordinates of a normalized device: (-1, -1, -1), (-1,1, -1), (1,1, -1), (1, -1, -1) for the 4 corners of your viewport if you replace the traditional model / projection matrix with an identity matrix (or just don't multiply the position of the vertices by any matrix in your vertex shader).
You should also use CLAMP_TO_EDGE as the transfer state, because it ensures that you never create texture coordinates outside the range of the center of the first text and the center of the last text in the given direction (s, t). CLAMP will actually generate values ββ0 and 1 (which are not texel centers) for anything at the edge of the FBO texture attachment or beyond.
As a bonus, if you ALWAYS intend to display in 1: 1 (FBO vs. viewport), you can generally not use the texture coordinates on top and use gl_FragCoord . By default in GLSL, gl_FragCoord will give you the coordinate of the fragment center (0.5, 0.5), which is also the corresponding texel center in your FBO. You can pass gl_FragCoord.st directly to your texture search in this special case.