Why is precalculating a distortion different from calculating it in the shader?
I finally found the problem.

My precalculation was done with pixel coordinates from 0 to res[0] - 1 (i.e. the resolution width):

for (int i = 0; i &lt; res[0]; i++)
{
    for (int j = 0; j &lt; res[1]; j++)
    {
        reinterpret_cast&lt;float*&gt;(img-&gt;data(i, j))[0] = i / res[0];
        reinterpret_cast&lt;float*&gt;(img-&gt;data(i, j))[1] = j / res[1];
    }
}


However, pixels in OpenGL are accessed via their center, as can be read in another SO answer or in Real-Time Rendering (3rd and 4th Edition, p.21). Meaning, that if I have a pixel with 0 as its left border and 0.1 as its right border, then 0.05 is its center.
That means in my case, I have to offset the precalculated texture coordinate by half a pixel to read out of my prerendered image:

for (int i = 0; i &lt; res[0]; i++)
{
    for (int j = 0; j &lt; res[1]; j++)
    {
        reinterpret_cast&lt;float*&gt;(img-&gt;data(i, j))[0] = i / res[0] + 0.5 * pixelWidth;
        reinterpret_cast&lt;float*&gt;(img-&gt;data(i, j))[1] = j / res[1] + 0.5 * pixelHeight;
    }
}